Compare commits

...
173 Commits
Author SHA1 Message Date
Irmen de Jong aa10997171 upgrade to kotlin 1.5.20 2021-06-25 19:38:44 +02:00
Irmen de Jong 7880ac1909 wording and version 2021-06-24 21:34:11 +02:00
Irmen de Jong f53848b4b9 wording and version 2021-06-24 21:25:35 +02:00
Irmen de Jong 82f2f38680 Merge pull request #48 from meisl/issue40(EOF,EOL)
* fix 47, add tests
2021-06-19 01:55:44 +02:00
meisl dcc2549574 * fix 47, add tests 2021-06-18 21:55:03 +02:00
Irmen de Jong cfe4753913 Merge pull request #45 from meisl/issue40(EOF,EOL)
Issue #40, EOF/EOL
2021-06-18 20:22:59 +02:00
meisl fcb1a7e4d4 * #40: fix grammar rules module and block s.t. we don't need a "synthesized double EOF" (behavior remains exactly the same) 2021-06-14 22:17:30 +02:00
meisl ce76a7dfa5 * #40: fix grammar wrt line endings - tests pass 2021-06-14 22:04:22 +02:00
meisl 7c1de81861 * #40: fix mixed line endings test, now intentionally failing (!): also test sole \r AND do not allow any recovery, neither from parser not lexer. 2021-06-14 22:02:26 +02:00
meisl eddad20acc Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 22:56:24 +02:00
meisl 7daad57862 + #40: test for mixed (Unix/Win/Mac) line endings - *TODO: test doesn't actually fail with old grammar, but a built jar does - WHY?!* 2021-06-13 22:49:54 +02:00
meisl 1468049fe9 + #40: test that (module-level) blocks *before the last* still must have a newline after their closing } 2021-06-13 22:43:27 +02:00
meisl 3b91e59a79 * #40: refactor tests 2021-06-13 20:28:01 +02:00
meisl 3496a30528 * #40: put back in fix for EOL-after-block - tests pass 2021-06-13 20:10:35 +02:00
meisl 32bad5df15 +/* #40: add tests; temporarily undo fix for EOL-after-block so we can see that tests actually fail 2021-06-13 20:08:50 +02:00
Irmen de Jong 3f58eca1be updated gradle scripts (fixed warnings), updated some library dependencies 2021-06-13 18:10:07 +02:00
meisl 2350843d1d Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 16:06:50 +02:00
Irmen de Jong a2588a178c added some simple unit tests to the ast parser 2021-06-13 14:59:57 +02:00
meisl e5292696c4 - #40 grammar: remove obsolete note about line endings 2021-06-13 14:38:25 +02:00
meisl 34b2a65ccb Merge branch 'irmen:master' into issue40(EOF,EOL) 2021-06-12 20:29:37 +02:00
meisl 3aa3659bc7 * #40 grammar: handle different EOLs (Win, Unix, Mac) purely in grammar 2021-06-12 20:24:15 +02:00
meisl b8117394c0 * #40 grammar: don't require EOL after blocks, so .p8 files need not end with that 2021-06-12 17:52:44 +02:00
Irmen de Jong fd2bbd2b59 no longer allow subroutine name same as its block name due to asm symbol scoping issues 2021-06-12 17:31:09 +02:00
Irmen de Jong 127c470746 add some explanation about Cx16 v38 - v39 issue 2021-06-12 15:48:04 +02:00
Irmen de Jong f2844bdf1a fix crash when using labels in pointerexpression lab+index 2021-06-10 00:44:12 +02:00
Irmen de Jong c5bfef4264 slight improvement on scope doc, added doc example for %asminclude/%asmbinary 2021-06-09 23:46:07 +02:00
Irmen de Jong 73863acc12 version bump 2021-06-06 10:50:05 +02:00
Irmen de Jong 19e99204b9 fix asm symbol name scoping bug and add unit tests for this 2021-06-04 22:42:28 +02:00
Irmen de Jong 13f5b94c3e Clarified instructions of how to obtain the compiler. Fixed sphinx css config issue. 2021-06-03 21:21:44 +02:00
Irmen de Jong 3a2498401d working on unit tests for symbol scope bug 2021-06-03 21:21:38 +02:00
Irmen de Jong 53b20ba625 name 2021-06-01 22:22:58 +02:00
Irmen de Jong e7f6f0950f identified asm symbol name scoping bugs 2021-06-01 22:21:50 +02:00
Irmen de Jong 9fbe1b38a5 fix old block syntax in ast print routine 2021-06-01 22:08:23 +02:00
Irmen de Jong 078485134d split up unittests files 2021-06-01 22:07:39 +02:00
Irmen de Jong 67b1766e32 don't use ./ prefix for %asmbinary paths 2021-06-01 19:30:53 +02:00
Irmen de Jong d4b69ac79c improved repeat counter vars allocation (re-use var if possible) 2021-05-30 15:30:34 +02:00
Irmen de Jong e61a2d7083 slightly optimized repeat loop asmgen 2021-05-30 13:10:05 +02:00
Irmen de Jong c03f6604af added free words counting method to zeropage 2021-05-30 00:55:11 +02:00
Irmen de Jong 572bb38ddb update to kotlin 1.5.10 2021-05-29 15:25:17 +02:00
Irmen de Jong 42c5c0cb9f start of cx16 colorbars example 2021-05-26 22:13:23 +02:00
Irmen de Jong e145d2255e added palette.set_all_black() and set_all_white() 2021-05-26 21:33:18 +02:00
Irmen de Jong 442fa07dd4 relax name conflict rule regarding block names vs subroutine params 2021-05-26 21:32:54 +02:00
Irmen de Jong 31ae9e1243 refactor repeat loop counter var creation into single routine 2021-05-22 13:01:51 +02:00
Irmen de Jong d7f83f8df2 version bump 2021-05-20 23:38:41 +02:00
Irmen de Jong 29e2d4e0c8 give error when passing invalid command line option 2021-05-20 23:34:20 +02:00
Irmen de Jong 2732d2c844 exclude d64 files 2021-05-19 18:49:38 +02:00
Irmen de Jong c4a037b277 added '@shared' to syntax files 2021-05-19 18:48:18 +02:00
Irmen de Jong 0e614ad6fc added @shared flag to vardecl to mark variable as shared with assembly code elsewhere, to not have it optimized away 2021-05-19 01:19:25 +02:00
Irmen de Jong ca1a8cd617 improve doc about string (im)mutability 2021-05-19 00:15:17 +02:00
Irmen de Jong ba96a637be remove strdedup compiler argument again
(string deduplication is the default again but only for known-const strings, i.e. string literals)
2021-05-18 23:52:43 +02:00
Irmen de Jong c2cac772e3 validate string interning 2021-05-18 23:37:52 +02:00
Irmen de Jong 6b7216f4ec todo 2021-05-17 19:00:20 +02:00
Irmen de Jong e4fb5946dd optimize cx16 sys.wait and sys.waitvsync to use WAI instruction 2021-05-17 18:44:42 +02:00
Irmen de Jong ca61248861 printing 2-letter strings is now only optimized into direct CHROUT if it's a const string literal 2021-05-16 15:00:40 +02:00
Irmen de Jong 68d7b4649e label and directive location docs 2021-05-16 12:32:08 +02:00
Irmen de Jong 0416aacbbd fix %asminclude by removing scopelabel argument and improving docs to remove false promise about labels 2021-05-16 00:14:57 +02:00
Irmen de Jong bc731e6f8e fix compiler crash when taking address of label 2021-05-16 00:07:48 +02:00
Irmen de Jong ae5d7705bb allow correct parsing of source files that don't end in a EOL character. Fixes #40 2021-05-14 17:14:44 +02:00
Irmen de Jong b9bd541532 restored optimization of printing short strings into just CHROUT
but added comment about known-constness still to be resolved
2021-05-13 01:46:43 +02:00
Irmen de Jong 83639c2535 code style 2021-05-13 01:00:19 +02:00
Irmen de Jong 25d80f4df1 added compiler option to choose string literal deduplication yes/no -- default changed to NO 2021-05-13 00:35:22 +02:00
Irmen de Jong 74f918d911 fix crashes for string encoding errors: give normal compiler error instead 2021-05-11 21:33:11 +02:00
Irmen de Jong a20efa56eb print unmappable character in escaped form in errormessage 2021-05-11 18:09:09 +02:00
Irmen de Jong f4d83075be Merge pull request #35 from meisl/master
Notepad++ syntax-file: add notes re update / alt installation
2021-05-07 21:44:58 +02:00
Irmen de Jong 254592c383 Merge pull request #36 from meisl/pull36
docs: fix typo
2021-05-07 20:04:00 +02:00
meisl ee23ac0537 * docs: fix typo 2021-05-07 15:28:22 +02:00
meisl a48cf0bb24 + #23 Notepad++ syntax-file: add notes re update / alt installation 2021-05-07 15:12:01 +02:00
Irmen de Jong dae59238cd fix array type checking crash when attempting to use str literal to initialize a byte array.
Fixes #34
2021-05-07 00:04:29 +02:00
Irmen de Jong 8736da1a21 strings of 1 and 2 length no longer optimized into one call to CHROUT - also upgrade to kotlin 1.5.0 2021-05-06 23:46:18 +02:00
Irmen de Jong 09a1de69e7 Merge pull request #33 from meisl/master
+ docs: add missing word
2021-05-06 23:45:44 +02:00
meisl 63d67bc6cb + docs: add missing word 2021-05-06 15:49:58 +02:00
Irmen de Jong 7099245204 Notepad++ syntax file contributor added 2021-05-05 00:39:19 +02:00
Irmen de Jong 4d097d2139 Merge pull request #32 from meisl/master
syntax file for Notepad++

thanks for your contribution!!
2021-05-05 00:33:06 +02:00
meisl 6485bf9ad9 +/- #23 add test file and screenshot; fix: remove if/else as "Folding in code 2", just keywords 2021-05-04 21:55:12 +02:00
meisl b7c5b1bfc7 * #23 rename to .md for nicer link to homepage 2021-05-04 21:34:04 +02:00
meisl 2b7546e827 + #23 syntax file for Notepad++ 2021-05-04 21:30:53 +02:00
Irmen de Jong 3549ccf4b3 software license 2021-05-02 15:31:14 +02:00
Irmen de Jong e2f5752d9a add f_open_w, f_write, f_close_w to diskio to be able to save parts of memory sequentially 2021-05-01 19:13:56 +02:00
Irmen de Jong 1a59019fc8 add generic error in diskio.status() if drive status can't be read 2021-05-01 15:39:39 +02:00
Irmen de Jong 7bac7bdc3e more precise 2021-05-01 13:39:02 +02:00
Irmen de Jong 19fe58dbac fix regression bug that left variables uninitialized 2021-05-01 01:35:03 +02:00
Irmen de Jong 0a5b30e21c added fast code for x*640 2021-04-30 22:30:21 +02:00
Irmen de Jong 664818fd29 try fixing a weird problem with pointervar[idx] -> memread rewriting
this was introduced in the removal of structs somehow
2021-04-30 01:34:03 +02:00
Irmen de Jong d5214e2505 fix import paths 2021-04-30 00:16:36 +02:00
Irmen de Jong d906fcea0e refactor some type checks 2021-04-30 00:09:15 +02:00
Irmen de Jong 29c8e8b740 doc 2021-04-29 19:57:14 +02:00
Irmen de Jong 71fec4c555 added a few more simple special codegen segements for the logic operators on a memmory-read 2021-04-29 19:38:42 +02:00
Irmen de Jong 5ee36c897d todo 2021-04-29 00:57:32 +02:00
Irmen de Jong 4aba0c7405 unused variables are removed more aggressively (no longer checking asm blocks for their names) 2021-04-29 00:48:16 +02:00
Irmen de Jong ed7479c854 version 7 due to removal of structs and v39 cx16 support changes 2021-04-29 00:15:54 +02:00
Irmen de Jong 8d3d5f726a removed Datatype.STRUCT 2021-04-29 00:13:17 +02:00
Irmen de Jong a9a7068818 removed support for structs. It was too much hassle and complexity and subtle bugs. 2021-04-29 00:01:20 +02:00
Irmen de Jong 1bde7c7718 ver 2021-04-28 20:05:56 +02:00
Irmen de Jong 17068130bb removed PROG8_LIBDIR env variables and replaced with -libdirs command line option 2021-04-28 20:04:23 +02:00
Irmen de Jong 81a91d62cb improved horizontal_line in highres 4c 2021-04-28 02:55:49 +02:00
Irmen de Jong 2575263438 optimized gfx2.plot() for hires-4c 2021-04-28 02:49:25 +02:00
Irmen de Jong 7f0e25cb50 optimized gfx2.plot() for hires-monochrome 2021-04-28 02:32:11 +02:00
Irmen de Jong a1e4e9c50f optimized gfx2.plot() for lores-256c 2021-04-28 02:22:21 +02:00
Irmen de Jong 98eff2701b optimized gfx2.plot() for lores-monochrome 2021-04-28 02:15:07 +02:00
Irmen de Jong 8b84f87217 removed fastrnd8() because it was hilariously bad, just use rnd() 2021-04-28 01:53:12 +02:00
Irmen de Jong 306a1b7bc2 optimized gfx2.vertical_line for hires monochrome mode 2021-04-28 01:19:10 +02:00
Irmen de Jong 481214c46e optimized gfx2.vertical_line for lores monochrome mode 2021-04-28 01:02:29 +02:00
Irmen de Jong a5961cbeab optimized gfx2.vertical_line for highres 4c mode 2021-04-28 00:29:21 +02:00
Irmen de Jong 3bf335e0a0 todo 2021-04-27 23:13:46 +02:00
Irmen de Jong 68f696d165 added 'callrom' builtin function (for cx16 target) that calls a routine in banked ROM 2021-04-25 18:04:56 +02:00
Irmen de Jong 1170aed026 added 'callfar' builtin function (for cx16 target) that uses jsrfar to call a routine in banked RAM 2021-04-25 17:47:13 +02:00
Irmen de Jong bf1b2066b6 fix crashes in peekw() and pokew() 2021-04-22 18:26:46 +02:00
Irmen de Jong 4c080afb76 added compiler check against impossible for loop range (unsigned downto exactly 0 with non-const startvalue and step != -1) 2021-04-21 23:03:29 +02:00
Irmen de Jong ee1c43ca91 improved scanning for return statement in routines that should return a value. 2021-04-21 20:31:29 +02:00
Irmen de Jong 1c2e6f9e4c lower() and upper() now also return the lenght of the processed string. 2021-04-21 20:21:58 +02:00
Irmen de Jong dd379430d9 added docs on flexible string character mapping to petscii 2021-04-20 01:22:49 +02:00
Irmen de Jong 42033ebd35 added petscii mappings for ^, _, \, {, } and | 2021-04-19 02:18:55 +02:00
Irmen de Jong a086d6e009 allow labels also in blocks instead of only in subroutines 2021-04-18 23:03:18 +02:00
Irmen de Jong c70bbdab26 fixed missing type checking in vardecl initializer values. Fixes #29
Also fix wrong assert of 0 const check in assembly gen for if-statement comparisons.
2021-04-18 22:46:21 +02:00
Irmen de Jong 3d956ef554 fix wrong values for register used in array indexing expressions
added the L/H byte parts of the cx16 virtual registers
2021-04-18 13:53:02 +02:00
Irmen de Jong 329f491c30 fix compiler crash with scoped const vardecls 2021-04-18 01:56:26 +02:00
Irmen de Jong e93701f50e fix compiler error when initializing var with memory(...) in block scope instead of subroutine 2021-04-17 15:49:41 +02:00
Irmen de Jong e680de05ea workaround for the joystick_get() irq problem 2021-04-15 22:56:52 +02:00
Irmen de Jong 56fec674c5 actually not simplifying if-code generation, leads to larger code at the moment 2021-04-13 00:03:22 +02:00
Irmen de Jong 54d92a027a fix problems with moving vardecls from inner scope to subroutine scope 2021-04-12 22:53:25 +02:00
Irmen de Jong 319ac3a641 preparing optimizations for if statements 2021-04-12 03:34:58 +02:00
Irmen de Jong 0a03c46351 preparing optimization plan for if statements 2021-04-12 02:37:15 +02:00
Irmen de Jong ae1b62e147 optimized integer comparison expressions some more 2021-04-12 01:23:17 +02:00
Irmen de Jong 8d567f6b06 added cx16.joystick_get2() for convenience api 2021-04-12 01:07:46 +02:00
Irmen de Jong b1ef09675b fix compiler crash for some struct/array initialization assignment literals containing not just numbers 2021-04-10 00:28:32 +02:00
Irmen de Jong 2b7b925090 codegen now uses correct machine target's string encoder/decoder. Encoding more robust by checking upper case mapping if lowercase mapping fails. 2021-04-09 23:33:32 +02:00
Irmen de Jong e0454e95db warn about for-loop wrapped iteration if loop range is inverted from normal 2021-04-08 22:54:47 +02:00
Irmen de Jong 91e421d961 optimize x % p where p=power-of-2, into just x & (p-1) 2021-04-08 22:21:16 +02:00
Irmen de Jong c853afe769 fix compiler crash due to certain redundant typecast expressions 2021-04-08 19:45:44 +02:00
Irmen de Jong 1a64cb38d5 fix compiler crash with assigning certain array values as vardecl initializer 2021-04-08 19:21:17 +02:00
Irmen de Jong ccebd22856 callgraph: mark start() also in use 2021-04-08 02:43:59 +02:00
Irmen de Jong a1f3b82333 vtui update 2021-04-08 01:36:25 +02:00
Irmen de Jong 3dda29781e changed MEMTOP2 into cx16.numbanks() to query the number of RAM banks installed 2021-04-08 01:05:38 +02:00
Irmen de Jong a9d297ee31 fix inlining of sub with var that has default initialization 2021-04-08 00:35:02 +02:00
Irmen de Jong e5ff61f201 allow inlining of subroutines with parameters, and fix inlining of subroutines with variables 2021-04-07 23:38:25 +02:00
Irmen de Jong d116eb7655 paranoid, be sure to not kill carry 2021-04-06 23:55:20 +02:00
Irmen de Jong bc726c6334 optimized slow evaluation of byte-to-wordarray assignment 2021-04-06 22:50:16 +02:00
Irmen de Jong 123473dfc8 cleanup 2021-04-06 00:16:29 +02:00
Irmen de Jong d9eccd4fba set correct rom banks when using floats 2021-04-05 23:21:07 +02:00
Irmen de Jong 5b890847e5 make sure BASIC rom is banked in again when program exits 2021-04-05 23:12:10 +02:00
Irmen de Jong 64c85b9617 fix cx16 rom v39 float changes 2021-04-05 22:54:40 +02:00
Irmen de Jong 3e3b0bcd8b callgraph improved unused node checking 2021-04-05 20:45:18 +02:00
Irmen de Jong 4c1eb1b12a callgraph 2021-04-05 20:32:30 +02:00
Irmen de Jong 530d03d284 callgraph 2021-04-05 18:50:46 +02:00
Irmen de Jong 619fa9b65e callgraph 2021-04-05 18:03:36 +02:00
Irmen de Jong 0032235933 tweak to fix for windows line ending (\r\n) parse errors 2021-04-05 01:49:52 +02:00
Irmen de Jong 61d1f1ea87 oops 2021-04-05 01:18:22 +02:00
Irmen de Jong 238d27acdc more pleasing bob image and pattern 2021-04-05 01:14:55 +02:00
Irmen de Jong 2f62271453 callgraph 2021-04-05 00:55:27 +02:00
Irmen de Jong 75d5117a2d fix struct flattening parent node mismatch 2021-04-05 00:30:42 +02:00
Irmen de Jong b4700af2f5 fix windows line ending (\r\n) parse errors 2021-04-05 00:12:04 +02:00
Irmen de Jong 374e2b311d refactoring unused code removal and noModification 2021-04-04 16:36:33 +02:00
Irmen de Jong 49036abbaf docs 2021-04-04 12:55:29 +02:00
Irmen de Jong 38ccbac97c stop after a couple of parse errors (it's not useful to continue for long if there are parse errors) 2021-04-04 12:29:56 +02:00
Irmen de Jong 6b4896b8f5 doc 2021-04-02 21:28:23 +02:00
Irmen de Jong d582d1cc42 fix inlining subroutines multiple times 2021-04-02 21:23:40 +02:00
Irmen de Jong 9e2b8a2aa9 fix ast node duplication/reference bug in certain optimizers 2021-04-02 19:01:46 +02:00
Irmen de Jong 6fdc733941 inlining subroutines that contain variable declarations is now possible (gives a warning though) 2021-04-02 18:30:32 +02:00
Irmen de Jong 422b390c48 fix ast node duplication/reference bug in certain optimizers 2021-04-02 16:56:52 +02:00
Irmen de Jong 67a9d1285c some words about how the X register can't or can be used 2021-04-02 00:19:46 +02:00
Irmen de Jong 8e26e38ecc fix RTS-issue with inlined return statement 2021-04-01 23:30:19 +02:00
Irmen de Jong 02e12d8575 improvements for inlined subroutines: fix identifier scoping 2021-04-01 23:16:04 +02:00
Irmen de Jong fe2954ce08 todo 2021-04-01 22:10:04 +02:00
Irmen de Jong 1fe4439395 fixed wrong return value when calling other subroutines in the return expression 2021-04-01 21:56:24 +02:00
Irmen de Jong 2ff04d2abd cleanup 2021-04-01 19:10:55 +02:00
Irmen de Jong 3f30d3aa89 added sys.waitrastborder() for c64 2021-04-01 18:53:16 +02:00
Irmen de Jong 129e17b33a added sys.waitvsync() + missing documentation 2021-04-01 18:31:33 +02:00
Irmen de Jong bf2d8c3f4b update kotlin plugin to 1.4.32 2021-03-31 20:52:05 +02:00
Irmen de Jong b29f04ce01 fix unittest 2021-03-31 20:49:35 +02:00
Irmen de Jong d185ebad48 Merge pull request #27 from Elektron72/cbm-package
Move code used for all Commodore systems to new package
2021-03-31 20:34:58 +02:00
Elektron72 605df7c91c Move code used for all CBM systems to new package
AssemblyProgram.kt and Petscii.kt are not only used for the Commodore
64; they are also used for the Commander X16, and will likely be used
for any future Commodore systems added to Prog8. Therefore, they should
be moved to a new package containing functionality shared between these
systems.
2021-03-29 17:21:48 -04:00
Irmen de Jong ec60cad8bb commander-x16 prototype board #2 (rom v39+) address changes 2021-03-27 22:20:46 +01:00
Irmen de Jong 6aa0f5a392 small optimization 2021-03-27 15:45:30 +01:00
144 changed files with 4773 additions and 13026 deletions

No files matched your search

+1 -1
View File
@@ -30,4 +30,4 @@ parsetab.py
.gradle
/prog8compiler.jar
sd*.img
*.d64
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
</component>
</project>
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="antlr-4.9-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9-complete.jar!/" />
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9.2-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="antlr-runtime-4.9">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.jar!/" />
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="libraryTable">
<library name="kotlinx-cli-jvm">
<CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" />
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+7
View File
@@ -1,3 +1,10 @@
This sofware license is for Prog8 the compiler + associated libraries.
The software generated by running the compiler is excluded from this.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
+13 -10
View File
@@ -7,9 +7,6 @@ Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
*Written by Irmen de Jong (irmen@razorvine.net)*
*Software license: GNU GPL 3.0, see file LICENSE*
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
as used in many home computers from that era. It is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
@@ -19,29 +16,35 @@ Documentation
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
https://prog8.readthedocs.io/
Software license
----------------
GNU GPL 3.0, see file LICENSE
- prog8 (the compiler + libraries) is licensed under GNU GPL 3.0
- *exception:* the resulting files created by running the compiler are free to use in whatever way desired.
What does Prog8 provide?
------------------------
- reduction of source code length over raw assembly
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- small program boilerplate/compilersupport overhead
- sane variable initialization, programs can be restarted again just fine after exiting to basic
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- floating point operations (requires the C64 Basic ROM routines for this)
- 'when' statement to provide a concise jump table alternative to if/elseif chains
- structs to group together sets of variables and manipulate them at once
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- fast execution speed due to compilation to native assembly code
- variables are allocated statically
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
*Rapid edit-compile-run-debug cycle:*
+3
View File
@@ -0,0 +1,3 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.5.20" apply false
}
+18 -17
View File
@@ -1,14 +1,14 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '6.1.0'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.0.0'
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
@@ -21,9 +21,7 @@ dependencies {
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
// implementation 'net.razorvine:ksim65:1.8'
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2'
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
@@ -31,6 +29,20 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
exclude group: "org.antlr", module: "antlr4"
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
@@ -67,7 +79,6 @@ startScripts.enabled = true
application {
mainClass = 'prog8.CompilerMainKt'
mainClassName = 'prog8.CompilerMainKt' // deprecated
applicationName = 'p8compile'
}
@@ -95,13 +106,3 @@ test {
events "skipped", "failed"
}
}
dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/kdoc"
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}
+1 -1
View File
@@ -13,7 +13,7 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="unittest-libs" level="project" />
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -83,7 +83,7 @@ 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 $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -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)
@@ -195,7 +195,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}
+60 -3
View File
@@ -214,7 +214,7 @@ romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (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 $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
@@ -246,7 +246,7 @@ asmsub STOP2() -> ubyte @A {
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits for convenience
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
stx P8ZP_SCRATCH_REG
jsr c64.RDTIM
@@ -478,7 +478,7 @@ sys {
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
lda #14
@@ -489,6 +489,7 @@ sys {
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
@@ -497,6 +498,29 @@ sys {
}
}
asmsub waitvsync() clobbers(A) {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
- lda c64.RASTER
beq -
- lda c64.RASTER
bne -
bit c64.SCROLY
bmi -
rts
}}
}
inline asmsub waitrastborder() {
; --- busy wait till the raster position has reached the bottom screen border (approximately)
; note: a more accurate way to do this is by using a raster irq handler instead.
%asm {{
- bit c64.SCROLY
bpl -
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
@@ -679,4 +703,37 @@ cx16 {
&uword r14 = $cf1c
&uword r15 = $cf1e
&ubyte r0L = $cf00
&ubyte r1L = $cf02
&ubyte r2L = $cf04
&ubyte r3L = $cf06
&ubyte r4L = $cf08
&ubyte r5L = $cf0a
&ubyte r6L = $cf0c
&ubyte r7L = $cf0e
&ubyte r8L = $cf10
&ubyte r9L = $cf12
&ubyte r10L = $cf14
&ubyte r11L = $cf16
&ubyte r12L = $cf18
&ubyte r13L = $cf1a
&ubyte r14L = $cf1c
&ubyte r15L = $cf1e
&ubyte r0H = $cf01
&ubyte r1H = $cf03
&ubyte r2H = $cf05
&ubyte r3H = $cf07
&ubyte r4H = $cf09
&ubyte r5H = $cf0b
&ubyte r6H = $cf0d
&ubyte r7H = $cf0f
&ubyte r8H = $cf11
&ubyte r9H = $cf13
&ubyte r10H = $cf15
&ubyte r11H = $cf17
&ubyte r12H = $cf19
&ubyte r13H = $cf1b
&ubyte r14H = $cf1d
&ubyte r15H = $cf1f
}
+32 -32
View File
@@ -8,7 +8,9 @@
%option enable_floats
floats {
; ---- this block contains C-64 floating point related functions ----
; ---- this block contains C-64 compatible floating point related functions ----
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
@@ -43,36 +45,34 @@ romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe33 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe36 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $fe3c = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
romsub $fe6f = 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
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
; note: there is no FPWR() on the Cx16
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe48 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe4b = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe4e = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe57 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $fe66 = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $fe69 = 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
romsub $fe72 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe7b = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe81 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe84 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $fe8a = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $fe8d = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe96 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $fe99 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fe9c = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $fe9f = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $fea2 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
@@ -158,7 +158,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}
+139 -122
View File
@@ -22,6 +22,7 @@
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
gfx2 {
@@ -274,9 +275,9 @@ _done
ora colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne +
bne ++
inc cx16.VERA_ADDR_L
bne +
bne ++
inc cx16.VERA_ADDR_M
+ bne +
inc cx16.VERA_ADDR_H
@@ -292,92 +293,56 @@ _done
}
sub vertical_line(uword x, uword y, uword height, ubyte color) {
position(x,y)
when active_mode {
1, 5 -> {
; monochrome, either resolution
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=5
cx16.r14 = 640/8
else
cx16.r14 = 320/8
; monochrome, lo-res
cx16.r15L = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if color {
if monochrome_dont_stipple_flag {
; draw continuous line.
position2(x,y,true)
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat height {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
} else {
; stippling.
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
%asm {{
lda x
eor y
and #1
bne +
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
+
asl cx16.r14
ldy height
beq +
- lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next-next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
dey
bne -
+
}}
; draw stippled line.
if x&1 {
y++
height--
}
position2(x,y,true)
if active_mode==1
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
else
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
repeat height/2 {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
} else {
cx16.r15 = ~cx16.r15
position2(x,y,true)
cx16.r15 = ~cx16.r15 ; erase pixels
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat height {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; the bitmap size is small enough to not have to deal with the _H part:
; lda cx16.VERA_ADDR_H
; adc #0
; sta cx16.VERA_ADDR_H
and cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
@@ -385,6 +350,7 @@ _done
4 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
position(x,y)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy height
@@ -398,36 +364,33 @@ _done
}
6 -> {
; highres 4c
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
; TODO optimize this vertical line loop in pure assembly
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
if height==0
return
position2(x,y,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3]
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat height {
ubyte value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
%asm {{
; 24 bits add 160 (640/4)
clc
lda cx16.r0
adc #640/4
sta cx16.r0
lda cx16.r0+1
adc #0
sta cx16.r0+1
bcc +
inc cx16.r1
+
lda cx16.VERA_DATA0
and mask
ora color
sta cx16.VERA_DATA1
}}
}
}
}
sub set_both_strides(ubyte stride) {
stride <<= 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
}
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
@@ -587,8 +550,6 @@ _done
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] shift4c = [6,4,2,0]
uword addr
ubyte value
when active_mode {
1 -> {
@@ -600,23 +561,43 @@ _done
and #1
}}
if_nz {
addr = x/8 + y*(320/8)
value = bits[lsb(x)&7]
if color
cx16.vpoke_or(0, addr, value)
else {
value = ~value
cx16.vpoke_and(0, addr, value)
}
cx16.r0L = lsb(x) & 7 ; xbits
x /= 8
x += y*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
lda bits,y
ldy color
beq +
tsb cx16.VERA_DATA0
bra ++
+ trb cx16.VERA_DATA0
+
}}
}
}
; TODO mode 2,3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.vpoke(lsb(cx16.r1), cx16.r0, color)
; activate vera auto-increment mode so next_pixel() can be used after this
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | %00010000
color = cx16.VERA_DATA0
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
ora #%00010000 ; enable auto-increment so next_pixel() can be used after this
sta cx16.VERA_ADDR_H
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda cx16.r0
sta cx16.VERA_ADDR_L
lda color
sta cx16.VERA_DATA0
}}
}
5 -> {
; highres monochrome
@@ -627,26 +608,48 @@ _done
and #1
}}
if_nz {
addr = x/8 + y*(640/8)
value = bits[lsb(x)&7]
if color
cx16.vpoke_or(0, addr, value)
else {
value = ~value
cx16.vpoke_and(0, addr, value)
}
cx16.r0L = lsb(x) & 7 ; xbits
x /= 8
x += y*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
lda bits,y
ldy color
beq +
tsb cx16.VERA_DATA0
bra ++
+ trb cx16.VERA_DATA0
+
}}
}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(x) & 3 ; xbits
color &= 3
color <<= shift4c[lsb(x) & 3]
; TODO optimize the vera memory manipulation in pure assembly
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask4c[lsb(x) & 3] | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
color <<= shift4c[cx16.r2L]
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
ldy cx16.r2L ; xbits
lda mask4c,y
and cx16.VERA_DATA0
ora color
sta cx16.VERA_DATA0
}}
}
}
}
@@ -680,6 +683,20 @@ _done
}
}
sub position2(uword @zp x, uword y, ubyte also_port_1) {
position(x, y)
if also_port_1 {
when active_mode {
1, 5 -> cx16.vaddr(0, cx16.r0, 1, 1)
; TODO modes 2, 3
4, 6 -> {
ubyte bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 1, 1)
}
}
}
}
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
@@ -759,13 +776,13 @@ _done
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! TODO allow per-pixel horizontal positioning
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
uword chardataptr
when active_mode {
1, 5 -> {
; monochrome mode, either resolution
cx16.r2 = 40
if active_mode>=5
if active_mode==5
cx16.r2 = 80
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
+8
View File
@@ -70,6 +70,14 @@ palette {
}
}
inline sub set_all_black() {
set_monochrome($000, $000)
}
inline sub set_all_white() {
set_monochrome($fff, $fff)
}
sub set_grayscale() {
vera_palette_ptr = $fa00
repeat 16 {
+95 -34
View File
@@ -24,7 +24,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
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. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See MEMTOP2
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
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
@@ -47,7 +47,7 @@ romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (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 $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
@@ -74,7 +74,7 @@ asmsub STOP2() -> ubyte @A {
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits for convenience
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
phx
jsr c64.RDTIM
@@ -87,17 +87,6 @@ asmsub RDTIM16() -> uword @AY {
}}
}
asmsub MEMTOP2() -> ubyte @A {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks.
%asm {{
phx
sec
jsr c64.MEMTOP
plx
rts
}}
}
}
cx16 {
@@ -127,6 +116,41 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
&ubyte r3L = $0008
&ubyte r4L = $000a
&ubyte r5L = $000c
&ubyte r6L = $000e
&ubyte r7L = $0010
&ubyte r8L = $0012
&ubyte r9L = $0014
&ubyte r10L = $0016
&ubyte r11L = $0018
&ubyte r12L = $001a
&ubyte r13L = $001c
&ubyte r14L = $001e
&ubyte r15L = $0020
&ubyte r0H = $0003
&ubyte r1H = $0005
&ubyte r2H = $0007
&ubyte r3H = $0009
&ubyte r4H = $000b
&ubyte r5H = $000d
&ubyte r6H = $000f
&ubyte r7H = $0011
&ubyte r8H = $0013
&ubyte r9H = $0015
&ubyte r10H = $0017
&ubyte r11H = $0019
&ubyte r12H = $001b
&ubyte r13H = $001d
&ubyte r14H = $001f
&ubyte r15H = $0021
; VERA registers
const uword VERA_BASE = $9F20
@@ -172,7 +196,7 @@ cx16 {
; I/O
const uword via1 = $9f60 ;VIA 6522 #1
const uword via1 = $9f00 ;VIA 6522 #1
&ubyte d1prb = via1+0
&ubyte d1pra = via1+1
&ubyte d1ddrb = via1+2
@@ -190,7 +214,7 @@ cx16 {
&ubyte d1ier = via1+14
&ubyte d1ora = via1+15
const uword via2 = $9f70 ;VIA 6522 #2
const uword via2 = $9f10 ;VIA 6522 #2
&ubyte d2prb = via2+0
&ubyte d2pra = via2+1
&ubyte d2ddrb = via2+2
@@ -208,6 +232,11 @@ cx16 {
&ubyte d2ier = via2+14
&ubyte d2ora = via2+15
&ubyte ym2151adr = $9f40
&ubyte ym2151dat = $9f41
const uword extdev = $9f60
; ---- Commander X-16 additions on top of C64 kernal routines ----
; spelling of the names is taken from the Commander X-16 rom sources
@@ -294,16 +323,25 @@ romsub $fecc = monitor() clobbers(A,X,Y)
inline asmsub rombank(ubyte rombank @A) {
; -- set the rom banks
%asm {{
sta $01 ; rom bank register (new)
sta cx16.d1prb ; rom bank register (old)
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
}}
}
inline asmsub rambank(ubyte rambank @A) {
; -- set the ram bank
%asm {{
sta $00 ; ram bank register (new)
sta cx16.d1pra ; ram bank register (old)
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
}}
}
asmsub numbanks() -> ubyte @A {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
%asm {{
phx
sec
jsr c64.MEMTOP
plx
rts
}}
}
@@ -458,6 +496,17 @@ asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) ->
}}
}
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
; TODO once that issue is resolved, this routine can be redefined as: romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX
%asm {{
sei
jsr cx16.joystick_get
cli
}}
}
sub FB_set_pixels_from_buf(uword buffer, uword count) {
%asm {{
@@ -500,11 +549,9 @@ asmsub init_system() {
%asm {{
sei
cld
;stz $00
;stz $01
;stz d1prb ; select rom bank 0 (enable kernal)
lda #$80
sta VERA_CTRL
stz $01 ; select rom bank 0 (enable kernal)
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
@@ -704,23 +751,37 @@ sys {
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
stz $01 ; bank the kernal in (new rom bank register)
stz cx16.d1prb ; bank the kernal in (old rom bank register)
stz $01 ; bank the kernal in
jmp (cx16.RESET_VEC)
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: regular system vsync irq handler must be running, and no nother irqs
%asm {{
- wai ; wait for irq (assume it was vsync)
cmp #0
bne +
dey
+ dec a
bne -
cpy #0
bne -
rts
}}
}
inline asmsub waitvsync() {
; --- suspend execution until the next vsync has occurred, without depending on custom irq handling.
; note: system vsync irq handler has to be active for this routine to work (and no other irqs-- which is the default).
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
wai
}}
}
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
+45 -3
View File
@@ -336,6 +336,45 @@ _end rts
}
; ----- iterative file saver functions (uses io channel 14) -----
sub f_open_w(ubyte drivenumber, uword filenameptr) -> ubyte {
; -- open a file for iterative writing with f_write
f_close_w()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(14, drivenumber, 1)
void c64.OPEN() ; open 14,8,1,"filename"
if_cc {
void c64.CHKOUT(14) ; use #14 as input channel
return not c64.READST()
}
f_close_w()
return false
}
sub f_write(uword bufferpointer, uword num_bytes) -> ubyte {
; -- write the given umber of bytes to the currently open file
if num_bytes!=0 {
void c64.CHKOUT(14) ; use #14 as input channel again
repeat num_bytes {
c64.CHROUT(@(bufferpointer))
bufferpointer++
}
return not c64.READST()
}
return true
}
sub f_close_w() {
; -- end an iterative file writing session (close channels).
c64.CLRCHN()
c64.CLOSE(14)
}
; ---- other functions ----
sub status(ubyte drivenumber) -> uword {
; -- retrieve the disk drive's current status message
uword messageptr = &filename
@@ -353,18 +392,22 @@ _end rts
messageptr++
}
io_error:
@(messageptr) = 0
done:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15)
return filename
}
io_error:
filename = "?disk error"
goto done
}
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size
first_byte = 0 ; result var reuse
%asm {{
lda address
@@ -381,7 +424,6 @@ io_error:
plp
}}
first_byte = 0 ; result var reuse
if_cc
first_byte = c64.READST()==0
+43 -56
View File
@@ -244,25 +244,6 @@ randseed .proc
.pend
fast_randbyte .proc
; -- fast but bad 8-bit pseudo random number generator into A
lda _seed
beq _eor
asl a
beq _done ; if the input was $80, skip the EOR
bcc _done
_eor eor #$1d ; xor with magic value see below for possible values
_done sta _seed
rts
_seed .byte $3a
; possible 'magic' eor bytes are:
; $1d, $2b, $2d, $4d, $5f, $63, $65, $69
; $71, $87, $8d, $a9, $c3, $cf, $e7, $f5
.pend
randbyte .proc
; -- 8 bit pseudo random number generator into A (by just reusing randword)
jmp randword
@@ -271,45 +252,37 @@ randbyte .proc
randword .proc
; -- 16 bit pseudo random number generator into AY
magic_eor = $3f1d
; possible magic eor words are:
; $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109
; $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb
; rand64k ;Factors of 65535: 3 5 17 257
lda sr1+1
asl a
asl a
eor sr1+1
asl a
eor sr1+1
asl a
asl a
eor sr1+1
asl a
rol sr1 ;shift this left, "random" bit comes from low
rol sr1+1
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
lda sr2+1
asl a
eor sr2+1
asl a
asl a
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
rol sr2+1
lda sr1+1 ;can be left out
eor sr2+1 ;if you dont use
tay ;y as suggested
lda sr1 ;mix up lowbytes of SR1
eor sr2 ;and SR2 to combine both
rts
lda _seed
beq _lowZero ; $0000 and $8000 are special values to test for
sr1 .word $a55a
sr2 .word $7653
; Do a normal shift
asl _seed
lda _seed+1
rol a
bcc _noEor
_doEor ; high byte is in A
eor #>magic_eor
sta _seed+1
lda _seed
eor #<magic_eor
sta _seed
ldy _seed+1
rts
_lowZero lda _seed+1
beq _doEor ; High byte is also zero, so apply the EOR
; For speed, you could store 'magic' into 'seed' directly
; instead of running the EORs
; wasn't zero, check for $8000
asl a
beq _noEor ; if $00 is left after the shift, then it was $80
bcs _doEor ; else, do the EOR based on the carry bit as usual
_noEor sta _seed+1
tay
lda _seed
rts
_seed .word $2c9e
.pend
@@ -801,6 +774,13 @@ stack_mul_word_320 .proc
rts
.pend
stack_mul_word_640 .proc
; stackW = (stackLo * 2 * 320) (stackHi doesn't matter)
asl P8ESTACK_LO+1,x
jmp stack_mul_word_320
.pend
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
mul_byte_3 .proc
; A = A + A*2
@@ -1291,6 +1271,13 @@ mul_word_320 .proc
rts
.pend
mul_word_640 .proc
; AY = (A * 2 * 320) (msb in Y doesn't matter)
asl a
jmp mul_word_320
.pend
; ----------- end optimized multiplications -----------
+1 -1
View File
@@ -3,5 +3,5 @@
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
math {
%asminclude "library:math.asm", ""
%asminclude "library:math.asm"
}
-8
View File
@@ -387,14 +387,6 @@ func_sqrt16_into_A .proc
rts
.pend
func_fastrnd8_stack .proc
; -- put a random ubyte on the estack (using fast but bad RNG)
jsr math.fast_randbyte
sta P8ESTACK_LO,x
dex
rts
.pend
func_rnd_stack .proc
; -- put a random ubyte on the estack
jsr math.randbyte
+2 -2
View File
@@ -3,8 +3,8 @@
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
prog8_lib {
%asminclude "library:prog8_lib.asm", ""
%asminclude "library:prog8_funcs.asm", ""
%asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm"
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
+5 -5
View File
@@ -178,7 +178,7 @@ _found sty P8ZP_SCRATCH_B1
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depeding on wether string1 sorts before, equal or after string2.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%asm {{
@@ -190,8 +190,8 @@ _found sty P8ZP_SCRATCH_B1
}}
}
asmsub lower(uword st @AY) {
; Lowercases the petscii string in-place.
asmsub lower(uword st @AY) -> ubyte @Y {
; Lowercases the petscii string in-place. Returns length of the string.
; (for efficiency, non-letter characters > 128 will also not be left intact,
; but regular text doesn't usually contain those characters anyway.)
%asm {{
@@ -213,8 +213,8 @@ _done rts
}}
}
asmsub upper(uword st @AY) {
; Uppercases the petscii string in-place.
asmsub upper(uword st @AY) -> ubyte @Y {
; Uppercases the petscii string in-place. Returns length of the string.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
+1 -1
View File
@@ -1 +1 @@
6.4
7.0
+15 -6
View File
@@ -1,15 +1,13 @@
package prog8
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.multiple
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.parser.ParsingFailedError
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
@@ -42,6 +40,7 @@ private fun compileMain(args: Array<String>) {
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
@@ -57,6 +56,16 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
if(faultyOption!=null) {
System.err.println("Unknown command line option given: $faultyOption")
exitProcess(1)
}
val libdirs = libDirs.toMutableList()
if(libdirs.firstOrNull()!=".")
libdirs.add(0, ".")
if(watchMode==true) {
val watchservice = FileSystems.getDefault().newWatchService()
val allImportedFiles = mutableSetOf<Path>()
@@ -66,7 +75,7 @@ private fun compileMain(args: Array<String>) {
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
results.add(compilationResult)
}
@@ -103,7 +112,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
@@ -14,20 +14,20 @@ import prog8.compiler.target.ICompilationTarget
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
// A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value.
// This allows you to restart the program and have the same starting values of the variables
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
decl.value = null
else
else {
decl.value = decl.zeroElementValue()
}
}
}
return noModifications
@@ -68,30 +68,37 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
addedIfConditionVars.clear()
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine()
if (sub != null) {
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
val replaceVardecls =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(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.ReplaceNode(it, assign, scope)
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
val replacements = mutableListOf<IAstModification>()
val movements = mutableListOf<IAstModification.InsertFirst>()
for(decl in decls) {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, sub))
}
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
return replaceVardecls + moveVardeclsUp
return replacements + movements
}
return noModifications
}
@@ -139,7 +146,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// 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)
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
@@ -185,9 +192,55 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
(binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
throw CompilerException("if 0==X should have been swapped to if X==0")
// split the conditional expression into separate variables if the operand(s) is not simple.
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
// val modifications = mutableListOf<IAstModification>()
// if(!binExpr.left.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// if(!binExpr.right.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// return modifications
return noModifications
}
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> {
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
// val assign = Assignment(tgt, operand, operand.position)
// if(Pair(sub, varname) in addedIfConditionVars) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// return Triple(vardecl, false, assign)
// }
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
// return if (existing == null) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// Triple(vardecl, true, assign)
// } else {
// Triple(existing, false, assign)
// }
// }
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
@@ -213,8 +266,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
val arg1 = functionCallStatement.args[0]
val arg2 = functionCallStatement.args[1]
val dt1 = arg1.inferType(program).typeOrElse(DataType.STRUCT)
val dt2 = arg2.inferType(program).typeOrElse(DataType.STRUCT)
val dt1 = arg1.inferType(program).typeOrElse(DataType.UNDEFINED)
val dt2 = arg2.inferType(program).typeOrElse(DataType.UNDEFINED)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes)
return noModifications
@@ -287,9 +340,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement()
val dt = expr.indexer.indexExpr.inferType(program)
val register = if(dt.istype(DataType.UBYTE) || dt.istype(DataType.BYTE)) "r9L" else "r9"
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val target = AssignTarget(IdentifierReference(listOf("cx16", "r9"), expr.indexer.position), null, null, expr.indexer.position)
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
+64 -44
View File
@@ -70,6 +70,7 @@ fun compileProgram(filepath: Path,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
libdirs: List<String>,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
@@ -89,14 +90,14 @@ fun compileProgram(filepath: Path,
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, libdirs)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
@@ -165,14 +166,18 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
builtinFunctionReturnType(name, args, program)
}
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget): Triple<Program, CompilationOptions, List<Path>> {
private fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = compTarget.name
println("Compiler target: $compilationTargetName. Parsing...")
val importer = ModuleImporter()
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
bf.program = programAst
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
val importer = ModuleImporter(programAst, compTarget, compilationTargetName, libdirs)
importer.importModule(filepath)
errors.report()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
@@ -182,11 +187,11 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
importer.importLibraryModule(lib)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math", compTarget, compilationTargetName)
importer.importLibraryModule(programAst, "prog8_lib", compTarget, compilationTargetName)
importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib")
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
}
@@ -194,58 +199,59 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
as? Directive)?.args?.single()?.name?.uppercase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
as? Directive)?.args?.single()?.name?.uppercase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
as? Directive)?.args?.single()?.name?.uppercase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }
.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpoption == null)
if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
if (outputType != null && !OutputType.values().any { it.name == outputType }) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
if (launcherType != null && !LauncherType.values().any { it.name == launcherType }) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
)
}
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
errors.report()
@@ -253,40 +259,54 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
errors.report()
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.variousCleanups(programAst, errors)
errors.report()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
// optimize the parse tree
println("Optimizing...")
val remover = UnusedCodeRemover(programAst, errors, compTarget)
remover.visit(programAst)
remover.applyModifications()
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
remover.visit(programAst)
remover.applyModifications()
val inliner = SubroutineInliner(programAst, errors, options)
inliner.visit(programAst)
errors.report()
if(errors.noErrors()) {
inliner.applyModifications()
inliner.fixCallsToInlinedSubroutines()
val remover2 = UnusedCodeRemover(programAst, errors, compTarget)
remover2.visit(programAst)
remover2.applyModifications()
}
errors.report()
}
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.variousCleanups(programAst, errors)
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()
@@ -7,7 +7,7 @@ import prog8.parser.ParsingFailedError
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun isEmpty(): Boolean
fun noErrors(): Boolean
fun report()
}
@@ -53,5 +53,5 @@ internal class ErrorReporter: IErrorReporter {
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
override fun isEmpty() = messages.isEmpty()
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
}
+23 -1
View File
@@ -19,7 +19,29 @@ abstract class Zeropage(protected val options: CompilationOptions) {
val allowedDatatypes = NumericDatatypes
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty()
fun availableWords(): Int {
if(options.zeropage==ZeropageType.DONTUSE)
return 0
val words = free.windowed(2).filter { it[0] == it[1]-1 }
var nonOverlappingWordsCount = 0
var prevMsbLoc = -1
for(w in words) {
if(w[0]!=prevMsbLoc) {
nonOverlappingWordsCount++
prevMsbLoc = w[1]
}
}
return nonOverlappingWordsCount
}
fun hasWordAvailable(): Boolean {
if(options.zeropage==ZeropageType.DONTUSE)
return false
return free.windowed(2).any { it[0] == it[1] - 1 }
}
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
@@ -9,12 +9,15 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.ZeropageType
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import java.io.CharConversionException
import java.io.File
import java.util.*
internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions,
@@ -41,6 +44,11 @@ internal class AstChecker(private val program: Program,
}
}
if(compilerOptions.floats) {
if (compilerOptions.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
errors.err("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'", program.mainModule.position)
}
super.visit(program)
}
@@ -70,9 +78,9 @@ internal class AstChecker(private val program: Program,
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
val valueDt = returnStmt.value!!.inferType(program)
if(!valueDt.isKnown) {
errors.err("return value type mismatch", returnStmt.value!!.position)
errors.err("return value type mismatch or unknown symbol", returnStmt.value!!.position)
} else {
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT))
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.UNDEFINED))
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}", returnStmt.value!!.position)
}
}
@@ -80,12 +88,24 @@ internal class AstChecker(private val program: Program,
}
override fun visit(ifStatement: IfStatement) {
if(ifStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
if(!ifStatement.condition.inferType(program).isInteger())
errors.err("condition value should be an integer type", ifStatement.condition.position)
super.visit(ifStatement)
}
override fun visit(forLoop: ForLoop) {
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
if(range==null)
return
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0
if(step < -1.0) {
val limit = range.to.constValue(program)?.number?.toDouble()
if(limit==0.0 && range.from.constValue(program)==null)
errors.err("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping", forLoop.position)
}
}
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
errors.err("can only loop over an iterable type", forLoop.position)
@@ -98,11 +118,15 @@ internal class AstChecker(private val program: Program,
DataType.UBYTE -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
}
DataType.UWORD -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
}
DataType.BYTE -> {
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
@@ -118,7 +142,7 @@ internal class AstChecker(private val program: Program,
}
else -> errors.err("loop variable must be numeric type", forLoop.position)
}
if(errors.isEmpty()) {
if(errors.noErrors()) {
// check loop range values
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
@@ -140,6 +164,7 @@ internal class AstChecker(private val program: Program,
super.visit(forLoop)
}
override fun visit(jump: Jump) {
val ident = jump.identifier
if(ident!=null) {
@@ -190,6 +215,28 @@ internal class AstChecker(private val program: Program,
super.visit(label)
}
private fun hasReturnOrJump(scope: INameScope): Boolean {
class Searcher: IAstVisitor
{
var count=0
override fun visit(returnStmt: Return) {
count++
}
override fun visit(jump: Jump) {
count++
}
}
val s=Searcher()
for(stmt in scope.statements) {
stmt.accept(s)
if(s.count>0)
return true
}
return s.count > 0
}
override fun visit(subroutine: Subroutine) {
fun err(msg: String) = errors.err(msg, subroutine.position)
@@ -203,13 +250,6 @@ internal class AstChecker(private val program: Program,
if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names must be unique")
if(subroutine.inline) {
if (subroutine.containsDefinedVariables())
err("can't inline a subroutine that defines variables")
if (!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
err("can't inline a non-asm subroutine that has parameters")
}
super.visit(subroutine)
// user-defined subroutines can only have zero or one return type
@@ -219,7 +259,7 @@ internal class AstChecker(private val program: Program,
// subroutine must contain at least one 'return' or 'goto'
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
if(subroutine.statements.count { it is Return || it is Jump } == 0) {
if(!hasReturnOrJump(subroutine)) {
if (subroutine.amountOfRtsInAsm() == 0) {
if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible.
@@ -348,13 +388,13 @@ internal class AstChecker(private val program: Program,
}
override fun visit(untilLoop: UntilLoop) {
if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
if(!untilLoop.condition.inferType(program).isInteger())
errors.err("condition value should be an integer type", untilLoop.condition.position)
super.visit(untilLoop)
}
override fun visit(whileLoop: WhileLoop) {
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
if(!whileLoop.condition.inferType(program).isInteger())
errors.err("condition value should be an integer type", whileLoop.condition.position)
super.visit(whileLoop)
}
@@ -380,37 +420,13 @@ internal class AstChecker(private val program: Program,
}
}
val targetIdent = assignment.target.identifier
if(targetIdent!=null) {
val targetVar = targetIdent.targetVarDecl(program)
if(targetVar?.struct != null) {
val sourceStructLv = assignment.value as? ArrayLiteralValue
if (sourceStructLv != null) {
if (sourceStructLv.value.size != targetVar.struct?.numberOfElements)
errors.err("number of elements doesn't match struct definition", sourceStructLv.position)
} else {
val sourceIdent = assignment.value as? IdentifierReference
if (sourceIdent != null) {
val sourceVar = sourceIdent.targetVarDecl(program)
if (sourceVar?.struct != null) {
if (sourceVar.struct !== targetVar.struct)
errors.err("assignment of different struct types", assignment.position)
} else if(sourceVar?.isArray==true) {
if((sourceVar.value as ArrayLiteralValue).value.size != targetVar.struct?.numberOfElements)
errors.err("number of elements doesn't match struct definition", sourceVar.position)
}
}
}
}
}
val targetDt = assignment.target.inferType(program)
val valueDt = assignment.value.inferType(program)
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
if(targetDt.isIterable())
errors.err("cannot assign value to string or array", assignment.value.position)
else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD)))
errors.err("value's type doesn't match target", assignment.value.position)
errors.err("type of value doesn't match target", assignment.value.position)
}
if(assignment.value is TypecastExpression) {
@@ -480,12 +496,8 @@ internal class AstChecker(private val program: Program,
override fun visit(addressOf: AddressOf) {
val variable=addressOf.identifier.targetVarDecl(program)
if(variable!=null
&& variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY
&& variable.struct == null
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
errors.err("invalid pointer-of operand type", addressOf.position)
if(variable!=null && variable.type==VarDeclType.CONST)
errors.err("invalid pointer-of operand type", addressOf.position)
super.visit(addressOf)
}
@@ -527,17 +539,6 @@ internal class AstChecker(private val program: Program,
when(decl.type) {
VarDeclType.VAR, VarDeclType.CONST -> {
if(decl.datatype==DataType.STRUCT) {
if(decl.struct==null)
throw FatalAstException("struct vardecl should be linked to its struct $decl")
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
err("struct can not be in zeropage")
}
if(decl.struct!=null) {
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
err("struct can not be in zeropage")
}
when(decl.value) {
null -> {
// a vardecl without an initial value, don't bother with it
@@ -547,30 +548,8 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
}
is ArrayLiteralValue -> {
if(decl.datatype==DataType.STRUCT) {
val struct = decl.struct!!
val structLv = decl.value as ArrayLiteralValue
if(struct.numberOfElements != structLv.value.size) {
errors.err("struct value has incorrect number of elements", structLv.position)
return
}
for(value in structLv.value.zip(struct.statements)) {
val memberdecl = value.second as VarDecl
val constValue = value.first.constValue(program)
if(constValue==null) {
errors.err("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position)
return
}
val memberDt = memberdecl.datatype
if(!checkValueTypeAndRange(memberDt, constValue)) {
errors.err("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position)
return
}
}
} else {
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
}
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
checkValueTypeAndRangeArray(decl.datatype, arraySpec, decl.value as ArrayLiteralValue)
}
is NumericLiteralValue -> {
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
@@ -585,8 +564,9 @@ internal class AstChecker(private val program: Program,
}
}
VarDeclType.MEMORY -> {
if(decl.arraysize!=null) {
val arraySize = decl.arraysize!!.constIndex() ?: 1
val arraysize = decl.arraysize
if(arraysize!=null) {
val arraySize = arraysize.constIndex() ?: 1
when(decl.datatype) {
DataType.ARRAY_B, DataType.ARRAY_UB ->
if(arraySize > 256)
@@ -600,10 +580,9 @@ internal class AstChecker(private val program: Program,
else -> {}
}
}
if(decl.value is NumericLiteralValue) {
val value = decl.value as NumericLiteralValue
if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) {
val numvalue = decl.value as? NumericLiteralValue
if(numvalue!=null) {
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
}
} else {
@@ -614,23 +593,16 @@ internal class AstChecker(private val program: Program,
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR) {
if(decl.datatype==DataType.STRUCT) {
val valueIdt = declValue.inferType(program)
if(!valueIdt.isKnown)
throw AstException("unknown dt")
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
if(valueDt !in ArrayDatatypes)
err("initialisation of struct should be with array value", declValue.position)
} else if (!declValue.inferType(program).istype(decl.datatype)) {
if (!declValue.inferType(program).istype(decl.datatype)) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
}
}
// array length limits and constant lenghts
if(decl.isArray) {
val length = decl.arraysize!!.constIndex()
val length = decl.arraysize?.constIndex()
if(length==null)
err("array length must be a constant")
err("array length must be known at compile-time")
else {
when (decl.datatype) {
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
@@ -712,20 +684,20 @@ internal class AstChecker(private val program: Program,
}
"%breakpoint" -> {
if(directive.parent !is INameScope || directive.parent is Module)
err("this directive may only occur in a block")
err("this directive can't be used here")
if(directive.args.isNotEmpty())
err("invalid breakpoint directive, expected no arguments")
}
"%asminclude" -> {
if(directive.parent !is INameScope || directive.parent is Module)
err("this directive may only occur in a block")
if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
err("this directive can't be used here")
if(directive.args.size!=1 || directive.args[0].str==null)
err("invalid asminclude directive, expected argument: \"filename\"")
checkFileExists(directive, directive.args[0].str!!)
}
"%asmbinary" -> {
if(directive.parent !is INameScope || directive.parent is Module)
err("this directive may only occur in a block")
err("this directive can't be used here")
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
if(directive.args.isEmpty()) err(errormsg)
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
@@ -765,11 +737,11 @@ internal class AstChecker(private val program: Program,
override fun visit(array: ArrayLiteralValue) {
if(array.type.isKnown) {
if (!compilerOptions.floats && array.type.typeOrElse(DataType.STRUCT) in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
if (!compilerOptions.floats && array.type.typeOrElse(DataType.UNDEFINED) in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.err("floating point used, but that is not enabled via options", array.position)
}
val arrayspec = ArrayIndex.forArray(array)
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.UNDEFINED), arrayspec, array)
}
fun isPassByReferenceElement(e: Expression): Boolean {
@@ -793,6 +765,13 @@ internal class AstChecker(private val program: Program,
override fun visit(string: StringLiteralValue) {
checkValueTypeAndRangeString(DataType.STR, string)
try {
compTarget.encodeString(string.value, string.altEncoding)
} catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode string", string.position)
}
super.visit(string)
}
@@ -801,7 +780,7 @@ internal class AstChecker(private val program: Program,
if(!idt.isKnown)
return // any error should be reported elsewhere
val dt = idt.typeOrElse(DataType.STRUCT)
val dt = idt.typeOrElse(DataType.UNDEFINED)
if(expr.operator=="-") {
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position)
@@ -826,8 +805,8 @@ internal class AstChecker(private val program: Program,
if(!leftIDt.isKnown || !rightIDt.isKnown)
return // hopefully this error will be detected elsewhere
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
val leftDt = leftIDt.typeOrElse(DataType.UNDEFINED)
val rightDt = rightIDt.typeOrElse(DataType.UNDEFINED)
when(expr.operator){
"/", "%" -> {
@@ -1028,7 +1007,7 @@ internal class AstChecker(private val program: Program,
errors.err("swap requires 2 variables, not constant value(s)", position)
else if(args[0] isSameAs args[1])
errors.err("swap should have 2 different args", position)
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
else if(!dt1.isNumeric())
errors.err("swap requires args of numerical type", position)
}
else if(target.name=="all" || target.name=="any") {
@@ -1059,7 +1038,7 @@ internal class AstChecker(private val program: Program,
ident = fcall.args[0] as? IdentifierReference
}
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].toUpperCase())
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].uppercase())
val same = params.filter { it.value.registerOrPair==reg }
for(s in same) {
if(s.index!=arg.index) {
@@ -1133,8 +1112,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(whenStatement: WhenStatement) {
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
if(conditionType !in IntegerDatatypes)
if(!whenStatement.condition.inferType(program).isInteger())
errors.err("when condition must be an integer value", whenStatement.position)
val tally = mutableSetOf<Int>()
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
@@ -1165,7 +1143,7 @@ internal class AstChecker(private val program: Program,
when {
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> errors.err("choice value datatype differs from condition value", whenChoice.position)
constvalue.type != conditionType.typeOrElse(DataType.UNDEFINED) -> errors.err("choice value datatype differs from condition value", whenChoice.position)
}
}
} else {
@@ -1175,24 +1153,6 @@ internal class AstChecker(private val program: Program,
super.visit(whenChoice)
}
override fun visit(structDecl: StructDecl) {
// a struct can only contain 1 or more vardecls and can not be nested
if(structDecl.statements.isEmpty())
errors.err("struct must contain at least one member", structDecl.position)
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl==null)
errors.err("struct can only contain variable declarations", structDecl.position)
else {
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
errors.err("struct can not contain zeropage members", decl.position)
if(decl.datatype !in NumericDatatypes)
errors.err("structs can only contain numerical types", decl.position)
}
}
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
val targetStatement = target.targetStatement(program)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
@@ -1216,8 +1176,7 @@ internal class AstChecker(private val program: Program,
else false
}
private fun checkValueTypeAndRangeArray(targetDt: DataType, struct: StructDecl?,
arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
private fun checkValueTypeAndRangeArray(targetDt: DataType, arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
fun err(msg: String) : Boolean {
errors.err(msg, value.position)
return false
@@ -1290,22 +1249,6 @@ internal class AstChecker(private val program: Program,
}
return err("invalid float array initialization value ${value.type}, expected $targetDt")
}
DataType.STRUCT -> {
if(value.type.typeOrElse(DataType.STRUCT) in ArrayDatatypes) {
if(value.value.size != struct!!.numberOfElements)
return err("number of values is not the same as the number of members in the struct")
for(elt in value.value.zip(struct.statements)) {
val vardecl = elt.second as VarDecl
val valuetype = elt.first.inferType(program)
if (!valuetype.isKnown || valuetype isNotAssignableTo vardecl.datatype) {
errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)
return false
}
}
return true
}
return false
}
else -> return false
}
}
@@ -1408,15 +1351,6 @@ internal class AstChecker(private val program: Program,
DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD
DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype== DataType.STR
DataType.STRUCT -> {
if(sourceDatatype==DataType.STRUCT) {
val structLv = sourceValue as ArrayLiteralValue
val numValues = structLv.value.size
val targetstruct = target.identifier!!.targetVarDecl(program)!!.struct!!
return targetstruct.numberOfElements == numValues
}
false
}
else -> {
errors.err("cannot assign new value to variable of type $targetDatatype", position)
false
@@ -1430,10 +1364,15 @@ internal class AstChecker(private val program: Program,
errors.err("cannot assign word to byte, use msb() or lsb()?", position)
}
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
errors.err("cannot assign float to ${targetDatatype.name.lowercase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
else {
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)
errors.err(
"cannot assign ${sourceDatatype.name.lowercase()} to ${
targetDatatype.name.lowercase(
Locale.getDefault()
)
}", position)
}
@@ -17,13 +17,20 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IEr
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
fixer.visit(this)
fixer.applyModifications()
while(errors.noErrors() && fixer.applyModifications()>0) {
fixer.visit(this)
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
reorder.applyModifications()
if(errors.noErrors()) {
reorder.applyModifications()
reorder.visit(this)
if(errors.noErrors())
reorder.applyModifications()
}
}
internal fun Program.addTypecasts(errors: IErrorReporter) {
@@ -37,12 +44,12 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
checker2.visit(this)
if(errors.isEmpty()) {
if(errors.noErrors()) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
@@ -56,12 +63,14 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompi
}
}
internal fun Program.variousCleanups() {
val process = VariousCleanups()
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
val process = VariousCleanups(program, errors)
process.visit(this)
process.applyModifications()
if(errors.noErrors())
process.applyModifications()
}
internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
@@ -69,29 +78,27 @@ internal fun Program.moveMainAndStartToFirst() {
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint()
if(start!=null) {
val mod = start.definingModule()
val block = start.definingBlock()
if(!modules.remove(mod))
throw FatalAstException("module wrong")
modules.add(0, mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
val mod = start.definingModule()
val block = start.definingBlock()
if(!modules.remove(mod))
throw FatalAstException("module wrong")
modules.add(0, mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
}
@@ -2,11 +2,7 @@ package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
@@ -66,29 +62,6 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
if(decl.datatype==DataType.STRUCT) {
if (decl.structHasBeenFlattened)
return super.visit(decl) // don't do this multiple times
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 })
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if (decl.value is NumericLiteralValue) {
errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl)
}
if (decl.value != null && decl.value !is ArrayLiteralValue) {
errors.err("initializing a struct requires array 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)
@@ -116,7 +89,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay.
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet()
@@ -128,14 +101,17 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, subroutine.position, sub)
val block = program.allBlocks().firstOrNull { it.name==name }
if(block!=null)
nameError(name, subroutine.position, block)
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
if(subroutine.name == subroutine.definingBlock().name) {
// subroutines cannot have the same name as their enclosing block,
// because this causes symbol scoping issues in the resulting assembly source
nameError(subroutine.name, subroutine.position, subroutine.definingBlock())
}
}
super.visit(subroutine)
@@ -169,14 +145,4 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
super.visit(string)
}
override fun visit(structDecl: StructDecl) {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
errors.err("structs can only contain numerical types", decl.position)
}
super.visit(structDecl)
}
}
@@ -3,33 +3,16 @@ package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.ParameterVarDecl
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
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-kernal subroutines and non-asm parameters:
@@ -83,6 +66,10 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
}
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
val rightStrval = expr.right as? StringLiteralValue
val leftStrval = expr.left as? StringLiteralValue
@@ -109,3 +96,25 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
}
}
}
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
return emptyList()
}
@@ -13,7 +13,6 @@ import prog8.ast.walk.IAstModification
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
@@ -39,7 +38,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
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))
val litval2 = array.cast(arrayDt.typeOrElse(DataType.UNDEFINED))
if(litval2!=null) {
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
@@ -4,7 +4,8 @@ import prog8.ast.IFunctionCall
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
@@ -21,12 +22,10 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
// - 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) struct value assignment is expanded into several struct member assignments.
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
// - 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")
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
@@ -87,27 +86,17 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
return noModifications
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
// but the current assembly code generator for IF statements now also depends on it so we do it here regardless of optimization.)
if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null)
return listOf(IAstModification.SwapOperands(expr))
// when using a simple bit shift and assigning it to a variable of a different type,
// try to make the bit shifting 'wide enough' to fall into the variable's type.
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
@@ -117,7 +106,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
is Assignment -> {
val targetDt = parent.target.inferType(program)
if(leftDt != targetDt) {
val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.STRUCT), true, parent.position)
val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.UNDEFINED), true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
@@ -197,43 +186,13 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
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
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
if(decl.datatype!=DataType.FLOAT) {
decl.value = null
decl.allowInitializeWithZero = false
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope())
)
}
}
}
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program)
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
if(targetType.isArray() && valueType.isArray() ) {
if (assignment.value is ArrayLiteralValue) {
errors.err("cannot assign non-const array value, use separate assignment per field", assignment.position)
} else {
return copyStructValue(assignment)
}
}
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
if (assignment.value is ArrayLiteralValue) {
errors.err("cannot assign non-const array value, use separate assignment per element", assignment.position)
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
} else {
return copyArrayValue(assignment)
}
@@ -313,7 +272,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
errors.err("element type mismatch", assign.position)
}
if(!errors.isEmpty())
if(!errors.noErrors())
return noModifications
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
@@ -327,73 +286,4 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
}
private fun copyStructValue(structAssignment: Assignment): List<IAstModification> {
val identifier = structAssignment.target.identifier!!
val targetVar = identifier.targetVarDecl(program)!!
val struct = targetVar.struct!!
when (structAssignment.value) {
is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program)!!
val memsize = struct.memsize(program.memsizer)
when {
sourceVar.struct!=null -> {
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
errors.err("struct type mismatch", structAssignment.position)
return listOf()
}
if(struct.statements.size!=sourceStruct.statements.size) {
errors.err("struct element count mismatch", structAssignment.position)
return listOf()
}
if(memsize!=sourceStruct.memsize(program.memsizer)) {
errors.err("memory size mismatch", structAssignment.position)
return listOf()
}
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
mutableListOf(
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
AddressOf(identifier, structAssignment.position),
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
),
true,
structAssignment.position
)
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
}
sourceVar.isArray -> {
val array = sourceVar.value as ArrayLiteralValue
if(struct.statements.size!=array.value.size) {
errors.err("struct element count mismatch", structAssignment.position)
return listOf()
}
if(memsize!=array.memsize(program.memsizer)) {
errors.err("memory size mismatch", structAssignment.position)
return listOf()
}
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
mutableListOf(
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
AddressOf(identifier, structAssignment.position),
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
),
true,
structAssignment.position
)
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
}
else -> {
throw FatalAstException("can only assign arrays or structs to structs")
}
}
}
is ArrayLiteralValue -> {
throw IllegalArgumentException("not going to do a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
}
}
@@ -18,16 +18,18 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
* (this includes function call arguments)
*/
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
if(decl.type==VarDeclType.VAR && declValue!=null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
// don't add a typecast on an array initializer value
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
if(valueDt.isInteger() && decl.datatype in ArrayDatatypes)
return noModifications
// don't add a typecast if the initializer value is inherently not assignable
if(valueDt isNotAssignableTo decl.datatype)
return noModifications
return listOf(IAstModification.ReplaceNode(
@@ -45,7 +47,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
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), expr.left, expr.right)
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.UNDEFINED), rightDt.typeOrElse(DataType.UNDEFINED), expr.left, expr.right)
if(toFix!=null) {
return when {
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
@@ -64,8 +66,8 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val valueItype = assignment.value.inferType(program)
val targetItype = assignment.target.inferType(program)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
val targettype = targetItype.typeOrElse(DataType.UNDEFINED)
val valuetype = valueItype.typeOrElse(DataType.UNDEFINED)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
@@ -124,7 +126,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val argtype = argItype.typeOrElse(DataType.UNDEFINED)
val requiredType = pair.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
@@ -157,7 +159,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
func.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val argtype = argItype.typeOrElse(DataType.UNDEFINED)
if (pair.first.possibleDatatypes.all { argtype != it }) {
for (possibleType in pair.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
@@ -3,18 +3,17 @@ package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
@@ -32,21 +31,12 @@ internal class VariousCleanups: AstWalker() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
scope.statements.forEach { it.parent = into as Node }
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
@@ -70,4 +60,62 @@ internal class VariousCleanups: AstWalker() {
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.parent!==parent)
throw FatalAstException("parent node mismatch at $typecast")
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
val sourceDt = typecast.expression.inferType(program)
if(sourceDt.istype(typecast.type))
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.parent!==parent)
throw FatalAstException("parent node mismatch at $subroutine")
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.parent!==parent)
throw FatalAstException("parent node mismatch at $assignment")
return noModifications
}
override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
if(assignTarget.parent!==parent)
throw FatalAstException("parent node mismatch at $assignTarget")
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent!==parent)
throw FatalAstException("parent node mismatch at $decl")
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.parent!==parent)
throw FatalAstException("parent node mismatch at $scope")
return noModifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
if(returnStmt.parent!==parent)
throw FatalAstException("parent node mismatch at $returnStmt")
return noModifications
}
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
if(identifier.parent!==parent)
throw FatalAstException("parent node mismatch at $identifier")
return noModifications
}
}
@@ -44,7 +44,7 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0)
return "argument ${firstUnknownDt+1} invalid argument type"
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val argtypes = argITypes.map { it.typeOrElse(DataType.UNDEFINED) }
val target = call.target.targetStatement(program)
if (target is Subroutine) {
if(call.args.size != target.parameters.size)
@@ -4,7 +4,6 @@ import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException
import kotlin.math.*
@@ -105,7 +104,6 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
// normal functions follow:
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
@@ -138,12 +136,13 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
FSignature("fastrnd8" , false, emptyList(), DataType.UBYTE),
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
FSignature("memory" , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD),
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
FSignature("callfar" , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null),
FSignature("callrom" , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null),
)
@@ -154,7 +153,7 @@ fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble()
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
fun builtinSum(array: List<Number>): Number = array.sumOf { it.toDouble() }
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
@@ -165,7 +164,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
fun datatypeFromIterableArg(arglist: Expression): DataType {
if(arglist is ArrayLiteralValue) {
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.UNDEFINED)}.toSet()
if(dt.any { it !in NumericDatatypes }) {
throw FatalAstException("fuction $function only accepts array of numeric values")
}
@@ -179,9 +178,9 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
val idt = arglist.inferType(program)
if(!idt.isKnown)
throw FatalAstException("couldn't determine type of iterable $arglist")
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
return when(val dt = idt.typeOrElse(DataType.UNDEFINED)) {
DataType.STR, in NumericDatatypes -> dt
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
in ArrayDatatypes -> ArrayToElementTypes.getValue(dt)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
}
}
@@ -196,7 +195,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
return when (function) {
"abs" -> {
val dt = args.single().inferType(program)
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return if(dt.isNumeric())
dt
else
InferredTypes.InferredType.unknown()
@@ -205,7 +204,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
when(val dt = datatypeFromIterableArg(args.single())) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in NumericDatatypes -> InferredTypes.knownFor(dt)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(dt))
else -> InferredTypes.unknown()
}
}
@@ -288,28 +287,6 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
}
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("offsetof requires one argument", position)
val idref = args[0] as? IdentifierReference
?: throw SyntaxError("offsetof argument should be an identifier", position)
val vardecl = idref.targetVarDecl(program)!!
val struct = vardecl.struct
if (struct == null || vardecl.datatype == DataType.STRUCT)
throw SyntaxError("offsetof can only be used on struct members", position)
val membername = idref.nameInSource.last()
var offset = 0
for(member in struct.statements) {
if((member as VarDecl).name == membername)
return NumericLiteralValue(DataType.UBYTE, offset, position)
offset += memsizer.memorySize(member.datatype)
}
throw SyntaxError("undefined struct member", position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
@@ -322,23 +299,14 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) = NumericLiteralValue(DataType.UBYTE, target.memsize(memsizer), position)
return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
dt.isArray() -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
val elementDt = ArrayToElementTypes.getValue(dt.typeOrElse(DataType.UNDEFINED))
numericLiteral(memsizer.memorySize(elementDt) * length, position)
}
dt.istype(DataType.STRUCT) -> {
when (target) {
is VarDecl -> structSize(target.struct!!)
is StructDecl -> structSize(target)
else -> throw CompilerException("weird struct type $target")
}
}
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.UNDEFINED)), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
@@ -373,7 +341,6 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
}
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
else -> throw CompilerException("weird datatype")
}
@@ -11,9 +11,10 @@ import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.io.CharConversionException
import java.nio.file.Path
@@ -70,9 +71,17 @@ internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
try {
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
@@ -89,9 +98,17 @@ internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
try {
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
@@ -39,7 +39,7 @@ internal object C64MachineDefinition: IMachineDefinition {
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "$programName.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
@@ -1,4 +1,4 @@
package prog8.compiler.target.c64
package prog8.compiler.target.cbm
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
@@ -1,5 +1,6 @@
package prog8.compiler.target.c64
package prog8.compiler.target.cbm
import prog8.ast.antlr.escape
import java.io.CharConversionException
object Petscii {
@@ -172,7 +173,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@@ -236,7 +237,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@@ -431,7 +432,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@@ -495,7 +496,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@@ -626,7 +627,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
@@ -885,7 +886,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
@@ -1049,51 +1050,90 @@ object Petscii {
private val encodingScreencodeLowercase = decodingScreencodeLowercase.withIndex().associate{it.value to it.index}
private val encodingScreencodeUppercase = decodingScreencodeUppercase.withIndex().associate{it.value to it.index}
private fun replaceSpecial(chr: Char): Char =
// characters often used in C like source code can be translated with a little bit of fantasy:
when(chr) {
'^' -> '↑'
'_' -> '▁'
'{' -> '┤'
'}' -> '├'
'|' -> '│'
'\\' -> '╲'
else -> chr
}
fun encodePetscii(text: String, lowercase: Boolean = false): List<Short> {
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
return text.map {
val petscii = lookup[it]
petscii?.toShort() ?: when (it) {
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
return screencode?.toShort() ?: when (chr) {
'\u0000' -> 0.toShort()
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(it.toInt() - 0x8000).toShort()
(chr.code - 0x8000).toShort()
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
throw CharConversionException("no ${case}Petscii character for '${escape(chr.toString())}' (${chr.code})")
}
}
}
return text.map{
try {
encodeChar(it, lowercase)
} catch (x: CharConversionException) {
encodeChar(it, !lowercase)
}
}
}
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
val decodeTable = if(lowercase) decodingPetsciiLowercase else decodingPetsciiUppercase
return petscii.map { decodeTable[it.toInt()] }.joinToString("")
return petscii.map {
val code = it.toInt()
try {
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
} catch(x: CharConversionException) {
if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code]
}
}.joinToString("")
}
fun encodeScreencode(text: String, lowercase: Boolean = false): List<Short> {
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
return text.map{
val screencode = lookup[it]
screencode?.toShort() ?: when (it) {
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toShort() ?: when (chr) {
'\u0000' -> 0.toShort()
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(it.toInt() - 0x8000).toShort()
(chr.code - 0x8000).toShort()
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
throw CharConversionException("no ${case}Screencode character for '${escape(chr.toString())}' (${chr.code})")
}
}
}
return text.map{
try {
encodeChar(it, lowercase)
} catch (x: CharConversionException) {
encodeChar(it, !lowercase)
}
}
}
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
val decodeTable = if(lowercase) decodingScreencodeLowercase else decodingScreencodeUppercase
return screencode.map { decodeTable[it.toInt()] }.joinToString("")
return screencode.map {
val code = it.toInt()
try {
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
} catch (x: CharConversionException) {
if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code]
}
}.joinToString("")
}
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short {
@@ -9,11 +9,10 @@ import prog8.compiler.*
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.FSignature
import prog8.compiler.target.*
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cbm.AssemblyProgram
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import java.io.CharConversionException
import prog8.optimizer.CallGraph
import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDate
@@ -31,7 +30,8 @@ internal class AsmGen(private val program: Program,
// for expressions and augmented assignments:
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320)
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
private val callGraph = CallGraph(program)
private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
@@ -128,7 +128,7 @@ internal class AsmGen(private val program: Program,
out("* = ${program.actualLoadAddress.toHex()}")
val year = LocalDate.now().year
out(" .word (+), $year")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
if(!options.noSysInit)
@@ -157,7 +157,16 @@ internal class AsmGen(private val program: Program,
pha""")
}
jmp("main.start")
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
when(compTarget.name) {
Cx16Target.name -> {
if(options.floats)
out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
out(" jsr main.start | lda #4 | sta $01 | rts")
}
C64Target.name -> out(" jsr main.start | lda #31 | sta $01 | rts")
else -> jmp("main.start")
}
}
private fun slaballocations() {
@@ -246,15 +255,6 @@ internal class AsmGen(private val program: Program,
} else assemblyLines.add(fragment)
}
private fun encode(str: String, altEncoding: Boolean): List<Short> {
try {
val bytes = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return bytes.plus(0)
} catch(x: CharConversionException) {
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
}
}
private fun zeropagevars2asm(statements: List<Statement>) {
out("; vars allocated on zeropage")
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
@@ -290,10 +290,9 @@ internal class AsmGen(private val program: Program,
DataType.UWORD -> out("$name\t.word 0")
DataType.WORD -> out("$name\t.sint 0")
DataType.FLOAT -> out("$name\t.byte 0,0,0,0,0 ; float")
DataType.STRUCT -> {} // is flattened
DataType.STR -> {
val str = decl.value as StringLiteralValue
outputStringvar(decl, encode(str.value, str.altEncoding))
outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0))
}
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(decl)
@@ -352,6 +351,9 @@ internal class AsmGen(private val program: Program,
for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
else -> {
throw AssemblyError("weird dt")
}
}
}
@@ -379,38 +381,28 @@ internal class AsmGen(private val program: Program,
out("\n; non-zeropage variables")
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
// first output the flattened struct member variables *in order*
// after that, the other variables sorted by their datatype
val (structMembers, normalVars) = vars.partition { it.struct!=null }
structMembers.forEach { vardecl2asm(it) }
// special treatment for string types: merge strings that are identical
val encodedstringVars = normalVars
val encodedstringVars = vars
.filter {it.datatype == DataType.STR }
.map {
val str = it.value as StringLiteralValue
it to encode(str.value, str.altEncoding)
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
}
.groupBy({it.second}, {it.first})
for((encoded, variables) in encodedstringVars) {
variables.dropLast(1).forEach { out(it.name) }
val lastvar = variables.last()
outputStringvar(lastvar, encoded)
for((decl, variables) in encodedstringVars) {
outputStringvar(decl, variables)
}
// non-string variables
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
if(it.makeScopedName(it.name) !in allocatedZeropageVariables)
vardecl2asm(it)
}
}
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
val sv = lastvar.value as StringLiteralValue
private fun outputStringvar(strdecl: VarDecl, bytes: List<Short>) {
val sv = strdecl.value as StringLiteralValue
val altEncoding = if(sv.altEncoding) "@" else ""
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
@@ -437,10 +429,10 @@ internal class AsmGen(private val program: Program,
"$" + it.number.toInt().toString(16).padStart(4, '0')
}
is AddressOf -> {
it.identifier.firstStructVarName(program) ?: asmSymbolName(it.identifier)
asmSymbolName(it.identifier)
}
is IdentifierReference -> {
it.firstStructVarName(program) ?: asmSymbolName(it)
asmSymbolName(it)
}
else -> throw AssemblyError("weird array elt dt")
}
@@ -502,29 +494,55 @@ internal class AsmGen(private val program: Program,
}
internal fun asmSymbolName(identifier: IdentifierReference): String {
return if(identifier.memberOfStruct(program)!=null) {
val name = identifier.targetVarDecl(program)!!.name
fixNameSymbols(name)
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs")
return identifier.nameInSource.joinToString(".")
val tgt2 = identifier.targetStatement(program)
if(tgt2==null && (identifier.nameInSource[0].startsWith("_prog8") || identifier.nameInSource[0].startsWith("prog8")))
return identifier.nameInSource.joinToString(".")
val target = identifier.targetStatement(program)!!
val targetScope = target.definingSubroutine()
val identScope = identifier.definingSubroutine()
return if(targetScope !== identScope) {
val scopedName = getScopedSymbolNameForTarget(identifier.nameInSource.last(), target)
if(target is Label) {
val last = scopedName.removeLast()
scopedName.add("_$last")
}
fixNameSymbols(scopedName.joinToString("."))
} else {
fixNameSymbols(identifier.nameInSource.joinToString("."))
if(target is Label) {
val scopedName = identifier.nameInSource.toMutableList()
val last = scopedName.removeLast()
scopedName.add("_$last")
fixNameSymbols(scopedName.joinToString("."))
}
else fixNameSymbols(identifier.nameInSource.joinToString("."))
}
}
internal fun asmVariableName(identifier: IdentifierReference) =
fixNameSymbols(identifier.nameInSource.joinToString("."))
private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> {
val scopedName = mutableListOf(actualName)
var node: Node = target
while (node !is Block) {
node = node.parent
if(node is INameScope) {
scopedName.add(0, node.name)
}
}
return scopedName
}
internal fun asmSymbolName(regs: RegisterOrPair): String =
if(regs in Cx16VirtualRegisters)
"cx16." + regs.toString().toLowerCase()
if (regs in Cx16VirtualRegisters)
"cx16." + regs.toString().lowercase()
else
throw AssemblyError("no symbol name for register $regs")
internal fun asmVariableName(identifier: IdentifierReference): String {
return if(identifier.memberOfStruct(program)!=null) {
val name = identifier.targetVarDecl(program)!!.name
fixNameSymbols(name)
} else {
fixNameSymbols(identifier.nameInSource.joinToString("."))
}
}
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
internal fun asmVariableName(name: String) = fixNameSymbols(name)
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
@@ -533,38 +551,48 @@ internal class AsmGen(private val program: Program,
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda (P8ZP_SCRATCH_W1)""")
Pair(false, sourceName)
val target = pointervar.targetStatement(program)
when (target) {
is Label -> {
val sourceName = asmSymbolName(pointervar)
out(" lda $sourceName")
return Pair(true, sourceName)
}
} else {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" ldy #0 | lda ($sourceName),y")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y""")
Pair(false, sourceName)
is VarDecl -> {
val sourceName = asmVariableName(pointervar)
val scopedName = target.makeScopedName(target.name)
if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda (P8ZP_SCRATCH_W1)""")
Pair(false, sourceName)
}
} else {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" ldy #0 | lda ($sourceName),y")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y""")
Pair(false, sourceName)
}
}
}
else -> throw AssemblyError("invalid pointervar")
}
}
@@ -671,7 +699,7 @@ internal class AsmGen(private val program: Program,
when(stmt) {
is ParameterVarDecl -> { /* subroutine parameter vardecls don't get any special treatment here */ }
is VarDecl -> translate(stmt)
is StructDecl, is NopStatement -> {}
is NopStatement -> {}
is Directive -> translate(stmt)
is Return -> translate(stmt)
is Subroutine -> translateSubroutine(stmt)
@@ -708,14 +736,16 @@ internal class AsmGen(private val program: Program,
}
}
internal fun loadScaledArrayIndexIntoRegister(expr: ArrayIndexedExpression,
elementDt: DataType,
register: CpuRegister,
addOneExtra: Boolean=false) {
val reg = register.toString().toLowerCase()
internal fun loadScaledArrayIndexIntoRegister(
expr: ArrayIndexedExpression,
elementDt: DataType,
register: CpuRegister,
addOneExtra: Boolean = false
) {
val reg = register.toString().lowercase()
val indexnum = expr.indexer.constIndex()
if(indexnum!=null) {
val indexValue = indexnum * compTarget.memorySize(elementDt) + if(addOneExtra) 1 else 0
if (indexnum != null) {
val indexValue = indexnum * compTarget.memorySize(elementDt) + if (addOneExtra) 1 else 0
out(" ld$reg #$indexValue")
return
}
@@ -724,35 +754,40 @@ internal class AsmGen(private val program: Program,
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.indexer.position}")
val indexName = asmVariableName(indexVar)
if(addOneExtra) {
if (addOneExtra) {
// add 1 to the result
when(elementDt) {
when (elementDt) {
in ByteDatatypes -> {
out(" ldy $indexName | iny")
when(register) {
when (register) {
CpuRegister.A -> out(" tya")
CpuRegister.X -> out(" tyx")
CpuRegister.Y -> {}
CpuRegister.Y -> {
}
}
}
in WordDatatypes -> {
out(" lda $indexName | sec | rol a")
when(register) {
CpuRegister.A -> {}
when (register) {
CpuRegister.A -> {
}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(compTarget.memorySize(DataType.FLOAT)==5)
out("""
require(compTarget.memorySize(DataType.FLOAT) == 5)
out(
"""
lda $indexName
asl a
asl a
sec
adc $indexName""")
when(register) {
CpuRegister.A -> {}
adc $indexName"""
)
when (register) {
CpuRegister.A -> {
}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
@@ -760,26 +795,30 @@ internal class AsmGen(private val program: Program,
else -> throw AssemblyError("weird dt")
}
} else {
when(elementDt) {
when (elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in WordDatatypes -> {
out(" lda $indexName | asl a")
when(register) {
CpuRegister.A -> {}
when (register) {
CpuRegister.A -> {
}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(compTarget.memorySize(DataType.FLOAT)==5)
out("""
require(compTarget.memorySize(DataType.FLOAT) == 5)
out(
"""
lda $indexName
asl a
asl a
clc
adc $indexName""")
when(register) {
CpuRegister.A -> {}
adc $indexName"""
)
when (register) {
CpuRegister.A -> {
}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
@@ -821,9 +860,18 @@ internal class AsmGen(private val program: Program,
private fun translateSubroutine(sub: Subroutine) {
var onlyVariables = false
if(sub.inline) {
if(options.optimize)
return // inline subroutines don't exist anymore on their own
if(options.optimize) {
if(sub.isAsmSubroutine ||callGraph.unused(sub))
return
// from an inlined subroutine only the local variables are generated,
// all other code statements are omitted in the subroutine itself
// (they've been inlined at the call site, remember?)
onlyVariables = true
}
else if(sub.amountOfRtsInAsm()==0) {
// make sure the NOT INLINED subroutine actually does an rts at the end
sub.statements.add(Return(null, Position.DUMMY))
@@ -840,7 +888,7 @@ internal class AsmGen(private val program: Program,
// asmsub with most likely just an inline asm in it
out("${sub.name}\t.proc")
sub.statements.forEach{ translate(it) }
sub.statements.forEach { translate(it) }
out(" .pend\n")
} else {
// regular subroutine
@@ -864,8 +912,10 @@ internal class AsmGen(private val program: Program,
clc""")
}
out("; statements")
sub.statements.forEach{ translate(it) }
if(!onlyVariables) {
out("; statements")
sub.statements.forEach { translate(it) }
}
for(removal in removals.toList()) {
if(removal.second==sub) {
@@ -875,7 +925,15 @@ internal class AsmGen(private val program: Program,
}
out("; variables")
out("; register saves")
for((dt, name, addr) in sub.asmGenInfo.extraVars) {
if(addr!=null)
out("$name = $addr")
else when(dt) {
DataType.UBYTE -> out("$name .byte 0")
DataType.UWORD -> out("$name .word 0")
else -> throw AssemblyError("weird dt")
}
}
if(sub.asmGenInfo.usedRegsaveA)
out("_prog8_regsaveA .byte 0")
if(sub.asmGenInfo.usedRegsaveX)
@@ -883,9 +941,9 @@ internal class AsmGen(private val program: Program,
if(sub.asmGenInfo.usedRegsaveY)
out("_prog8_regsaveY .byte 0")
if(sub.asmGenInfo.usedFloatEvalResultVar1)
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(sub.asmGenInfo.usedFloatEvalResultVar2)
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
vardecls2asm(sub.statements)
out(" .pend\n")
}
@@ -920,6 +978,10 @@ internal class AsmGen(private val program: Program,
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
// DISABLED FOR NOW:
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
// throw AssemblyError("both operands for if comparison expression should have been simplified")
if (stmt.elsepart.containsNoCodeNorVars()) {
// empty else
val endLabel = makeLabel("if_end")
@@ -966,11 +1028,11 @@ internal class AsmGen(private val program: Program,
iterations == 0 -> {}
iterations <= 256 -> {
out(" lda #${iterations and 255}")
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt)
}
else -> {
out(" lda #<${iterations} | ldy #>${iterations}")
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt)
}
}
}
@@ -980,11 +1042,11 @@ internal class AsmGen(private val program: Program,
when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> {
assignVariableToRegister(name, RegisterOrPair.A)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
}
DataType.UWORD, DataType.WORD -> {
assignVariableToRegister(name, RegisterOrPair.AY)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
}
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
}
@@ -993,14 +1055,14 @@ internal class AsmGen(private val program: Program,
val dt = stmt.iterations!!.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
in ByteDatatypes -> {
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.A)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
}
in WordDatatypes -> {
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.AY)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
}
else -> throw AssemblyError("invalid loop expression datatype $dt")
}
@@ -1010,13 +1072,13 @@ internal class AsmGen(private val program: Program,
loopEndLabels.pop()
}
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
// note: A/Y must have been loaded with the number of iterations!
if(constIterations==0)
return
// no need to explicitly test for 0 iterations as this is done in the count down logic below
val counterVar = makeLabel("repeatcounter")
val counterVar: String = createRepeatCounterVar(DataType.UWORD, constIterations, stmt)
out("""
sta $counterVar
sty $counterVar+1
@@ -1029,44 +1091,67 @@ $repeatLabel lda $counterVar
dec $counterVar+1
+ dec $counterVar
""")
translate(body)
translate(stmt.body)
jmp(repeatLabel)
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
// allocate count var on ZP TODO can be shared with countervars from other subroutines
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
out("$counterVar = $zpAddr ; auto zp UWORD")
} else {
out("$counterVar .word 0")
}
out(endLabel)
}
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
// note: A must have been loaded with the number of iterations!
if(constIterations==0)
return
if(constIterations==null)
out(" beq $endLabel ; skip loop if zero iters")
val counterVar = makeLabel("repeatcounter")
val counterVar = createRepeatCounterVar(DataType.UBYTE, constIterations, stmt)
out(" sta $counterVar")
out(repeatLabel)
translate(body)
out("""
dec $counterVar
bne $repeatLabel
beq $endLabel""")
translate(stmt.body)
out(" dec $counterVar | bne $repeatLabel")
if(constIterations==null)
out(endLabel)
}
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
// allocate count var on ZP TODO can be shared with countervars from other subroutines
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
out("$counterVar = $zpAddr ; auto zp UBYTE")
} else {
out("$counterVar .byte 0")
private fun createRepeatCounterVar(dt: DataType, constIterations: Int?, stmt: RepeatLoop): String {
val asmInfo = stmt.definingSubroutine()!!.asmGenInfo
var parent = stmt.parent
while(parent !is ParentSentinel) {
if(parent is RepeatLoop)
break
parent = parent.parent
}
val isNested = parent is RepeatLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt }
if(existingVar!=null)
return existingVar.second
}
out(endLabel)
val counterVar = makeLabel("repeatcounter")
when(dt) {
DataType.UBYTE -> {
if(constIterations!=null && constIterations>=16 && zeropage.hasByteAvailable()) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, stmt.position, errors)
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, zpAddr))
} else {
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, null))
}
}
DataType.UWORD -> {
if(constIterations!=null && constIterations>=16 && zeropage.hasWordAvailable()) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, stmt.position, errors)
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, zpAddr))
} else {
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, null))
}
}
else -> throw AssemblyError("invalidt dt")
}
return counterVar
}
private fun translate(stmt: WhileLoop) {
@@ -1227,19 +1312,14 @@ $repeatLabel lda $counterVar
when(stmt.directive) {
"%asminclude" -> {
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
val scopeprefix = stmt.args[1].str ?: ""
if(scopeprefix.isNotBlank())
out("$scopeprefix\t.proc")
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
if(scopeprefix.isNotBlank())
out(" .pend\n")
}
"%asmbinary" -> {
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
val relPath = Paths.get("").relativize(includedSourcePath)
out(" .binary \"./$relPath\" $offset $length")
out(" .binary \"$relPath\" $offset $length")
}
"%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
@@ -1258,21 +1338,14 @@ $label nop""")
val label = jump.generatedLabel
val addr = jump.address
return when {
ident!=null -> {
val target = ident.targetStatement(program)
val asmName = asmSymbolName(ident)
if(target is Label)
"_$asmName" // prefix with underscore to jump to local label
else
asmName
}
ident!=null -> asmSymbolName(ident)
label!=null -> label
addr!=null -> addr.toHex()
else -> "????"
}
}
private fun translate(ret: Return) {
internal fun translate(ret: Return, withRts: Boolean=true) {
ret.value?.let { returnvalue ->
val sub = ret.definingSubroutine()!!
val returnType = sub.returntypes.single()
@@ -1291,7 +1364,9 @@ $label nop""")
}
}
}
out(" rts")
if(withRts)
out(" rts")
}
private fun translate(asm: InlineAssembly) {
@@ -1405,38 +1480,49 @@ $label nop""")
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
if(ptrAndIndex!=null) {
val pointervar = ptrAndIndex.first as? IdentifierReference
if(write) {
if(pointervar!=null && isZpVar(pointervar)) {
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
val target = pointervar?.targetStatement(program)
when(target) {
is Label -> {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
out(" lda ${asmSymbolName(pointervar)},y")
return true
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
is VarDecl, null -> {
if(write) {
if(pointervar!=null && isZpVar(pointervar)) {
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
}
}
return true
}
else -> throw AssemblyError("invalid pointervar")
}
return true
}
}
return false
@@ -13,6 +13,7 @@ import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.FSignature
import prog8.compiler.target.CpuType
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.compiler.target.subroutineFloatEvalResultVar2
@@ -51,7 +52,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
"fastrnd8", "rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
@@ -65,15 +66,119 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"pokew" -> funcPokeW(fcall)
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"callrom" -> funcCallRom(fcall)
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
}
}
private fun funcCallFar(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt()
if(bank==null || address==null)
throw AssemblyError("callfar (jsrfar) requires constant arguments")
if(address !in 0xa000..0xbfff)
throw AssemblyError("callfar done on address outside of cx16 banked ram")
if(bank==0)
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0) {
asmgen.out("""
jsr cx16.jsrfar
.word ${address.toHex()}
.byte ${bank.toHex()}""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callfar done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr cx16.jsrfar
.word ${address.toHex()}
.byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
}
is NumericLiteralValue -> {
asmgen.out("""
lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar
.word ${address.toHex()}
.byte ${bank.toHex()}
sta ${argAddrArg.number.toHex()}""")
}
else -> throw AssemblyError("callfar only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
}
private fun funcCallRom(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
throw AssemblyError("callrom only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt()
if(bank==null || address==null)
throw AssemblyError("callrom requires constant arguments")
if(address !in 0xc000..0xffff)
throw AssemblyError("callrom done on address outside of cx16 banked rom")
if(bank>=32)
throw AssemblyError("callrom bank must be <32")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0) {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
jsr ${address.toHex()}
pla
sta $01""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callrom done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr ${address.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}
pla
sta $01""")
}
is NumericLiteralValue -> {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${argAddrArg.number.toHex()}
jsr ${address.toHex()}
sta ${argAddrArg.number.toHex()}
pla
sta $01""")
}
else -> throw AssemblyError("callrom only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
}
private fun funcCmp(fcall: IFunctionCall) {
val arg1 = fcall.args[0]
val arg2 = fcall.args[1]
val dt1 = arg1.inferType(program).typeOrElse(DataType.STRUCT)
val dt2 = arg2.inferType(program).typeOrElse(DataType.STRUCT)
val dt1 = arg1.inferType(program).typeOrElse(DataType.UNDEFINED)
val dt2 = arg2.inferType(program).typeOrElse(DataType.UNDEFINED)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes) {
when (arg2) {
@@ -263,7 +368,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRor2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@@ -306,7 +411,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRor(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@@ -364,7 +469,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRol2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@@ -407,7 +512,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRol(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@@ -481,7 +586,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
@@ -490,7 +595,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
@@ -506,14 +611,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
@@ -527,7 +632,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_stack")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
@@ -536,7 +641,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
@@ -566,7 +671,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_stack")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_stack")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
@@ -575,7 +680,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.typeOrElse(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
@@ -645,7 +750,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) {
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+i1), @(ptr+i2))
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
@@ -677,7 +782,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val elementIDt = first.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val elementDt = elementIDt.typeOrElse(DataType.UNDEFINED)
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
val firstVar = first.indexer.indexExpr as? IdentifierReference
@@ -710,7 +815,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
val datatype = first.inferType(program).typeOrElse(DataType.UNDEFINED)
when(datatype) {
in ByteDatatypes, in WordDatatypes -> {
asmgen.assignExpressionToVariable(first, "P8ZP_SCRATCH_W1", datatype, null)
@@ -981,7 +1086,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT)
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.UNDEFINED)
if(resultToStack) {
when (dt) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
@@ -1010,14 +1115,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
when(func.name) {
"fastrnd8" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_fastrnd8_stack")
else {
asmgen.out(" jsr math.fast_randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
}
"rnd" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rnd_stack")
@@ -1072,18 +1169,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
txa
iny
sta ($varname),y""")
asmgen.restoreRegisterLocal(CpuRegister.X)
return
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
txa
iny
sta ($varname),y""")
asmgen.restoreRegisterLocal(CpuRegister.X)
return
}
}
}
}
@@ -1127,15 +1227,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
@@ -1154,7 +1260,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
RegisterOrPair.AY -> {}
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
RegisterOrPair.XY -> asmgen.out(" tax")
in Cx16VirtualRegisters -> asmgen.out(" sta cx16.${resultRegister.toString().toLowerCase()} | sty cx16.${resultRegister.toString().toLowerCase()}+1")
in Cx16VirtualRegisters -> asmgen.out(
" sta cx16.${
resultRegister.toString().lowercase()
} | sty cx16.${resultRegister.toString().lowercase()}+1")
else -> throw AssemblyError("invalid reg")
}
}
@@ -1204,9 +1313,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}")
asmgen.out(" sta cx16.${reg.toString().lowercase()}")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}+1")
asmgen.out(" sta cx16.${reg.toString().lowercase()}+1")
}
else -> throw AssemblyError("invalid mkword target reg")
}
@@ -1215,7 +1324,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
if (!arg.inferType(program).isWords())
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("msb(const) should have been const-folded away")
@@ -1259,7 +1368,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
if (!arg.inferType(program).isWords())
throw AssemblyError("lsb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("lsb(const) should have been const-folded away")
@@ -1323,7 +1432,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
val callConv = signature.callConvention(args.map { it.inferType(program).typeOrElse(DataType.STRUCT) })
val callConv = signature.callConvention(args.map { it.inferType(program).typeOrElse(DataType.UNDEFINED) })
fun getSourceForFloat(value: Expression): AsmAssignSource {
return when (value) {
@@ -57,37 +57,26 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val idt = left.inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
val dt = idt.typeOrElse(DataType.UNDEFINED)
when (operator) {
"==" -> {
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
// and use the result value directly to determine the boolean result. Shortcut only for integers.
if(rightConstVal?.number?.toDouble() == 0.0) {
when(left) {
is PrefixExpression,
is BinaryExpression,
is ArrayIndexedExpression,
is TypecastExpression,
is AddressOf,
is RangeExpr,
is FunctionCall -> {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall)
asmgen.out(" cmp #0")
asmgen.out(" bne $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
bne $jumpIfFalseLabel""")
return
}
}
else -> {}
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
asmgen.out(" bne $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
bne $jumpIfFalseLabel""")
return
}
}
@@ -103,31 +92,20 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
// and use the result value directly to determine the boolean result. Shortcut only for integers.
if(rightConstVal?.number?.toDouble() == 0.0) {
when(left) {
is PrefixExpression,
is BinaryExpression,
is ArrayIndexedExpression,
is TypecastExpression,
is AddressOf,
is RangeExpr,
is FunctionCall -> {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall)
asmgen.out(" cmp #0")
asmgen.out(" beq $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
beq $jumpIfFalseLabel""")
return
}
}
else -> {}
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
asmgen.out(" beq $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
beq $jumpIfFalseLabel""")
return
}
}
@@ -397,7 +375,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -432,7 +410,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -440,7 +418,36 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
return code("P8ZP_SCRATCH_B1")
}
private fun byteJumpForSimpleRightOperands(left: Expression, right: Expression, code: (String)->Unit): Boolean {
private fun wordJumpForSimpleLeftOperand(left: Expression, right: Expression, code: (String, String)->Unit): Boolean {
when (left) {
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY)
val number = left.number.toHex()
code("#>$number", "#<$number")
return true
}
is AddressOf -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY)
val name = asmgen.asmSymbolName(left.identifier)
code("#>$name", "#<$name")
return true
}
is IdentifierReference -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(left)
code("$varname+1", varname)
return true
}
else -> return false
}
}
private fun byteJumpForSimpleRightOperand(left: Expression, right: Expression, code: (String)->Unit): Boolean {
if(right is NumericLiteralValue) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
code("#${right.number.toHex()}")
return true
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
code(asmgen.asmVariableName(right))
@@ -460,6 +467,30 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
return false
}
private fun wordJumpForSimpleRightOperands(left: Expression, right: Expression, code: (String, String)->Unit): Boolean {
when (right) {
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val number = right.number.toHex()
code("#>$number", "#<$number")
return true
}
is AddressOf -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmgen.asmSymbolName(right.identifier)
code("#>$name", "#<$name")
return true
}
is IdentifierReference -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(right)
code("$varname+1", varname)
return true
}
else -> return false
}
}
private fun translateUwordLessJump(left: Expression, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
fun code(msbCpyOperand: String, lsbCmpOperand: String) {
@@ -489,11 +520,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(right)
return code("$varname+1", varname)
}
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -531,11 +559,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(right)
return code("$varname+1", varname)
}
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -574,7 +599,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -611,7 +636,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -654,11 +679,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(right)
return code("$varname+1", varname)
}
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -667,11 +689,11 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateWordGreaterJump(left: Expression, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
fun code(leftName: String) {
fun code(msbCmpOperand: String, lsbCmpOperand: String) {
asmgen.out("""
cmp $leftName
cmp $lsbCmpOperand
tya
sbc $leftName+1
sbc $msbCmpOperand
bvc +
eor #$80
+ bpl $jumpIfFalseLabel""")
@@ -686,7 +708,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if (left is IdentifierReference) {
return if(rightConstVal.number.toInt()!=0) {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY)
code(asmgen.asmVariableName(left))
val varname = asmgen.asmVariableName(left)
code("$varname+1", varname)
}
else {
val name = asmgen.asmVariableName(left)
@@ -700,10 +723,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(left is IdentifierReference) {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY)
return code(asmgen.asmVariableName(left))
}
if(wordJumpForSimpleLeftOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY)
@@ -743,7 +764,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -780,7 +801,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -825,11 +846,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(right)
return code("$varname+1", varname)
}
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -913,7 +931,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -947,7 +965,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -982,11 +1000,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(right)
return code("$varname+1", varname)
}
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
asmgen.assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY)
@@ -1024,11 +1039,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val varname = asmgen.asmVariableName(right)
return code("$varname+1", varname)
}
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -1063,7 +1075,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -1100,7 +1112,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(byteJumpForSimpleRightOperands(left, right, ::code))
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
@@ -1136,24 +1148,47 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp ${asmgen.asmVariableName(right)}
bne $jumpIfFalseLabel
cpy ${asmgen.asmVariableName(right)}+1
bne $jumpIfFalseLabel
""")
return
when (right) {
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val number = right.number.toHex()
asmgen.out("""
cmp #<$number
bne $jumpIfFalseLabel
cpy #>$number
bne $jumpIfFalseLabel
""")
}
is IdentifierReference -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp ${asmgen.asmVariableName(right)}
bne $jumpIfFalseLabel
cpy ${asmgen.asmVariableName(right)}+1
bne $jumpIfFalseLabel
""")
}
is AddressOf -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmgen.asmSymbolName(right.identifier)
asmgen.out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel
""")
}
else -> {
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp P8ZP_SCRATCH_W2
bne $jumpIfFalseLabel
cpy P8ZP_SCRATCH_W2+1
bne $jumpIfFalseLabel""")
}
}
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp P8ZP_SCRATCH_W2
bne $jumpIfFalseLabel
cpy P8ZP_SCRATCH_W2+1
bne $jumpIfFalseLabel""")
}
private fun translateWordNotEqualsJump(left: Expression, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
@@ -1187,25 +1222,49 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
if(right is IdentifierReference) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp ${asmgen.asmVariableName(right)}
bne +
cpy ${asmgen.asmVariableName(right)}+1
beq $jumpIfFalseLabel
when (right) {
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val number = right.number.toHex()
asmgen.out("""
cmp #<$number
bne +
cpy #>$number
beq $jumpIfFalseLabel
+""")
return
}
is IdentifierReference -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp ${asmgen.asmVariableName(right)}
bne +
cpy ${asmgen.asmVariableName(right)}+1
beq $jumpIfFalseLabel
+""")
}
is AddressOf -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmgen.asmSymbolName(right.identifier)
asmgen.out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
}
else -> {
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp P8ZP_SCRATCH_W2
bne +
cpy P8ZP_SCRATCH_W2+1
beq $jumpIfFalseLabel
+"""
)
}
}
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
cmp P8ZP_SCRATCH_W2
bne +
cpy P8ZP_SCRATCH_W2+1
beq $jumpIfFalseLabel
+""")
}
private fun translateFloatEqualsJump(left: Expression, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
@@ -1514,10 +1573,11 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
asmgen.out("""
lda cx16.${reg.registerOrPair.toString().toLowerCase()}
asmgen.out(
"""
lda cx16.${reg.registerOrPair.toString().lowercase()}
sta P8ESTACK_LO,x
lda cx16.${reg.registerOrPair.toString().toLowerCase()}+1
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
sta P8ESTACK_HI,x
dex
""")
@@ -1566,7 +1626,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(typecast: TypecastExpression) {
translateExpression(typecast.expression)
when(typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
when(typecast.expression.inferType(program).typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
@@ -1636,7 +1696,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
internal fun translateDirectMemReadExpression(expr: DirectMemoryRead, pushResultOnEstack: Boolean) {
fun assignViaExprEval() {
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.assignExpressionToVariable(expr.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (pushResultOnEstack) {
asmgen.out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
@@ -1697,7 +1757,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(expr: IdentifierReference) {
val varname = asmgen.asmVariableName(expr)
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
when(expr.inferType(program).typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
}
@@ -1721,8 +1781,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
val leftDt = leftIDt.typeOrElse(DataType.UNDEFINED)
val rightDt = rightIDt.typeOrElse(DataType.UNDEFINED)
// see if we can apply some optimized routines
// TODO avoid using evaluation on stack everywhere
when(expr.operator) {
@@ -2048,7 +2108,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val itype = expr.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.typeOrElse(DataType.STRUCT)
val type = itype.typeOrElse(DataType.UNDEFINED)
when(expr.operator) {
"+" -> {}
"-" -> {
@@ -2086,7 +2146,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val elementIDt = arrayExpr.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val elementDt = elementIDt.typeOrElse(DataType.UNDEFINED)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
val constIndexNum = arrayExpr.indexer.constIndex()
if(constIndexNum!=null) {
@@ -1,7 +1,7 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.ArrayToElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference
@@ -21,13 +21,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as RangeExpr)
} else {
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
@@ -40,6 +40,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
val stepsize=range.step.constValue(program)!!.number.toInt()
if(stepsize < -1) {
val limit = range.to.constValue(program)?.number?.toDouble()
if(limit==0.0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
@@ -49,8 +56,8 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
@@ -67,8 +74,8 @@ $modifiedLabel cmp #0 ; modified
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
if(stepsize>0) {
@@ -281,7 +288,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@@ -320,7 +327,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@@ -581,5 +588,5 @@ $loopLabel""")
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine())
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.UNDEFINED), stmt.definingSubroutine())
}
@@ -111,17 +111,24 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
if(sub.inline && asmgen.options.optimize) {
if(sub.containsDefinedVariables())
throw AssemblyError("can't inline sub with vars")
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
throw AssemblyError("can't inline a non-asm subroutine with parameters")
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
statements.forEach { asmgen.translate(it) }
}
else {
if(!sub.inline || !asmgen.options.optimize) {
asmgen.out(" jsr $subName")
} else {
// inline the subroutine.
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
statements.forEach {
if(it is Return) {
asmgen.translate(it, false) // don't use RTS for the inlined return statement
} else {
if(!sub.inline || it !is VarDecl)
asmgen.translate(it)
}
}
asmgen.out(" \t; inlined routine end: ${sub.name}")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
@@ -168,21 +175,31 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
when (sub.parameters[argi.index].type) {
in ByteDatatypes -> {
// only load the lsb of the virtual register
asmgen.out("""
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
""")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
asmgen.out(
" stz cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
else
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
asmgen.out(
" lda #0 | sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
}
in WordDatatypes, in IterableDatatypes ->
asmgen.out("""
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
lda P8ESTACK_HI$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1
sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1
""")
else -> throw AssemblyError("weird dt")
}
@@ -237,7 +254,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
@@ -250,7 +267,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
@@ -302,10 +319,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
} else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
@@ -19,7 +19,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
when {
targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
when (stmt.target.inferType(program).typeOrElse(DataType.UNDEFINED)) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
@@ -65,7 +65,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
targetArrayIdx!=null -> {
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.UNDEFINED)
val constIndex = targetArrayIdx.indexer.constIndex()
if(constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
@@ -59,7 +59,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
val dt = idt.typeOrElse(DataType.UNDEFINED)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
@@ -132,12 +132,12 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
val dt = value.inferType(program).typeOrElse(DataType.UNDEFINED)
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(dt==DataType.UWORD && varName.toLowerCase().startsWith("cx16.r")) {
val regStr = varName.toLowerCase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.toUpperCase())
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
val regStr = varName.lowercase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.uppercase())
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
} else {
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
@@ -147,7 +147,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
val dt = value.inferType(program).typeOrElse(DataType.UNDEFINED)
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is FunctionCall -> {
@@ -162,7 +162,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val returnType = value.inferType(program)
if(!returnType.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.UNDEFINED), expression = value)
}
else -> {
throw AssemblyError("weird call")
@@ -173,7 +173,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val dt = value.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.UNDEFINED), expression = value)
}
}
}
@@ -206,7 +206,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source storage size must be less or equal to target datatype storage size"
}
@@ -115,7 +115,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD, assign.target.scope)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
@@ -145,7 +145,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.EXPRESSION -> {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = value.identifier.firstStructVarName(program) ?: asmgen.asmVariableName(value.identifier)
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
@@ -217,7 +217,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.typeOrElse(DataType.STRUCT)) {
when(returntype.typeOrElse(DataType.UNDEFINED)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
@@ -299,7 +299,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED)
if(valueDt==targetDt)
throw AssemblyError("type cast to identical dt should have been removed")
@@ -320,7 +320,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if(targetDt in WordDatatypes) {
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
@@ -359,7 +359,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// special case optimizations
if(target.kind== TargetStorageKind.VARIABLE) {
if(value is IdentifierReference && valueDt != DataType.STRUCT)
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
when (valueDt) {
@@ -431,8 +431,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
// give up, do it via eval stack
// TODO optimize typecasts for more special cases?
// note: cannot use assignTypeCastedValue because that is ourselves :P
// TODO optimize typecasts for more special cases?
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
@@ -587,13 +587,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.UBYTE -> {
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
DataType.UWORD, DataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
DataType.FLOAT -> {
when(regs) {
@@ -615,13 +621,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.BYTE -> {
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
DataType.UWORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
DataType.WORD -> {
when(regs) {
@@ -653,7 +665,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.UWORD -> {
when(targetDt) {
DataType.BYTE, DataType.UBYTE -> {
asmgen.out(" st${regs.toString().toLowerCase().first()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
DataType.WORD, DataType.UWORD -> {
when(regs) {
@@ -681,7 +693,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.WORD -> {
when(targetDt) {
DataType.BYTE, DataType.UBYTE -> {
asmgen.out(" st${regs.toString().toLowerCase().first()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
DataType.WORD, DataType.UWORD -> {
when(regs) {
@@ -822,12 +834,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> asmgen.out(" inx | txy | ldx #0 | lda P8ESTACK_LO,y")
RegisterOrPair.AY -> asmgen.out(" inx | ldy #0 | lda P8ESTACK_LO,x")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
inx
lda P8ESTACK_LO,x
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't assign byte from stack to register pair XY")
@@ -839,12 +852,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY-> asmgen.out(" inx | ldy P8ESTACK_HI,x | lda P8ESTACK_LO,x")
RegisterOrPair.XY-> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
inx
lda P8ESTACK_LO,x
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda P8ESTACK_HI,x
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't assign word to single byte register")
@@ -888,11 +902,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda #<$sourceName
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #>$sourceName
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't load address in a single 8-bit register")
@@ -1029,11 +1044,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" ldy $sourceName+1 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy $sourceName+1 | ldx $sourceName")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda $sourceName
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda $sourceName+1
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't load word in a single 8-bit register")
@@ -1203,11 +1219,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda $sourceName
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("weird register")
@@ -1236,11 +1253,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize slow stack evaluation for this case, see assignVariableUByteIntoWord
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for sign-extend byte typecast at ${bytevar.position}")
asmgen.translateExpression(wordtarget.origAssign.source.expression!!)
assignStackValue(wordtarget)
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(DataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
}
else {
asmgen.saveRegisterLocal(CpuRegister.X, wordtarget.scope!!)
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.X)
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(DataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname},x | inx | tya | sta ${wordtarget.asmVarname},x")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
TargetStorageKind.REGISTER -> {
when(wordtarget.register!!) {
@@ -1346,7 +1372,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}")
asmgen.out(" st${register.name.lowercase()} ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
when(register) {
@@ -1386,7 +1412,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sta cx16.${target.register.toString().toLowerCase()}")
asmgen.out(" sta cx16.${target.register.toString().lowercase()}")
}
else -> throw AssemblyError("weird register")
}
@@ -1400,7 +1426,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" stx cx16.${target.register.toString().toLowerCase()}")
asmgen.out(" stx cx16.${target.register.toString().lowercase()}")
}
else -> throw AssemblyError("weird register")
}
@@ -1414,7 +1440,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sty cx16.${target.register.toString().toLowerCase()}")
asmgen.out(" sty cx16.${target.register.toString().lowercase()}")
}
else -> throw AssemblyError("weird register")
}
@@ -1504,9 +1530,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { }
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
stx cx16.${target.register.toString().toLowerCase()}+1
asmgen.out(
"""
sta cx16.${target.register.toString().lowercase()}
stx cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
@@ -1516,9 +1543,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { asmgen.out(" tax") }
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
sty cx16.${target.register.toString().toLowerCase()}+1
asmgen.out(
"""
sta cx16.${target.register.toString().lowercase()}
sty cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
@@ -1528,9 +1556,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { }
in Cx16VirtualRegisters -> {
asmgen.out("""
stx cx16.${target.register.toString().toLowerCase()}
sty cx16.${target.register.toString().toLowerCase()}+1
asmgen.out(
"""
stx cx16.${target.register.toString().lowercase()}
sty cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
@@ -1597,7 +1626,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" lda #0 | tay")
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
in Cx16VirtualRegisters -> {
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
asmgen.out(
" stz cx16.${
target.register.toString().lowercase()
} | stz cx16.${target.register.toString().lowercase()}+1")
}
else -> throw AssemblyError("invalid register for word value")
}
@@ -1647,11 +1679,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" ldy #>${word.toHex()} | lda #<${word.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldy #>${word.toHex()} | ldx #<${word.toHex()}")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda #<${word.toHex()}
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #>${word.toHex()}
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("invalid register for word value")
@@ -1698,7 +1731,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
asmgen.out(
" stz cx16.${
target.register.toString().lowercase()
} | stz cx16.${target.register.toString().lowercase()}+1")
}
else -> throw AssemblyError("weird register")
}
@@ -1738,11 +1774,17 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx #${byte.toHex()}")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
asmgen.out(
" lda #${byte.toHex()} | sta cx16.${
target.register.toString().lowercase()
}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
asmgen.out(" stz cx16.${target.register.toString().lowercase()}+1\n")
else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
asmgen.out(
" lda #0 | sta cx16.${
target.register.toString().lowercase()
}+1\n")
}
else -> throw AssemblyError("weird register")
}
@@ -1918,11 +1960,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldy ${address.toHex()}")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda ${address.toHex()}
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("weird register")
@@ -1958,10 +2001,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
asmgen.out(
"""
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("weird register")
@@ -2045,7 +2089,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun storeViaExprEval() {
when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
@@ -2054,7 +2098,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.out(" pla")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
@@ -25,7 +25,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
val itype = value.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.typeOrElse(DataType.STRUCT)
val type = itype.typeOrElse(DataType.UNDEFINED)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign.target, type)
@@ -110,7 +110,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
val ident = value as? IdentifierReference
val memread = value as? DirectMemoryRead
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
when (target.datatype) {
in ByteDatatypes -> {
@@ -160,7 +160,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
// TODO more specialized code for types such as memory read etc. -> inplaceModification_byte_memread_to_variable()
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
@@ -204,7 +204,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when {
indexNum!=null -> {
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when(target.datatype) {
when (target.datatype) {
in ByteDatatypes -> {
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
@@ -244,7 +244,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
indexVar!=null -> {
when(target.datatype) {
when (target.datatype) {
in ByteDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
@@ -280,7 +280,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
val childIDt = value.expression.inferType(program)
if(!childIDt.isKnown)
throw AssemblyError("unknown dt")
val childDt = childIDt.typeOrElse(DataType.STRUCT)
val childDt = childIDt.typeOrElse(DataType.UNDEFINED)
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
// (works for integer types, not for float.)
@@ -672,7 +672,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
when(operator) {
when (operator) {
"+" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out("""
@@ -688,8 +688,20 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sec
sbc P8ZP_SCRATCH_B1
sta $name""")
// TODO: tuned code for more operators
}
"|", "or" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out(" ora $name | sta $name")
}
"&", "and" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out(" and $name | sta $name")
}
"^", "xor" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out(" eor $name | sta $name")
}
// TODO: tuned code for more operators
else -> {
inplaceModification_byte_value_to_variable(name, dt, operator, memread)
}
@@ -697,7 +709,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
when(operator) {
when (operator) {
"+" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out("""
@@ -719,8 +731,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
bcc +
dec $name+1
+""")
// TODO: tuned code for more operators
}
"|", "or" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out(" ora $name | sta $name")
}
"&", "and" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out(" and $name | sta $name")
if(dt in WordDatatypes) {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
}
}
"^", "xor" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out(" eor $name | sta $name")
}
// TODO: tuned code for more operators
else -> {
inplaceModification_word_value_to_variable(name, dt, operator, memread)
}
@@ -1225,7 +1255,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
val valueiDt = value.inferType(program)
if(!valueiDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueiDt.typeOrElse(DataType.STRUCT)
val valueDt = valueiDt.typeOrElse(DataType.UNDEFINED)
fun multiplyVarByWordInAY() {
asmgen.out("""
@@ -1274,7 +1304,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
}
when(valueDt) {
when (valueDt) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that
when (operator) {
@@ -1331,21 +1361,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"*" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
// TODO use an optimized word * byte multiplication routine
// TODO use an optimized word * byte multiplication routine?
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.signExtendAYlsb(valueDt)
multiplyVarByWordInAY()
}
"/" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
// TODO use an optimized word / byte divmod routine
// TODO use an optimized word / byte divmod routine?
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.signExtendAYlsb(valueDt)
divideVarByWordInAY()
}
"%" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
// TODO use an optimized word / byte divmod routine
// TODO use an optimized word / byte divmod routine?
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.signExtendAYlsb(valueDt)
remainderVarByWordInAY()
@@ -1671,7 +1701,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
DataType.UWORD, DataType.WORD -> {
when (outerCastDt) {
DataType.UBYTE, DataType.BYTE -> {
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}+1")
@@ -1715,7 +1745,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.UBYTE -> {
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
@@ -1748,7 +1778,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
asmgen.assignExpressionToVariable(mem.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, target.scope)
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
@@ -1765,7 +1795,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
DataType.UWORD -> {
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
@@ -1790,7 +1820,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.UBYTE -> {
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
@@ -1816,7 +1846,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
asmgen.assignExpressionToVariable(memory.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, target.scope)
asmgen.assignExpressionToVariable(memory.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
@@ -1831,7 +1861,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
DataType.UWORD -> {
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
@@ -1869,7 +1899,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
DataType.WORD -> {
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #0
@@ -1887,7 +1917,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
DataType.FLOAT -> {
when(target.kind) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
// simply flip the sign bit in the float
asmgen.out("""
@@ -1,8 +1,10 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.augmentAssignmentOperators
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
@@ -11,7 +13,6 @@ import prog8.compiler.target.ICompilationTarget
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
@@ -57,13 +58,14 @@ X = BinExpr X = LeftExpr
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
if(binExpr.right.isSimple && !assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope)
)
}
}
@@ -75,9 +77,6 @@ X = BinExpr X = LeftExpr
return noModifications
}
private fun isSimpleExpression(expr: Expression) =
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
private fun isSimpleTarget(target: AssignTarget, program: Program) =
if (target.identifier!=null || target.memoryAddress!=null)
compTarget.isInRegularRAM(target, program)
+73 -150
View File
@@ -1,139 +1,71 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import java.nio.file.Path
private val alwaysKeepSubroutines = setOf(
Pair("main", "start")
)
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr|bra)[ \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, private val asmFileLoader: (filename: String, source: Path)->String) : IAstVisitor {
class CallGraph(private val program: Program) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
val usedSymbols = mutableSetOf<Statement>()
val imports = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
val importedBy = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
val calls = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { setOf() }
val calledBy = mutableMapOf<Subroutine, Set<Node>>().withDefault { setOf() }
private val allIdentifiersAndTargets = mutableMapOf<Pair<IdentifierReference, Position>, Statement>()
private val allAssemblyNodes = mutableListOf<InlineAssembly>()
init {
visit(program)
}
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
fun findSubs(scope: INameScope) {
scope.statements.forEach {
if (it is Subroutine)
sub(it)
if (it is INameScope)
findSubs(it)
private val usedSubroutines: Set<Subroutine> by lazy {
calledBy.keys + program.entrypoint()
}
private val usedBlocks: Set<Block> by lazy {
val blocksFromSubroutines = usedSubroutines.map { it.definingBlock() }
val blocksFromLibraries = program.allBlocks().filter { it.isInLibrary }
val used = mutableSetOf<Block>()
allIdentifiersAndTargets.forEach {
if(it.key.first.definingBlock() in blocksFromSubroutines) {
val target = it.value.definingBlock()
used.add(target)
}
}
findSubs(scope)
used + blocksFromLibraries + program.entrypoint().definingBlock()
}
override fun visit(program: Program) {
super.visit(program)
program.modules.forEach {
it.importedBy.clear()
it.imports.clear()
it.importedBy.addAll(importedBy.getValue(it))
it.imports.addAll(imports.getValue(it))
}
val rootmodule = program.modules.first()
rootmodule.importedBy.add(rootmodule) // don't discard root module
}
override fun visit(block: Block) {
if (block.definingModule().isLibraryModule) {
// make sure the block is not removed
addNodeAndParentScopes(block)
}
super.visit(block)
private val usedModules: Set<Module> by lazy {
usedBlocks.map { it.definingModule() }.toSet()
}
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 }
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = asmFileLoader(directive.args[0].str!!, thisModule.source)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
}
imports[thisModule] = imports.getValue(thisModule) + importedModule
importedBy[importedModule] = importedBy.getValue(importedModule) + thisModule
}
super.visit(directive)
}
override fun visit(identifier: IdentifierReference) {
// track symbol usage
val target = identifier.targetStatement(program)
if (target != null) {
addNodeAndParentScopes(target)
}
super.visit(identifier)
}
private fun addNodeAndParentScopes(stmt: Statement) {
usedSymbols.add(stmt)
var node: Node = stmt
do {
if (node is INameScope && node is Statement) {
usedSymbols.add(node)
}
node = node.parent
} while (node !is Module && node !is ParentSentinel)
}
override fun visit(subroutine: Subroutine) {
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)
}
super.visit(subroutine)
}
override fun visit(decl: VarDecl) {
if (decl.autogeneratedDontRemove || decl.datatype==DataType.STRUCT)
addNodeAndParentScopes(decl)
else if(decl.parent is Block && decl.definingModule().isLibraryModule)
addNodeAndParentScopes(decl)
super.visit(decl)
}
override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program)
if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCall
}
}
super.visit(functionCall)
@@ -143,8 +75,8 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
val otherSub = functionCallStatement.target.targetSubroutine(program)
if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCallStatement
}
}
super.visit(functionCallStatement)
@@ -154,8 +86,8 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
val otherSub = addressOf.identifier.targetSubroutine(program)
if(otherSub!=null) {
addressOf.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(thisSub)
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + thisSub
}
}
super.visit(addressOf)
@@ -165,63 +97,19 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
val otherSub = jump.identifier?.targetSubroutine(program)
if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + jump
}
}
super.visit(jump)
}
override fun visit(structDecl: StructDecl) {
usedSymbols.add(structDecl)
usedSymbols.addAll(structDecl.statements)
override fun visit(identifier: IdentifierReference) {
allIdentifiersAndTargets[Pair(identifier, identifier.position)] = identifier.targetStatement(program)!!
}
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr, bra)
val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
val jumptarget = matches.groups[2]?.value
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
}
} else {
val matches2 = asmRefRx.matchEntire(line)
if (matches2 != null) {
val target = matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
}
}
}
}
allAssemblyNodes.add(inlineAssembly)
}
fun checkRecursiveCalls(errors: IErrorReporter) {
@@ -274,4 +162,39 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
recStack[sub] = false
return false
}
fun unused(module: Module) = module !in usedModules
fun unused(sub: Subroutine): Boolean {
return sub !in usedSubroutines && !nameInAssemblyCode(sub.name)
}
fun unused(block: Block): Boolean {
return block !in usedBlocks && !nameInAssemblyCode(block.name)
}
fun unused(decl: VarDecl): Boolean {
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm)
return false
if(decl.definingBlock() !in usedBlocks)
return false
val allReferencedVardecls = allIdentifiersAndTargets.filter { it.value is VarDecl }.map { it.value }.toSet()
return decl !in allReferencedVardecls // Don't check assembly just for occurrences of variables, if they're not used in prog8 itself, just kill them
}
private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { it.assembly.contains(name) }
inline fun unused(label: Label) = false // just always output labels
fun unused(stmt: ISymbolStatement): Boolean {
return when(stmt) {
is Subroutine -> unused(stmt)
is Block -> unused(stmt)
is VarDecl -> unused(stmt)
is Label -> false // just always output labels
else -> false
}
}
}
@@ -14,7 +14,6 @@ import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
@@ -107,7 +106,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
// optimize various simple cases of ** :
// optimize away 1 ** x into just 1 and 0 ** x into just 0
// optimize 2 ** x into (1<<x) if both operands are integer.
val leftDt = leftconst.inferType(program).typeOrElse(DataType.STRUCT)
val leftDt = leftconst.inferType(program).typeOrElse(DataType.UNDEFINED)
when (leftconst.number.toDouble()) {
0.0 -> {
val value = NumericLiteralValue(leftDt, 0, expr.position)
@@ -122,11 +121,11 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
} else {
val rightDt = expr.right.inferType(program).typeOrElse(DataType.STRUCT)
val rightDt = expr.right.inferType(program).typeOrElse(DataType.UNDEFINED)
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val targetDt =
when (parent) {
is Assignment -> parent.target.inferType(program).typeOrElse(DataType.STRUCT)
is Assignment -> parent.target.inferType(program).typeOrElse(DataType.UNDEFINED)
is VarDecl -> parent.datatype
else -> leftDt
}
@@ -188,7 +187,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
} else {
val arrayDt = array.guessDatatype(program)
if (arrayDt.isKnown) {
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
val newArray = array.cast(arrayDt.typeOrElse(DataType.UNDEFINED))
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
}
@@ -1,13 +1,11 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
@@ -15,7 +13,6 @@ import prog8.compiler.target.ICompilationTarget
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
try {
@@ -31,6 +28,28 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
errors.err(x.message, x.position)
}
// move vardecl to the containing subroutine and add initialization assignment in its place if needed
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val subroutine = decl.definingSubroutine() as? INameScope
if(subroutine!=null && subroutine!==parent) {
val declValue = decl.value
decl.value = null
decl.allowInitializeWithZero = false
return if (declValue == null) {
listOf(
IAstModification.Remove(decl, parent as INameScope),
IAstModification.InsertFirst(decl, subroutine)
)
} else {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, subroutine)
)
}
}
}
return noModifications
}
}
@@ -40,7 +59,6 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
// 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: IErrorReporter, private val compTarget: ICompilationTarget) : 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
@@ -154,7 +172,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
@@ -191,7 +209,6 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}
@@ -25,7 +25,6 @@ import kotlin.math.pow
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(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
@@ -135,8 +134,8 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
))
}
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
val leftDt = leftIDt.typeOrElse(DataType.UNDEFINED)
val rightDt = rightIDt.typeOrElse(DataType.UNDEFINED)
if (expr.operator == "+" || expr.operator == "-"
&& leftVal == null && rightVal == null
@@ -490,10 +489,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val idt = expr.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
return NumericLiteralValue(idt.typeOrElse(DataType.UNDEFINED), 0, expr.position)
} else if (cv in powersOfTwo) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
return null
}
}
@@ -514,7 +513,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val leftIDt = expr.left.inferType(program)
if (!leftIDt.isKnown)
return null
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val leftDt = leftIDt.typeOrElse(DataType.UNDEFINED)
when (cv) {
-1.0 -> {
// '/' -> -left
@@ -591,14 +590,14 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return expr2.left
}
in powersOfTwo -> {
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
if (leftValue.inferType(program).isInteger()) {
// 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) {
if (leftValue.inferType(program).isInteger()) {
// 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)
@@ -622,7 +621,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val targetIDt = expr.left.inferType(program)
if(!targetIDt.isKnown)
throw FatalAstException("unknown dt")
when (val targetDt = targetIDt.typeOrElse(DataType.STRUCT)) {
when (val targetDt = targetIDt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
@@ -657,7 +656,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val idt = expr.left.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
when (idt.typeOrElse(DataType.STRUCT)) {
when (idt.typeOrElse(DataType.UNDEFINED)) {
DataType.UBYTE -> {
if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position)
+8 -10
View File
@@ -4,31 +4,30 @@ import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.isEmpty()) {
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
replacer.visit(this)
if (errors.isEmpty()) {
if (errors.noErrors()) {
replacer.applyModifications()
valuetypefixer.visit(this)
if(errors.isEmpty()) {
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this, compTarget)
optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
while (errors.noErrors() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
}
if (errors.isEmpty()) {
if (errors.noErrors()) {
replacer.visit(this)
replacer.applyModifications()
}
@@ -36,16 +35,15 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
}
}
if(errors.isEmpty())
if(errors.noErrors())
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
}
internal fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget, asmFileLoader)
compTarget: ICompilationTarget): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
@@ -12,68 +12,30 @@ import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
import kotlin.math.floor
internal const val retvarName = "prog8_retval"
internal class StatementOptimizer(private val program: Program,
private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String
) : AstWalker() {
private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program, asmFileLoader)
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
if(block.name != program.internedStringsModuleName)
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
if (block !in callgraph.usedSymbols) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
}
return noModifications
}
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() && !subroutine.inline) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.definingScope())
}.toMutableList()
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
return removals
}
for(returnvar in subsThatNeedReturnVariable) {
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null,
isArray = false,
autogeneratedDontRemove = true,
sharedWithAsm = false,
position = returnvar.third
)
returnvar.first.statements.add(0, decl)
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
if(!subroutine.isAsmSubroutine) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
}
return noModifications
}
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)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, decl.definingScope()))
}
subsThatNeedReturnVariable.clear()
return noModifications
}
@@ -87,6 +49,7 @@ internal class StatementOptimizer(private val program: Program,
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// only do this optimization if the arg is a known-constant string literal instead of a user defined variable.
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
@@ -94,30 +57,29 @@ internal class StatementOptimizer(private val program: Program,
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program)!!
val string = vardecl.value as? StringLiteralValue
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
val string = stringVar.targetVarDecl(program)?.value as? StringLiteralValue
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
IdentifierReference(listOf("txt", "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 = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
@@ -376,7 +338,7 @@ internal class StatementOptimizer(private val program: Program,
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val targetDt = targetIDt.typeOrElse(DataType.UNDEFINED)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
@@ -394,7 +356,7 @@ internal class StatementOptimizer(private val program: Program,
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
}
@@ -408,7 +370,7 @@ internal class StatementOptimizer(private val program: Program,
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
decs.statements.add(PostIncrDecr(assignment.target.copy(), "--", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
}
@@ -436,21 +398,17 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
fun returnViaIntermediary(value: Expression): Iterable<IAstModification>? {
val returnDt = returnStmt.definingSubroutine()!!.returntypes.single()
fun returnViaIntermediaryVar(value: Expression): Iterable<IAstModification>? {
val subr = returnStmt.definingSubroutine()!!
val returnDt = subr.returntypes.single()
if (returnDt in IntegerDatatypes) {
// first assign to intermediary, then return that register
val returnValueIntermediary =
when(returnDt) {
DataType.UBYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_ub"), returnStmt.position)
DataType.BYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_b"), returnStmt.position)
DataType.UWORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_uw"), returnStmt.position)
DataType.WORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_w"), returnStmt.position)
else -> throw FatalAstException("weird return dt")
}
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
// first assign to intermediary variable, then return that
subsThatNeedReturnVariable.add(Triple(subr, returnDt, returnStmt.position))
val returnValueIntermediary1 = IdentifierReference(listOf(retvarName), returnStmt.position)
val returnValueIntermediary2 = IdentifierReference(listOf(retvarName), returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary1, null, null, returnStmt.position)
val assign = Assignment(tgt, value, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
@@ -461,12 +419,12 @@ internal class StatementOptimizer(private val program: Program,
when(returnStmt.value) {
is PrefixExpression -> {
val mod = returnViaIntermediary(returnStmt.value!!)
val mod = returnViaIntermediaryVar(returnStmt.value!!)
if(mod!=null)
return mod
}
is BinaryExpression -> {
val mod = returnViaIntermediary(returnStmt.value!!)
val mod = returnViaIntermediaryVar(returnStmt.value!!)
if(mod!=null)
return mod
}
@@ -0,0 +1,96 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
internal class SubroutineInliner(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() {
private var callsToInlinedSubroutines = mutableListOf<Pair<IFunctionCall, Node>>()
fun fixCallsToInlinedSubroutines() {
for((call, parent) in callsToInlinedSubroutines) {
val sub = call.target.targetSubroutine(program)!!
val intermediateReturnValueVar = sub.statements.filterIsInstance<VarDecl>().singleOrNull { it.name.endsWith(retvarName) }
if(intermediateReturnValueVar!=null) {
val scope = parent.definingScope()
if(!scope.statements.filterIsInstance<VarDecl>().any { it.name==intermediateReturnValueVar.name}) {
val decl = intermediateReturnValueVar.copy()
scope.statements.add(0, decl)
decl.linkParents(scope as Node)
}
}
}
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
return if(compilerOptions.optimize && subroutine.inline && !subroutine.isAsmSubroutine)
annotateInlinedSubroutineIdentifiers(subroutine)
else
noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return after(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return after(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun after(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
val sub = functionCall.target.targetSubroutine(program)
if(sub != null && compilerOptions.optimize && sub.inline && !sub.isAsmSubroutine)
callsToInlinedSubroutines.add(Pair(functionCall, parent))
return noModifications
}
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): List<IAstModification> {
// this adds name prefixes to the identifiers used in the subroutine,
// so that the statements can be inlined (=copied) in the call site and still reference
// the correct symbols as seen from the scope of the subroutine.
class Annotator: AstWalker() {
var numReturns=0
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
val stmt = identifier.targetStatement(program)!!
if(stmt is BuiltinFunctionStatementPlaceholder)
return noModifications
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).split('.')
val withPrefix = IdentifierReference(prefixed, identifier.position)
return listOf(IAstModification.ReplaceNode(identifier, withPrefix, parent))
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
numReturns++
if(parent !== sub || sub.indexOfChild(returnStmt)<sub.statements.size-1)
errors.err("return statement must be the very last statement in the inlined subroutine", sub.position)
return noModifications
}
fun theModifications(): List<IAstModification> {
return this.modifications.map { it.first }.toList()
}
}
val annotator = Annotator()
sub.accept(annotator, sub.parent)
if(annotator.numReturns>1) {
errors.err("inlined subroutine can only have one return statement", sub.position)
return noModifications
}
return annotator.theModifications()
}
}
@@ -1,8 +1,7 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.PrefixExpression
@@ -12,45 +11,21 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget,
private val asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
private val compTarget: ICompilationTarget): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program, asmFileLoader)
val removals = mutableListOf<IAstModification>()
private val callgraph = CallGraph(program)
// remove all subroutines that aren't called, or are empty
// NOTE: part of this is also done already in the StatementOptimizer
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
val forceOutput = "force_output" in sub.definingBlock().options()
if (sub !== entrypoint && !forceOutput && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope()))
}
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope()))
}
// 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.definingScope()))
}
return removals
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
return if (!module.isLibraryModule && (module.containsNoCodeNorVars() || callgraph.unused(module)))
listOf(IAstModification.Remove(module, module.definingScope()))
else
noModifications
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as INameScope)
return emptyList()
@@ -74,7 +49,7 @@ internal class UnusedCodeRemover(private val program: Program,
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
when(val next = parent.nextSibling(stmt)) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is StructDecl -> {}
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position)
}
}
@@ -85,15 +60,60 @@ internal class UnusedCodeRemover(private val program: Program,
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
if(block.name != internedStringsModuleName)
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
if(callgraph.unused(block)) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
}
val removeDoubleAssignments = deduplicateAssignments(block.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock().options()
if (subroutine !== program.entrypoint() && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
if(callgraph.unused(subroutine)) {
if(!subroutine.definingModule().isLibraryModule)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
if(subroutine.containsNoCodeNorVars()) {
if(!subroutine.definingModule().isLibraryModule)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
callgraph.calledBy[subroutine]?.let {
for(node in it)
removals.add(IAstModification.Remove(node, node.definingScope()))
}
return removals
}
}
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR) {
val forceOutput = "force_output" in decl.definingBlock().options()
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock().isInLibrary) {
if (callgraph.unused(decl)) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, decl.definingScope()))
}
}
}
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
// removes 'duplicate' assignments that assign the same target directly after another
val linesToRemove = mutableListOf<Assignment>()
+153
View File
@@ -0,0 +1,153 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cpu6502.codegen.AsmGen
import java.nio.file.Path
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAsmGen6502 {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
private fun createTestProgram(): Program {
/*
main {
label_outside:
uword var_outside
sub start () {
uword localvar = 1234
uword tgt
locallabel:
tgt = localvar
tgt = &locallabel
tgt = &var_outside
tgt = &label_outside
tgt = &main.start.localvar
tgt = &main.start.locallabel
tgt = &main.var_outside
tgt = &main.label_outside
}
}
*/
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
val labelInSub = Label("locallabel", Position.DUMMY)
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
val labelInBlock = Label("label_outside", Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
val module = Module("test", mutableListOf(block), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.program = program
return program
}
private fun createTestAsmGen(program: Program): AsmGen {
val errors = ErrorReporter()
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
val zp = C64MachineDefinition.C64Zeropage(options)
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of("."))
return asmgen
}
@Test
fun testSymbolNameFromStrings() {
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
assertThat(asmgen.asmSymbolName("name"), equalTo("name"))
assertThat(asmgen.asmSymbolName("<name>"), equalTo("prog8_name"))
assertThat(asmgen.asmSymbolName(RegisterOrPair.R15), equalTo("cx16.r15"))
assertThat(asmgen.asmSymbolName(listOf("a", "b", "name")), equalTo("a.b.name"))
assertThat(asmgen.asmVariableName("name"), equalTo("name"))
assertThat(asmgen.asmVariableName("<name>"), equalTo("prog8_name"))
assertThat(asmgen.asmVariableName(listOf("a", "b", "name")), equalTo("a.b.name"))
}
@Test
fun testSymbolNameFromVarIdentifier() {
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
val sub = program.entrypoint()
// local variable
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
assertThat(asmgen.asmSymbolName(localvarIdent), equalTo("localvar"))
assertThat(asmgen.asmVariableName(localvarIdent), equalTo("localvar"))
val localvarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "start", "localvar") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(localvarIdentScoped), equalTo("main.start.localvar"))
assertThat(asmgen.asmVariableName(localvarIdentScoped), equalTo("main.start.localvar"))
// variable from outer scope (note that for Variables, no scoping prefix symbols are required,
// because they're not outputted as locally scoped symbols for the assembler
val scopedVarIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("var_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedVarIdent), equalTo("main.var_outside"))
assertThat(asmgen.asmVariableName(scopedVarIdent), equalTo("var_outside"))
val scopedVarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "var_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedVarIdentScoped), equalTo("main.var_outside"))
assertThat(asmgen.asmVariableName(scopedVarIdentScoped), equalTo("main.var_outside"))
}
@Test
fun testSymbolNameFromLabelIdentifier() {
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
val sub = program.entrypoint()
// local label
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(localLabelIdent), equalTo("_locallabel"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdent), equalTo("locallabel"))
val localLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","start","locallabel") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(localLabelIdentScoped), equalTo("main.start._locallabel"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdentScoped), equalTo("main.start.locallabel"))
// label from outer scope needs sope prefixes because it is outputted as a locally scoped symbol for the assembler
val scopedLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("label_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedLabelIdent), equalTo("main._label_outside"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdent), equalTo("label_outside"))
val scopedLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","label_outside") }.value as AddressOf).identifier
assertThat(asmgen.asmSymbolName(scopedLabelIdentScoped), equalTo("main._label_outside"))
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdentScoped), equalTo("main.label_outside"))
}
}
+192
View File
@@ -0,0 +1,192 @@
package prog8tests
import org.junit.jupiter.api.Test
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import java.nio.file.Path
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memory_identifiers() {
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
return target
}
@Test
fun testInValidRamC64_memory_expression() {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_variable() {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memmap_variable() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memmap_variable() {
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array() {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array_memmapped() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotValidRamC64_array_memmapped() {
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
}
+116
View File
@@ -0,0 +1,116 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.toHex
import prog8.compiler.CompilerException
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 kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestNumbers {
@Test
fun testToHex() {
assertEquals("0", 0.toHex())
assertEquals("1", 1.toHex())
assertEquals("1", 1.234.toHex())
assertEquals("10", 10.toHex())
assertEquals("10", 10.99.toHex())
assertEquals("15", 15.toHex())
assertEquals("\$10", 16.toHex())
assertEquals("\$ff", 255.toHex())
assertEquals("\$0100", 256.toHex())
assertEquals("\$4e5c", 20060.toHex())
assertEquals("\$c382", 50050.toHex())
assertEquals("\$ffff", 65535.toHex())
assertEquals("\$ffff", 65535L.toHex())
assertEquals("0", 0.toHex())
assertEquals("-1", (-1).toHex())
assertEquals("-1", (-1.234).toHex())
assertEquals("-10", (-10).toHex())
assertEquals("-10", (-10.99).toHex())
assertEquals("-15", (-15).toHex())
assertEquals("-\$10", (-16).toHex())
assertEquals("-\$ff", (-255).toHex())
assertEquals("-\$0100", (-256).toHex())
assertEquals("-\$4e5c", (-20060).toHex())
assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
}
@Test
fun testFloatToMflpt5() {
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
}
@Test
fun testFloatRange() {
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
}
@Test
fun testMflpt5ToFloat() {
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, 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, 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, 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, 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, epsilon))
}
}
@@ -14,13 +14,13 @@ import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
return lv1.type==lv2.type && lv1==lv2
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserNumericLiteralValue {
class TestNumericLiteralValue {
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
return lv1.type==lv2.type && lv1==lv2
}
private val dummyPos = Position("test", 0, 0, 0)
+108
View File
@@ -0,0 +1,108 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
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.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.target.cbm.Petscii
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestPetscii {
@Test
fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(listOf<Short>(0)))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(listOf<Short>(0)))
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
}
@Test
fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("✓", true), equalTo(listOf<Short>(0xfa)))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(listOf<Short>(255)))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(listOf<Short>(0xd3)))
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256), true) }
}
@Test
fun testUppercase() {
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("♥"), equalTo(listOf<Short>(0xd3)))
assertThat(Petscii.encodePetscii("π"), equalTo(listOf<Short>(0xff)))
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(listOf<Short>(250)))
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256)) }
}
@Test
fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)
))
assertThat(Petscii.encodeScreencode("✓", true), equalTo(listOf<Short>(0x7a)))
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(listOf<Short>(83)))
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(listOf<Short>(94)))
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256), true) }
}
@Test
fun testScreencodeUppercase() {
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)))
assertThat(Petscii.encodeScreencode("♥"), equalTo(listOf<Short>(0x53)))
assertThat(Petscii.encodeScreencode("π"), equalTo(listOf<Short>(0x5e)))
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(listOf<Short>(122)))
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256)) }
}
@Test
fun testLiteralValueComparisons() {
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)
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)
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
}
}
-601
View File
@@ -1,601 +0,0 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
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.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import java.io.CharConversionException
import java.nio.file.Path
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompiler {
@Test
fun testToHex() {
assertEquals("0", 0.toHex())
assertEquals("1", 1.toHex())
assertEquals("1", 1.234.toHex())
assertEquals("10", 10.toHex())
assertEquals("10", 10.99.toHex())
assertEquals("15", 15.toHex())
assertEquals("\$10", 16.toHex())
assertEquals("\$ff", 255.toHex())
assertEquals("\$0100", 256.toHex())
assertEquals("\$4e5c", 20060.toHex())
assertEquals("\$c382", 50050.toHex())
assertEquals("\$ffff", 65535.toHex())
assertEquals("\$ffff", 65535L.toHex())
assertEquals("0", 0.toHex())
assertEquals("-1", (-1).toHex())
assertEquals("-1", (-1.234).toHex())
assertEquals("-10", (-10).toHex())
assertEquals("-10", (-10.99).toHex())
assertEquals("-15", (-15).toHex())
assertEquals("-\$10", (-16).toHex())
assertEquals("-\$ff", (-255).toHex())
assertEquals("-\$0100", (-256).toHex())
assertEquals("-\$4e5c", (-20060).toHex())
assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
}
@Test
fun testFloatToMflpt5() {
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
}
@Test
fun testFloatRange() {
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
}
@Test
fun testMflpt5ToFloat() {
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, 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, 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, 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, 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, epsilon))
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage {
private val errors = ErrorReporter()
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
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, errors)
}
zp.allocate("varname2", DataType.UBYTE, null, errors)
}
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null, errors)
}
}
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(85, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.available())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
assertTrue(49 in zp1.free)
assertTrue(101 in zp1.free)
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.available())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
assertTrue(49 in zp2.free)
assertTrue(101 in zp2.free)
assertFalse(200 in zp2.free)
assertFalse(255 in zp2.free)
assertTrue(199 in zp2.free)
}
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
zp.allocate("", DataType.FLOAT, null, errors)
}
for (i in 0 until zp.available()) {
val loc = zp.allocate("", DataType.UBYTE, null, errors)
assertTrue(loc > 0)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UWORD, null, errors)
}
}
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.available())
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, errors)
}
assertEquals(6,zp.available())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null, errors)
}
for(i in 0..5) {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null, errors)
}
}
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
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(0x9b, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available())
}
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
@Test
fun testReservedLocations() {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
@Test
fun testFreeSpaces() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.available())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp3.available())
val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.available())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestPetscii {
@Test
fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(listOf<Short>(0)))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(listOf<Short>(0)))
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
}
@Test
fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("✓", true), equalTo(listOf<Short>(0xfa)))
assertFailsWith<CharConversionException> { Petscii.encodePetscii("π", true) }
assertFailsWith<CharConversionException> { Petscii.encodePetscii("♥", true) }
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256), true) }
}
@Test
fun testUppercase() {
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(listOf<Short>(0x12))) // reverse vid
assertThat(Petscii.encodePetscii("♥"), equalTo(listOf<Short>(0xd3)))
assertThat(Petscii.encodePetscii("π"), equalTo(listOf<Short>(0xff)))
assertFailsWith<CharConversionException> { Petscii.encodePetscii("✓") }
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256)) }
}
@Test
fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)
))
assertThat(Petscii.encodeScreencode("✓", true), equalTo(listOf<Short>(0x7a)))
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("♥", true) }
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("π", true) }
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256), true) }
}
@Test
fun testScreencodeUppercase() {
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)))
assertThat(Petscii.encodeScreencode("♥"), equalTo(listOf<Short>(0x53)))
assertThat(Petscii.encodeScreencode("π"), equalTo(listOf<Short>(0x5e)))
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("✓") }
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("hello") }
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256)) }
}
@Test
fun testLiteralValueComparisons() {
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)
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)
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
}
}
class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memory_identifiers() {
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
return target
}
@Test
fun testInValidRamC64_memory_expression() {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_variable() {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memmap_variable() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memmap_variable() {
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array() {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array_memmapped() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotValidRamC64_array_memmapped() {
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
}
+265
View File
@@ -0,0 +1,265 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage {
private val errors = ErrorReporter()
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
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, errors)
}
zp.allocate("varname2", DataType.UBYTE, null, errors)
}
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free)
assertEquals(0, zp.availableBytes())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null, errors)
}
}
@Test
fun testFreeSpacesBytes() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.availableBytes())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(85, zp2.availableBytes())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.availableBytes())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.availableBytes())
zp4.allocate("test", DataType.UBYTE, null, errors)
assertEquals(237, zp4.availableBytes())
zp4.allocate("test2", DataType.UBYTE, null, errors)
assertEquals(236, zp4.availableBytes())
}
@Test
fun testFreeSpacesWords() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(6, zp1.availableWords())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(38, zp2.availableWords())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(57, zp3.availableWords())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(116, zp4.availableWords())
zp4.allocate("test", DataType.UWORD, null, errors)
assertEquals(115, zp4.availableWords())
zp4.allocate("test2", DataType.UWORD, null, errors)
assertEquals(114, zp4.availableWords())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.availableBytes())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
assertTrue(49 in zp1.free)
assertTrue(101 in zp1.free)
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.availableBytes())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
assertTrue(49 in zp2.free)
assertTrue(101 in zp2.free)
assertFalse(200 in zp2.free)
assertFalse(255 in zp2.free)
assertTrue(199 in zp2.free)
}
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.availableBytes())
assertTrue(zp.hasByteAvailable())
assertTrue(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
zp.allocate("", DataType.FLOAT, null, errors)
}
for (i in 0 until zp.availableBytes()) {
val loc = zp.allocate("", DataType.UBYTE, null, errors)
assertTrue(loc > 0)
}
assertEquals(0, zp.availableBytes())
assertFalse(zp.hasByteAvailable())
assertFalse(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UWORD, null, errors)
}
}
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.availableBytes())
assertTrue(zp.hasByteAvailable())
assertTrue(zp.hasWordAvailable())
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
assertFalse(loc in zp.free)
val num = zp.availableBytes() / 2
for(i in 0..num-4) {
zp.allocate("", DataType.UWORD, null, errors)
}
assertEquals(6,zp.availableBytes())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null, errors)
}
for(i in 0..5) {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertEquals(0, zp.availableBytes())
assertFalse(zp.hasByteAvailable())
assertFalse(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null, errors)
}
}
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.availableBytes())
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(0x9b, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.availableBytes())
}
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
private val errors = ErrorReporter()
@Test
fun testReservedLocations() {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
@Test
fun testFreeSpacesBytes() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.availableBytes())
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp2.availableBytes())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp3.availableBytes())
zp3.allocate("test", DataType.UBYTE, null, errors)
assertEquals(215, zp3.availableBytes())
zp3.allocate("test2", DataType.UBYTE, null, errors)
assertEquals(214, zp3.availableBytes())
}
@Test
fun testFreeSpacesWords() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(108, zp1.availableWords())
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(87, zp2.availableWords())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(44, zp3.availableWords())
zp3.allocate("test", DataType.UWORD, null, errors)
assertEquals(43, zp3.availableWords())
zp3.allocate("test2", DataType.UWORD, null, errors)
assertEquals(42, zp3.availableWords())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.availableBytes())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
}
-993
View File
@@ -1,993 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy10 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf10 {
const ubyte numforLave = 7 ; Lave is 7th generated planet10 in galaxy10 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy10.travel_to(1, numforLave)
market10.init(0) ; Lave's market10 is seeded with 0
ship10.init()
planet10.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util10.print_10s(ship10.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market10 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader10.do_buy()
's' -> trader10.do_sell()
'f' -> trader10.do_fuel()
'j' -> trader10.do_jump()
't' -> trader10.do_teleport()
'g' -> trader10.do_next_galaxy10()
'i' -> trader10.do_info()
'm' -> trader10.do_show_market10()
'l' -> trader10.do_local()
'c' -> trader10.do_cash()
'h' -> trader10.do_hold()
'<' -> trader10.do_load()
'>' -> trader10.do_save()
}
}
}
}
}
trader10 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy10
ubyte planet10
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship10.cash = savedata.cash
ship10.Max_cargo = savedata.max_cargo
ship10.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship10.cargohold, len(ship10.cargohold))
galaxy10.travel_to(savedata.galaxy10, savedata.planet10)
planet10.display(false)
}
sub do_save() {
savedata.galaxy10 = galaxy10.number
savedata.planet10 = planet10.number
savedata.cash = ship10.cash
savedata.max_cargo = ship10.Max_cargo
savedata.fuel = ship10.fuel
sys.memcopy(ship10.cargohold, &savedata.cargo0, len(ship10.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship10.fuel
ship10.fuel = 255
jump_to_system()
ship10.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet10 = planet10.number
ubyte x = planet10.x
ubyte y = planet10.y
if galaxy10.search_closest_planet10(input) {
ubyte distance = planet10.distance(x, y)
if distance <= ship10.fuel {
galaxy10.init_market10_for_planet10()
ship10.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet10.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy10.travel_to(galaxy10.number, current_planet10)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market10.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market10.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market10.current_price[ci] * amount
txt.print(" Total price: ")
util10.print_10s(price)
if price > ship10.cash {
txt.print(" Not enough cash!\n")
} else {
ship10.cash -= price
ship10.cargohold[ci] += amount
market10.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market10.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship10.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market10.current_price[ci] * amount
txt.print(" Total price: ")
util10.print_10s(price)
ship10.cash += price
ship10.cargohold[ci] -= amount
market10.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship10.Max_fuel - ship10.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship10.Fuel_cost
if price > ship10.cash {
txt.print("Not enough cash!\n")
} else {
ship10.cash -= price
ship10.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship10.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship10.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy10() {
galaxy10.travel_to(galaxy10.number+1, planet10.number)
planet10.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet10 = planet10.number
if galaxy10.search_closest_planet10(input) {
planet10.display(false)
} else {
txt.print(" Not found!")
}
galaxy10.travel_to(galaxy10.number, current_planet10)
} else {
planet10.display(false)
}
}
sub do_local() {
galaxy10.local_area()
}
sub do_show_market10() {
market10.display()
txt.print("\nFuel: ")
util10.print_10s(ship10.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship10.cargo_free())
txt.print("t\n")
}
}
ship10 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market10.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market10 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet10's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market10 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet10.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet10.print_name_uppercase()
txt.print(" trade market10:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util10.print_right(13, names[ci])
txt.print(" ")
util10.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship10.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util10.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy10 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy10
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy10num) {
number = 1
planet10.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy10num-1 {
nextgalaxy10()
}
}
sub nextgalaxy10() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy10num, ubyte system) {
init(galaxy10num)
generate_next_planet10() ; always at least planet10 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet10()
}
planet10.name = make_current_planet10_name()
init_market10_for_planet10()
}
sub init_market10_for_planet10() {
market10.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet10(uword nameptr) -> ubyte {
ubyte x = planet10.x
ubyte y = planet10.y
ubyte current_planet10_num = planet10.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet10()
planet10.name = make_current_planet10_name()
if util10.prefix_matches(nameptr, planet10.name) {
ubyte distance = planet10.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet10_num)
return found
}
sub local_area() {
ubyte current_planet10 = planet10.number
ubyte px = planet10.x
ubyte py = planet10.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet10()
ubyte distance = planet10.distance(px, py)
if distance <= ship10.Max_fuel {
if distance <= ship10.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet10.name = make_current_planet10_name()
planet10.display(true)
txt.print(" (")
util10.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet10)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet10() {
determine_planet10_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet10_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet10_properties() {
; create the planet10's characteristics
planet10.number++
planet10.x = msb(seed[1])
planet10.y = msb(seed[0])
planet10.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet10.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet10.govtype <= 1
planet10.economy = (planet10.economy | 2)
planet10.techlevel = (msb(seed[1]) & 3) + (planet10.economy ^ 7)
planet10.techlevel += planet10.govtype >> 1
if planet10.govtype & 1
planet10.techlevel++
planet10.population = 4 * planet10.techlevel + planet10.economy
planet10.population += planet10.govtype + 1
planet10.productivity = ((planet10.economy ^ 7) + 3) * (planet10.govtype + 4)
planet10.productivity *= planet10.population * 8
ubyte seed2_msb = msb(seed[2])
planet10.radius = mkword((seed2_msb & 15) + 11, planet10.x)
planet10.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet10.species_is_alien {
planet10.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet10.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet10.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet10.species_kind = (planet10.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet10.goatsoup_seed[0] = lsb(seed[1])
planet10.goatsoup_seed[1] = msb(seed[1])
planet10.goatsoup_seed[2] = lsb(seed[2])
planet10.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy10 #")
txt.print_ub(number)
txt.print("\ngalaxy10 seed0=")
txt.print_uwhex(galaxy10.seed[0], true)
txt.print("\ngalaxy10 seed1=")
txt.print_uwhex(galaxy10.seed[1], true)
txt.print("\ngalaxy10 seed2=")
txt.print_uwhex(galaxy10.seed[2], true)
txt.nl()
}
}
planet10 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship10", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet10 \xB0", "The world \xB0", "This planet10", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet10", "world", "place", "little planet10", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy10, then increases by 1 for each generated planet10
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet10_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet10_result
reset_rnd()
recursive_soup()
return planet10_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util10 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-994
View File
@@ -1,994 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy2 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf2 {
const ubyte numforLave = 7 ; Lave is 7th generated planet2 in galaxy2 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy2.travel_to(1, numforLave)
market2.init(0) ; Lave's market2 is seeded with 0
ship2.init()
planet2.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util2.print_10s(ship2.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market2 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader2.do_buy()
's' -> trader2.do_sell()
'f' -> trader2.do_fuel()
'j' -> trader2.do_jump()
't' -> trader2.do_teleport()
'g' -> trader2.do_next_galaxy2()
'i' -> trader2.do_info()
'm' -> trader2.do_show_market2()
'l' -> trader2.do_local()
'c' -> trader2.do_cash()
'h' -> trader2.do_hold()
'<' -> trader2.do_load()
'>' -> trader2.do_save()
}
}
}
}
}
trader2 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy2
ubyte planet2
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship2.cash = savedata.cash
ship2.Max_cargo = savedata.max_cargo
ship2.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship2.cargohold, len(ship2.cargohold))
galaxy2.travel_to(savedata.galaxy2, savedata.planet2)
planet2.display(false)
}
sub do_save() {
savedata.galaxy2 = galaxy2.number
savedata.planet2 = planet2.number
savedata.cash = ship2.cash
savedata.max_cargo = ship2.Max_cargo
savedata.fuel = ship2.fuel
sys.memcopy(ship2.cargohold, &savedata.cargo0, len(ship2.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship2.fuel
ship2.fuel = 255
jump_to_system()
ship2.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet2 = planet2.number
ubyte x = planet2.x
ubyte y = planet2.y
if galaxy2.search_closest_planet2(input) {
ubyte distance = planet2.distance(x, y)
if distance <= ship2.fuel {
galaxy2.init_market2_for_planet2()
ship2.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet2.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy2.travel_to(galaxy2.number, current_planet2)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market2.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market2.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market2.current_price[ci] * amount
txt.print(" Total price: ")
util2.print_10s(price)
if price > ship2.cash {
txt.print(" Not enough cash!\n")
} else {
ship2.cash -= price
ship2.cargohold[ci] += amount
market2.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market2.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship2.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market2.current_price[ci] * amount
txt.print(" Total price: ")
util2.print_10s(price)
ship2.cash += price
ship2.cargohold[ci] -= amount
market2.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship2.Max_fuel - ship2.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship2.Fuel_cost
if price > ship2.cash {
txt.print("Not enough cash!\n")
} else {
ship2.cash -= price
ship2.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship2.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship2.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy2() {
galaxy2.travel_to(galaxy2.number+1, planet2.number)
planet2.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet2 = planet2.number
if galaxy2.search_closest_planet2(input) {
planet2.display(false)
} else {
txt.print(" Not found!")
}
galaxy2.travel_to(galaxy2.number, current_planet2)
} else {
planet2.display(false)
}
}
sub do_local() {
galaxy2.local_area()
}
sub do_show_market2() {
market2.display()
txt.print("\nFuel: ")
util2.print_10s(ship2.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship2.cargo_free())
txt.print("t\n")
}
}
ship2 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market2.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market2 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet2's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market2 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet2.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet2.print_name_uppercase()
txt.print(" trade market2:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util2.print_right(13, names[ci])
txt.print(" ")
util2.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship2.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util2.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy2 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy2
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy2num) {
number = 1
planet2.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy2num-1 {
nextgalaxy2()
}
}
sub nextgalaxy2() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy2num, ubyte system) {
init(galaxy2num)
generate_next_planet2() ; always at least planet2 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet2()
}
planet2.name = make_current_planet2_name()
init_market2_for_planet2()
}
sub init_market2_for_planet2() {
market2.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet2(uword nameptr) -> ubyte {
ubyte x = planet2.x
ubyte y = planet2.y
ubyte current_planet2_num = planet2.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet2()
planet2.name = make_current_planet2_name()
if util2.prefix_matches(nameptr, planet2.name) {
ubyte distance = planet2.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet2_num)
return found
}
sub local_area() {
ubyte current_planet2 = planet2.number
ubyte px = planet2.x
ubyte py = planet2.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet2()
ubyte distance = planet2.distance(px, py)
if distance <= ship2.Max_fuel {
if distance <= ship2.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet2.name = make_current_planet2_name()
planet2.display(true)
txt.print(" (")
util2.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet2)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet2() {
determine_planet2_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet2_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet2_properties() {
; create the planet2's characteristics
planet2.number++
planet2.x = msb(seed[1])
planet2.y = msb(seed[0])
planet2.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet2.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet2.govtype <= 1
planet2.economy = (planet2.economy | 2)
planet2.techlevel = (msb(seed[1]) & 3) + (planet2.economy ^ 7)
planet2.techlevel += planet2.govtype >> 1
if planet2.govtype & 1
planet2.techlevel++
planet2.population = 4 * planet2.techlevel + planet2.economy
planet2.population += planet2.govtype + 1
planet2.productivity = ((planet2.economy ^ 7) + 3) * (planet2.govtype + 4)
planet2.productivity *= planet2.population * 8
ubyte seed2_msb = msb(seed[2])
planet2.radius = mkword((seed2_msb & 15) + 11, planet2.x)
planet2.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet2.species_is_alien {
planet2.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet2.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet2.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet2.species_kind = (planet2.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet2.goatsoup_seed[0] = lsb(seed[1])
planet2.goatsoup_seed[1] = msb(seed[1])
planet2.goatsoup_seed[2] = lsb(seed[2])
planet2.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy2 #")
txt.print_ub(number)
txt.print("\ngalaxy2 seed0=")
txt.print_uwhex(galaxy2.seed[0], true)
txt.print("\ngalaxy2 seed1=")
txt.print_uwhex(galaxy2.seed[1], true)
txt.print("\ngalaxy2 seed2=")
txt.print_uwhex(galaxy2.seed[2], true)
txt.nl()
}
}
planet2 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship2", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet2 \xB0", "The world \xB0", "This planet2", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet2", "world", "place", "little planet2", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy2, then increases by 1 for each generated planet2
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet2_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet2_result
reset_rnd()
recursive_soup()
return planet2_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util2 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-994
View File
@@ -1,994 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy3 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf3 {
const ubyte numforLave = 7 ; Lave is 7th generated planet3 in galaxy3 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy3.travel_to(1, numforLave)
market3.init(0) ; Lave's market3 is seeded with 0
ship3.init()
planet3.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util3.print_10s(ship3.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market3 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader3.do_buy()
's' -> trader3.do_sell()
'f' -> trader3.do_fuel()
'j' -> trader3.do_jump()
't' -> trader3.do_teleport()
'g' -> trader3.do_next_galaxy3()
'i' -> trader3.do_info()
'm' -> trader3.do_show_market3()
'l' -> trader3.do_local()
'c' -> trader3.do_cash()
'h' -> trader3.do_hold()
'<' -> trader3.do_load()
'>' -> trader3.do_save()
}
}
}
}
}
trader3 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy3
ubyte planet3
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship3.cash = savedata.cash
ship3.Max_cargo = savedata.max_cargo
ship3.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship3.cargohold, len(ship3.cargohold))
galaxy3.travel_to(savedata.galaxy3, savedata.planet3)
planet3.display(false)
}
sub do_save() {
savedata.galaxy3 = galaxy3.number
savedata.planet3 = planet3.number
savedata.cash = ship3.cash
savedata.max_cargo = ship3.Max_cargo
savedata.fuel = ship3.fuel
sys.memcopy(ship3.cargohold, &savedata.cargo0, len(ship3.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship3.fuel
ship3.fuel = 255
jump_to_system()
ship3.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet3 = planet3.number
ubyte x = planet3.x
ubyte y = planet3.y
if galaxy3.search_closest_planet3(input) {
ubyte distance = planet3.distance(x, y)
if distance <= ship3.fuel {
galaxy3.init_market3_for_planet3()
ship3.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet3.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy3.travel_to(galaxy3.number, current_planet3)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market3.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market3.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market3.current_price[ci] * amount
txt.print(" Total price: ")
util3.print_10s(price)
if price > ship3.cash {
txt.print(" Not enough cash!\n")
} else {
ship3.cash -= price
ship3.cargohold[ci] += amount
market3.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market3.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship3.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market3.current_price[ci] * amount
txt.print(" Total price: ")
util3.print_10s(price)
ship3.cash += price
ship3.cargohold[ci] -= amount
market3.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship3.Max_fuel - ship3.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship3.Fuel_cost
if price > ship3.cash {
txt.print("Not enough cash!\n")
} else {
ship3.cash -= price
ship3.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship3.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship3.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy3() {
galaxy3.travel_to(galaxy3.number+1, planet3.number)
planet3.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet3 = planet3.number
if galaxy3.search_closest_planet3(input) {
planet3.display(false)
} else {
txt.print(" Not found!")
}
galaxy3.travel_to(galaxy3.number, current_planet3)
} else {
planet3.display(false)
}
}
sub do_local() {
galaxy3.local_area()
}
sub do_show_market3() {
market3.display()
txt.print("\nFuel: ")
util3.print_10s(ship3.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship3.cargo_free())
txt.print("t\n")
}
}
ship3 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market3.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market3 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet3's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market3 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet3.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet3.print_name_uppercase()
txt.print(" trade market3:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util3.print_right(13, names[ci])
txt.print(" ")
util3.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship3.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util3.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy3 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy3
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy3num) {
number = 1
planet3.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy3num-1 {
nextgalaxy3()
}
}
sub nextgalaxy3() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy3num, ubyte system) {
init(galaxy3num)
generate_next_planet3() ; always at least planet3 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet3()
}
planet3.name = make_current_planet3_name()
init_market3_for_planet3()
}
sub init_market3_for_planet3() {
market3.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet3(uword nameptr) -> ubyte {
ubyte x = planet3.x
ubyte y = planet3.y
ubyte current_planet3_num = planet3.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet3()
planet3.name = make_current_planet3_name()
if util3.prefix_matches(nameptr, planet3.name) {
ubyte distance = planet3.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet3_num)
return found
}
sub local_area() {
ubyte current_planet3 = planet3.number
ubyte px = planet3.x
ubyte py = planet3.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet3()
ubyte distance = planet3.distance(px, py)
if distance <= ship3.Max_fuel {
if distance <= ship3.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet3.name = make_current_planet3_name()
planet3.display(true)
txt.print(" (")
util3.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet3)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet3() {
determine_planet3_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet3_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet3_properties() {
; create the planet3's characteristics
planet3.number++
planet3.x = msb(seed[1])
planet3.y = msb(seed[0])
planet3.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet3.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet3.govtype <= 1
planet3.economy = (planet3.economy | 2)
planet3.techlevel = (msb(seed[1]) & 3) + (planet3.economy ^ 7)
planet3.techlevel += planet3.govtype >> 1
if planet3.govtype & 1
planet3.techlevel++
planet3.population = 4 * planet3.techlevel + planet3.economy
planet3.population += planet3.govtype + 1
planet3.productivity = ((planet3.economy ^ 7) + 3) * (planet3.govtype + 4)
planet3.productivity *= planet3.population * 8
ubyte seed2_msb = msb(seed[2])
planet3.radius = mkword((seed2_msb & 15) + 11, planet3.x)
planet3.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet3.species_is_alien {
planet3.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet3.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet3.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet3.species_kind = (planet3.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet3.goatsoup_seed[0] = lsb(seed[1])
planet3.goatsoup_seed[1] = msb(seed[1])
planet3.goatsoup_seed[2] = lsb(seed[2])
planet3.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy3 #")
txt.print_ub(number)
txt.print("\ngalaxy3 seed0=")
txt.print_uwhex(galaxy3.seed[0], true)
txt.print("\ngalaxy3 seed1=")
txt.print_uwhex(galaxy3.seed[1], true)
txt.print("\ngalaxy3 seed2=")
txt.print_uwhex(galaxy3.seed[2], true)
txt.nl()
}
}
planet3 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship3", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet3 \xB0", "The world \xB0", "This planet3", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet3", "world", "place", "little planet3", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy3, then increases by 1 for each generated planet3
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet3_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet3_result
reset_rnd()
recursive_soup()
return planet3_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util3 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-994
View File
@@ -1,994 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy4 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf4 {
const ubyte numforLave = 7 ; Lave is 7th generated planet4 in galaxy4 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy4.travel_to(1, numforLave)
market4.init(0) ; Lave's market4 is seeded with 0
ship4.init()
planet4.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util4.print_10s(ship4.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market4 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader4.do_buy()
's' -> trader4.do_sell()
'f' -> trader4.do_fuel()
'j' -> trader4.do_jump()
't' -> trader4.do_teleport()
'g' -> trader4.do_next_galaxy4()
'i' -> trader4.do_info()
'm' -> trader4.do_show_market4()
'l' -> trader4.do_local()
'c' -> trader4.do_cash()
'h' -> trader4.do_hold()
'<' -> trader4.do_load()
'>' -> trader4.do_save()
}
}
}
}
}
trader4 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy4
ubyte planet4
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship4.cash = savedata.cash
ship4.Max_cargo = savedata.max_cargo
ship4.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship4.cargohold, len(ship4.cargohold))
galaxy4.travel_to(savedata.galaxy4, savedata.planet4)
planet4.display(false)
}
sub do_save() {
savedata.galaxy4 = galaxy4.number
savedata.planet4 = planet4.number
savedata.cash = ship4.cash
savedata.max_cargo = ship4.Max_cargo
savedata.fuel = ship4.fuel
sys.memcopy(ship4.cargohold, &savedata.cargo0, len(ship4.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship4.fuel
ship4.fuel = 255
jump_to_system()
ship4.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet4 = planet4.number
ubyte x = planet4.x
ubyte y = planet4.y
if galaxy4.search_closest_planet4(input) {
ubyte distance = planet4.distance(x, y)
if distance <= ship4.fuel {
galaxy4.init_market4_for_planet4()
ship4.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet4.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy4.travel_to(galaxy4.number, current_planet4)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market4.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market4.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market4.current_price[ci] * amount
txt.print(" Total price: ")
util4.print_10s(price)
if price > ship4.cash {
txt.print(" Not enough cash!\n")
} else {
ship4.cash -= price
ship4.cargohold[ci] += amount
market4.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market4.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship4.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market4.current_price[ci] * amount
txt.print(" Total price: ")
util4.print_10s(price)
ship4.cash += price
ship4.cargohold[ci] -= amount
market4.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship4.Max_fuel - ship4.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship4.Fuel_cost
if price > ship4.cash {
txt.print("Not enough cash!\n")
} else {
ship4.cash -= price
ship4.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship4.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship4.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy4() {
galaxy4.travel_to(galaxy4.number+1, planet4.number)
planet4.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet4 = planet4.number
if galaxy4.search_closest_planet4(input) {
planet4.display(false)
} else {
txt.print(" Not found!")
}
galaxy4.travel_to(galaxy4.number, current_planet4)
} else {
planet4.display(false)
}
}
sub do_local() {
galaxy4.local_area()
}
sub do_show_market4() {
market4.display()
txt.print("\nFuel: ")
util4.print_10s(ship4.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship4.cargo_free())
txt.print("t\n")
}
}
ship4 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market4.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market4 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet4's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market4 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet4.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet4.print_name_uppercase()
txt.print(" trade market4:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util4.print_right(13, names[ci])
txt.print(" ")
util4.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship4.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util4.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy4 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy4
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy4num) {
number = 1
planet4.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy4num-1 {
nextgalaxy4()
}
}
sub nextgalaxy4() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy4num, ubyte system) {
init(galaxy4num)
generate_next_planet4() ; always at least planet4 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet4()
}
planet4.name = make_current_planet4_name()
init_market4_for_planet4()
}
sub init_market4_for_planet4() {
market4.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet4(uword nameptr) -> ubyte {
ubyte x = planet4.x
ubyte y = planet4.y
ubyte current_planet4_num = planet4.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet4()
planet4.name = make_current_planet4_name()
if util4.prefix_matches(nameptr, planet4.name) {
ubyte distance = planet4.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet4_num)
return found
}
sub local_area() {
ubyte current_planet4 = planet4.number
ubyte px = planet4.x
ubyte py = planet4.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet4()
ubyte distance = planet4.distance(px, py)
if distance <= ship4.Max_fuel {
if distance <= ship4.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet4.name = make_current_planet4_name()
planet4.display(true)
txt.print(" (")
util4.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet4)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet4() {
determine_planet4_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet4_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet4_properties() {
; create the planet4's characteristics
planet4.number++
planet4.x = msb(seed[1])
planet4.y = msb(seed[0])
planet4.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet4.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet4.govtype <= 1
planet4.economy = (planet4.economy | 2)
planet4.techlevel = (msb(seed[1]) & 3) + (planet4.economy ^ 7)
planet4.techlevel += planet4.govtype >> 1
if planet4.govtype & 1
planet4.techlevel++
planet4.population = 4 * planet4.techlevel + planet4.economy
planet4.population += planet4.govtype + 1
planet4.productivity = ((planet4.economy ^ 7) + 3) * (planet4.govtype + 4)
planet4.productivity *= planet4.population * 8
ubyte seed2_msb = msb(seed[2])
planet4.radius = mkword((seed2_msb & 15) + 11, planet4.x)
planet4.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet4.species_is_alien {
planet4.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet4.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet4.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet4.species_kind = (planet4.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet4.goatsoup_seed[0] = lsb(seed[1])
planet4.goatsoup_seed[1] = msb(seed[1])
planet4.goatsoup_seed[2] = lsb(seed[2])
planet4.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy4 #")
txt.print_ub(number)
txt.print("\ngalaxy4 seed0=")
txt.print_uwhex(galaxy4.seed[0], true)
txt.print("\ngalaxy4 seed1=")
txt.print_uwhex(galaxy4.seed[1], true)
txt.print("\ngalaxy4 seed2=")
txt.print_uwhex(galaxy4.seed[2], true)
txt.nl()
}
}
planet4 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship4", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet4 \xB0", "The world \xB0", "This planet4", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet4", "world", "place", "little planet4", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy4, then increases by 1 for each generated planet4
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet4_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet4_result
reset_rnd()
recursive_soup()
return planet4_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util4 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-994
View File
@@ -1,994 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy5 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf5 {
const ubyte numforLave = 7 ; Lave is 7th generated planet5 in galaxy5 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy5.travel_to(1, numforLave)
market5.init(0) ; Lave's market5 is seeded with 0
ship5.init()
planet5.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util5.print_10s(ship5.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market5 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader5.do_buy()
's' -> trader5.do_sell()
'f' -> trader5.do_fuel()
'j' -> trader5.do_jump()
't' -> trader5.do_teleport()
'g' -> trader5.do_next_galaxy5()
'i' -> trader5.do_info()
'm' -> trader5.do_show_market5()
'l' -> trader5.do_local()
'c' -> trader5.do_cash()
'h' -> trader5.do_hold()
'<' -> trader5.do_load()
'>' -> trader5.do_save()
}
}
}
}
}
trader5 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy5
ubyte planet5
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship5.cash = savedata.cash
ship5.Max_cargo = savedata.max_cargo
ship5.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship5.cargohold, len(ship5.cargohold))
galaxy5.travel_to(savedata.galaxy5, savedata.planet5)
planet5.display(false)
}
sub do_save() {
savedata.galaxy5 = galaxy5.number
savedata.planet5 = planet5.number
savedata.cash = ship5.cash
savedata.max_cargo = ship5.Max_cargo
savedata.fuel = ship5.fuel
sys.memcopy(ship5.cargohold, &savedata.cargo0, len(ship5.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship5.fuel
ship5.fuel = 255
jump_to_system()
ship5.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet5 = planet5.number
ubyte x = planet5.x
ubyte y = planet5.y
if galaxy5.search_closest_planet5(input) {
ubyte distance = planet5.distance(x, y)
if distance <= ship5.fuel {
galaxy5.init_market5_for_planet5()
ship5.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet5.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy5.travel_to(galaxy5.number, current_planet5)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market5.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market5.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market5.current_price[ci] * amount
txt.print(" Total price: ")
util5.print_10s(price)
if price > ship5.cash {
txt.print(" Not enough cash!\n")
} else {
ship5.cash -= price
ship5.cargohold[ci] += amount
market5.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market5.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship5.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market5.current_price[ci] * amount
txt.print(" Total price: ")
util5.print_10s(price)
ship5.cash += price
ship5.cargohold[ci] -= amount
market5.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship5.Max_fuel - ship5.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship5.Fuel_cost
if price > ship5.cash {
txt.print("Not enough cash!\n")
} else {
ship5.cash -= price
ship5.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship5.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship5.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy5() {
galaxy5.travel_to(galaxy5.number+1, planet5.number)
planet5.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet5 = planet5.number
if galaxy5.search_closest_planet5(input) {
planet5.display(false)
} else {
txt.print(" Not found!")
}
galaxy5.travel_to(galaxy5.number, current_planet5)
} else {
planet5.display(false)
}
}
sub do_local() {
galaxy5.local_area()
}
sub do_show_market5() {
market5.display()
txt.print("\nFuel: ")
util5.print_10s(ship5.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship5.cargo_free())
txt.print("t\n")
}
}
ship5 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market5.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market5 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet5's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market5 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet5.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet5.print_name_uppercase()
txt.print(" trade market5:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util5.print_right(13, names[ci])
txt.print(" ")
util5.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship5.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util5.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy5 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy5
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy5num) {
number = 1
planet5.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy5num-1 {
nextgalaxy5()
}
}
sub nextgalaxy5() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy5num, ubyte system) {
init(galaxy5num)
generate_next_planet5() ; always at least planet5 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet5()
}
planet5.name = make_current_planet5_name()
init_market5_for_planet5()
}
sub init_market5_for_planet5() {
market5.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet5(uword nameptr) -> ubyte {
ubyte x = planet5.x
ubyte y = planet5.y
ubyte current_planet5_num = planet5.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet5()
planet5.name = make_current_planet5_name()
if util5.prefix_matches(nameptr, planet5.name) {
ubyte distance = planet5.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet5_num)
return found
}
sub local_area() {
ubyte current_planet5 = planet5.number
ubyte px = planet5.x
ubyte py = planet5.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet5()
ubyte distance = planet5.distance(px, py)
if distance <= ship5.Max_fuel {
if distance <= ship5.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet5.name = make_current_planet5_name()
planet5.display(true)
txt.print(" (")
util5.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet5)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet5() {
determine_planet5_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet5_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet5_properties() {
; create the planet5's characteristics
planet5.number++
planet5.x = msb(seed[1])
planet5.y = msb(seed[0])
planet5.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet5.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet5.govtype <= 1
planet5.economy = (planet5.economy | 2)
planet5.techlevel = (msb(seed[1]) & 3) + (planet5.economy ^ 7)
planet5.techlevel += planet5.govtype >> 1
if planet5.govtype & 1
planet5.techlevel++
planet5.population = 4 * planet5.techlevel + planet5.economy
planet5.population += planet5.govtype + 1
planet5.productivity = ((planet5.economy ^ 7) + 3) * (planet5.govtype + 4)
planet5.productivity *= planet5.population * 8
ubyte seed2_msb = msb(seed[2])
planet5.radius = mkword((seed2_msb & 15) + 11, planet5.x)
planet5.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet5.species_is_alien {
planet5.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet5.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet5.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet5.species_kind = (planet5.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet5.goatsoup_seed[0] = lsb(seed[1])
planet5.goatsoup_seed[1] = msb(seed[1])
planet5.goatsoup_seed[2] = lsb(seed[2])
planet5.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy5 #")
txt.print_ub(number)
txt.print("\ngalaxy5 seed0=")
txt.print_uwhex(galaxy5.seed[0], true)
txt.print("\ngalaxy5 seed1=")
txt.print_uwhex(galaxy5.seed[1], true)
txt.print("\ngalaxy5 seed2=")
txt.print_uwhex(galaxy5.seed[2], true)
txt.nl()
}
}
planet5 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship5", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet5 \xB0", "The world \xB0", "This planet5", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet5", "world", "place", "little planet5", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy5, then increases by 1 for each generated planet5
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet5_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet5_result
reset_rnd()
recursive_soup()
return planet5_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util5 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-994
View File
@@ -1,994 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy6 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf6 {
const ubyte numforLave = 7 ; Lave is 7th generated planet6 in galaxy6 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy6.travel_to(1, numforLave)
market6.init(0) ; Lave's market6 is seeded with 0
ship6.init()
planet6.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util6.print_10s(ship6.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market6 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader6.do_buy()
's' -> trader6.do_sell()
'f' -> trader6.do_fuel()
'j' -> trader6.do_jump()
't' -> trader6.do_teleport()
'g' -> trader6.do_next_galaxy6()
'i' -> trader6.do_info()
'm' -> trader6.do_show_market6()
'l' -> trader6.do_local()
'c' -> trader6.do_cash()
'h' -> trader6.do_hold()
'<' -> trader6.do_load()
'>' -> trader6.do_save()
}
}
}
}
}
trader6 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy6
ubyte planet6
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship6.cash = savedata.cash
ship6.Max_cargo = savedata.max_cargo
ship6.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship6.cargohold, len(ship6.cargohold))
galaxy6.travel_to(savedata.galaxy6, savedata.planet6)
planet6.display(false)
}
sub do_save() {
savedata.galaxy6 = galaxy6.number
savedata.planet6 = planet6.number
savedata.cash = ship6.cash
savedata.max_cargo = ship6.Max_cargo
savedata.fuel = ship6.fuel
sys.memcopy(ship6.cargohold, &savedata.cargo0, len(ship6.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship6.fuel
ship6.fuel = 255
jump_to_system()
ship6.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet6 = planet6.number
ubyte x = planet6.x
ubyte y = planet6.y
if galaxy6.search_closest_planet6(input) {
ubyte distance = planet6.distance(x, y)
if distance <= ship6.fuel {
galaxy6.init_market6_for_planet6()
ship6.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet6.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy6.travel_to(galaxy6.number, current_planet6)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market6.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market6.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market6.current_price[ci] * amount
txt.print(" Total price: ")
util6.print_10s(price)
if price > ship6.cash {
txt.print(" Not enough cash!\n")
} else {
ship6.cash -= price
ship6.cargohold[ci] += amount
market6.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market6.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship6.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market6.current_price[ci] * amount
txt.print(" Total price: ")
util6.print_10s(price)
ship6.cash += price
ship6.cargohold[ci] -= amount
market6.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship6.Max_fuel - ship6.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship6.Fuel_cost
if price > ship6.cash {
txt.print("Not enough cash!\n")
} else {
ship6.cash -= price
ship6.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship6.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship6.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy6() {
galaxy6.travel_to(galaxy6.number+1, planet6.number)
planet6.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet6 = planet6.number
if galaxy6.search_closest_planet6(input) {
planet6.display(false)
} else {
txt.print(" Not found!")
}
galaxy6.travel_to(galaxy6.number, current_planet6)
} else {
planet6.display(false)
}
}
sub do_local() {
galaxy6.local_area()
}
sub do_show_market6() {
market6.display()
txt.print("\nFuel: ")
util6.print_10s(ship6.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship6.cargo_free())
txt.print("t\n")
}
}
ship6 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market6.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market6 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet6's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market6 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet6.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet6.print_name_uppercase()
txt.print(" trade market6:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util6.print_right(13, names[ci])
txt.print(" ")
util6.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship6.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util6.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy6 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy6
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy6num) {
number = 1
planet6.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy6num-1 {
nextgalaxy6()
}
}
sub nextgalaxy6() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy6num, ubyte system) {
init(galaxy6num)
generate_next_planet6() ; always at least planet6 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet6()
}
planet6.name = make_current_planet6_name()
init_market6_for_planet6()
}
sub init_market6_for_planet6() {
market6.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet6(uword nameptr) -> ubyte {
ubyte x = planet6.x
ubyte y = planet6.y
ubyte current_planet6_num = planet6.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet6()
planet6.name = make_current_planet6_name()
if util6.prefix_matches(nameptr, planet6.name) {
ubyte distance = planet6.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet6_num)
return found
}
sub local_area() {
ubyte current_planet6 = planet6.number
ubyte px = planet6.x
ubyte py = planet6.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet6()
ubyte distance = planet6.distance(px, py)
if distance <= ship6.Max_fuel {
if distance <= ship6.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet6.name = make_current_planet6_name()
planet6.display(true)
txt.print(" (")
util6.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet6)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet6() {
determine_planet6_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet6_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet6_properties() {
; create the planet6's characteristics
planet6.number++
planet6.x = msb(seed[1])
planet6.y = msb(seed[0])
planet6.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet6.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet6.govtype <= 1
planet6.economy = (planet6.economy | 2)
planet6.techlevel = (msb(seed[1]) & 3) + (planet6.economy ^ 7)
planet6.techlevel += planet6.govtype >> 1
if planet6.govtype & 1
planet6.techlevel++
planet6.population = 4 * planet6.techlevel + planet6.economy
planet6.population += planet6.govtype + 1
planet6.productivity = ((planet6.economy ^ 7) + 3) * (planet6.govtype + 4)
planet6.productivity *= planet6.population * 8
ubyte seed2_msb = msb(seed[2])
planet6.radius = mkword((seed2_msb & 15) + 11, planet6.x)
planet6.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet6.species_is_alien {
planet6.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet6.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet6.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet6.species_kind = (planet6.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet6.goatsoup_seed[0] = lsb(seed[1])
planet6.goatsoup_seed[1] = msb(seed[1])
planet6.goatsoup_seed[2] = lsb(seed[2])
planet6.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy6 #")
txt.print_ub(number)
txt.print("\ngalaxy6 seed0=")
txt.print_uwhex(galaxy6.seed[0], true)
txt.print("\ngalaxy6 seed1=")
txt.print_uwhex(galaxy6.seed[1], true)
txt.print("\ngalaxy6 seed2=")
txt.print_uwhex(galaxy6.seed[2], true)
txt.nl()
}
}
planet6 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship6", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet6 \xB0", "The world \xB0", "This planet6", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet6", "world", "place", "little planet6", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy6, then increases by 1 for each generated planet6
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet6_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet6_result
reset_rnd()
recursive_soup()
return planet6_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util6 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-994
View File
@@ -1,994 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy7 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf7 {
const ubyte numforLave = 7 ; Lave is 7th generated planet7 in galaxy7 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy7.travel_to(1, numforLave)
market7.init(0) ; Lave's market7 is seeded with 0
ship7.init()
planet7.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util7.print_10s(ship7.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market7 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader7.do_buy()
's' -> trader7.do_sell()
'f' -> trader7.do_fuel()
'j' -> trader7.do_jump()
't' -> trader7.do_teleport()
'g' -> trader7.do_next_galaxy7()
'i' -> trader7.do_info()
'm' -> trader7.do_show_market7()
'l' -> trader7.do_local()
'c' -> trader7.do_cash()
'h' -> trader7.do_hold()
'<' -> trader7.do_load()
'>' -> trader7.do_save()
}
}
}
}
}
trader7 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy7
ubyte planet7
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship7.cash = savedata.cash
ship7.Max_cargo = savedata.max_cargo
ship7.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship7.cargohold, len(ship7.cargohold))
galaxy7.travel_to(savedata.galaxy7, savedata.planet7)
planet7.display(false)
}
sub do_save() {
savedata.galaxy7 = galaxy7.number
savedata.planet7 = planet7.number
savedata.cash = ship7.cash
savedata.max_cargo = ship7.Max_cargo
savedata.fuel = ship7.fuel
sys.memcopy(ship7.cargohold, &savedata.cargo0, len(ship7.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship7.fuel
ship7.fuel = 255
jump_to_system()
ship7.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet7 = planet7.number
ubyte x = planet7.x
ubyte y = planet7.y
if galaxy7.search_closest_planet7(input) {
ubyte distance = planet7.distance(x, y)
if distance <= ship7.fuel {
galaxy7.init_market7_for_planet7()
ship7.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet7.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy7.travel_to(galaxy7.number, current_planet7)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market7.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market7.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market7.current_price[ci] * amount
txt.print(" Total price: ")
util7.print_10s(price)
if price > ship7.cash {
txt.print(" Not enough cash!\n")
} else {
ship7.cash -= price
ship7.cargohold[ci] += amount
market7.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market7.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship7.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market7.current_price[ci] * amount
txt.print(" Total price: ")
util7.print_10s(price)
ship7.cash += price
ship7.cargohold[ci] -= amount
market7.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship7.Max_fuel - ship7.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship7.Fuel_cost
if price > ship7.cash {
txt.print("Not enough cash!\n")
} else {
ship7.cash -= price
ship7.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship7.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship7.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy7() {
galaxy7.travel_to(galaxy7.number+1, planet7.number)
planet7.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet7 = planet7.number
if galaxy7.search_closest_planet7(input) {
planet7.display(false)
} else {
txt.print(" Not found!")
}
galaxy7.travel_to(galaxy7.number, current_planet7)
} else {
planet7.display(false)
}
}
sub do_local() {
galaxy7.local_area()
}
sub do_show_market7() {
market7.display()
txt.print("\nFuel: ")
util7.print_10s(ship7.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship7.cargo_free())
txt.print("t\n")
}
}
ship7 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market7.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market7 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet7's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market7 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet7.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet7.print_name_uppercase()
txt.print(" trade market7:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util7.print_right(13, names[ci])
txt.print(" ")
util7.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship7.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util7.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy7 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy7
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy7num) {
number = 1
planet7.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy7num-1 {
nextgalaxy7()
}
}
sub nextgalaxy7() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy7num, ubyte system) {
init(galaxy7num)
generate_next_planet7() ; always at least planet7 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet7()
}
planet7.name = make_current_planet7_name()
init_market7_for_planet7()
}
sub init_market7_for_planet7() {
market7.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet7(uword nameptr) -> ubyte {
ubyte x = planet7.x
ubyte y = planet7.y
ubyte current_planet7_num = planet7.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet7()
planet7.name = make_current_planet7_name()
if util7.prefix_matches(nameptr, planet7.name) {
ubyte distance = planet7.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet7_num)
return found
}
sub local_area() {
ubyte current_planet7 = planet7.number
ubyte px = planet7.x
ubyte py = planet7.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet7()
ubyte distance = planet7.distance(px, py)
if distance <= ship7.Max_fuel {
if distance <= ship7.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet7.name = make_current_planet7_name()
planet7.display(true)
txt.print(" (")
util7.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet7)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet7() {
determine_planet7_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet7_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet7_properties() {
; create the planet7's characteristics
planet7.number++
planet7.x = msb(seed[1])
planet7.y = msb(seed[0])
planet7.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet7.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet7.govtype <= 1
planet7.economy = (planet7.economy | 2)
planet7.techlevel = (msb(seed[1]) & 3) + (planet7.economy ^ 7)
planet7.techlevel += planet7.govtype >> 1
if planet7.govtype & 1
planet7.techlevel++
planet7.population = 4 * planet7.techlevel + planet7.economy
planet7.population += planet7.govtype + 1
planet7.productivity = ((planet7.economy ^ 7) + 3) * (planet7.govtype + 4)
planet7.productivity *= planet7.population * 8
ubyte seed2_msb = msb(seed[2])
planet7.radius = mkword((seed2_msb & 15) + 11, planet7.x)
planet7.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet7.species_is_alien {
planet7.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet7.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet7.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet7.species_kind = (planet7.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet7.goatsoup_seed[0] = lsb(seed[1])
planet7.goatsoup_seed[1] = msb(seed[1])
planet7.goatsoup_seed[2] = lsb(seed[2])
planet7.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy7 #")
txt.print_ub(number)
txt.print("\ngalaxy7 seed0=")
txt.print_uwhex(galaxy7.seed[0], true)
txt.print("\ngalaxy7 seed1=")
txt.print_uwhex(galaxy7.seed[1], true)
txt.print("\ngalaxy7 seed2=")
txt.print_uwhex(galaxy7.seed[2], true)
txt.nl()
}
}
planet7 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship7", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet7 \xB0", "The world \xB0", "This planet7", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet7", "world", "place", "little planet7", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy7, then increases by 1 for each generated planet7
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet7_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet7_result
reset_rnd()
recursive_soup()
return planet7_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util7 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-994
View File
@@ -1,994 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy8 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf8 {
const ubyte numforLave = 7 ; Lave is 7th generated planet8 in galaxy8 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy8.travel_to(1, numforLave)
market8.init(0) ; Lave's market8 is seeded with 0
ship8.init()
planet8.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util8.print_10s(ship8.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market8 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader8.do_buy()
's' -> trader8.do_sell()
'f' -> trader8.do_fuel()
'j' -> trader8.do_jump()
't' -> trader8.do_teleport()
'g' -> trader8.do_next_galaxy8()
'i' -> trader8.do_info()
'm' -> trader8.do_show_market8()
'l' -> trader8.do_local()
'c' -> trader8.do_cash()
'h' -> trader8.do_hold()
'<' -> trader8.do_load()
'>' -> trader8.do_save()
}
}
}
}
}
trader8 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy8
ubyte planet8
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship8.cash = savedata.cash
ship8.Max_cargo = savedata.max_cargo
ship8.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship8.cargohold, len(ship8.cargohold))
galaxy8.travel_to(savedata.galaxy8, savedata.planet8)
planet8.display(false)
}
sub do_save() {
savedata.galaxy8 = galaxy8.number
savedata.planet8 = planet8.number
savedata.cash = ship8.cash
savedata.max_cargo = ship8.Max_cargo
savedata.fuel = ship8.fuel
sys.memcopy(ship8.cargohold, &savedata.cargo0, len(ship8.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship8.fuel
ship8.fuel = 255
jump_to_system()
ship8.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet8 = planet8.number
ubyte x = planet8.x
ubyte y = planet8.y
if galaxy8.search_closest_planet8(input) {
ubyte distance = planet8.distance(x, y)
if distance <= ship8.fuel {
galaxy8.init_market8_for_planet8()
ship8.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet8.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy8.travel_to(galaxy8.number, current_planet8)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market8.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market8.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market8.current_price[ci] * amount
txt.print(" Total price: ")
util8.print_10s(price)
if price > ship8.cash {
txt.print(" Not enough cash!\n")
} else {
ship8.cash -= price
ship8.cargohold[ci] += amount
market8.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market8.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship8.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market8.current_price[ci] * amount
txt.print(" Total price: ")
util8.print_10s(price)
ship8.cash += price
ship8.cargohold[ci] -= amount
market8.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship8.Max_fuel - ship8.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship8.Fuel_cost
if price > ship8.cash {
txt.print("Not enough cash!\n")
} else {
ship8.cash -= price
ship8.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship8.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship8.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy8() {
galaxy8.travel_to(galaxy8.number+1, planet8.number)
planet8.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet8 = planet8.number
if galaxy8.search_closest_planet8(input) {
planet8.display(false)
} else {
txt.print(" Not found!")
}
galaxy8.travel_to(galaxy8.number, current_planet8)
} else {
planet8.display(false)
}
}
sub do_local() {
galaxy8.local_area()
}
sub do_show_market8() {
market8.display()
txt.print("\nFuel: ")
util8.print_10s(ship8.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship8.cargo_free())
txt.print("t\n")
}
}
ship8 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market8.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market8 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet8's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market8 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet8.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet8.print_name_uppercase()
txt.print(" trade market8:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util8.print_right(13, names[ci])
txt.print(" ")
util8.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship8.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util8.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy8 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy8
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy8num) {
number = 1
planet8.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy8num-1 {
nextgalaxy8()
}
}
sub nextgalaxy8() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy8num, ubyte system) {
init(galaxy8num)
generate_next_planet8() ; always at least planet8 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet8()
}
planet8.name = make_current_planet8_name()
init_market8_for_planet8()
}
sub init_market8_for_planet8() {
market8.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet8(uword nameptr) -> ubyte {
ubyte x = planet8.x
ubyte y = planet8.y
ubyte current_planet8_num = planet8.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet8()
planet8.name = make_current_planet8_name()
if util8.prefix_matches(nameptr, planet8.name) {
ubyte distance = planet8.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet8_num)
return found
}
sub local_area() {
ubyte current_planet8 = planet8.number
ubyte px = planet8.x
ubyte py = planet8.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet8()
ubyte distance = planet8.distance(px, py)
if distance <= ship8.Max_fuel {
if distance <= ship8.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet8.name = make_current_planet8_name()
planet8.display(true)
txt.print(" (")
util8.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet8)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet8() {
determine_planet8_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet8_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet8_properties() {
; create the planet8's characteristics
planet8.number++
planet8.x = msb(seed[1])
planet8.y = msb(seed[0])
planet8.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet8.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet8.govtype <= 1
planet8.economy = (planet8.economy | 2)
planet8.techlevel = (msb(seed[1]) & 3) + (planet8.economy ^ 7)
planet8.techlevel += planet8.govtype >> 1
if planet8.govtype & 1
planet8.techlevel++
planet8.population = 4 * planet8.techlevel + planet8.economy
planet8.population += planet8.govtype + 1
planet8.productivity = ((planet8.economy ^ 7) + 3) * (planet8.govtype + 4)
planet8.productivity *= planet8.population * 8
ubyte seed2_msb = msb(seed[2])
planet8.radius = mkword((seed2_msb & 15) + 11, planet8.x)
planet8.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet8.species_is_alien {
planet8.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet8.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet8.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet8.species_kind = (planet8.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet8.goatsoup_seed[0] = lsb(seed[1])
planet8.goatsoup_seed[1] = msb(seed[1])
planet8.goatsoup_seed[2] = lsb(seed[2])
planet8.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy8 #")
txt.print_ub(number)
txt.print("\ngalaxy8 seed0=")
txt.print_uwhex(galaxy8.seed[0], true)
txt.print("\ngalaxy8 seed1=")
txt.print_uwhex(galaxy8.seed[1], true)
txt.print("\ngalaxy8 seed2=")
txt.print_uwhex(galaxy8.seed[2], true)
txt.nl()
}
}
planet8 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship8", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet8 \xB0", "The world \xB0", "This planet8", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet8", "world", "place", "little planet8", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy8, then increases by 1 for each generated planet8
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet8_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet8_result
reset_rnd()
recursive_soup()
return planet8_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util8 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
-993
View File
@@ -1,993 +0,0 @@
%import textio
%import conv
%import diskio
%import string
%import test_stack
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy9 system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
perf9 {
const ubyte numforLave = 7 ; Lave is 7th generated planet9 in galaxy9 one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy9.travel_to(1, numforLave)
market9.init(0) ; Lave's market9 is seeded with 0
ship9.init()
planet9.display(false)
repeat {
; test_stack.test()
str input = "????????"
txt.print("\nCash: ")
util9.print_10s(ship9.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.nl()
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market9 hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader9.do_buy()
's' -> trader9.do_sell()
'f' -> trader9.do_fuel()
'j' -> trader9.do_jump()
't' -> trader9.do_teleport()
'g' -> trader9.do_next_galaxy9()
'i' -> trader9.do_info()
'm' -> trader9.do_show_market9()
'l' -> trader9.do_local()
'c' -> trader9.do_cash()
'h' -> trader9.do_hold()
'<' -> trader9.do_load()
'>' -> trader9.do_save()
}
}
}
}
}
trader9 {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy9
ubyte planet9
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
return
}
ship9.cash = savedata.cash
ship9.Max_cargo = savedata.max_cargo
ship9.fuel = savedata.fuel
sys.memcopy(&savedata.cargo0, ship9.cargohold, len(ship9.cargohold))
galaxy9.travel_to(savedata.galaxy9, savedata.planet9)
planet9.display(false)
}
sub do_save() {
savedata.galaxy9 = galaxy9.number
savedata.planet9 = planet9.number
savedata.cash = ship9.cash
savedata.max_cargo = ship9.Max_cargo
savedata.fuel = ship9.fuel
sys.memcopy(ship9.cargohold, &savedata.cargo0, len(ship9.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
txt.print(diskio.status(8))
txt.nl()
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship9.fuel
ship9.fuel = 255
jump_to_system()
ship9.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet9 = planet9.number
ubyte x = planet9.x
ubyte y = planet9.y
if galaxy9.search_closest_planet9(input) {
ubyte distance = planet9.distance(x, y)
if distance <= ship9.fuel {
galaxy9.init_market9_for_planet9()
ship9.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet9.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy9.travel_to(galaxy9.number, current_planet9)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market9.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market9.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market9.current_price[ci] * amount
txt.print(" Total price: ")
util9.print_10s(price)
if price > ship9.cash {
txt.print(" Not enough cash!\n")
} else {
ship9.cash -= price
ship9.cargohold[ci] += amount
market9.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market9.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship9.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market9.current_price[ci] * amount
txt.print(" Total price: ")
util9.print_10s(price)
ship9.cash += price
ship9.cargohold[ci] -= amount
market9.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship9.Max_fuel - ship9.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship9.Fuel_cost
if price > ship9.cash {
txt.print("Not enough cash!\n")
} else {
ship9.cash -= price
ship9.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship9.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship9.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy9() {
galaxy9.travel_to(galaxy9.number+1, planet9.number)
planet9.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet9 = planet9.number
if galaxy9.search_closest_planet9(input) {
planet9.display(false)
} else {
txt.print(" Not found!")
}
galaxy9.travel_to(galaxy9.number, current_planet9)
} else {
planet9.display(false)
}
}
sub do_local() {
galaxy9.local_area()
}
sub do_show_market9() {
market9.display()
txt.print("\nFuel: ")
util9.print_10s(ship9.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship9.cargo_free())
txt.print("t\n")
}
}
ship9 {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
sys.memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market9.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market9 {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet9's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market9 prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet9.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.nl()
planet9.print_name_uppercase()
txt.print(" trade market9:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util9.print_right(13, names[ci])
txt.print(" ")
util9.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship9.cargohold[ci])
txt.nl()
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util9.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy9 {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy9
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxy9num) {
number = 1
planet9.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy9num-1 {
nextgalaxy9()
}
}
sub nextgalaxy9() {
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxy9num, ubyte system) {
init(galaxy9num)
generate_next_planet9() ; always at least planet9 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet9()
}
planet9.name = make_current_planet9_name()
init_market9_for_planet9()
}
sub init_market9_for_planet9() {
market9.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet9(uword nameptr) -> ubyte {
ubyte x = planet9.x
ubyte y = planet9.y
ubyte current_planet9_num = planet9.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet9()
planet9.name = make_current_planet9_name()
if util9.prefix_matches(nameptr, planet9.name) {
ubyte distance = planet9.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet9_num)
return found
}
sub local_area() {
ubyte current_planet9 = planet9.number
ubyte px = planet9.x
ubyte py = planet9.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet9()
ubyte distance = planet9.distance(px, py)
if distance <= ship9.Max_fuel {
if distance <= ship9.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.spc()
planet9.name = make_current_planet9_name()
planet9.display(true)
txt.print(" (")
util9.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet9)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet9() {
determine_planet9_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet9_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet9_properties() {
; create the planet9's characteristics
planet9.number++
planet9.x = msb(seed[1])
planet9.y = msb(seed[0])
planet9.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet9.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet9.govtype <= 1
planet9.economy = (planet9.economy | 2)
planet9.techlevel = (msb(seed[1]) & 3) + (planet9.economy ^ 7)
planet9.techlevel += planet9.govtype >> 1
if planet9.govtype & 1
planet9.techlevel++
planet9.population = 4 * planet9.techlevel + planet9.economy
planet9.population += planet9.govtype + 1
planet9.productivity = ((planet9.economy ^ 7) + 3) * (planet9.govtype + 4)
planet9.productivity *= planet9.population * 8
ubyte seed2_msb = msb(seed[2])
planet9.radius = mkword((seed2_msb & 15) + 11, planet9.x)
planet9.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet9.species_is_alien {
planet9.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet9.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet9.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet9.species_kind = (planet9.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet9.goatsoup_seed[0] = lsb(seed[1])
planet9.goatsoup_seed[1] = msb(seed[1])
planet9.goatsoup_seed[2] = lsb(seed[2])
planet9.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy9 #")
txt.print_ub(number)
txt.print("\ngalaxy9 seed0=")
txt.print_uwhex(galaxy9.seed[0], true)
txt.print("\ngalaxy9 seed1=")
txt.print_uwhex(galaxy9.seed[1], true)
txt.print("\ngalaxy9 seed2=")
txt.print_uwhex(galaxy9.seed[2], true)
txt.nl()
}
}
planet9 {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship9", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet9 \xB0", "The world \xB0", "This planet9", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet9", "world", "place", "little planet9", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy9, then increases by 1 for each generated planet9
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet9_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet9_result
reset_rnd()
recursive_soup()
return planet9_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.spc()
txt.print(econnames[economy])
txt.spc()
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.spc()
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.spc()
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.spc()
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.spc()
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.nl()
txt.print(soup())
txt.nl()
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util9 {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword st) {
repeat width - string.length(st) {
txt.spc()
}
txt.print(st)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
}
File diff suppressed because it is too large. Load diff
+21 -17
View File
@@ -1,7 +1,6 @@
plugins {
id 'antlr'
id 'java'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id "org.jetbrains.kotlin.jvm"
}
targetCompatibility = 11
@@ -12,22 +11,18 @@ repositories {
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
dependencies {
antlr 'org.antlr:antlr4:4.9'
implementation 'org.antlr:antlr4-runtime:4.9'
implementation 'org.antlr:antlr4-runtime:4.9.2'
implementation project(':parser')
// antlr('org.antlr:antlr4:4.9') {
// exclude group: 'com.ibm.icu', module: 'icu4j'
// }
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'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
}
compileKotlin {
@@ -62,6 +57,15 @@ sourceSets {
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}
+2
View File
@@ -4,11 +4,13 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
<orderEntry type="library" scope="TEST" name="unittest-libs" level="project" />
</component>
</module>
+10 -23
View File
@@ -33,7 +33,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(block: Block) {
val addr = if(block.address!=null) block.address.toHex() else ""
outputln("~ ${block.name} $addr {")
outputln("${block.name} $addr {")
scopelevel++
for(stmt in block.statements) {
outputi("")
@@ -78,31 +78,18 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
private fun datatypeString(dt: DataType): String {
return when(dt) {
in NumericDatatypes -> dt.toString().toLowerCase()
DataType.STR -> dt.toString().toLowerCase()
return when (dt) {
in NumericDatatypes -> dt.toString().lowercase()
DataType.STR -> dt.toString().lowercase()
DataType.ARRAY_UB -> "ubyte["
DataType.ARRAY_B -> "byte["
DataType.ARRAY_UW -> "uword["
DataType.ARRAY_W -> "word["
DataType.ARRAY_F -> "float["
DataType.STRUCT -> "" // the name of the struct is enough
else -> "?????"
}
}
override fun visit(structDecl: StructDecl) {
outputln("struct ${structDecl.name} {")
scopelevel++
for(decl in structDecl.statements) {
outputi("")
decl.accept(this)
output("\n")
}
scopelevel--
outputlni("}")
}
override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again
@@ -116,9 +103,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
VarDeclType.MEMORY -> output("&")
}
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
output(decl.struct!!.name)
output(datatypeString(decl.datatype))
if(decl.arraysize!=null) {
decl.arraysize!!.indexExpr.accept(this)
@@ -137,8 +121,11 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(subroutine: Subroutine) {
output("\n")
outputi("")
if(subroutine.inline)
output("inline ")
if(subroutine.isAsmSubroutine) {
outputi("asmsub ${subroutine.name} (")
output("asmsub ${subroutine.name} (")
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
val reg =
when {
@@ -152,7 +139,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
}
else {
outputi("sub ${subroutine.name} (")
output("sub ${subroutine.name} (")
for(param in subroutine.parameters) {
output("${datatypeString(param.type)} ${param.name}")
if(param!==subroutine.parameters.last())
@@ -248,7 +235,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(branchStatement: BranchStatement) {
output("if_${branchStatement.condition.toString().toLowerCase()} ")
output("if_${branchStatement.condition.toString().lowercase()} ")
branchStatement.truepart.accept(this)
if(branchStatement.elsepart.statements.isNotEmpty()) {
output(" else ")
+39 -53
View File
@@ -8,6 +8,8 @@ import prog8.ast.walk.IAstVisitor
import java.nio.file.Path
import kotlin.math.abs
const val internedStringsModuleName = "prog8_interned_strings"
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
@@ -124,18 +126,7 @@ interface INameScope {
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
if(scopedName.size>1) {
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
// try the struct first.
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
val struct = thing?.struct
if (struct != null) {
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
// return ref to the mangled name variable
val mangled = mangledStructMemberName(thing.name, scopedName.last())
return thing.definingScope().getLabelOrVariable(mangled)
}
}
// a scoped name refers to a name in another module.
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
@@ -178,7 +169,6 @@ interface INameScope {
}
}
fun containsDefinedVariables() = statements.any { it is VarDecl && (it !is ParameterVarDecl) }
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()
@@ -268,48 +258,64 @@ class Program(val name: String,
get() = mainModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>()
val internedStringsModuleName = "prog8_interned_strings"
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
init {
// insert a container module for all interned strings later
if(modules.firstOrNull()?.name != internedStringsModuleName) {
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, false, Path.of(""))
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, true, Path.of(""))
modules.add(0, internedStringsModule)
val block = Block(internedStringsModuleName, null, mutableListOf(), false, Position.DUMMY)
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
internedStringsModule.statements.add(block)
internedStringsModule.linkParents(this)
internedStringsModule.program = this
}
}
fun entrypoint(): Subroutine? {
fun entrypoint(): Subroutine {
val mainBlocks = allBlocks().filter { it.name=="main" }
if(mainBlocks.size > 1)
throw FatalAstException("more than one 'main' block")
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScope("start") as Subroutine?
}
if(mainBlocks.isEmpty())
throw FatalAstException("no 'main' block")
return mainBlocks[0].subScope("start") as Subroutine
}
fun internString(string: StringLiteralValue): List<String> {
// Move a string literal into the internal, deduplicated, string pool
// replace it with a variable declaration that points to the entry in the pool.
if(string.parent is VarDecl) {
// deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)!
throw FatalAstException("cannot intern a string literal that's part of a vardecl")
}
fun getScopedName(string: StringLiteralValue): List<String> {
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
return listOf(internedStringsModuleName, decl.name)
}
val key = Pair(string.value, string.altEncoding)
val existing = internedStrings[key]
if(existing!=null)
val existing = internedStringsUnique[key]
if (existing != null)
return existing
val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName}
(internedStringsBlock as Block).statements.add(decl)
decl.linkParents(internedStringsBlock)
val scopedName = listOf(internedStringsModuleName, decl.name)
internedStrings[key] = scopedName
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY
@@ -339,8 +345,6 @@ class Module(override val name: String,
override lateinit var parent: Node
lateinit var program: Program
val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<Module>()
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
@@ -389,20 +393,6 @@ class GlobalNamespace(val modules: List<Module>, private val builtinFunctionName
return builtinPlaceholder
}
if(scopedName.size>1) {
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
// try the struct first.
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
val struct = thing?.struct
if (struct != null) {
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
// return ref to the mangled name variable
val mangled = mangledStructMemberName(thing.name, scopedName.last())
return thing.definingScope().getLabelOrVariable(mangled)
}
}
}
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
@@ -412,7 +402,7 @@ class GlobalNamespace(val modules: List<Module>, private val builtinFunctionName
// lookup something from the module.
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw SyntaxError("invalid identifier target type", stmt.position)
}
@@ -428,10 +418,6 @@ object BuiltinFunctionScopePlaceholder : INameScope {
}
// prefix for struct member variables
fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"
fun Number.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
+16 -50
View File
@@ -45,7 +45,8 @@ private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean, encoding: IStri
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst(encoding)
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
else -> throw FatalAstException("weird block statement $it")
it.labeldef()!=null -> it.labeldef().toAst()
else -> throw FatalAstException("weird block node $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
@@ -61,62 +62,31 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi
val vd = it.vardecl()
return VarDecl(
VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT,
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(encoding),
vd.varname.text,
null,
it.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
it.toPosition()
)
}
structvarinitializer()?.let {
val vd = it.structvardecl()
return VarDecl(
VarDeclType.VAR,
DataType.STRUCT,
ZeropageWish.NOT_IN_ZEROPAGE,
null,
vd.varname.text,
vd.structname.text,
it.expression().toAst(encoding),
isArray = false,
autogeneratedDontRemove = false,
position = it.toPosition()
)
}
structvardecl()?.let {
return VarDecl(
VarDeclType.VAR,
DataType.STRUCT,
ZeropageWish.NOT_IN_ZEROPAGE,
null,
it.varname.text,
it.structname.text,
null,
isArray = false,
autogeneratedDontRemove = false,
position = it.toPosition()
)
}
constdecl()?.let {
val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl()
return VarDecl(
VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT,
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(encoding),
vd.varname.text,
null,
cvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED() != null,
cvarinit.toPosition()
)
}
@@ -126,24 +96,18 @@ private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncodi
val vd = mvarinit.vardecl()
return VarDecl(
VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT,
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(encoding),
vd.varname.text,
null,
mvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
mvarinit.toPosition()
)
}
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst(encoding) }.toMutableList(),
toPosition())
}
throw FatalAstException("weird variable decl $this")
}
@@ -294,7 +258,7 @@ private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map {
val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
val datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
val register = it.register().text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
@@ -360,7 +324,7 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
val datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
@@ -379,7 +343,7 @@ private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
return names.map { CpuRegister.valueOf(it) }.toSet()
}
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
private fun prog8Parser.ArrayindexContext.toAst(encoding: IStringEncoding) : ArrayIndex =
ArrayIndex(expression().toAst(encoding), toPosition())
@@ -575,7 +539,9 @@ private fun prog8Parser.Branch_stmtContext.toAst(encoding: IStringEncoding): Bra
return BranchStatement(branchcondition, trueScope, elseScope, toPosition())
}
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(
text.substringAfter('_').uppercase()
)
private fun prog8Parser.ForloopContext.toAst(encoding: IStringEncoding): ForLoop {
val loopvar = identifier().toAst()
@@ -633,14 +599,14 @@ private fun prog8Parser.When_choiceContext.toAst(encoding: IStringEncoding): Whe
private fun prog8Parser.VardeclContext.toAst(encoding: IStringEncoding): VarDecl {
return VarDecl(
VarDeclType.VAR,
datatype()?.toAst() ?: DataType.STRUCT,
datatype()?.toAst() ?: DataType.UNDEFINED,
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
arrayindex()?.toAst(encoding),
varname.text,
null,
null,
ARRAYSIG() != null || arrayindex() != null,
false,
SHARED()!=null,
toPosition()
)
}
@@ -10,9 +10,9 @@ fun escape(str: String): String {
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u8000'..'\u80ff' -> "\\x" + (it.code - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
else -> "\\u" + it.code.toString(16).padStart(4, '0')
}
}
return es.joinToString("")
+4 -4
View File
@@ -17,7 +17,7 @@ enum class DataType {
ARRAY_UW, // pass by reference
ARRAY_W, // pass by reference
ARRAY_F, // pass by reference
STRUCT; // pass by reference
UNDEFINED;
/**
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
@@ -140,8 +140,8 @@ val IterableDatatypes = setOf(
DataType.ARRAY_F
)
val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf(
val PassByReferenceDatatypes = IterableDatatypes
val ArrayToElementTypes = mapOf(
DataType.STR to DataType.UBYTE,
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
@@ -149,7 +149,7 @@ val ArrayElementTypes = mapOf(
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT
)
val ElementArrayTypes = mapOf(
val ElementToArrayTypes = mapOf(
DataType.BYTE to DataType.ARRAY_B,
DataType.UBYTE to DataType.ARRAY_UB,
DataType.WORD to DataType.ARRAY_W,
@@ -6,7 +6,7 @@ import prog8.ast.base.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import java.util.Objects
import java.util.*
import kotlin.math.abs
@@ -22,6 +22,7 @@ sealed class Expression: Node {
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
abstract val isSimple: Boolean
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
@@ -88,14 +89,14 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
return when(operator) {
"+" -> inferred
"~", "not" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
when(inferred.typeOrElse(DataType.UNDEFINED)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.UWORD)
else -> inferred
}
}
"-" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
when(inferred.typeOrElse(DataType.UNDEFINED)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.BYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.WORD)
else -> inferred
@@ -105,6 +106,8 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
}
}
override val isSimple = false
override fun toString(): String {
return "Prefix($operator $expression)"
}
@@ -133,6 +136,8 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
return "[$left $operator $right]"
}
override val isSimple = false
// binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): NumericLiteralValue? = null
@@ -242,6 +247,8 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
indexer.linkParents(this)
}
override val isSimple = indexer.indexExpr is NumericLiteralValue || indexer.indexExpr is IdentifierReference
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===arrayvar -> arrayvar = replacement as IdentifierReference
@@ -261,7 +268,7 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
if (target is VarDecl) {
return when (target.datatype) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(target.datatype))
else -> InferredTypes.unknown()
}
}
@@ -271,6 +278,8 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
override fun toString(): String {
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
}
fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
}
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
@@ -281,6 +290,8 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
expression.linkParents(this)
}
override val isSimple = false
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===expression)
expression = replacement
@@ -314,6 +325,8 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
identifier.parent=this
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is IdentifierReference && node===identifier)
identifier = replacement
@@ -335,6 +348,8 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
this.addressExpression.linkParents(this)
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
@@ -351,6 +366,8 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun toString(): String {
return "DirectMemoryRead($addressExpression)"
}
fun copy() = DirectMemoryRead(addressExpression, position)
}
class NumericLiteralValue(val type: DataType, // only numerical types allowed
@@ -358,6 +375,8 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override val position: Position) : Expression() {
override lateinit var parent: Node
override val isSimple = true
companion object {
fun fromBoolean(bool: Boolean, position: Position) =
NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
@@ -489,6 +508,8 @@ class StringLiteralValue(val value: String,
this.parent = parent
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
@@ -519,6 +540,8 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
value.forEach {it.linkParents(this)}
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
val idx = value.indexOfFirst { it===node }
@@ -544,7 +567,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
fun memsize(memsizer: IMemSizer): Int {
if(type.isKnown) {
val eltType = ArrayElementTypes.getValue(type.typeOrElse(DataType.STRUCT))
val eltType = ArrayToElementTypes.getValue(type.typeOrElse(DataType.UNDEFINED))
return memsizer.memorySize(eltType) * value.size
}
else throw IllegalArgumentException("array datatype is not yet known")
@@ -557,17 +580,17 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
if(forloop != null) {
val loopvarDt = forloop.loopVarDt(program)
if(loopvarDt.isKnown) {
return if(loopvarDt.typeOrElse(DataType.STRUCT) !in ElementArrayTypes)
return if(!loopvarDt.isArrayElement())
InferredTypes.InferredType.unknown()
else
InferredTypes.InferredType.known(ElementArrayTypes.getValue(loopvarDt.typeOrElse(DataType.STRUCT)))
InferredTypes.InferredType.known(ElementToArrayTypes.getValue(loopvarDt.typeOrElse(DataType.UNDEFINED)))
}
}
// 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) }
val dts = datatypesInArray.map { it.typeOrElse(DataType.UNDEFINED) }
return when {
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
DataType.STR in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
@@ -579,8 +602,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
DataType.ARRAY_W in dts ||
DataType.ARRAY_UB in dts ||
DataType.ARRAY_B in dts ||
DataType.ARRAY_F in dts ||
DataType.STRUCT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.ARRAY_F in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
else -> InferredTypes.InferredType.unknown()
}
}
@@ -589,7 +611,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
if(type.istype(targettype))
return this
if(targettype in ArrayDatatypes) {
val elementType = ArrayElementTypes.getValue(targettype)
val elementType = ArrayToElementTypes.getValue(targettype)
val castArray = value.map{
val num = it as? NumericLiteralValue
if(num==null) {
@@ -625,6 +647,8 @@ class RangeExpr(var from: Expression,
step.linkParents(this)
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
@@ -652,12 +676,12 @@ class RangeExpr(var from: Expression,
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 -> {
val fdt = fromDt.typeOrElse(DataType.STRUCT)
val tdt = toDt.typeOrElse(DataType.STRUCT)
val fdt = fromDt.typeOrElse(DataType.UNDEFINED)
val tdt = toDt.typeOrElse(DataType.UNDEFINED)
if(fdt largerThan tdt)
InferredTypes.knownFor(ElementArrayTypes.getValue(fdt))
InferredTypes.knownFor(ElementToArrayTypes.getValue(fdt))
else
InferredTypes.knownFor(ElementArrayTypes.getValue(tdt))
InferredTypes.knownFor(ElementToArrayTypes.getValue(tdt))
}
}
}
@@ -711,20 +735,21 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
}
}
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(),
IAssignable {
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override val isSimple = true
fun targetStatement(program: Program) =
if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent)
else
program.namespace.lookup(nameInSource, this)
fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl
fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource // NOTE: only compare by the name, not the position!
override fun hashCode() = nameInSource.hashCode()
override fun linkParents(parent: Node) {
@@ -760,26 +785,18 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program)) {
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
is Label -> InferredTypes.InferredType.known(DataType.UWORD)
else -> InferredTypes.InferredType.unknown()
}
}
fun memberOfStruct(program: Program) = this.targetVarDecl(program)?.struct
fun wasStringLiteral(program: Program): Boolean {
val decl = targetVarDecl(program)
if(decl == null || !decl.autogeneratedDontRemove)
return false
fun firstStructVarName(program: Program): String? {
// take the name of the first struct member of the structvariable instead
// if it's just a regular variable, return null.
val struct = memberOfStruct(program) ?: return null
val decl = targetVarDecl(program)!!
if(decl.datatype!=DataType.STRUCT)
return null
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
val firstVar = definingScope().lookup(firstVarName, this) as VarDecl
return firstVar.name
val scope=decl.definingModule()
return scope.name==internedStringsModuleName
}
}
@@ -794,6 +811,8 @@ class FunctionCall(override var target: IdentifierReference,
args.forEach { it.linkParents(this) }
}
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in setOf("msb", "lsb", "peek", "peekw"))
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target=replacement as IdentifierReference
@@ -1,6 +1,6 @@
package prog8.ast.expressions
import prog8.ast.base.DataType
import prog8.ast.base.*
import java.util.*
@@ -10,7 +10,7 @@ object InferredTypes {
require(!(datatype!=null && (isUnknown || isVoid))) { "invalid combination of args" }
}
val isKnown = datatype!=null
val isKnown = datatype!=null && datatype!=DataType.UNDEFINED
fun typeOrElse(alternative: DataType) = if(isUnknown || isVoid) alternative else datatype!!
infix fun istype(type: DataType): Boolean = if(isUnknown || isVoid) false else this.datatype==type
@@ -42,6 +42,17 @@ object InferredTypes {
isKnown && (datatype!! isAssignableTo targetDt)
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
fun isBytes() = datatype in ByteDatatypes
fun isWords() = datatype in WordDatatypes
fun isInteger() = datatype in IntegerDatatypes
fun isNumeric() = datatype in NumericDatatypes
fun isArray() = datatype in ArrayDatatypes
fun isString() = datatype in StringlyDatatypes
fun isIterable() = datatype in IterableDatatypes
fun isPassByReference() = datatype in PassByReferenceDatatypes
fun isPassByValue() = datatype in PassByValueDatatypes
fun isArrayElement() = datatype in ElementToArrayTypes
}
private val unknownInstance = InferredType.unknown()
@@ -57,8 +68,7 @@ object InferredTypes {
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
DataType.ARRAY_W to InferredType.known(DataType.ARRAY_W),
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F),
DataType.STRUCT to InferredType.known(DataType.STRUCT)
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F)
)
fun void() = voidInstance
@@ -7,6 +7,10 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
interface ISymbolStatement {
val name: String
}
sealed class Statement : Node {
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
@@ -31,8 +35,7 @@ sealed class Statement : Node {
}
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position, override var parent: Node) : Statement() {
override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstVisitor) = throw FatalAstException("should not iterate over this node")
override fun accept(visitor: AstWalker, parent: Node) = throw FatalAstException("should not iterate over this node")
@@ -48,7 +51,7 @@ class Block(override val name: String,
val address: Int?,
override var statements: MutableList<Statement>,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope {
override val position: Position) : Statement(), INameScope, ISymbolStatement {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -95,7 +98,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
data class Label(val name: String, override val position: Position) : Statement() {
data class Label(override val name: String, override val position: Position) : Statement(), ISymbolStatement {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -153,22 +156,17 @@ enum class ZeropageWish {
NOT_IN_ZEROPAGE
}
open class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: ZeropageWish,
var arraysize: ArrayIndex?,
val name: String,
private val structName: String?,
override val name: String,
var value: Expression?,
val isArray: Boolean,
val autogeneratedDontRemove: Boolean,
override val position: Position) : Statement() {
val sharedWithAsm: Boolean,
override val position: Position) : Statement(), ISymbolStatement {
override lateinit var parent: Node
var struct: StructDecl? = null // set later (because at parse time, we only know the name)
private set
var structHasBeenFlattened = false // set later
private set
var allowInitializeWithZero = true
// prefix for literal values that are turned into a variable on the heap
@@ -182,11 +180,11 @@ open class VarDecl(val type: VarDeclType,
if(!array.type.isKnown)
throw FatalAstException("unknown dt")
else
array.type.typeOrElse(DataType.STRUCT)
val declaredType = ArrayElementTypes.getValue(arrayDt)
array.type.typeOrElse(DataType.UNDEFINED)
val declaredType = ArrayToElementTypes.getValue(arrayDt)
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)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, position = array.position)
}
fun defaultZero(dt: DataType, position: Position) = when(dt) {
@@ -219,22 +217,10 @@ open class VarDecl(val type: VarDeclType,
this.parent = parent
arraysize?.linkParents(this)
value?.linkParents(this)
if(structName!=null) {
val structStmt = definingScope().lookup(listOf(structName), this)
if(structStmt!=null) {
val node = definingScope().lookup(listOf(structName), this)
if(node is StructDecl)
struct = node
else
datatypeErrors.add(SyntaxError("invalid datatype declaration", position))
}
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value)
// NOTE: ideally you also want to check that node===value but this sometimes crashes the optimizer when queueing multiple node replacements
// just accept the risk of having the wrong node specified in the IAstModification object...
require(replacement is Expression && (value==null || node===value))
value = replacement
replacement.parent = this
}
@@ -243,7 +229,7 @@ open class VarDecl(val type: VarDeclType,
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)"
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
}
fun zeroElementValue(): NumericLiteralValue {
@@ -253,32 +239,16 @@ open class VarDecl(val type: VarDeclType,
throw IllegalArgumentException("attempt to get zero value for vardecl that shouldn't get it")
}
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.mapIndexed { index, statement ->
val member = statement as VarDecl
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[index] else null
VarDecl(
VarDeclType.VAR,
member.datatype,
ZeropageWish.NOT_IN_ZEROPAGE,
member.arraysize,
mangledStructMemberName(name, member.name),
struct!!.name,
initvalue,
member.isArray,
true,
member.position
)
}.toMutableList<Statement>()
structHasBeenFlattened = true
return result
fun copy(): VarDecl {
val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, sharedWithAsm, position)
c.allowInitializeWithZero = this.allowInitializeWithZero
return c
}
}
// a vardecl used only for subroutine parameters
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, false, position)
class ArrayIndex(var indexExpr: Expression,
override val position: Position) : Node {
@@ -312,6 +282,7 @@ class ArrayIndex(var indexExpr: Expression,
fun constIndex() = (indexExpr as? NumericLiteralValue)?.number?.toInt()
infix fun isSameAs(other: ArrayIndex): Boolean = indexExpr isSameAs other.indexExpr
fun copy() = ArrayIndex(indexExpr, position)
}
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
@@ -429,9 +400,10 @@ data class AssignTarget(var identifier: IdentifierReference?,
}
fun toExpression(): Expression {
// return a copy of the assignment target but as a source expression.
return when {
identifier != null -> identifier!!
arrayindexed != null -> arrayindexed!!
identifier != null -> identifier!!.copy()
arrayindexed != null -> arrayindexed!!.copy()
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
else -> throw FatalAstException("invalid assignmenttarget $this")
}
@@ -476,8 +448,9 @@ data class AssignTarget(var identifier: IdentifierReference?,
}
return false
}
}
fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
}
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
override lateinit var parent: Node
@@ -564,8 +537,8 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
}
class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() {
override val name: String
override val position: Position) : INameScope, Statement() { // TODO this isn't really a namescope...(names are scoped to the subroutine level)
override val name: String = "<anon-$sequenceNumber>"
override lateinit var parent: Node
companion object {
@@ -573,7 +546,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
}
init {
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
// make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
@@ -605,7 +578,6 @@ class NopStatement(override val position: Position): Statement() {
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class AsmGenInfo {
// This class contains various attributes that influence the assembly code generator.
// Conceptually it should be part of any INameScope.
@@ -616,6 +588,8 @@ class AsmGenInfo {
var usedRegsaveY = false
var usedFloatEvalResultVar1 = false
var usedFloatEvalResultVar2 = false
val extraVars = mutableListOf<Triple<DataType, String, Int?>>()
}
// the subroutine class covers both the normal user-defined subroutines,
@@ -631,7 +605,7 @@ class Subroutine(override val name: String,
val isAsmSubroutine: Boolean,
val inline: Boolean,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {
override val position: Position) : Statement(), INameScope, ISymbolStatement {
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)
@@ -694,7 +668,6 @@ 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 || " bra" in it || "\tbra" in it}
}
open class SubroutineParameter(val name: String,
val type: DataType,
override val position: Position) : Node {
@@ -941,37 +914,6 @@ class WhenChoice(var values: MutableList<Expression>?, // if null, th
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class StructDecl(override val name: String,
override var statements: MutableList<Statement>, // actually, only vardecls here
override val position: Position): Statement(), INameScope {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
this.statements.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
val numberOfElements: Int
get() = this.statements.size
fun memsize(memsizer: IMemSizer) =
statements.map { memsizer.memorySize((it as VarDecl).datatype) }.sum()
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun nameOfFirstMember() = (statements.first() as VarDecl).name
}
class DirectMemoryWrite(var addressExpression: Expression, override val position: Position) : Node {
override lateinit var parent: Node
@@ -992,4 +934,5 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun copy() = DirectMemoryWrite(addressExpression, position)
}
+92 -89
View File
@@ -76,89 +76,88 @@ interface IAstModification {
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(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(repeatLoop: RepeatLoop, 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(untilLoop: UntilLoop, 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(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()
protected val noModifications = emptyList<IAstModification>()
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(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(repeatLoop: RepeatLoop, 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(untilLoop: UntilLoop, 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(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()
open fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
open fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
open fun before(block: Block, parent: Node): Iterable<IAstModification> = noModifications
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = noModifications
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun before(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
open fun before(module: Module, parent: Node): Iterable<IAstModification> = noModifications
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(program: Program, parent: Node): Iterable<IAstModification> = noModifications
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
private val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
// private val modificationsReplacedNodes = mutableSetOf<Pair<Node, Position>>()
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
open fun after(block: Block, parent: Node): Iterable<IAstModification> = noModifications
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = noModifications
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = noModifications
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun after(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
open fun after(module: Module, parent: Node): Iterable<IAstModification> = noModifications
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(program: Program, parent: Node): Iterable<IAstModification> = noModifications
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
private fun track(mods: Iterable<IAstModification>, node: Node, parent: Node) {
for (it in mods) {
@@ -174,12 +173,23 @@ abstract class AstWalker {
}
fun applyModifications(): Int {
// check if there are double removes, keep only the last one
val removals = modifications.filter { it.first is IAstModification.Remove }
if(removals.isNotEmpty()) {
val doubles = removals.groupBy { (it.first as IAstModification.Remove).node }.filter { it.value.size>1 }
doubles.forEach {
for(doubleRemove in it.value.dropLast(1)) {
if(!modifications.removeIf { mod-> mod.first === doubleRemove.first })
throw FatalAstException("ast remove problem")
}
}
}
modifications.forEach {
it.first.perform()
}
val amount = modifications.size
modifications.clear()
// modificationsReplacedNodes.clear()
return amount
}
@@ -223,7 +233,6 @@ abstract class AstWalker {
track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl)
decl.struct?.accept(this, decl)
track(after(decl, parent), decl, parent)
}
@@ -423,11 +432,5 @@ abstract class AstWalker {
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)
}
}
@@ -33,7 +33,6 @@ interface IAstVisitor {
fun visit(decl: VarDecl) {
decl.value?.accept(this)
decl.arraysize?.accept(this)
decl.struct?.accept(this)
}
fun visit(subroutine: Subroutine) {
@@ -170,8 +169,4 @@ interface IAstVisitor {
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
}
fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) }
}
}
+20 -24
View File
@@ -25,9 +25,12 @@ fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
class ModuleImporter {
class ModuleImporter(private val program: Program,
private val encoder: IStringEncoding,
private val compilationTargetName: String,
private val libdirs: List<String>) {
fun importModule(program: Program, filePath: Path, encoder: IStringEncoding, compilationTargetName: String): Module {
fun importModule(filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
@@ -41,16 +44,14 @@ class ModuleImporter {
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false, encoder, compilationTargetName)
return importModule(CharStreams.fromPath(filePath), filePath, false)
}
fun importLibraryModule(program: Program, name: String,
encoder: IStringEncoding, compilationTargetName: String): Module? {
fun importLibraryModule(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(""), encoder, compilationTargetName)
return executeImportDirective(import, Paths.get(""))
}
private class MyErrorListener: ConsoleErrorListener() {
@@ -62,11 +63,12 @@ class ModuleImporter {
is prog8Parser -> System.err.println("${recognizer.inputStream.sourceName}:$line:$charPositionInLine: $msg")
else -> System.err.println("$line:$charPositionInLine $msg")
}
if(numberOfErrors>=5)
throw ParsingFailedError("There are too many parse errors. Stopping.")
}
}
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean,
encoder: IStringEncoding, compilationTargetName: String): Module {
private fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
lexer.removeErrorListeners()
@@ -95,14 +97,13 @@ class ModuleImporter {
lines.asSequence()
.mapIndexed { i, it -> i to it }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath, encoder, compilationTargetName) }
.forEach { executeImportDirective(it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
private fun executeImportDirective(program: Program, import: Directive, source: Path,
encoder: IStringEncoding, compilationTargetName: String): Module? {
private fun executeImportDirective(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!!
@@ -120,12 +121,12 @@ class ModuleImporter {
val (resource, resourcePath) = rsc
resource.use {
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"),
true, encoder, compilationTargetName)
val content = it.reader().readText().replace("\r\n", "\n")
importModule(CharStreams.fromString(content), Paths.get("@embedded@/$resourcePath"), true)
}
} else {
val modulePath = tryGetModuleFromFile(moduleName, source, import.position)
importModule(program, modulePath, encoder, compilationTargetName)
importModule(modulePath)
}
removeDirectivesFromImportedModule(importedModule)
@@ -157,15 +158,10 @@ class ModuleImporter {
private fun tryGetModuleFromFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = if(source.toString().isEmpty()) mutableListOf<Path>() else mutableListOf(source.parent ?: Path.of("."))
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"))
val libpaths = libdirs.map {Path.of(it)}
val locations =
(if(source.toString().isEmpty()) libpaths else libpaths.drop(1) + listOf(source.parent ?: Path.of("."))) +
listOf(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach {
val file = pathFrom(it.toString(), fileName)
+256
View File
@@ -0,0 +1,256 @@
package prog8tests
import org.antlr.v4.runtime.*
import org.antlr.v4.runtime.misc.ParseCancellationException
import org.junit.jupiter.api.Test
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.ast.antlr.toAst
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Block
import prog8.parser.*
import java.nio.file.Path
import kotlin.test.*
class TestAntlrParser {
class MyErrorListener: ConsoleErrorListener() {
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
throw ParsingFailedError("line $line:$charPositionInLine $msg")
}
}
class MyErrorStrategy: BailErrorStrategy() {
override fun recover(recognizer: Parser?, e: RecognitionException?) {
try {
// let it fill in e in all the contexts
super.recover(recognizer, e)
} catch (pce: ParseCancellationException) {
reportError(recognizer, e)
}
}
override fun recoverInline(recognizer: Parser?): Token {
throw InputMismatchException(recognizer)
}
}
private fun parseModule(srcText: String): prog8Parser.ModuleContext {
return parseModule(CharStreams.fromString(srcText))
}
private fun parseModule(srcFile: Path): prog8Parser.ModuleContext {
return parseModule(CharStreams.fromPath(srcFile))
}
private fun parseModule(srcStream: CharStream): prog8Parser.ModuleContext {
val errorListener = MyErrorListener()
val lexer = prog8Lexer(srcStream)
lexer.removeErrorListeners()
lexer.addErrorListener(errorListener)
val tokens = CommonTokenStream(lexer)
val parser = prog8Parser(tokens)
parser.errorHandler = MyErrorStrategy()
parser.removeErrorListeners()
parser.addErrorListener(errorListener)
return parser.module()
}
object DummyEncoding: IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
TODO("Not yet implemented")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
TODO("Not yet implemented")
}
}
object DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
object DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testModuleSourceNeedNotEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" // source ends with '}' (= NO newline, issue #40)
// before the fix, prog8Parser would have reported (thrown) "missing <EOL> at '<EOF>'"
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testModuleSourceMayEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testAllBlocksButLastMustEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
assertFailsWith<ParsingFailedError> { parseModule(srcBad) }
val parseTree = parseModule(srcGood)
assertEquals(parseTree.block().size, 2)
}
@Test
fun testWindowsAndMacNewlinesAreAlsoFine() {
val nlWin = "\r\n"
val nlUnix = "\n"
val nlMac = "\r"
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").toAbsolutePath())
// a good mix of all kinds of newlines:
val srcText =
"foo {" +
nlMac +
nlWin +
"}" +
nlMac + // <-- do test a single \r (!) where an EOL is expected
"bar {" +
nlUnix +
"}" +
nlUnix + nlMac // both should be "eaten up" by just one EOL token
"combi {" +
nlMac + nlWin + nlUnix // all three should be "eaten up" by just one EOL token
"}" +
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 2)
}
@Test
fun testInterleavedEolAndCommentBeforeFirstBlock() {
// issue: #47
val srcText = """
; comment
; comment
blockA {
}
"""
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testInterleavedEolAndCommentBetweenBlocks() {
// issue: #47
val srcText = """
blockA {
}
; comment
; comment
blockB {
}
"""
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 2)
}
@Test
fun testInterleavedEolAndCommentAfterLastBlock() {
// issue: #47
val srcText = """
blockA {
}
; comment
; comment
"""
val parseTree = parseModule(srcText)
assertEquals(parseTree.block().size, 1)
}
@Test
fun testNewlineBetweenTwoBlocksOrDirectivesStillRequired() {
// issue: #47
// block and block
assertFailsWith<ParsingFailedError>{ parseModule("""
blockA {
} blockB {
}
""") }
// block and directive
assertFailsWith<ParsingFailedError>{ parseModule("""
blockB {
} %import textio
""") }
// The following two are bogus due to directive *args* expected to follow the directive name.
// Leaving them in anyways.
// dir and block
assertFailsWith<ParsingFailedError>{ parseModule("""
%import textio blockB {
}
""") }
assertFailsWith<ParsingFailedError>{ parseModule("""
%import textio %import syslib
""") }
}
/*
@Test
fun testImportLibraryModule() {
val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer)
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
//assertFailsWith<ParsingFailedError>(){ importer.importLibraryModule("import_file_with_syntax_error") }
}
*/
@Test
fun testProg8Ast() {
// can create charstreams from many other sources as well;
val charstream = CharStreams.fromString("""
main {
sub start() {
return
}
}
""")
val lexer = prog8Lexer(charstream)
val tokens = CommonTokenStream(lexer)
val parser = prog8Parser(tokens)
parser.errorHandler = BailErrorStrategy()
// parser.removeErrorListeners()
// parser.addErrorListener(MyErrorListener())
val ast = parser.module().toAst("test", false, Path.of(""), DummyEncoding)
assertIs<Block>(ast.statements.first())
assertEquals((ast.statements.first() as Block).name, "main")
}
}
+9 -9
View File
@@ -2,8 +2,8 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id 'com.github.johnrengelman.shadow' version '6.1.0'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.0.0'
}
targetCompatibility = 11
@@ -18,7 +18,7 @@ repositories {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2'
implementation "com.github.hypfvieh:dbus-java:3.2.4"
implementation "org.slf4j:slf4j-simple:1.7.30"
@@ -28,6 +28,11 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
exclude group: "org.antlr", module: "antlr4"
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
@@ -62,8 +67,7 @@ startScripts.enabled = true
application {
mainClass = 'prog8.dbus.TestdbusKt'
mainClassName = 'prog8.dbus.TestdbusKt' // deprecated
applicationName = 'testdbus'
applicationName = 'prog8compilerservicedbus'
}
artifacts {
@@ -90,7 +94,3 @@ test {
events "skipped", "failed"
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}
+3 -3
View File
@@ -2,10 +2,10 @@
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/../dbusCompilerService">
<sourceFolder url="file://$MODULE_DIR$/../dbusCompilerService/src" isTestSource="false" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
+10 -9
View File
@@ -16,7 +16,7 @@ Then you can choose a few ways to get a compiler:
#. download a recent "fat-jar" (called something like "prog8compiler-all.jar") from `the releases on Github <https://github.com/irmen/prog8/releases>`_
#. run the compiler with "java -jar prog8compiler-all.jar" to see how you can use it.
**using the Gradle build system to make it yourself:**
**Using the Gradle build system to build it yourself:**
The Gradle build system is used to build the compiler.
The most interesting gradle commands to run are probably:
@@ -42,9 +42,10 @@ of that build task, you can start the compiler with:
(You should probably make an alias...)
.. hint::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
Development and testing is done on Linux using the IntelliJ IDEA IDE,
but the compiler should run on most operating systems that provide a fairly modern
java runtime. If you do have trouble building or running the compiler on your
operating system, please let me know!
@@ -94,7 +95,7 @@ Start the compiler with the ``-watch`` argument to enable this.
It will compile your program and then instead of exiting, it waits for any changes in the module source files.
As soon as a change happens, the program gets compiled again.
It is possible to use the watch mode with multiple modules as well, but it will
recompile everything in that list even if only of the files got updated.
recompile everything in that list even if only one of the files got updated.
Other options
^^^^^^^^^^^^^
@@ -117,11 +118,11 @@ They are embedded into the packaged release version of the compiler so you don't
where they are, but their names are still reserved.
User defined library files
^^^^^^^^^^^^^^^^^^^^^^^^^^
User defined library files and -location
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can create library files yourself too that can be shared among programs.
You can tell the compiler where it should look for these files, by setting the java command line property ``prog8.libdir``
or by setting the ``PROG8_LIBDIR`` environment variable to the correct directory.
You can tell the compiler where it should look for these files, by using
the libdirs command line option.
.. _debugging:
+1 -1
View File
@@ -33,7 +33,7 @@ release = version
def setup(app):
# add custom css
app.add_stylesheet("css/customize.css")
app.add_css_file("css/customize.css")
# -- General configuration ---------------------------------------------------
+40 -15
View File
@@ -42,8 +42,9 @@ Language 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.
- Provide a very convenient edit/compile/run cycle by being able to directly launch
It generates a machine code program runnable on actual 8-bit 6502 hardware.
- Fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
- Provides a very convenient edit/compile/run cycle by being able to directly launch
the compiled program in an emulator and provide debugging information to this emulator.
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
- Modular programming and scoping via modules, code blocks, and subroutines.
@@ -51,12 +52,15 @@ Language features
still able to directly use memory addresses and ROM subroutines,
and inline assembly to have full control when every register, cycle or byte matters
- Subroutines with parameters and return values
- complex nested expressions are possible
- Complex nested expressions are possible
- Variables are allocated statically
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Variable data types include signed and unsigned bytes and words, arrays, strings and floats.
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
- Strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest petscii equivalents.
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the C64.
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
@@ -64,6 +68,16 @@ Language features
Code example
------------
Here is a hello world program::
%import textio
main {
sub start() {
txt.print("hello world i ♥ prog8\n")
}
}
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import textio
@@ -126,11 +140,18 @@ when the exact same program is compiled for the Commander X16 target, and run on
:alt: result when run on CX16 emulator
Getting the compiler
--------------------
Usually you just download a fat jar of an official released version, but you can also build
it yourself from source.
Detailed instructions on how to obtain a version of the compiler are in :ref:`building_compiler`.
.. _requirements:
Required tools
--------------
Required additional tools
-------------------------
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
It's very easy to compile yourself.
@@ -144,29 +165,33 @@ For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
Finally: an **emulator** (or a real machine ofcourse) to test and run your programs on.
In C64 mode, thhe compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
In C64 mode, the compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the CommanderX16 instead, there's the `x16emu <https://github.com/commanderx16/x16-emulator>`_.
.. important::
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
.. attention:: **Commander-X16 V38 versus V39**
(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.
Starting with Prog8 7.0 the CX16 compilation target is configured for the upcoming v39 version of the emulator
and roms, that are most accurately reflecting the current state of the hardware design of the CX16.
Unfortunately, v39 has not yet been officially released. At the time of writing, v38 is still
the latest official release. So, you have to either compile the v39 emulator+roms from the source in git yourself
or obtain a precompiled version from someone else.
A cx16 program compiled by prog8 7.0 is meant for v39 but *may* still work on the older v38 release of the emulator!
For this to work you should make sure that the program is not using floating point, nor the ram/rom bank switching logic provided by the libraries.
You can also choose to just stick with Prog8 6.4 (which still targets cx16 v38) and wait it out till
the emulator v39 is officially released - but you won't be able to benefit from the compiler improvements
made since the previous release of prog8.
Instructions on how to obtain a prebuilt compiler are in :ref:`building_compiler`.
.. toctree::
:maxdepth: 2
:caption: Contents of this manual:
targetsystem.rst
building.rst
programming.rst
syntaxreference.rst
libraries.rst
targetsystem.rst
technical.rst
todo.rst
+19
View File
@@ -99,6 +99,25 @@ sys (part of syslib)
Returns the last address of the program in memory + 1.
Can be used to load dynamic data after the program, instead of hardcoding something.
``wait(uword jiffies)``
wait approximately the given number of jiffies (1/60th seconds)
note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
``waitvsync()``
busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
can be used to avoid screen flicker/tearing when updating screen contents.
note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
note for cx16: the system irq handler has to be active for this to work (this is not required on c64)
``waitrastborder()`` (c64 target only)
busy wait till the raster position has reached the bottom screen border (approximately)
can be used to avoid screen flicker/tearing when updating screen contents.
note: a more accurate way to do this is by using a raster irq handler instead.
``reset_system()``
Soft-reset the system back to initial power-on Basic prompt.
(called automatically by Prog8 when the main subroutine returns and the program is not using basicsafe zeropage option)
conv
----
+74 -60
View File
@@ -39,7 +39,7 @@ Variable declarations
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.
Variable declarations tend to appear at the top of the code block that uses them, but this is not mandatory.
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.
@@ -62,26 +62,28 @@ Subroutine
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.
Subroutines do not have to be declared before they can be called.
Label
This is a named position in your code where you can jump to from another place.
You can jump to it with a jump statement elsewhere. It is also possible to use a
subroutine call to a label (but without parameters and return value).
Labels can only be defined in a block or in another subroutine, so you can't define a label
inside a loop statement block for instance.
Scope
Also known as 'namespace', this is a named box around the symbols defined in it.
This prevents name collisions (or 'namespace pollution'), because the name of the scope
is needed as prefix to be able to access the symbols in it.
Anything *inside* the scope can refer to symbols in the same scope without using a prefix.
There are three scopes in Prog8:
Also known as 'namespace', this is a named box around the symbols defined in it.
This prevents name collisions (or 'namespace pollution'), because the name of the scope
is needed as prefix to be able to access the symbols in it.
Anything *inside* the scope can refer to symbols in the same scope without using a prefix.
There are three scope levels in Prog8:
- global (no prefix)
- code block
- subroutine
- global (no prefix)
- code block
- subroutine
Modules are *not* a scope! Everything defined in a module is merged into the global scope.
While Modules are separate files, they are *not* separate scopes!
Everything defined in a module is merged into the global scope.
This is different from most other languages that have modules.
The global scope can only contain blocks and some directives, while the others can contain variables and subroutines too.
.. _blocks:
@@ -204,6 +206,16 @@ Example::
byte @zp zeropageCounter = 42
*shared tag:*
If you add the ``@shared`` tag to the variable declaration, the compiler will know that this variable
is a prog8 variable shared with some assembly code elsewhere. This means that the assembly code can
refer to the variable even if it's otherwise not used in prog8 code itself.
(usually, these kinds of 'unused' variables are optimized away by the compiler, resulting in an error
when assembling the rest of the code). Example::
byte @shared assemblyVariable = 42
Integers
^^^^^^^^
@@ -233,6 +245,10 @@ to worry about this yourself)
The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (negative: **-1.7014118345e+38**)
.. note::
On the Commander X16, to use floating point operations, ROM bank 4 has to be enabled (BASIC).
Importing the ``floats`` library will do this for you if needed.
Arrays
^^^^^^
@@ -316,47 +332,22 @@ read the syntax reference on strings.
For regular assignments you still need to use an explicit ``&`` (address-of) to take
the address of the string or array.
.. caution::
It's probably best to avoid changing the contents in strings and treat them as static.
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 the changes persist in memory. If your program exits and is restarted
(without reloading it from disk), it will then start working with the modified strings
instead of the original ones!
The same is true for arrays! So be careful to (re)initialize them if needed.
.. note:: Strings and their (im)mutability
*String literals outside of a string variable's initialization value*,
are considered to be "constant", i.e. the string isn't going to change
during the execution of the program. The compiler takes advantage of this in certain
ways. For instance, multiple identical occurrences of a string literal are folded into
just one string allocation in memory. Examples of such strings are the string literals
passed to a subroutine as arguments.
Structs
^^^^^^^
A struct is a group of one or more other variables.
This allows you to reuse the definition and manipulate it as a whole.
Individual variables in the struct are accessed as you would expect, just
use a scoped name to refer to them: ``structvariable.membername``.
Structs are a bit limited in Prog8: you can only use numerical variables
as member of a struct, so strings and arrays and other structs can not be part of a struct.
Also, it is not possible to use a struct itself inside an array.
Structs are mainly syntactic sugar for repeated groups of vardecls
and assignments that belong together. However,
*they are layed out in sequence in memory as the members are defined*
which may be usefulif you want to pass pointers around.
To create a variable of a struct type you need to define the struct itself,
and then create a variable with it::
struct Color {
ubyte red
ubyte green
ubyte blue
}
Color rgb = [255,122,0] ; note that struct initializer value is same as an array
Color another ; the init value is optional, like arrays
another = rgb ; assign all of the values of rgb to another
another.blue = 255 ; set a single member
*Strings that aren't such string literals are considered to be unique*, even if they
are the same as a string defined elsewhere. This includes the strings assigned to
a string variable in its declaration! These kind of strings are not deduplicated and
are just copied into the program in their own unique part of memory. This means that
it is okay to treat those strings as mutable; you can safely change the contents
of such a string without destroying other occurrences (as long as you stay within
the size of the allocated string!)
Special types: const and memory-mapped
@@ -451,6 +442,14 @@ Breaking out of a loop prematurely is possible with the ``break`` statement.
after the loop without first assigning a new value to it!
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
.. warning::
For efficiency reasons, it is assumed that the ending value of the for loop is actually >= the starting value
(or <= if the step is negative). This means that for loops in prog8 behave differently than in other
languages if this is *not* the case! A for loop from ubyte 10 to ubyte 2, for example, will iterate through
all values 10, 11, 12, 13, .... 254, 255, 0 (wrapped), 1, 2. In other languages the entire loop will
be skipped in such cases. But prog8 omits the overhead of an extra loop range check and/or branch for every for loop
by assuming the normal ranges.
Conditional Execution
---------------------
@@ -642,7 +641,7 @@ Subroutines can be defined in a Block, but also nested inside another subroutine
With ``asmsub`` you can define a low-level subroutine that is implemented in inline assembly and takes any parameters
in registers directly.
Trivial subroutines can be tagged as inline to tell the compiler to copy their code
Trivial subroutines can be tagged as ``inline`` to tell the compiler to copy their code
in-place to the locations where the subroutine is called, rather than inserting an actual call and return to the
subroutine. This may increase code size significantly and can only be used in limited scenarios, so YMMV.
@@ -833,9 +832,6 @@ rndw()
rndf()
returns a pseudo-random float between 0.0 and 1.0
fastrnd8()
returns a pseudo-random byte from 0..255 (using a fast but not very good rng)
rol(x)
Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
@@ -866,11 +862,6 @@ sizeof(name)
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
offsetof(membername)
Number of bytes from the start of a struct variable that this member variable is located.
For now, this only works on members of a declared struct variable and not yet on members
referenced from the struct type itself. This might be improved in a future version of the language.
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
@@ -885,6 +876,29 @@ memory(name, size)
The return value is just a simple uword address so it cannot be used as an array in your program.
You can only treat it as a pointer or use it in inline assembly.
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 compiler target for now
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte).
Notice that bank $00 is used by the Kernal and should not be used by user code.
The third argument can be used to designate the memory address
of an argument for the routine; it will be loaded into the A register and will
receive the result value returned by the routine in the A register. If you leave this at zero,
no argument passing will be done.
If the routine requires different arguments or return values, ``callfar`` cannot be used
and you'll have to set up a call to ``jsrfar`` yourself to process this.
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 compiler target for now
Calls an assembly routine in another rom-bank on the CommanderX16
The banked ROM is located in the address range $C000-$FFFF (16 kilobyte).
There are 32 banks (0 to 31).
The third argument can be used to designate the memory address
of an argument for the routine; it will be loaded into the A register and will
receive the result value returned by the routine in the A register. If you leave this at zero,
no argument passing will be done.
If the routine requires different arguments or return values, ``callrom`` cannot be used
and you'll have to set up a call in assembly code yourself that handles the banking and
argument/returnvalues.
Library routines
----------------
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff. Show more