Compare commits

...
163 Commits
Author SHA1 Message Date
Irmen de Jong 5fd83f2757 version 7.3 2021-11-14 22:55:13 +01:00
Irmen de Jong c80df4140b until-loop condition now also simplified to avoid stack-eval 2021-11-14 22:51:02 +01:00
Irmen de Jong 53e1729e2f introduce option to use internal scratch variables via prog8_lib definitions (ony for compiler, not for user code!) 2021-11-14 16:01:54 +01:00
Irmen de Jong ab2d1122a9 conditional expressions are optimized more intelligently (simple ones are not split off in separate assignments) 2021-11-14 12:38:56 +01:00
Irmen de Jong 5190594c8a added several more assembly-level optimizations to remove redundant instructions 2021-11-14 12:23:46 +01:00
Irmen de Jong c858ceeb58 compiler shouldn't use cx16.r15 as temp var 2021-11-14 02:38:59 +01:00
Irmen de Jong f0f52b9166 optimize typecasted binary expression to avoid even more estack use. also fix wrong parent crash in removal of unused variable's assignments. 2021-11-13 14:22:37 +01:00
Irmen de Jong 00c6f74481 tweak temp float 2021-11-13 12:56:59 +01:00
Irmen de Jong 2177ba0ed2 added signed versions of the cx16 virtual registers 2021-11-13 02:42:21 +01:00
Irmen de Jong 3483515346 preparing for more optimizations 2021-11-12 23:23:51 +01:00
Irmen de Jong 75a06d2a40 preparing for more optimizations 2021-11-12 02:17:37 +01:00
Irmen de Jong 53ac11983b better unused variable removal 2021-11-11 03:03:21 +01:00
Irmen de Jong 69f4a4d4f8 tweak expr.typecastTo() a bit 2021-11-11 00:15:09 +01:00
Irmen de Jong 222bcb808f optimize load-store-load combo in output asm 2021-11-10 23:47:35 +01:00
Irmen de Jong 686483f51a fixed division of signed byte number by 2. (!) 2021-11-10 00:17:56 +01:00
Irmen de Jong 8df3da11e3 add cosr8, sinr8, cosr16 and sinr16 builtin functions that take a degree 0..179 (= 0..358 in 2 degree steps)
to more easily scale halves/quarters etc of a circle than possible with the ones that take 0..255 'degrees'.
2021-11-09 23:39:26 +01:00
Irmen de Jong 84dafda0e4 fix error message for type mismatch on builtin-function parameter 2021-11-09 22:19:07 +01:00
Irmen de Jong b909facfe5 fix compiler stackoverflow crash on certain typecasted expressions containing floats. 2021-11-09 19:31:19 +01:00
Irmen de Jong 7780d94de1 discovered crash related to float typecasting in asm assignment codegen 2021-11-09 03:45:07 +01:00
Irmen de Jong f2c440e466 new sin/cos idea 2021-11-09 02:38:43 +01:00
Irmen de Jong 4937e004b5 fix compiler crash where it used wrong datatype in split assignment
fixes crash for "ubyte bb ;; uword ww ;; bb = not bb or not ww"
2021-11-09 01:13:23 +01:00
Irmen de Jong 4cb383dccb discovered crash about storage size mismatch 2021-11-08 21:44:06 +01:00
Irmen de Jong c8a4b6f23c refactor expressionsAsmGen so that it now has just 1 single public function
this makes replacing it by a non-stack based solution easier in the future.
2021-11-08 19:21:55 +01:00
Irmen de Jong 857724c7e6 attempt to make if-statement not use stack eval anymore 2021-11-08 19:07:36 +01:00
Irmen de Jong a9b0400d13 fixed 'not' operator priority: it now has higher priority as or/and/xor. 2021-11-08 18:38:04 +01:00
Irmen de Jong 2d1e5bbc7e remove unimportant empty tests 2021-11-08 17:00:10 +01:00
Irmen de Jong 60627ce756 kotest migration done, fixes #70 2021-11-08 16:19:24 +01:00
Irmen de Jong 7961a09d16 converting compiler module's testcases to kotest assertions 2021-11-08 16:14:22 +01:00
Irmen de Jong 613efcacc7 converting compiler module's testcases to kotest (ongoing) 2021-11-08 15:08:48 +01:00
Irmen de Jong 7e8db16e18 moved to kotest assertions in compilerAst module tests 2021-11-07 21:18:18 +01:00
Irmen de Jong 1fbbed7e23 remove unittest machinery from modules that don't have tests 2021-11-07 17:34:14 +01:00
Irmen de Jong 984272beb4 migrated compilerAst module to KoTest (but not finished with the assertions yet) 2021-11-07 17:25:53 +01:00
Irmen de Jong b9ce94bb68 migrated codeGeneration module to KoTest 2021-11-07 15:40:05 +01:00
Irmen de Jong f4c4ee78d9 re-use global returnvalue temp var instead of duplicating it in every subroutine that needs it 2021-11-07 14:19:21 +01:00
Irmen de Jong 793596614e attempt to fix ReadTheDocs build issue 2021-11-07 00:37:31 +01:00
Irmen de Jong 136280100c attempt to fix ReadTheDocs build issue 2021-11-07 00:23:44 +01:00
Irmen de Jong 29f1e4d2c9 attempt to fix ReadTheDocs build issue 2021-11-07 00:18:51 +01:00
Irmen de Jong 72a7e61fd0 version 7.2 2021-11-06 23:42:13 +01:00
Irmen de Jong 381cfca67f Merge branch 'v7.2'
# Conflicts:
#	compiler/res/version.txt
2021-11-06 23:41:39 +01:00
Irmen de Jong f40620aa25 "not x" as a condition (if, while, until) is optimized into "x==0", this avoids calculating the value 2021-11-06 23:25:32 +01:00
Irmen de Jong 57a9fed42b todo 2021-11-06 19:09:33 +01:00
Irmen de Jong 18d820da94 correct assignment type 2021-11-06 18:52:54 +01:00
Irmen de Jong 26e66f046f implement some more missing codegen for inplace Prefix expressions 2021-11-06 18:48:42 +01:00
Irmen de Jong 4270c04856 don't crash but give proper error on "-X" expression where X is not a signed type 2021-11-06 18:06:01 +01:00
Irmen de Jong 74456d1135 optimized prefix-expression in to use stack evaluation less 2021-11-06 17:57:00 +01:00
Irmen de Jong 62dc824bc0 tweaks 2021-11-06 17:14:07 +01:00
Irmen de Jong 1605791f1b float swap() no longer uses evaluation stack but a single temp var instead + FAC1 2021-11-06 03:36:14 +01:00
Irmen de Jong 37a46aa2cf complex memory assignment also tries to avoid estack evaluation (but not done yet) 2021-11-06 00:03:19 +01:00
Irmen de Jong 1d2d217b94 non-optimized typecast assignments now attempt to not use evalstack 2021-11-05 23:25:07 +01:00
Irmen de Jong 23961f695d fixed some parse tree node position end-columns. cleanup some todo's 2021-11-05 22:48:28 +01:00
Irmen de Jong 730b208617 relaxed some type checks on certain word register assignment
preparing to optimize asmsub arg passing for complex expressions
2021-11-04 23:57:25 +01:00
Irmen de Jong f09c04eeac fix invalid asm addressing mode for certain value-to-evalstack transfers 2021-11-04 22:44:31 +01:00
Irmen de Jong be73739c62 todo 2021-11-03 23:08:11 +01:00
Irmen de Jong eea3fb48a8 add command line option 'optfloatx' to explicitly re-enable float expr optimization as this can increase code size significantly.
The output size of the various example programs using floating point, when not using this optimization, has been reduced significantly.
The resulting code runs a (tiny) bit slower though.
2021-11-03 22:52:08 +01:00
Irmen de Jong b4fa72c058 fix parent node linkage for reading array parameter 2021-11-03 21:57:31 +01:00
Irmen de Jong b0a865b0f1 update todo 2021-11-02 23:55:50 +01:00
Irmen de Jong 7f49731618 fix: don't initialize block vars twice, fix: make sure the prog8_init_vars generated routine is correctly called when needed 2021-11-02 23:13:28 +01:00
Irmen de Jong 3410aea788 fix regression: don't add 0 initializer when variable is assigned to anyway (or is loopvar in a for-loop) 2021-11-02 21:23:59 +01:00
Irmen de Jong bc0a133bb1 doc 2021-11-02 20:24:45 +01:00
Irmen de Jong 7e287a5359 proper parent node linkage in generated const values out of typecast expressions. Fixes crash mentioned in #72 2021-11-02 00:47:01 +01:00
Irmen de Jong 1110bd0851 fix vardecl initialization value to not use stack eval anymore but separate assignment
(this causes the optimized assignment code gen to be used instead)
but some programs now end up larger in output size
2021-11-01 00:24:15 +01:00
Irmen de Jong 1b576f826d remove unneeded sibling methods 2021-10-31 16:50:15 +01:00
Irmen de Jong fe17566370 improved reporting of slow stack based evaluation code 2021-10-31 14:18:49 +01:00
Irmen de Jong e3c00669c1 fixed improved asm generation for conditions that compare signed word to zero 2021-10-31 02:39:45 +02:00
Irmen de Jong 33d17afc32 improved asm generation for conditions that compare byte/word to zero 2021-10-31 01:58:16 +02:00
Irmen de Jong 2388359a99 improved asm generation for conditions that compare ubyte/uword to zero 2021-10-31 01:39:37 +02:00
Irmen de Jong 2df0c9503c improved asm generation for conditions that compare floats to zero 2021-10-31 01:28:08 +02:00
Irmen de Jong 61fa3bc77c comparisonjump tweak 2021-10-31 00:57:22 +02:00
Irmen de Jong 03ac9b6956 various cleanups, slight update to dbus 2021-10-30 19:30:19 +02:00
Irmen de Jong dfbef8495d got rid of ParsingFailedError 2021-10-30 17:05:23 +02:00
Irmen de Jong 7b17c49d8f update petscii tables with improvements to box drawing chars. fixes #68 2021-10-30 16:45:23 +02:00
Irmen de Jong 4b3f31c2ee added option to suppress assembler output (and enabled this in unit tests) 2021-10-30 15:26:40 +02:00
Irmen de Jong 9ccc65bf8f more petscii tests 2021-10-30 15:15:11 +02:00
Irmen de Jong f9e22add03 fix crash when using array as paramater type 2021-10-30 15:15:00 +02:00
Irmen de Jong 846951cda7 kotlin 1.5.31 2021-10-30 12:26:05 +02:00
Irmen de Jong 97836e18b2 simplified gradle config, automatically run installDist task after build 2021-10-30 12:01:52 +02:00
Irmen de Jong 7b69df4db2 todos 2021-10-30 00:38:48 +02:00
Irmen de Jong 3767b4bbe7 'Program' is not an ast Node 2021-10-30 00:25:34 +02:00
Irmen de Jong d7d2eefa4f implemented CharLiteral.constValue() 2021-10-30 00:05:55 +02:00
Irmen de Jong 6737f28d1e moved unittests of compilerInterfaces into compiler module itself 2021-10-29 23:46:51 +02:00
Irmen de Jong 3da9404c2d removed memsizer arg from all builtin functions 2021-10-29 23:38:31 +02:00
Irmen de Jong 4d5bd0fa32 simplify ZeroPage reserved locations handling a bit 2021-10-29 17:34:42 +02:00
Irmen de Jong 1137da37c3 reshuffle ErrorReporter 2021-10-29 17:02:03 +02:00
Irmen de Jong 495a18805c move asmgen test to codeGeneration module 2021-10-29 16:20:53 +02:00
Irmen de Jong a226b82d0b cleanup imports 2021-10-29 05:30:12 +02:00
Irmen de Jong 0b5ddcdc9b split out the code generator into own project submodule 2021-10-29 05:00:30 +02:00
Irmen de Jong 82da8f4946 adding tests to the new project's submodules 2021-10-29 03:36:42 +02:00
Irmen de Jong 5ff481ce3c make sure tmp folders exist for unit tests 2021-10-29 03:04:16 +02:00
Irmen de Jong f21dcaa6fb split out the code optimizers into own project submodule 2021-10-29 02:42:10 +02:00
Irmen de Jong 2c940de598 better name 2021-10-29 01:06:01 +02:00
Irmen de Jong ce75b776bb refactor loadAsmIncludeFile response 2021-10-29 01:01:24 +02:00
Irmen de Jong 7d22b9b9f9 simplified name conflict check for sub params 2021-10-29 00:20:33 +02:00
Irmen de Jong 6cb8b3b5cd removed unneeded scope param from lookup() 2021-10-29 00:01:28 +02:00
Irmen de Jong 2bf4017f2b fix nested label lookups in anon scopes
fixed non-global qualified names lookup
2021-10-28 23:48:01 +02:00
Irmen de Jong 08d2f8568b refactoring symbol lookups 2021-10-27 23:48:12 +02:00
Irmen de Jong ac5f45d2d4 fix nested label lookups in anon scopes (partly) 2021-10-27 02:41:24 +02:00
Irmen de Jong 3cc7ad7d20 slightly improve error message for unknown module import 2021-10-27 00:38:36 +02:00
Irmen de Jong d4513364fb fix compiler crash when file on command line doesn't exist 2021-10-27 00:23:54 +02:00
Irmen de Jong 9684f4e42a add unit tests for AnonScope refactoring, cleaned up imports 2021-10-27 00:05:46 +02:00
Irmen de Jong f4186981fd todo 2021-10-26 23:30:48 +02:00
Irmen de Jong 141689e697 change many uses of .definingScope to just the parent node 2021-10-26 23:25:16 +02:00
Irmen de Jong 743c8b44a2 AnonymousScope refactor: it's no longer a INameScope
because it doesn't contain scoped variables (these are moved to the subroutine's scope)
2021-10-26 23:01:51 +02:00
Irmen de Jong 5e1459564a no longer take AddressOf a str-variable that is a subroutine's parameter with str type (it's just an address/uword already) 2021-10-25 23:49:01 +02:00
Irmen de Jong 69a8813a3d first steps to add support for str parameter type 2021-10-24 20:57:10 +02:00
Irmen de Jong 17175df835 more precise error messages checks 2021-10-24 19:14:46 +02:00
Irmen de Jong 6b32535cb6 don't complain about uninitialized str var if it's not a var 2021-10-24 15:13:38 +02:00
Irmen de Jong 7f8fe75ab2 version 7.1 2021-10-24 14:00:11 +02:00
Irmen de Jong 2815a14bb5 (7.2) can now test for specific error messages, and specify to omit invoking assembler in tests 2021-10-22 01:25:26 +02:00
Irmen de Jong f4dfa60790 (7.2) tests for pass by ref parameters 2021-10-22 00:41:34 +02:00
Irmen de Jong 35e88dd529 (7.2) correctly parse datatype of array parameters 2021-10-21 22:06:21 +02:00
Irmen de Jong 4d5094a517 (7.2) cleanup Petscii converter errorhandling, add unit tests for error scenarios 2021-10-20 23:48:20 +02:00
Irmen de Jong dd5abae721 move testcase to proper location 2021-10-20 23:08:40 +02:00
Irmen de Jong 8f2fb20934 Merge branch 'v7.1' into v7.2 2021-10-20 22:51:14 +02:00
Irmen de Jong 44143f481a Merge branch 'v7.1' 2021-10-20 22:50:35 +02:00
Irmen de Jong 440abf4998 fix test to recognise inserted return statements 2021-10-20 22:50:18 +02:00
Irmen de Jong 3c10427e04 Merge branch 'v7.1' 2021-10-20 22:38:23 +02:00
Irmen de Jong 74555a32ed Merge branch 'v7.1' into v7.2 2021-10-20 22:37:43 +02:00
Irmen de Jong 85956b5828 code generator: add a return (RTS) to empty subroutines. Fixes #67 2021-10-20 22:36:13 +02:00
Irmen de Jong 41e40cad03 optimizer bug: don't remove empty subroutine if it's referenced. Fixes #67 2021-10-20 22:25:10 +02:00
Irmen de Jong df2d5c6585 tests for callgraph and unused subroutine removal in optimizer 2021-10-20 22:24:10 +02:00
Irmen de Jong 1a111b706e Merge branch 'v7.1' into v7.2 2021-10-19 23:59:31 +02:00
Irmen de Jong f696fce187 Merge branch 'v7.1' 2021-10-19 23:59:07 +02:00
Irmen de Jong 82d3d81bb2 don't want to complicate things by introducing a boolean literal 2021-10-19 23:58:50 +02:00
Irmen de Jong 4668932bac todo 2021-10-19 23:38:07 +02:00
Irmen de Jong e6c41eac93 Merge branch 'v7.1' into v7.2 2021-10-19 23:22:38 +02:00
Irmen de Jong f0cff661df Merge branch 'v7.1' 2021-10-19 23:21:44 +02:00
Irmen de Jong 82d20dea39 a few comment and TODO cleanups.
remove remark about chars UBYTE type, kotlin's closest native type that can contain 0-255 is a short.
2021-10-19 23:20:34 +02:00
Irmen de Jong 804bb06859 clarified isInRegularRAM() by making it an extension method on AssignTarget 2021-10-19 22:36:05 +02:00
Irmen de Jong 5afa7e53f8 got rid of program arg for isInRegularRAM 2021-10-19 22:30:30 +02:00
Irmen de Jong 7f15b7b716 remove unneeded check for duplicate module names as this is now caught by the logic in Program.addModule itself 2021-10-19 22:12:54 +02:00
Irmen de Jong 552e0c2248 rename mainModule to toplevelModule.
failed module no longer retains in the Ast.
improved some tests on that.
2021-10-19 21:49:05 +02:00
Irmen de Jong e5b9e1f5e7 string object identity hashcode can be negative sometimes, so allow a '-' character. 2021-10-19 21:08:15 +02:00
Irmen de Jong 502bf90007 comments 2021-10-19 01:12:28 +02:00
Irmen de Jong 40bf117497 avoid crash when parser doesn't report an offending token for a parse error 2021-10-19 00:44:33 +02:00
Irmen de Jong 4011dce31b added a few more tests for the file element of Position 2021-10-19 00:26:02 +02:00
Irmen de Jong 9e881e32f8 version 7.1-beta2 2021-10-16 18:47:16 +02:00
Irmen de Jong 14aad2358f version 7.2 started 2021-10-16 18:46:08 +02:00
Irmen de Jong 637a8899c5 Merge pull request #65 from irmen/v7.1
more V7.1 updates
2021-10-16 17:51:56 +02:00
Irmen de Jong cf0e395921 got rid of SourceCode.pathString() and the 'need' to strip < and > 2021-10-16 17:15:22 +02:00
Irmen de Jong 6ef438ce50 todo 2021-10-16 15:08:36 +02:00
Irmen de Jong 46e4b977a4 another attempt to fix Windows path issues 2021-10-16 15:02:15 +02:00
Irmen de Jong 9626c5dead attempt to fix Windows path issue with "library:" prefixes in AsmGen 2021-10-16 14:50:08 +02:00
Irmen de Jong aea364e43d paths are now always relative to the current directory. Fixes #64 2021-10-16 14:26:33 +02:00
Irmen de Jong 06defd0cb0 paths are now always relative 2021-10-16 02:43:22 +02:00
Irmen de Jong 0f80897c50 todo 2021-10-15 01:02:32 +02:00
Irmen de Jong 57bb1c2c0d performance optimized checks against short ranges of values 2021-10-15 00:51:45 +02:00
Irmen de Jong 7b35b414e8 tweak check of DataType against multiple values 2021-10-15 00:39:42 +02:00
Irmen de Jong 761aac7a23 replace inferredType.istype() by infix form 2021-10-15 00:28:23 +02:00
Irmen de Jong 15a02d7664 making InferredType easier to use 2021-10-15 00:18:13 +02:00
Irmen de Jong 16ed68c1ec Module.name is now derived back from the source's origin string 2021-10-14 23:58:14 +02:00
Irmen de Jong e63cf660c6 petscii now use Result instead of Either 2021-10-13 23:22:46 +02:00
Irmen de Jong aaff484306 refactor executeImportDirective 2021-10-13 23:14:27 +02:00
Irmen de Jong 3281d9a215 fix error when sourcepaths is empty 2021-10-13 23:08:51 +02:00
Irmen de Jong 0fcd61e00f refactor tryGetModuleFromResource 2021-10-13 23:00:22 +02:00
Irmen de Jong c4523ea470 refactor tryGetModuleFromFile 2021-10-13 22:32:52 +02:00
Irmen de Jong 0447b3e4cc remove testcase that attempted to check invalid %import syntax.
we only allow unquoted names, without filename suffix, in %import.
2021-10-13 22:10:35 +02:00
Irmen de Jong 4d27c2901b fix weird error printing when doing %import textio.p8 2021-10-13 21:55:51 +02:00
Irmen de Jong 855e18b31c fix SourceCode to properly set the sourceName of a resource or string as well 2021-10-13 21:46:38 +02:00
Irmen de Jong d790878af6 enabled test 2021-10-13 20:28:42 +02:00
Irmen de Jong 85b244df2f remove remains of %target 2021-10-13 20:13:57 +02:00
Irmen de Jong 6070afa6b6 cleanup SourceCode class 2021-10-13 19:16:01 +02:00
Irmen de Jong 975594703d doc 2021-10-13 18:21:48 +02:00
Irmen de Jong 6b8c3ef614 renamed command line option -libdirs to -srcdirs
this more clearly separates this meaning from the internal library modules
2021-10-13 18:16:51 +02:00
162 changed files with 11766 additions and 8821 deletions

No files matched your search

-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="11" />
</component>
</project>
+11 -11
View File
@@ -1,22 +1,22 @@
<component name="libraryTable">
<library name="github.hypfvieh.dbus.java" type="repository">
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.0" />
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.0/dbus-java-3.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.5/jnr-unixsocket-0.38.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.1/jnr-ffi-2.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.1/dbus-java-3.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.6/jnr-unixsocket-0.38.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.2/jnr-ffi-2.2.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.0/asm-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.1/asm-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.3/jnr-enxio-0.32.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.4/jnr-posix-3.1.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.4/jnr-enxio-0.32.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.5/jnr-posix-3.1.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />
+22
View File
@@ -0,0 +1,22 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:4.6.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="libraryTable">
<library name="io.kotest.property.jvm" type="repository">
<properties maven-id="io.kotest:kotest-property-jvm:4.6.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/4.6.3/kotest-property-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/mifmif/generex/1.0.2/generex-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/dk/brics/automaton/automaton/1.11-8/automaton-1.11-8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+56
View File
@@ -0,0 +1,56 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:4.6.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/4.6.3/kotest-runner-junit5-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/4.6.3/kotest-framework-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/4.6.3/kotest-framework-engine-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.105/classgraph-4.8.105.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-util/1.5.0/kotlin-script-util-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/trove4j/1.0.20181211/trove4j-1.0.20181211.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-daemon-client/1.5.0/kotlin-daemon-client-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.8/kotlinx-coroutines-core-1.3.8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.5.0/kotlin-scripting-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/1.5.0/kotlin-scripting-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/4.6.3/kotest-framework-discovery-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/4.6.3/kotest-extensions-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.6/commons-io-2.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.9.3/mockk-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.9.3/mockk-common-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.9.3/mockk-dsl-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.9.3/mockk-dsl-jvm-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.9.3/mockk-agent-jvm-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.9.3/mockk-agent-api-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.9.3/mockk-agent-common-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.0.1/objenesis-3.0.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.9.10/byte-buddy-1.9.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.9.10/byte-buddy-agent-1.9.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/4.6.3/kotest-framework-concurrency-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.6.2/junit-platform-suite-api-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.6.2/junit-platform-launcher-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-runtime/1.5.0/kotlin-script-runtime-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+4 -1
View File
@@ -16,7 +16,10 @@
</list>
</option>
</component>
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
</project>
+3
View File
@@ -2,8 +2,11 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" filepath="$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
+29
View File
@@ -0,0 +1,29 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
# You can also specify other tool versions:
# nodejs: "16"
# rust: "1.55"
# golang: "1.17"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
+8 -1
View File
@@ -1,3 +1,10 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
+52
View File
@@ -0,0 +1,52 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.8"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
test {
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
</component>
</module>
@@ -0,0 +1,3 @@
package prog8.compiler.target
class AssemblyError(msg: String) : RuntimeException(msg)
@@ -0,0 +1,35 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
@@ -0,0 +1,36 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
@@ -1,16 +1,13 @@
package prog8.compiler.target.c64
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
import kotlin.math.absoluteValue
import kotlin.math.pow
internal object C64MachineDefinition: IMachineDefinition {
object C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
@@ -30,7 +27,7 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
@@ -77,7 +74,7 @@ internal object C64MachineDefinition: IMachineDefinition {
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
@@ -86,13 +83,12 @@ internal object C64MachineDefinition: IMachineDefinition {
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
@@ -125,7 +121,7 @@ internal object C64MachineDefinition: IMachineDefinition {
))
}
if(options.zeropage!=ZeropageType.DONTUSE) {
if(options.zeropage!= ZeropageType.DONTUSE) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
@@ -136,17 +132,13 @@ internal object C64MachineDefinition: IMachineDefinition {
free.clear()
}
}
require(SCRATCH_B1 !in free)
require(SCRATCH_REG !in free)
require(SCRATCH_W1 !in free)
require(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
removeReservedFromFreePool()
}
}
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
IMachineFloat {
companion object {
val zero = Mflpt5(0, 0, 0, 0, 0)
@@ -157,7 +149,7 @@ internal object C64MachineDefinition: IMachineDefinition {
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
@@ -178,7 +170,7 @@ internal object C64MachineDefinition: IMachineDefinition {
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw CompilerException("floating point overflow: $this")
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
@@ -1,10 +1,17 @@
package prog8.compiler.target.cbm
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IAssemblyProgram
import prog8.compilerinterface.OutputType
import prog8.compilerinterface.generatedLabelPrefix
import prog8.parser.SourceCode
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
internal const val viceMonListPostfix = "vice-mon-list"
@@ -20,12 +27,15 @@ class AssemblyProgram(
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
override fun assemble(options: CompilationOptions): Int {
override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
if(quiet)
command.add("--quiet")
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
@@ -76,3 +86,18 @@ class AssemblyProgram(
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").readText()
}.mapError { NoSuchFileException(File(filename)) }
} else {
val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile())
Ok(SourceCode.File(sib).readText())
else
Ok(SourceCode.File(Path(filename)).readText())
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -118,15 +118,15 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
val mods = mutableListOf<Modification>()
for (pair in linesByFourteen) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
val third = pair[2].value.trimStart()
val fourth = pair[3].value.trimStart()
val fifth = pair[4].value.trimStart()
val sixth = pair[5].value.trimStart()
val seventh = pair[6].value.trimStart()
val eighth = pair[7].value.trimStart()
for (lines in linesByFourteen) {
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
val fourth = lines[3].value.trimStart()
val fifth = lines[4].value.trimStart()
val sixth = lines[5].value.trimStart()
val seventh = lines[6].value.trimStart()
val eighth = lines[7].value.trimStart()
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("sta") && eighth.startsWith("sty")) {
@@ -136,8 +136,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
mods.add(Modification(pair[4].index, true, null))
mods.add(Modification(pair[5].index, true, null))
mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null))
}
}
@@ -146,7 +146,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val secondvalue = third.substring(4)
if(firstvalue==secondvalue) {
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
mods.add(Modification(pair[2].index, true, null))
mods.add(Modification(lines[2].index, true, null))
}
}
@@ -154,12 +154,12 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
val nineth = pair[8].value.trimStart()
val tenth = pair[9].value.trimStart()
val eleventh = pair[10].value.trimStart()
val twelveth = pair[11].value.trimStart()
val thirteenth = pair[12].value.trimStart()
val fourteenth = pair[13].value.trimStart()
val nineth = lines[8].value.trimStart()
val tenth = lines[9].value.trimStart()
val eleventh = lines[10].value.trimStart()
val twelveth = lines[11].value.trimStart()
val thirteenth = lines[12].value.trimStart()
val fourteenth = lines[13].value.trimStart()
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
@@ -167,24 +167,138 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init
mods.add(Modification(pair[7].index, true, null))
mods.add(Modification(pair[8].index, true, null))
mods.add(Modification(pair[9].index, true, null))
mods.add(Modification(pair[10].index, true, null))
mods.add(Modification(lines[7].index, true, null))
mods.add(Modification(lines[8].index, true, null))
mods.add(Modification(lines[9].index, true, null))
mods.add(Modification(lines[10].index, true, null))
}
}
}
var overlappingMods = false
/*
sta prog8_lib.retval_intermX ; remove
sty prog8_lib.retval_intermY ; remove
lda prog8_lib.retval_intermX ; remove
ldy prog8_lib.retval_intermY ; remove
sta A1
sty A2
*/
if(first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && fourth.startsWith("ld")
&& fifth.startsWith("st") && sixth.startsWith("st")) {
val reg1 = first[2]
val reg2 = second[2]
val reg3 = third[2]
val reg4 = fourth[2]
val reg5 = fifth[2]
val reg6 = sixth[2]
if (reg1 == reg3 && reg1 == reg5 && reg2 == reg4 && reg2 == reg6) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
val fourthvalue = fourth.substring(4)
if(firstvalue.contains("prog8_lib.retval_interm") && secondvalue.contains("prog8_lib.retval_interm")
&& firstvalue==thirdvalue && secondvalue==fourthvalue) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
overlappingMods = true
}
}
}
/*
sta A1
sty A2
lda A1 ; can be removed
ldy A2 ; can be removed if not followed by a branch instuction
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && fourth.startsWith("ld")) {
val reg1 = first[2]
val reg2 = second[2]
val reg3 = third[2]
val reg4 = fourth[2]
if(reg1==reg3 && reg2==reg4) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
val fourthvalue = fourth.substring(4)
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
if(!fifth.startsWith('b'))
mods.add(Modification(lines[3].index, true, null))
}
}
}
/*
sta A1
sty A2
lda A1 ; can be removed if not followed by a branch instruction
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && !fourth.startsWith("b")) {
val reg1 = first[2]
val reg3 = third[2]
if(reg1==reg3) {
val firstvalue = first.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
}
}
}
/*
sta A1
<other st/ld instruction not involving A1>
sta A1 ; can be removed
*/
if(!overlappingMods && first.startsWith("st") && third.startsWith("st")) {
val reg1 = first[2]
val reg3 = third[2]
if(reg1==reg3 && (second.startsWith("ld") || second.startsWith("st"))) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue && firstvalue!=secondvalue) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
}
}
}
/*
sta A1
ldy A1 ; make tay
sta A1 ; remove
*/
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld")
&& third.startsWith("sta") && second.length>4) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
overlappingMods = true
val reg2 = second[2]
mods.add(Modification(lines[1].index, false, " ta$reg2"))
mods.add(Modification(lines[2].index, true, null))
}
}
}
return mods
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) but how does this code know?
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
for (lines in linesByFour) {
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
@@ -196,14 +310,25 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val third = pair[2].value.trimStart()
if(!third.startsWith("b")) {
// no branch instruction follows, we can potentiall remove the load instruction
val third = lines[3].value.trimStart()
val attemptRemove =
if(third.startsWith("b")) {
// a branch instruction follows, we can only remove the load instruction if
// another load instruction of the same register precedes the store instruction
// (otherwise wrong cpu flags are used)
val loadinstruction = second.substring(0, 3)
lines[0].value.trimStart().startsWith(loadinstruction)
}
else {
// no branch instruction follows, we can remove the load instruction
true
}
if(attemptRemove) {
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}
if (firstLoc == secondLoc)
mods.add(Modification(lines[2].index, true, null))
}
}
}
@@ -213,15 +338,15 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
for (lines in linesByFour) {
val first = lines[0].value
val second = lines[1].value
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
mods.add(Modification(pair[0].index, true, null))
mods.add(Modification(pair[1].index, true, null))
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
}
}
return mods
@@ -230,12 +355,12 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
for (lines in linesByFour) {
val first = lines[0].value
val second = lines[1].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
mods += Modification(pair[1].index, true, null)
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
}
}
return mods
@@ -10,12 +10,12 @@ import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.FSignature
import prog8.compiler.target.CpuType
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.compiler.target.subroutineFloatEvalResultVar2
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
@@ -46,7 +46,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sin8", "sin8u", "sin16", "sin16u",
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
"sinr8", "sinr8u", "sinr16", "sinr16u",
"cos8", "cos8u", "cos16", "cos16u",
"cosr8", "cosr8u", "cosr16", "cosr16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
@@ -261,7 +263,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen)
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
asmgen.slabs[name] = size
@@ -273,7 +275,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@@ -283,13 +285,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
else
when(func.name) {
"sin8", "sin8u", "cos8", "cos8u" -> {
"sin8", "sin8u", "sinr8", "sinr8u", "cos8", "cos8u", "cosr8", "cosr8u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
"sin16", "sin16u", "cos16", "cos16u" -> {
"sin16", "sin16u", "sinr16", "sinr16u", "cos16", "cos16u", "cosr16", "cosr16u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
}
@@ -578,7 +580,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr floats.func_${func.name}_stack")
else {
asmgen.out(" jsr floats.func_${func.name}_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
}
@@ -603,7 +605,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@@ -624,7 +626,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@@ -644,23 +646,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.getOr(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)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
@@ -683,23 +685,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.getOr(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)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_sum_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
@@ -715,11 +717,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val firstName = asmgen.asmVariableName(first)
val secondName = asmgen.asmVariableName(second)
val dt = first.inferType(program)
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
if(dt istype DataType.BYTE || dt istype DataType.UBYTE) {
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
return
}
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
if(dt istype DataType.WORD || dt istype DataType.UWORD) {
asmgen.out("""
ldy $firstName
lda $secondName
@@ -732,7 +734,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
""")
return
}
if(dt.istype(DataType.FLOAT)) {
if(dt istype DataType.FLOAT) {
asmgen.out("""
lda #<$firstName
sta P8ZP_SCRATCH_W1
@@ -785,7 +787,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
) {
val pointerVar = firstExpr.left as IdentifierReference
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
if(firstOffset!=secondOffset) {
swapArrayValues(
@@ -876,21 +877,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateNormalAssignment(assignSecond)
}
DataType.FLOAT -> {
// via evaluation stack
asmgen.translateExpression(first)
asmgen.translateExpression(second)
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
// via temp variable and FAC1
asmgen.assignExpressionTo(first, AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.FLOAT, first.definingSubroutine, "floats.tempvar_swap_float"))
asmgen.assignExpressionTo(second, AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, null, register=RegisterOrPair.FAC1))
asmgen.translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, datatype, register = RegisterOrPair.FAC1),
targetFromExpr(first, datatype),
false, program.memsizer, first.position
)
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
asmgen.translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, "floats.tempvar_swap_float"),
targetFromExpr(second, datatype),
false, program.memsizer, second.position
)
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
}
else -> throw AssemblyError("weird swap dt")
}
@@ -1140,15 +1143,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt) {
in ByteDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
in WordDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.abs_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type")
}
@@ -1162,7 +1165,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rnd_stack")
else {
asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
"rndw" -> {
@@ -1170,7 +1173,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rndw_stack")
else {
asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
else -> throw AssemblyError("wrong func")
@@ -1533,7 +1536,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
AsmAssignSource.fromAstSource(value, program, asmgen)
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}
@@ -0,0 +1,792 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) {
if (this.asmgen.options.slowCodegenWarnings) {
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
}
translateExpressionInternal(expression)
}
// the rest of the methods are all PRIVATE
private fun translateExpressionInternal(expression: Expression) {
when(expression) {
is PrefixExpression -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression)
is ArrayIndexedExpression -> translateExpression(expression)
is TypecastExpression -> translateExpression(expression)
is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is NumericLiteralValue -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
}
}
private fun translateFunctionCallResultOntoStack(call: FunctionCall) {
// only for use in nested expression evaluation
val sub = call.target.targetStatement(program)
if(sub is BuiltinFunctionStatementPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
} else {
sub as Subroutine
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can acces again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
}
asmgen.restoreXafterCall(call)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
asmgen.out(
"""
lda cx16.${reg.registerOrPair.toString().lowercase()}
sta P8ESTACK_LO,x
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
sta P8ESTACK_HI,x
dex
""")
}
}
} else when(reg.statusflag) {
Statusflag.Pc -> {
asmgen.out("""
lda #0
adc #0
sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pz -> {
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pv -> {
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pn -> {
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
null -> {}
}
}
}
}
private fun translateExpression(typecast: TypecastExpression) {
translateExpressionInternal(typecast.expression)
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_ub2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.FLOAT -> {
when(typecast.type) {
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
DataType.WORD -> asmgen.out(" jsr floats.stack_float2w")
DataType.FLOAT -> {}
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.STR -> {
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
throw AssemblyError("cannot typecast a string into another incompatitble type")
}
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: AddressOf) {
val name = asmgen.asmVariableName(expr.identifier)
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
}
private fun translateExpression(expr: NumericLiteralValue) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out("""
lda #<${expr.number.toHex()}
sta P8ESTACK_LO,x
lda #>${expr.number.toHex()}
sta P8ESTACK_HI,x
dex
""")
DataType.FLOAT -> {
val floatConst = asmgen.getFloatAsmConst(expr.number.toDouble())
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: IdentifierReference) {
val varname = asmgen.asmVariableName(expr)
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$varname | ldy #>$varname| jsr floats.push_float")
}
in IterableDatatypes -> {
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
}
else -> throw AssemblyError("stack push weird variable type $expr")
}
}
private fun translateExpression(expr: BinaryExpression) {
// Uses evalstack to evaluate the given expression.
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
// see if we can apply some optimized routines
when(expr.operator) {
"+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt()
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (leftVal!=null && leftVal in -4..4) {
translateExpressionInternal(expr.right)
if(rightDt in ByteDatatypes) {
val incdec = if(leftVal<0) "dec" else "inc"
repeat(leftVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(leftVal<0) {
repeat(leftVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(leftVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
else if (rightVal!=null && rightVal in -4..4)
{
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "dec" else "inc"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal<0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
"-" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (rightVal!=null && rightVal in -4..4)
{
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "inc" else "dec"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal>0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
">>" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) {
translateExpressionInternal(expr.left)
when (leftDt) {
DataType.UBYTE -> {
if (amount <= 2)
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
else {
asmgen.out(" lda P8ESTACK_LO+1,x")
repeat(amount) { asmgen.out(" lsr a") }
asmgen.out(" sta P8ESTACK_LO+1,x")
}
}
DataType.BYTE -> {
if (amount <= 2)
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
else {
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
asmgen.out(" sta P8ESTACK_LO+1,x")
}
}
DataType.UWORD -> {
if(amount>=16) {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
return
}
var left = amount
while (left >= 7) {
asmgen.out(" jsr math.shift_right_uw_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
else
asmgen.out(" jsr math.shift_right_uw_$left")
}
DataType.WORD -> {
if(amount>=16) {
asmgen.out("""
lda P8ESTACK_HI+1,x
bmi +
lda #0
sta P8ESTACK_LO+1,x
sta P8ESTACK_HI+1,x
beq ++
+ lda #255
sta P8ESTACK_LO+1,x
sta P8ESTACK_HI+1,x
+""")
return
}
var left = amount
while (left >= 7) {
asmgen.out(" jsr math.shift_right_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
else
asmgen.out(" jsr math.shift_right_w_$left")
}
else -> throw AssemblyError("weird type")
}
return
}
}
"<<" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) {
translateExpressionInternal(expr.left)
if (leftDt in ByteDatatypes) {
if (amount <= 2)
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
else {
asmgen.out(" lda P8ESTACK_LO+1,x")
repeat(amount) { asmgen.out(" asl a") }
asmgen.out(" sta P8ESTACK_LO+1,x")
}
} else {
var left = amount
while (left >= 7) {
asmgen.out(" jsr math.shift_left_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
else
asmgen.out(" jsr math.shift_left_w_$left")
}
return
}
}
"*" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVar = expr.left as? IdentifierReference
val rightVar = expr.right as? IdentifierReference
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
return translateSquared(leftVar, leftDt)
}
val value = expr.right.constValue(program)
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount==2) {
// optimize x*2 common case
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
asmgen.out(" asl P8ESTACK_LO+1,x")
} else {
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
}
return
}
when(rightDt) {
DataType.UBYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount")
return
}
}
DataType.BYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount")
return
}
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
return
}
}
DataType.UWORD -> {
if(amount in asmgen.optimizedWordMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount")
return
}
}
DataType.WORD -> {
if(amount in asmgen.optimizedWordMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount")
return
}
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
return
}
}
else -> {}
}
}
}
}
"/" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal!=null && rightVal==2) {
translateExpressionInternal(expr.left)
when(leftDt) {
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x")
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
else -> throw AssemblyError("wrong dt")
}
return
}
}
}
}
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
translateCompareStrings(expr.left, expr.operator, expr.right)
}
else {
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
translateExpressionInternal(expr.left)
translateExpressionInternal(expr.right)
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
}
}
}
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
val asmVar = asmgen.asmVariableName(variable)
when(dt) {
DataType.BYTE, DataType.UBYTE -> {
asmgen.out(" lda $asmVar")
asmgen.signExtendAYlsb(dt)
asmgen.out(" jsr math.square")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out(" lda $asmVar | ldy $asmVar+1 | jsr math.square")
}
else -> throw AssemblyError("require integer dt for square")
}
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
}
private fun translateExpression(expr: PrefixExpression) {
translateExpressionInternal(expr.expression)
val itype = expr.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.getOr(DataType.UNDEFINED)
when(expr.operator) {
"+" -> {}
"-" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
else -> throw AssemblyError("weird type")
}
}
"~" -> {
when(type) {
in ByteDatatypes ->
asmgen.out("""
lda P8ESTACK_LO+1,x
eor #255
sta P8ESTACK_LO+1,x
""")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
else -> throw AssemblyError("weird type")
}
}
"not" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
}
}
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
val elementIDt = arrayExpr.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
val constIndexNum = arrayExpr.indexer.constIndex()
if(constIndexNum!=null) {
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
}
in WordDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
}
else -> throw AssemblyError("weird element type")
}
} else {
when(elementDt) {
in ByteDatatypes -> {
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
}
in WordDatatypes -> {
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
asmgen.out("""
ldy #>$arrayVarName
clc
adc #<$arrayVarName
bcc +
iny
+ jsr floats.push_float""")
}
else -> throw AssemblyError("weird dt")
}
}
}
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
if(types==DataType.BYTE)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_ub")
}
"+" -> asmgen.out("""
lda P8ESTACK_LO+2,x
clc
adc P8ESTACK_LO+1,x
inx
sta P8ESTACK_LO+1,x
""")
"-" -> asmgen.out("""
lda P8ESTACK_LO+2,x
sec
sbc P8ESTACK_LO+1,x
inx
sta P8ESTACK_LO+1,x
""")
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
"and" -> asmgen.out(" jsr prog8_lib.and_b")
"or" -> asmgen.out(" jsr prog8_lib.or_b")
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorWords(operator: String, dt: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
"/" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
"%" -> {
if(dt==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_uw")
}
"+" -> asmgen.out(" jsr prog8_lib.add_w")
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
"<<" -> asmgen.out(" jsr math.shift_left_w")
">>" -> {
if(dt==DataType.UWORD)
asmgen.out(" jsr math.shift_right_uw")
else
asmgen.out(" jsr math.shift_right_w")
}
"<" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
">" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w") "&" -> asmgen.out(" jsr prog8_lib.bitand_w")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
"and" -> asmgen.out(" jsr prog8_lib.and_w")
"or" -> asmgen.out(" jsr prog8_lib.or_w")
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorFloats(operator: String) {
when(operator) {
"**" -> asmgen.out(" jsr floats.pow_f")
"*" -> asmgen.out(" jsr floats.mul_f")
"/" -> asmgen.out(" jsr floats.div_f")
"+" -> asmgen.out(" jsr floats.add_f")
"-" -> asmgen.out(" jsr floats.sub_f")
"<" -> asmgen.out(" jsr floats.less_f")
">" -> asmgen.out(" jsr floats.greater_f")
"<=" -> asmgen.out(" jsr floats.lesseq_f")
">=" -> asmgen.out(" jsr floats.greatereq_f")
"==" -> asmgen.out(" jsr floats.equal_f")
"!=" -> asmgen.out(" jsr floats.notequal_f")
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
when(operator) {
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
"<=" -> asmgen.out("""
bpl +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO,x""")
">=" -> asmgen.out("""
bmi +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO,x""")
"<" -> asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x""")
">" -> asmgen.out("""
bpl +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x""")
}
asmgen.out(" dex")
}
}
@@ -8,8 +8,8 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.astprocessing.toConstantIntegerRange
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.toConstantIntegerRange
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@@ -20,7 +20,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
throw AssemblyError("unknown dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
} else {
@@ -9,12 +9,12 @@ import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.CpuType
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
@@ -64,7 +64,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
require(sub.isAsmSubroutine)
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
@@ -135,14 +135,25 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
// TODO find another way to prepare the arguments, without using the eval stack
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
// TODO here's an alternative to the above, but for now generates bigger code due to intermediate register steps:
// for (arg in stmt.args.reversed()) {
// // note this stuff below is needed to (eventually) avoid calling asmgen.translateExpression()
// // TODO also This STILL requires the translateNormalAssignment() to be fixed to avoid stack eval for expressions...
// val dt = arg.inferType(program).getOr(DataType.UNDEFINED)
// asmgen.assignExpressionTo(arg, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, dt, sub))
// }
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
@@ -157,11 +168,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
argForCarry = argi
}
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
@@ -325,7 +336,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
AsmAssignTarget.fromRegisters(register, false, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
@@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.AssemblyError
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@@ -1,11 +1,11 @@
package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compilerinterface.IMemSizer
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
@@ -41,11 +41,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
}
lateinit var origAssign: AsmAssignment
@@ -68,14 +69,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
}
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
RegisterOrPair.FAC1,
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
RegisterOrPair.R0,
@@ -93,7 +94,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
}
}
}
@@ -205,10 +206,10 @@ internal class AsmAssignment(val source: AsmAssignSource,
val position: Position) {
init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
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"
"source dt size must be less or equal to target dt size at $position"
}
}
}
@@ -5,19 +5,16 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.CpuType
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.builtinFunctionReturnType
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
private val exprAsmgen: ExpressionsAsmGen
) {
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
fun translate(assignment: Assignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
@@ -33,6 +30,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
return
}
when(assign.source.kind) {
SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number
@@ -171,13 +173,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
// copy the actual string result into the target string variable
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("weird target dt")
}
@@ -249,30 +251,40 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign.target, assign.target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(assign.target, assign.target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(assign.target, assign.target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
else -> {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions to do it via registers here,
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
if (value.parent is Return) {
if (this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for return: $value target=${assign.target.kind} at ${value.position}")
}
exprAsmgen.translateExpression(value)
// TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
asmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
}
}
SourceStorageKind.REGISTER -> {
when(assign.source.datatype) {
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
else -> throw AssemblyError("invalid register dt")
}
asmgen.assignRegister(assign.source.register!!, assign.target)
}
SourceStorageKind.STACK -> {
assignStackValue(assign.target)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
}
}
@@ -364,16 +376,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (valueDt) {
in ByteDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.A)
assignExpressionToRegister(value, RegisterOrPair.A, valueDt==DataType.BYTE)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt)
}
in WordDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt==DataType.WORD)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt)
}
DataType.FLOAT -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1)
assignTypecastedFloatFAC1(target.asmVarname, targetDt)
assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
assignTypeCastedFloatFAC1(target.asmVarname, targetDt)
}
in PassByReferenceDatatypes -> {
// str/array value cast (most likely to UWORD, take address-of)
@@ -399,14 +411,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.X,
RegisterOrPair.Y -> {
// 'cast' an ubyte value to a byte register; no cast needed at all
return assignExpressionToRegister(value, target.register)
return assignExpressionToRegister(value, target.register, false)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
return assignExpressionToRegister(value, target.register!!)
return assignExpressionToRegister(value, target.register!!, false)
}
else -> {}
}
@@ -424,19 +436,41 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// 'cast' uword into a 16 bits register, just assign it
return assignExpressionToRegister(value, target.register!!)
return assignExpressionToRegister(value, target.register!!, false)
}
else -> {}
}
}
// give up, do it via eval stack
// 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)
assignStackValue(target)
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
when(valueDt) {
DataType.UBYTE -> {
assignExpressionToRegister(value, RegisterOrPair.Y, false)
asmgen.out(" jsr floats.FREADUY")
}
DataType.BYTE -> {
assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.out(" jsr floats.FREADSA")
}
DataType.UWORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.out(" jsr floats.GIVUAYFAY")
}
DataType.WORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" jsr floats.GIVAYFAY")
}
else -> throw AssemblyError("invalid dt")
}
if(target.register==RegisterOrPair.FAC2) {
asmgen.out(" jsr floats.MOVEF")
}
} else {
// No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
asmgen.assignExpressionTo(origTypeCastExpression, target)
}
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
@@ -447,7 +481,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
private fun assignTypecastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
private fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
if(targetDt==DataType.FLOAT)
throw AssemblyError("typecast to identical type")
@@ -954,7 +988,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
lda #<$sourceName
ldy #>$sourceName+1
sta P8ESTACK_LO,x
sty P8ESTACK_HI,x
tya
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("string-assign to weird target")
@@ -1363,7 +1398,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// these will be correctly typecasted from a byte to a word value
if(target.register !in Cx16VirtualRegisters &&
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
if(target.kind==TargetStorageKind.VARIABLE) {
if(target.kind== TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16")
require(target.datatype in ByteDatatypes)
@@ -1459,7 +1494,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in NumericDatatypes)
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
if(target.datatype==DataType.FLOAT)
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
@@ -1586,7 +1621,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
when(regs) {
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
@@ -2157,9 +2192,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
@@ -2171,8 +2206,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign)
@@ -5,14 +5,12 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
import prog8.compilerinterface.CpuType
internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen,
private val exprAsmGen: ExpressionsAsmGen,
private val asmgen: AsmGen
) {
fun translate(assign: AsmAssignment) {
@@ -105,7 +103,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
throw FatalAstException("assignment should be augmentable $binExpr")
}
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: Expression) {
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) {
// the asm-gen code can deal with situations where you want to assign a byte into a word.
// it will create the most optimized code to do this (so it type-extends for us).
// But we can't deal with writing a word into a byte - explicit typeconversion is required
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOr(DataType.UNDEFINED)) > program.memsizer.memorySize(target.datatype)) {
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position)
typecast.linkParents(origValue.parent)
typecast
}
else {
origValue
}
val valueLv = (value as? NumericLiteralValue)?.number
val ident = value as? IdentifierReference
val memread = value as? DirectMemoryRead
@@ -181,8 +193,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
else -> {
asmgen.translateExpression(memory.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
// TODO OTHER EVALUATION HERE, don't use the estack
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") // TODO don't use estack to transfer the address to read from
when {
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
@@ -193,7 +206,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
}
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx")
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") // TODO don't use estack to transfer the address to read from
}
}
}
@@ -246,19 +259,37 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
indexVar!=null -> {
when (target.datatype) {
in ByteDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.A,
target.datatype == DataType.BYTE, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
}
in WordDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.AY,
target.datatype == DataType.WORD, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen)
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.FAC1,
true, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
@@ -638,14 +669,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
when (operator) {
"+" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out("""
clc
adc $name
sta $name""")
}
"-" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out("""
sta P8ZP_SCRATCH_B1
lda $name
@@ -654,15 +685,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta $name""")
}
"|", "or" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out(" ora $name | sta $name")
}
"&", "and" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out(" and $name | sta $name")
}
"^", "xor" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out(" eor $name | sta $name")
}
// TODO: tuned code for more operators
@@ -675,7 +706,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) {
"+" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out("""
clc
adc $name
@@ -685,7 +716,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
}
"-" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out("""
sta P8ZP_SCRATCH_B1
lda $name
@@ -697,11 +728,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
}
"|", "or" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out(" ora $name | sta $name")
}
"&", "and" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out(" and $name | sta $name")
if(dt in WordDatatypes) {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
@@ -711,7 +742,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
"^", "xor" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out(" eor $name | sta $name")
}
// TODO: tuned code for more operators
@@ -1705,7 +1736,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
internal fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.UBYTE -> {
when (target.kind) {
@@ -1749,9 +1780,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out("""
cmp #0
beq +
lda #1
+ eor #1""")
RegisterOrPair.X -> asmgen.out("""
txa
beq +
lda #1
+ eor #1
tax""")
RegisterOrPair.Y -> asmgen.out("""
tya
beq +
lda #1
+ eor #1
tay""")
else -> throw AssemblyError("invalid reg dt for byte not")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack not")
else -> throw AssemblyError("missing codegen for in-place not of ubyte ${target.kind}")
}
}
DataType.UWORD -> {
@@ -1767,17 +1819,56 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
lsr a
sta ${target.asmVarname}+1""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
ora P8ZP_SCRATCH_REG
beq +
lda #0
tax
beq ++
+ lda #1
+""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sty P8ZP_SCRATCH_REG
ora P8ZP_SCRATCH_REG
beq +
lda #0
tay
beq ++
+ lda #1
+""")
}
RegisterOrPair.XY -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
tya
ora P8ZP_SCRATCH_REG
beq +
ldy #0
ldx #0
beq ++
+ ldx #1
+""")
}
in Cx16VirtualRegisters -> TODO()
else -> throw AssemblyError("invalid reg dt for word not")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory not")
TargetStorageKind.STACK -> TODO("missing codegen for word stack not")
else -> throw AssemblyError("missing codegen for in-place not of uword for ${target.kind}")
}
}
else -> throw AssemblyError("boolean-not of invalid type")
}
}
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.UBYTE -> {
when (target.kind) {
@@ -1812,9 +1903,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" eor #255")
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay")
else -> throw AssemblyError("invalid reg dt for byte invert")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack invert")
else -> throw AssemblyError("missing codegen for in-place invert ubyte for ${target.kind}")
}
}
DataType.UWORD -> {
@@ -1828,17 +1926,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
eor #255
sta ${target.asmVarname}+1""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register invert")
}
else -> throw AssemblyError("invalid reg dt for word invert")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory invert")
TargetStorageKind.STACK -> TODO("missing codegen for word stack invert")
else -> throw AssemblyError("missing codegen for in-place invert uword for ${target.kind}")
}
}
else -> throw AssemblyError("invert of invalid type")
}
}
private fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.BYTE -> {
when (target.kind) {
@@ -1849,10 +1957,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc ${target.asmVarname}
sta ${target.asmVarname}""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1")
RegisterOrPair.X -> asmgen.out(" stx P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tax")
RegisterOrPair.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tay")
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> TODO("can't in-place negate memory ubyte")
TargetStorageKind.STACK -> TODO("missing codegen for byte stack negate")
else -> throw AssemblyError("missing codegen for in-place negate byte array")
}
}
DataType.WORD -> {
@@ -1867,10 +1982,55 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc ${target.asmVarname}+1
sta ${target.asmVarname}+1""")
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array")
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
TargetStorageKind.REGISTER -> {
when(target.register!!) { //P8ZP_SCRATCH_REG
RegisterOrPair.AX -> {
asmgen.out("""
sta P8ZP_SCRATCH_REG
stx P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
tax
pla""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sta P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
tay
pla""")
}
RegisterOrPair.XY -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
tax
lda #0
sbc P8ZP_SCRATCH_REG+1
tay""")
}
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register negate")
}
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for word memory negate")
TargetStorageKind.STACK -> TODO("missing codegen for word stack negate")
else -> throw AssemblyError("missing codegen for in-place negate word array")
}
}
DataType.FLOAT -> {
@@ -1883,9 +2043,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate")
else -> throw AssemblyError("weird target kind for float")
TargetStorageKind.REGISTER -> TODO("missing codegen for float reg negate")
TargetStorageKind.MEMORY -> TODO("missing codegen for float memory negate")
TargetStorageKind.STACK -> TODO("missing codegen for stack float negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
}
}
else -> throw AssemblyError("negate of invalid type")
@@ -1,14 +1,13 @@
package prog8.compiler.target.cx16
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
internal object CX16MachineDefinition: IMachineDefinition {
object CX16MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
@@ -88,7 +87,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
"rmb", "smb", "stp", "wai")
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
@@ -97,38 +96,29 @@ internal object CX16MachineDefinition: IMachineDefinition {
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22..0x7f)
free.addAll(0xa9..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22..0x7f)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
require(SCRATCH_B1 !in free)
require(SCRATCH_REG !in free)
require(SCRATCH_W1 !in free)
require(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
removeReservedFromFreePool()
}
}
}
@@ -1,9 +1,8 @@
package prog8tests
package prog8tests.asmgen
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
@@ -11,30 +10,30 @@ import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
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 prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8.compilerinterface.*
import prog8.parser.SourceCode
import prog8tests.asmgen.helpers.DummyFunctions
import prog8tests.asmgen.helpers.DummyMemsizer
import prog8tests.asmgen.helpers.DummyStringEncoder
import prog8tests.asmgen.helpers.ErrorReporterForTests
import java.nio.file.Path
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAsmGen6502 {
private fun createTestProgram(): Program {
class AsmGenSymbolsTests: StringSpec({
fun createTestProgram(): Program {
/*
main {
main {
label_outside:
label_outside:
uword var_outside
sub start () {
uword localvar = 1234
uword tgt
locallabel:
locallabel:
tgt = localvar
tgt = &locallabel
tgt = &var_outside
@@ -44,7 +43,7 @@ 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)
@@ -62,84 +61,113 @@ locallabel:
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 subroutine = Subroutine("start", mutableListOf(), 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, null)
val program = Program("test", DummyFunctions, DummyMemsizer)
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkParents(program.namespace)
module.linkIntoProgram(program)
return program
}
private fun createTestAsmGen(program: Program): AsmGen {
val errors = ErrorReporter()
fun createTestAsmGen(program: Program): AsmGen {
val errors = ErrorReporterForTests()
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("."))
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))
return asmgen
}
@Test
fun testSymbolNameFromStrings() {
"symbol and variable names from strings" {
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"))
asmgen.asmSymbolName("name") shouldBe "name"
asmgen.asmSymbolName("name") shouldBe "name"
asmgen.asmSymbolName("<name>") shouldBe "prog8_name"
asmgen.asmSymbolName(RegisterOrPair.R15) shouldBe "cx16.r15"
asmgen.asmSymbolName(listOf("a", "b", "name")) shouldBe "a.b.name"
asmgen.asmVariableName("name") shouldBe "name"
asmgen.asmVariableName("<name>") shouldBe "prog8_name"
asmgen.asmVariableName(listOf("a", "b", "name")) shouldBe "a.b.name"
}
@Test
fun testSymbolNameFromVarIdentifier() {
"symbol and variable names from variable identifiers" {
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"))
asmgen.asmSymbolName(localvarIdent) shouldBe "localvar"
asmgen.asmVariableName(localvarIdent) shouldBe "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"))
asmgen.asmSymbolName(localvarIdentScoped) shouldBe "main.start.localvar"
asmgen.asmVariableName(localvarIdentScoped) shouldBe "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"))
asmgen.asmSymbolName(scopedVarIdent) shouldBe "main.var_outside"
asmgen.asmVariableName(scopedVarIdent) shouldBe "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"))
asmgen.asmSymbolName(scopedVarIdentScoped) shouldBe "main.var_outside"
asmgen.asmVariableName(scopedVarIdentScoped) shouldBe "main.var_outside"
}
@Test
fun testSymbolNameFromLabelIdentifier() {
"symbol and variable names from label identifiers" {
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"))
asmgen.asmSymbolName(localLabelIdent) shouldBe "_locallabel"
withClue("as a variable it uses different naming rules (no underscore prefix)") {
asmgen.asmVariableName(localLabelIdent) shouldBe "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"))
asmgen.asmSymbolName(localLabelIdentScoped) shouldBe "main.start._locallabel"
withClue("as a variable it uses different naming rules (no underscore prefix)") {
asmgen.asmVariableName(localLabelIdentScoped) shouldBe "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"))
asmgen.asmSymbolName(scopedLabelIdent) shouldBe "main._label_outside"
withClue("as a variable it uses different naming rules (no underscore prefix)") {
asmgen.asmVariableName(scopedLabelIdent) shouldBe "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"))
asmgen.asmSymbolName(scopedLabelIdentScoped) shouldBe "main._label_outside"
withClue("as a variable it uses different naming rules (no underscore prefix)") {
asmgen.asmVariableName(scopedLabelIdentScoped) shouldBe "main.label_outside"
}
}
}
"asm names for hooks to zp temp vars" {
/*
main {
sub start() {
prog8_lib.P8ZP_SCRATCH_REG = 1
prog8_lib.P8ZP_SCRATCH_B1 = 1
prog8_lib.P8ZP_SCRATCH_W1 = 1
prog8_lib.P8ZP_SCRATCH_W2 = 1
*/
val program = createTestProgram()
val asmgen = createTestAsmGen(program)
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_REG") shouldBe "P8ZP_SCRATCH_REG"
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_W2") shouldBe "P8ZP_SCRATCH_W2"
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_REG")) shouldBe "P8ZP_SCRATCH_REG"
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_W2")) shouldBe "P8ZP_SCRATCH_W2"
val id1 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_REG"), Position.DUMMY)
id1.linkParents(program.toplevelModule)
val id2 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_W2"), Position.DUMMY)
id2.linkParents(program.toplevelModule)
asmgen.asmSymbolName(id1) shouldBe "P8ZP_SCRATCH_REG"
asmgen.asmSymbolName(id2) shouldBe "P8ZP_SCRATCH_W2"
}
})
+8
View File
@@ -0,0 +1,8 @@
package prog8tests.asmgen
import io.kotest.core.config.AbstractProjectConfig
import kotlin.math.max
object ProjectConfig : AbstractProjectConfig() {
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
}
+37
View File
@@ -0,0 +1,37 @@
package prog8tests.asmgen.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.ast.base.DataType
import prog8.compilerinterface.IStringEncoding
internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(
name: String,
args: List<Expression>,
position: Position,
): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}
@@ -0,0 +1,30 @@
package prog8tests.asmgen.helpers
import prog8.ast.base.Position
import prog8.compilerinterface.IErrorReporter
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
errors.clear()
warnings.clear()
}
}
+32
View File
@@ -0,0 +1,32 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</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="compilerInterfaces" />
<orderEntry type="module" module-name="compilerAst" />
</component>
</module>
+2
View File
@@ -0,0 +1,2 @@
Unittests for things in this module are located in the Compiler module instead,
for convenience sake, and to not spread the test cases around too much.
@@ -0,0 +1,105 @@
package prog8.optimizer
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.augmentAssignmentOperators
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.isInRegularRAMof
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN expression ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
X RightExpr.
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
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.ReplaceNode(binExpr, augExpr, assignment),
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
)
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also work on
// the expressiontrees that are not used in an assignment statement...
}
val typecast = assignment.value as? TypecastExpression
if(typecast!=null) {
val origExpr = typecast.expression as? BinaryExpression
if(origExpr!=null) {
// it's a typecast of a binary expression.
// we can see if we can unwrap the binary expression by working on a new temporary variable
// (that has the type of the expression), and then finally doing the typecast.
// Once it's outside the typecast, the regular splitting can commence.
val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
val tempVar = when(tempDt) {
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
DataType.WORD -> listOf("prog8_lib", "retval_interm_w")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
else -> throw FatalAstException("invalid dt $tempDt")
}
val assignTempVar = Assignment(
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
typecast.expression, typecast.position
)
return listOf(
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVar, typecast.position), typecast)
)
}
}
return noModifications
}
private fun isSimpleTarget(target: AssignTarget) =
if (target.identifier!=null || target.memoryAddress!=null)
target.isInRegularRAMof(compTarget.machine)
else
false
}
@@ -12,7 +12,7 @@ import prog8.ast.walk.IAstModification
import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
@@ -137,7 +137,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
}
}
if(expr.inferType(program).istype(DataType.FLOAT)) {
if(expr.inferType(program) istype DataType.FLOAT) {
val subExpr: BinaryExpression? = when {
leftconst != null -> expr.right as? BinaryExpression
rightconst != null -> expr.left as? BinaryExpression
@@ -277,7 +277,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
val numval = decl.value as? NumericLiteralValue
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(!valueDt.istype(decl.datatype)) {
if(valueDt isnot decl.datatype) {
val cast = numval.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
@@ -1,6 +1,5 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@@ -8,20 +7,24 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.astprocessing.size
import prog8.compiler.astprocessing.toConstantIntegerRange
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
// Fix up the literal value's type to match that of the vardecl
// (also check range literal operands types before they get expanded into arrays for instance)
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& !declConstValue.inferType(program).istype(decl.datatype)) {
&& declConstValue.inferType(program) isnot decl.datatype) {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if(cast.isValid)
@@ -31,28 +34,6 @@ 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
}
@@ -160,9 +141,9 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
@@ -214,9 +195,9 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
@@ -15,14 +15,25 @@ import kotlin.math.log2
import kotlin.math.pow
/*
todo add more expression optimizations
todo add more peephole expression optimizations
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*(&X) => X
X % 1 => 0
X / 1 => X
X ^ -1 => ~x
X >= 1 => X > 0
X < 1 => X <= 0
X + С1 == C2 => X == C2 - C1
((X + C1) + C2) => (X + (C1 + C2))
((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2))
*/
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
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()
@@ -45,7 +56,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else {
if (typecast.expression.inferType(program).istype(typecast.type)) {
if (typecast.expression.inferType(program) istype typecast.type) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
@@ -289,13 +300,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless lsb() of byte value that was typecasted to word
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
}
@@ -305,7 +316,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
@@ -314,7 +325,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
@@ -2,11 +2,12 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.noErrors()) {
@@ -40,9 +41,10 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
}
internal fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget): Int {
fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget
): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
@@ -52,14 +54,14 @@ internal fun Program.optimizeStatements(errors: IErrorReporter,
return optimizationCount
}
internal fun Program.simplifyExpressions() : Int {
fun Program.simplifyExpressions() : Int {
val opti = ExpressionSimplifier(this)
opti.visit(this)
return opti.applyModifications()
}
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, compTarget)
fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, options, compTarget)
opti.visit(this)
return opti.applyModifications()
}
@@ -1,7 +1,7 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@@ -10,35 +10,17 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.astprocessing.size
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import kotlin.math.floor
internal const val retvarName = "prog8_retval"
internal class StatementOptimizer(private val program: Program,
class StatementOptimizer(private val program: Program,
private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget) : AstWalker() {
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
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)
}
subsThatNeedReturnVariable.clear()
return noModifications
}
private val compTarget: ICompilationTarget
) : AstWalker() {
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with a simple value,
@@ -79,7 +61,7 @@ internal class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}
@@ -117,7 +99,7 @@ internal class StatementOptimizer(private val program: Program,
functionCallStatement.void, pos
)
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
@@ -130,7 +112,7 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
return noModifications
@@ -152,11 +134,11 @@ internal class StatementOptimizer(private val program: Program,
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsNoCodeNorVars)
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope))
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsCodeOrVars) {
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
@@ -184,20 +166,20 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars) {
if(forLoop.body.isEmpty()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if (range.size(compTarget) == 1) {
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
@@ -268,7 +250,7 @@ internal class StatementOptimizer(private val program: Program,
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope))
listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
}
}
return noModifications
@@ -277,14 +259,14 @@ internal class StatementOptimizer(private val program: Program,
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val iter = repeatLoop.iterations
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars) {
if(repeatLoop.body.isEmpty()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
}
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
@@ -296,10 +278,10 @@ internal class StatementOptimizer(private val program: Program,
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope
val scope = jump.parent as IStatementContainer
val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, jump.definingScope))
return listOf(IAstModification.Remove(jump, scope))
return noModifications
}
@@ -332,7 +314,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope))
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
} else if (op2 == "-") {
// A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
@@ -343,7 +325,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope))
IAstModification.InsertAfter(assignment, subConstant, parent as IStatementContainer))
}
}
}
@@ -365,7 +347,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
val targetIDt = assignment.target.inferType(program)
@@ -385,7 +367,7 @@ internal class StatementOptimizer(private val program: Program,
when (bexpr.operator) {
"+" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
@@ -399,7 +381,7 @@ internal class StatementOptimizer(private val program: Program,
}
"-" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
@@ -411,18 +393,18 @@ internal class StatementOptimizer(private val program: Program,
}
}
}
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"<<" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
">>" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
}
@@ -438,14 +420,19 @@ internal class StatementOptimizer(private val program: Program,
val returnDt = subr.returntypes.single()
if (returnDt in IntegerDatatypes) {
// 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 returnVarName = "retval_interm_" + when(returnDt) {
DataType.UBYTE -> "ub"
DataType.BYTE -> "b"
DataType.UWORD -> "uw"
DataType.WORD -> "w"
else -> "<undefined>"
}
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, value, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
)
}
@@ -469,7 +456,7 @@ internal class StatementOptimizer(private val program: Program,
return super.after(returnStmt, parent)
}
private fun hasBreak(scope: INameScope): Boolean {
private fun hasBreak(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{
@@ -9,46 +9,49 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.CallGraph
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.isInRegularRAMof
internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget): AstWalker() {
class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
): AstWalker() {
private val callgraph = CallGraph(program)
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
listOf(IAstModification.Remove(module, module.definingScope))
listOf(IAstModification.Remove(module, parent as IStatementContainer))
else
noModifications
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as INameScope)
reportUnreachable(breakStmt, parent as IStatementContainer)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as INameScope)
reportUnreachable(jump, parent as IStatementContainer)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as INameScope)
reportUnreachable(returnStmt, parent as IStatementContainer)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as INameScope)
reportUnreachable(functionCallStatement, parent as IStatementContainer)
return emptyList()
}
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
when(val next = parent.nextSibling(stmt)) {
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
when(val next = stmt.nextSibling()) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position)
}
@@ -64,11 +67,11 @@ internal class UnusedCodeRemover(private val program: Program,
if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName)
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
}
@@ -80,19 +83,19 @@ internal class UnusedCodeRemover(private val program: Program,
val forceOutput = "force_output" in subroutine.definingBlock.options()
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
if(callgraph.unused(subroutine)) {
if(subroutine.containsNoCodeNorVars) {
if(!subroutine.definingModule.isLibrary)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = mutableListOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
callgraph.calledBy[subroutine]?.let {
for(node in it)
removals.add(IAstModification.Remove(node, node.parent as IStatementContainer))
}
return removals
}
if(!subroutine.definingModule.isLibrary)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope))
}
if(subroutine.containsNoCodeNorVars) {
if(!subroutine.definingModule.isLibrary)
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
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
}
}
@@ -104,9 +107,32 @@ internal class UnusedCodeRemover(private val program: Program,
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)) {
val usages = callgraph.usages(decl)
if (usages.isEmpty()) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, decl.definingScope))
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
} else {
// if all usages are just an assignment to this vardecl
// and it is in regular RAM, then remove the var as well including all assignments
val assignTargets = usages.mapNotNull {
if(it.parent is AssignTarget)
it.parent as AssignTarget
else if(it.parent.parent is AssignTarget)
it.parent.parent as AssignTarget
else null
}.filter {
it.isInRegularRAMof(compTarget.machine)
}
if(assignTargets.size==usages.size) {
// TODO FIX THAT A MEMREAD OF THE VARIABLE ISN'T RECOGNISED AS A USE (imageviewer iff_module.p8 pixptr)
errors.warn("removing unused variable '${decl.name}'", decl.position)
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet()
return assignmentsToRemove.map {
IAstModification.Remove(it.first, it.second)
} + listOf(
IAstModification.Remove(decl, parent as IStatementContainer)
)
}
}
}
}
@@ -122,7 +148,7 @@ internal class UnusedCodeRemover(private val program: Program,
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && compTarget.isInRegularRAM(assign1.target, program)) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAMof(compTarget.machine)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {
+11 -33
View File
@@ -3,31 +3,28 @@ plugins {
id 'application'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.1.0'
id "io.kotest" version "0.3.8"
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':compilerInterfaces')
implementation project(':codeOptimizers')
implementation project(':compilerAst')
implementation project(':codeGeneration')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
}
configurations.all {
@@ -44,22 +41,6 @@ configurations {
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets {
main {
java {
@@ -84,11 +65,6 @@ application {
applicationName = 'p8compile'
}
artifacts {
archives shadowJar
}
shadowJar {
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
@@ -108,3 +84,5 @@ test {
events "skipped", "failed"
}
}
build.finalizedBy installDist, installShadowDist
+5 -8
View File
@@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python">
<configuration sdkName="Python 3.9" />
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@@ -17,10 +12,12 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
<orderEntry type="library" name="hamcrest" level="project" />
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="codeGeneration" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
</component>
</module>
+1
View File
@@ -12,6 +12,7 @@ floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- C64 basic and kernal ROM float constants and functions ----
+51
View File
@@ -699,6 +699,23 @@ cx16 {
&uword r14 = $cf1c
&uword r15 = $cf1e
&word r0s = $cf00
&word r1s = $cf02
&word r2s = $cf04
&word r3s = $cf06
&word r4s = $cf08
&word r5s = $cf0a
&word r6s = $cf0c
&word r7s = $cf0e
&word r8s = $cf10
&word r9s = $cf12
&word r10s = $cf14
&word r11s = $cf16
&word r12s = $cf18
&word r13s = $cf1a
&word r14s = $cf1c
&word r15s = $cf1e
&ubyte r0L = $cf00
&ubyte r1L = $cf02
&ubyte r2L = $cf04
@@ -732,4 +749,38 @@ cx16 {
&ubyte r13H = $cf1b
&ubyte r14H = $cf1d
&ubyte r15H = $cf1f
&byte r0sL = $cf00
&byte r1sL = $cf02
&byte r2sL = $cf04
&byte r3sL = $cf06
&byte r4sL = $cf08
&byte r5sL = $cf0a
&byte r6sL = $cf0c
&byte r7sL = $cf0e
&byte r8sL = $cf10
&byte r9sL = $cf12
&byte r10sL = $cf14
&byte r11sL = $cf16
&byte r12sL = $cf18
&byte r13sL = $cf1a
&byte r14sL = $cf1c
&byte r15sL = $cf1e
&byte r0sH = $cf01
&byte r1sH = $cf03
&byte r2sH = $cf05
&byte r3sH = $cf07
&byte r4sH = $cf09
&byte r5sH = $cf0b
&byte r6sH = $cf0d
&byte r7sH = $cf0f
&byte r8sH = $cf11
&byte r9sH = $cf13
&byte r10sH = $cf15
&byte r11sH = $cf17
&byte r12sH = $cf19
&byte r13sH = $cf1b
&byte r14sH = $cf1d
&byte r15sH = $cf1f
}
+2 -1
View File
@@ -10,10 +10,11 @@ floats {
; ---- 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
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ----
+51 -1
View File
@@ -95,7 +95,7 @@ cx16 {
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
&uword r0 = $0002
&uword r1 = $0004
&uword r2 = $0006
@@ -113,6 +113,23 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
&word r3s = $0008
&word r4s = $000a
&word r5s = $000c
&word r6s = $000e
&word r7s = $0010
&word r8s = $0012
&word r9s = $0014
&word r10s = $0016
&word r11s = $0018
&word r12s = $001a
&word r13s = $001c
&word r14s = $001e
&word r15s = $0020
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
@@ -147,6 +164,39 @@ cx16 {
&ubyte r14H = $001f
&ubyte r15H = $0021
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
&byte r3sL = $0008
&byte r4sL = $000a
&byte r5sL = $000c
&byte r6sL = $000e
&byte r7sL = $0010
&byte r8sL = $0012
&byte r9sL = $0014
&byte r10sL = $0016
&byte r11sL = $0018
&byte r12sL = $001a
&byte r13sL = $001c
&byte r14sL = $001e
&byte r15sL = $0020
&byte r0sH = $0003
&byte r1sH = $0005
&byte r2sH = $0007
&byte r3sH = $0009
&byte r4sH = $000b
&byte r5sH = $000d
&byte r6sH = $000f
&byte r7sH = $0011
&byte r8sH = $0013
&byte r9sH = $0015
&byte r10sH = $0017
&byte r11sH = $0019
&byte r12sH = $001b
&byte r13sH = $001d
&byte r14sH = $001f
&byte r15sH = $0021
; VERA registers
+143
View File
@@ -91,6 +91,13 @@ func_sin8_into_A .proc
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
.pend
func_sinr8_into_A .proc
tay
lda _sinecosR8,y
rts
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
.pend
func_sin8u_into_A .proc
tay
lda _sinecos8u,y
@@ -98,6 +105,13 @@ func_sin8u_into_A .proc
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
.pend
func_sinr8u_into_A .proc
tay
lda _sinecosR8u,y
rts
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
.pend
func_sin8_stack .proc
tay
lda func_sin8_into_A._sinecos8,y
@@ -106,6 +120,14 @@ func_sin8_stack .proc
rts
.pend
func_sinr8_stack .proc
tay
lda func_sinr8_into_A._sinecosR8,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_sin8u_stack .proc
tay
lda func_sin8u_into_A._sinecos8u,y
@@ -114,18 +136,38 @@ func_sin8u_stack .proc
rts
.pend
func_sinr8u_stack .proc
tay
lda func_sinr8u_into_A._sinecosR8u,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_cos8_into_A .proc
tay
lda func_sin8_into_A._sinecos8+64,y
rts
.pend
func_cosr8_into_A .proc
tay
lda func_sinr8_into_A._sinecosR8+45,y
rts
.pend
func_cos8u_into_A .proc
tay
lda func_sin8u_into_A._sinecos8u+64,y
rts
.pend
func_cosr8u_into_A .proc
tay
lda func_sinr8u_into_A._sinecosR8u+45,y
rts
.pend
func_cos8_stack .proc
tay
lda func_sin8_into_A._sinecos8+64,y
@@ -134,6 +176,14 @@ func_cos8_stack .proc
rts
.pend
func_cosr8_stack .proc
tay
lda func_sinr8_into_A._sinecosR8+45,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_cos8u_stack .proc
tay
lda func_sin8u_into_A._sinecos8u+64,y
@@ -142,6 +192,14 @@ func_cos8u_stack .proc
rts
.pend
func_cosr8u_stack .proc
tay
lda func_sinr8u_into_A._sinecosR8u+45,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_sin16_into_AY .proc
tay
lda _sinecos8lo,y
@@ -155,6 +213,19 @@ _sinecos8lo .byte <_
_sinecos8hi .byte >_
.pend
func_sinr16_into_AY .proc
tay
lda _sinecosR8lo,y
pha
lda _sinecosR8hi,y
tay
pla
rts
_ := trunc(32767.0 * sin(range(180+45) * rad(360.0/180.0)))
_sinecosR8lo .byte <_
_sinecosR8hi .byte >_
.pend
func_sin16u_into_AY .proc
tay
lda _sinecos8ulo,y
@@ -168,6 +239,18 @@ _sinecos8ulo .byte <_
_sinecos8uhi .byte >_
.pend
func_sinr16u_into_AY .proc
tay
lda _sinecosR8ulo,y
pha
lda _sinecosR8uhi,y
tay
pla
rts
_ := trunc(32768.0 + 32767.5 * sin(range(180+45) * rad(360.0/180.0)))
_sinecosR8ulo .byte <_
_sinecosR8uhi .byte >_
.pend
func_sin16_stack .proc
tay
@@ -179,6 +262,16 @@ func_sin16_stack .proc
rts
.pend
func_sinr16_stack .proc
tay
lda func_sinr16_into_AY._sinecosR8lo,y
sta P8ESTACK_LO,x
lda func_sinr16_into_AY._sinecosR8hi,y
sta P8ESTACK_HI,x
dex
rts
.pend
func_sin16u_stack .proc
tay
lda func_sin16u_into_AY._sinecos8ulo,y
@@ -189,6 +282,16 @@ func_sin16u_stack .proc
rts
.pend
func_sinr16u_stack .proc
tay
lda func_sinr16u_into_AY._sinecosR8ulo,y
sta P8ESTACK_LO,x
lda func_sinr16u_into_AY._sinecosR8uhi,y
sta P8ESTACK_HI,x
dex
rts
.pend
func_cos16_into_AY .proc
tay
lda func_sin16_into_AY._sinecos8lo+64,y
@@ -199,6 +302,16 @@ func_cos16_into_AY .proc
rts
.pend
func_cosr16_into_AY .proc
tay
lda func_sinr16_into_AY._sinecosR8lo+45,y
pha
lda func_sinr16_into_AY._sinecosR8hi+45,y
tay
pla
rts
.pend
func_cos16u_into_AY .proc
tay
lda func_sin16u_into_AY._sinecos8ulo+64,y
@@ -209,6 +322,16 @@ func_cos16u_into_AY .proc
rts
.pend
func_cosr16u_into_AY .proc
tay
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
pha
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
tay
pla
rts
.pend
func_cos16_stack .proc
tay
lda func_sin16_into_AY._sinecos8lo+64,y
@@ -219,6 +342,16 @@ func_cos16_stack .proc
rts
.pend
func_cosr16_stack .proc
tay
lda func_sinr16_into_AY._sinecosR8lo+45,y
sta P8ESTACK_LO,x
lda func_sinr16_into_AY._sinecosR8hi+45,y
sta P8ESTACK_HI,x
dex
rts
.pend
func_cos16u_stack .proc
tay
lda func_sin16u_into_AY._sinecos8ulo+64,y
@@ -229,6 +362,16 @@ func_cos16u_stack .proc
rts
.pend
func_cosr16u_stack .proc
tay
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
sta P8ESTACK_LO,x
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
sta P8ESTACK_HI,x
dex
rts
.pend
abs_b_stack .proc
; -- push abs(A) on stack (as byte)
jsr abs_b_into_A
+18 -4
View File
@@ -6,10 +6,24 @@ prog8_lib {
%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)
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
; to store intermediary expression results for return values:
; NOTE: these variables are used in the StatementReorderer and StatementOptimizer
uword @zp retval_interm_uw
word @zp retval_interm_w
ubyte @zp retval_interm_ub
byte @zp retval_interm_b
word retval_interm_w2
byte retval_interm_b2
; prog8 "hooks" to be able to access the temporary scratch variables
; YOU SHOULD NOT USE THESE IN USER CODE - THESE ARE MEANT FOR INTERNAL COMPILER USE
; NOTE: the assembly code generator will match these names and not generate
; new variables/memdefs for them, rather, they'll point to the scratch variables directly.
&ubyte P8ZP_SCRATCH_REG = $ff
&byte P8ZP_SCRATCH_B1 = $ff
&uword P8ZP_SCRATCH_W1 = $ff
&word P8ZP_SCRATCH_W2 = $ff
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{
+1 -1
View File
@@ -1 +1 @@
7.1-beta
7.3
+15 -10
View File
@@ -6,7 +6,6 @@ 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
@@ -36,10 +35,12 @@ private fun compileMain(args: Array<String>): Boolean {
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
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 quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
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 sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, 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 {
@@ -61,9 +62,9 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
val libdirs = libDirs.toMutableList()
if(libdirs.firstOrNull()!=".")
libdirs.add(0, ".")
val srcdirs = sourceDirs.toMutableList()
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if(watchMode==true) {
val watchservice = FileSystems.getDefault().newWatchService()
@@ -74,7 +75,10 @@ private fun compileMain(args: Array<String>): Boolean {
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
val compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
results.add(compilationResult)
}
@@ -85,7 +89,7 @@ private fun compileMain(args: Array<String>): Boolean {
for (importedFile in allImportedFiles) {
print(" ")
println(importedFile)
val watchDir = importedFile.parent ?: Path.of(".")
val watchDir = importedFile.parent ?: Path.of("")
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
@@ -111,11 +115,12 @@ private fun compileMain(args: Array<String>): Boolean {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
if(!compilationResult.success)
return false
} catch (x: ParsingFailedError) {
return false
} catch (x: AstException) {
return false
}
@@ -1,3 +0,0 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)
@@ -1,6 +1,7 @@
package prog8.compiler
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@@ -9,27 +10,19 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.*
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
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.
// 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))
decl.value = null
else {
decl.value = decl.zeroElementValue()
}
}
}
return noModifications
}
@@ -39,8 +32,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& compTarget.isInRegularRAM(assignment.target, program)) {
&& assignment.target.isInRegularRAMof(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
@@ -49,16 +46,20 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
if(binExpr.operator in associativeOperators) {
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
val assignRight = Assignment(assignment.target, right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope),
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
} else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
val assignLeft = Assignment(assignment.target, left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope),
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
@@ -73,33 +74,35 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
addedIfConditionVars.clear()
if(!subroutine.isAsmSubroutine) {
// change 'str' parameters into 'uword' (just treat it as an address)
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
val parameterChanges = stringParams.map {
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
IAstModification.ReplaceNode(it, uwordParam, subroutine)
}
val stringParamNames = stringParams.map { it.name }.toSet()
val varsChanges = subroutine.statements
.filterIsInstance<VarDecl>()
.filter { it.autogeneratedDontRemove && it.name in stringParamNames }
.map {
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, it.position)
IAstModification.ReplaceNode(it, newvar, subroutine)
}
return parameterChanges + varsChanges
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
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 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))
}
return replacements + movements
}
return noModifications
}
@@ -119,13 +122,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& !subroutine.inline
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
if (subroutine.asmAddress == null && !subroutine.inline) {
if(subroutine.statements.isEmpty() ||
(subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
&& subroutine.statements.last() !is Subroutine)) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
@@ -143,9 +146,10 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions)
// see if we can remove redundant 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.
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
@@ -154,22 +158,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
}
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
// that the types of assignment values and their target are the same,
// and that the types of both operands of a binaryexpression node are the same.
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
if(typecast.expression is IdentifierReference) {
return listOf(IAstModification.ReplaceNode(
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
typecast.expression,
parent
))
))
} else {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
parent
))
}
} else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(
typecast,
@@ -185,7 +190,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return noModifications
}
@Suppress("DuplicatedCode")
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
val prefixExpr = ifStatement.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// if not x -> if x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
val binExpr = ifStatement.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0
@@ -193,71 +206,150 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
(binExpr.left as? NumericLiteralValue)?.number==0 &&
if((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")
throw FatalAstException("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
// simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
val simplify = simplifyConditionalExpression(binExpr)
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifStatement, simplify.rightVarAssignment, parent as IStatementContainer)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifStatement, simplify.leftVarAssignment, parent as IStatementContainer)
}
return modifications
}
// 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)
// }
// }
private class CondExprSimplificationResult(
val leftVarAssignment: Assignment?,
val leftOperandReplacement: Expression?,
val rightVarAssignment: Assignment?,
val rightOperandReplacement: Expression?
)
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall
if(separateLeftExpr) {
val dt = expr.left.inferType(program)
val name = when {
// TODO assume (hope) cx16.r9 isn't used for anything else...
dt.istype(DataType.UBYTE) -> listOf("cx16","r9L")
dt.istype(DataType.BYTE) -> listOf("cx16","r9sL")
dt.istype(DataType.UWORD) -> listOf("cx16","r9")
dt.istype(DataType.WORD) -> listOf("cx16","r9s")
else -> throw AssemblyError("invalid dt")
}
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.left,
expr.position
)
}
if(separateRightExpr) {
val dt = expr.right.inferType(program)
val name = when {
dt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub")
dt.istype(DataType.UWORD) -> listOf("prog8_lib","retval_interm_uw")
dt.istype(DataType.BYTE) -> listOf("prog8_lib","retval_interm_b2")
dt.istype(DataType.WORD) -> listOf("prog8_lib","retval_interm_w2")
else -> throw AssemblyError("invalid dt")
}
rightOperandReplacement = IdentifierReference(name, expr.position)
rightAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.right,
expr.position
)
}
return CondExprSimplificationResult(
leftAssignment, leftOperandReplacement,
rightAssignment, rightOperandReplacement
)
}
@Suppress("DuplicatedCode")
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = untilLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// until not x -> until x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// until x -> until x!=0, until x+5 -> until x+5 != 0
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
return noModifications
if((binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
throw FatalAstException("0==X should have been swapped to if X==0")
// simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
val simplify = simplifyConditionalExpression(binExpr)
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertLast(simplify.rightVarAssignment, untilLoop.body)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertLast(simplify.leftVarAssignment, untilLoop.body)
}
return modifications
}
@Suppress("DuplicatedCode")
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = whileLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// while not x -> while x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
val binExpr = whileLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// while x -> while x!=0, while x+5 -> while x+5 != 0
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
if((binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
throw FatalAstException("0==X should have been swapped to if X==0")
// TODO simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
// NOTE: this is nasty for a while-statement as the condition occurs at the top of the loop
// so the expression needs to be evaluated also before the loop is entered...
// but I don't want to duplicate the expression.
// val simplify = simplifyConditionalExpression(binExpr)
return noModifications
}
@@ -271,13 +363,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes)
return noModifications
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
} else {
if(dt2 in WordDatatypes)
return noModifications
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
}
}
return noModifications
@@ -341,12 +435,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
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"
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "retval_interm_ub" else "retval_interm_b"
// 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", register), expr.indexer.position), null, null, expr.indexer.position)
val target = AssignTarget(IdentifierReference(listOf("prog8_lib", 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.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
return modifications
}
+122 -134
View File
@@ -1,9 +1,8 @@
package prog8.compiler
import com.github.michaelbull.result.*
import prog8.ast.AstToSourceCode
import prog8.ast.AstToSourceTextConverter
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.Position
@@ -11,71 +10,41 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.compiler.astprocessing.*
import prog8.compiler.functions.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.asmGeneratorFor
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import prog8.optimizer.*
import prog8.parser.ParsingFailedError
import prog8.parser.SourceCode.Companion.libraryFilePrefix
import java.io.File
import prog8.parser.ParseError
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension
import kotlin.system.measureTimeMillis
enum class OutputType {
RAW,
PRG
}
enum class LauncherType {
BASIC,
NONE
}
enum class ZeropageType {
BASICSAFE,
FLOATSAFE,
KERNALSAFE,
FULL,
DONTUSE
}
data class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false
var optimize = false
}
class CompilerException(message: String?) : Exception(message)
class CompilationResult(val success: Boolean,
val programAst: Program,
val program: Program,
val programName: String,
val compTarget: ICompilationTarget,
val importedFiles: List<Path>)
// TODO refactor the gigantic list of parameters
fun compileProgram(filepath: Path,
optimize: Boolean,
optimizeFloatExpressions: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
quietAssembler: Boolean,
compilationTarget: String,
libdirs: List<String>,
outputDir: Path): CompilationResult {
sourceDirs: List<String>,
outputDir: Path,
errors: IErrorReporter = ErrorReporter()): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var program: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
val optimizeFloatExpr = if(optimize) optimizeFloatExpressions else false
val compTarget =
when(compilationTarget) {
@@ -87,25 +56,35 @@ fun compileProgram(filepath: Path,
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, libdirs)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
with(compilationOptions) {
this.slowCodegenWarnings = slowCodegenWarnings
this.optimize = optimize
this.optimizeFloatExpressions = optimizeFloatExpr
}
program = programresult
importedFiles = imported
processAst(programAst, errors, compilationOptions)
processAst(program, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions)
postprocessAst(programAst, errors, compilationOptions)
optimizeAst(
program,
compilationOptions,
errors,
BuiltinFunctionsFacade(BuiltinFunctions),
compTarget
)
postprocessAst(program, errors, compilationOptions)
// printAst(programAst)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// printAst(program)
if(writeAssembly) {
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
when(result) {
if (writeAssembly) {
val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
when (result) {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
System.err.println(result.error)
return CompilationResult(false, programAst, programName, compTarget, importedFiles)
return CompilationResult(false, program, programName, compTarget, importedFiles)
}
}
}
@@ -113,11 +92,20 @@ fun compileProgram(filepath: Path,
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
} catch (px: ParsingFailedError) {
return CompilationResult(true, program, programName, compTarget, importedFiles)
} catch (px: ParseError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
System.err.print("\u001b[0m") // reset
} catch (ac: AbortCompilation) {
if(!ac.message.isNullOrEmpty()) {
System.err.print("\u001b[91m") // bright red
System.err.println(ac.message)
System.err.print("\u001b[0m") // reset
}
} catch (nsf: NoSuchFileException) {
System.err.print("\u001b[91m") // bright red
System.err.println("File not found: ${nsf.message}")
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
@@ -137,7 +125,7 @@ fun compileProgram(filepath: Path,
throw x
}
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
}
@@ -147,13 +135,13 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null) {
return try {
exprfunc(args, position, program, memsizer)
exprfunc(args, position, program)
} catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
null
@@ -172,27 +160,23 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
}
fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
errors: IErrorReporter,
compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${compTarget.name}. Parsing...")
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
bf.program = programAst
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = program
val importer = ModuleImporter(programAst, compTarget.name, errors, libdirs)
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
val importedModuleResult = importer.importModule(filepath)
importedModuleResult.onFailure { throw it }
errors.report()
val importedFiles = programAst.modules
.mapNotNull { it.source }
.filter { !it.isFromResources } // TODO: parseImports/importedFiles - maybe rather `source.isFromFilesystem`?
.map { Path(it.pathString()) }
val compilerOptions = determineCompilationOptions(programAst, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
val importedFiles = program.modules.map { it.source }
.filter { it.isFromFilesystem }
.map { Path(it.origin) }
val compilerOptions = determineCompilationOptions(program, compTarget)
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
importer.importLibraryModule(lib)
@@ -200,17 +184,21 @@ fun parseImports(filepath: Path,
// always import prog8_lib and math
importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib")
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
return Triple(program, compilerOptions, importedFiles)
}
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
val launcherDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
val toplevelModule = program.toplevelModule
val outputDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
val launcherDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
val zpoption: String? = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
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()
@@ -232,7 +220,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
val zpReserved = toplevelModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
@@ -264,42 +252,44 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
)
}
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions)
program.preprocessAst()
program.checkIdentifiers(errors, compilerOptions)
errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.constantFold(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
program.reorderStatements(errors)
errors.report()
programAst.reorderStatements(errors)
program.addTypecasts(errors)
errors.report()
programAst.addTypecasts(errors)
program.variousCleanups(program, errors)
errors.report()
programAst.variousCleanups(programAst, errors)
program.checkValid(errors, compilerOptions)
errors.report()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions)
program.checkIdentifiers(errors, compilerOptions)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree
println("Optimizing...")
val remover = UnusedCodeRemover(programAst, errors, compTarget)
remover.visit(programAst)
val remover = UnusedCodeRemover(program, errors, compTarget)
remover.visit(program)
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)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
val optsDone1 = program.simplifyExpressions()
val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
program.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
@@ -308,17 +298,17 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
errors.report()
}
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.addTypecasts(errors)
errors.report()
programAst.variousCleanups(programAst, errors)
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
program.variousCleanups(program, errors)
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst)
val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
program.verifyFunctionArgTypes()
program.moveMainAndStartToFirst()
}
private sealed class WriteAssemblyResult {
@@ -326,58 +316,56 @@ private sealed class WriteAssemblyResult {
class Fail(val error: String): WriteAssemblyResult()
}
private fun writeAssembly(programAst: Program,
private fun writeAssembly(program: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): WriteAssemblyResult {
quietAssembler: Boolean,
compilerOptions: CompilationOptions
): WriteAssemblyResult {
// asm generation directly from the Ast
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report()
// printAst(programAst)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printAst(program)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst,
program,
errors,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
errors.report()
return if(assembly.valid && errors.noErrors()) {
val assemblerReturnStatus = assembly.assemble(compilerOptions)
val assemblerReturnStatus = assembly.assemble(quietAssembler, compilerOptions)
if(assemblerReturnStatus!=0)
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
else {
errors.report()
WriteAssemblyResult.Ok(assembly.name)
}
} else {
errors.report()
WriteAssemblyResult.Fail("compiler failed with errors")
}
}
fun printAst(programAst: Program) {
fun printAst(program: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
val printer = AstToSourceTextConverter(::print, program)
printer.visit(program)
println()
}
internal fun loadAsmIncludeFile(filename: String, sourcePath: Path): Result<String, NoSuchFileException> {
return if (filename.startsWith(libraryFilePrefix)) {
return runCatching {
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
stream!!.bufferedReader().use { r -> r.readText() }
}.mapError { NoSuchFileException(File(filename)) }
} else {
// first try in the isSameAs folder as where the containing file was imported from
val sib = sourcePath.resolveSibling(filename)
if (sib.toFile().isFile)
Ok(sib.toFile().readText())
else
Ok(File(filename).readText())
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
// at the moment we only have one code generation backend (for 6502 and 65c02)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}
@@ -1,16 +1,7 @@
package prog8.compiler
import prog8.ast.base.Position
import prog8.parser.ParsingFailedError
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun noErrors(): Boolean
fun report()
}
import prog8.compilerinterface.IErrorReporter
internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity {
@@ -33,24 +24,29 @@ internal class ErrorReporter: IErrorReporter {
var numErrors = 0
var numWarnings = 0
messages.forEach {
val printer = when(it.severity) {
MessageSeverity.WARNING -> System.out
MessageSeverity.ERROR -> System.err
}
when(it.severity) {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
MessageSeverity.ERROR -> printer.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> printer.print("\u001b[93m") // bright yellow
}
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
System.err.println(msg)
printer.println(msg)
alreadyReportedMessages.add(msg)
when(it.severity) {
MessageSeverity.WARNING -> numWarnings++
MessageSeverity.ERROR -> numErrors++
}
}
System.err.print("\u001b[0m") // reset color
printer.print("\u001b[0m") // reset color
}
System.out.flush()
System.err.flush()
messages.clear()
if(numErrors>0)
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
finalizeNumErrors(numErrors, numWarnings)
}
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
+64 -50
View File
@@ -1,16 +1,16 @@
package prog8.compiler
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import prog8.compilerinterface.IErrorReporter
import prog8.parser.Prog8Parser
import prog8.parser.SourceCode
import java.io.File
import java.nio.file.Path
import kotlin.io.path.*
@@ -18,13 +18,13 @@ import kotlin.io.path.*
class ModuleImporter(private val program: Program,
private val compilationTargetName: String,
val errors: IErrorReporter,
libdirs: List<String>) {
sourceDirs: List<String>) {
private val libpaths: List<Path> = libdirs.map { Path(it) }
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) }
fun importModule(filePath: Path): Result<Module, NoSuchFileException> {
val currentDir = Path("").absolute()
val searchIn = listOf(currentDir) + libpaths
val searchIn = listOf(currentDir) + sourcePaths
val candidates = searchIn
.map { it.absolute().div(filePath).normalize().absolute() }
.filter { it.exists() }
@@ -34,15 +34,19 @@ class ModuleImporter(private val program: Program,
val srcPath = when (candidates.size) {
0 -> return Err(NoSuchFileException(
file = filePath.normalize().toFile(),
reason = "searched in $searchIn"))
reason = "Searched in $searchIn"))
1 -> candidates.first()
else -> candidates.first() // TODO: report error if more than 1 candidate?
else -> candidates.first() // when more candiates, pick the one from the first location
}
val logMsg = "importing '${filePath.nameWithoutExtension}' (from file $srcPath)"
println(logMsg)
return Ok(importModule(SourceCode.fromPath(srcPath)))
val module = importModule(SourceCode.File(srcPath))
return if(module==null)
Err(NoSuchFileException(srcPath.toFile()))
else
Ok(module)
}
fun importLibraryModule(name: String): Module? {
@@ -52,94 +56,104 @@ class ModuleImporter(private val program: Program,
return executeImportDirective(import, null)
}
//private fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
private fun importModule(src: SourceCode) : Module {
private fun importModule(src: SourceCode) : Module? {
val moduleAst = Prog8Parser.parseModule(src)
program.addModule(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
try {
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> i to it }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(it.second as Directive, moduleAst) }
moduleAst.statements = lines
return moduleAst
moduleAst.statements = lines
return moduleAst
} catch (x: Exception) {
// in case of error, make sure the module we're importing is no longer in the Ast
program.removeModule(moduleAst)
throw x
}
}
private fun executeImportDirective(import: Directive, importingModule: Module?): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
if(import.directive!="%import" || import.args.size!=1)
throw SyntaxError("invalid import directive", import.position)
if(!import.args[0].str.isNullOrEmpty() || import.args[0].name==null)
throw SyntaxError("%import requires unquoted module name", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if (existing!=null)
return null // TODO: why return null instead of Module instance?
return existing
var srcCode = tryGetModuleFromResource("$moduleName.p8", compilationTargetName)
// try internal library first
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
val importedModule =
if (srcCode != null) {
println("importing '$moduleName' (from internal ${srcCode.origin})")
importModule(srcCode)
} else {
srcCode = tryGetModuleFromFile(moduleName, importingModule)
if (srcCode == null) {
errors.err("imported file not found: $moduleName.p8", import.position)
return null
//throw NoSuchFileException(File("$moduleName.p8"))
moduleResourceSrc.fold(
success = {
println("importing '$moduleName' (from internal ${it.origin})")
importModule(it)
},
failure = {
// try filesystem next
val moduleSrc = getModuleFromFile(moduleName, importingModule)
moduleSrc.fold(
success = {
println("importing '$moduleName' (from file ${it.origin})")
importModule(it)
},
failure = {
errors.err("no module found with name $moduleName. Searched in: $sourcePaths (and internal libraries)", import.position)
return null
}
)
}
importModule(srcCode)
}
)
removeDirectivesFromImportedModule(importedModule)
if(importedModule!=null)
removeDirectivesFromImportedModule(importedModule)
return importedModule
}
private fun removeDirectivesFromImportedModule(importedModule: Module) {
// Most global directives don't apply for imported modules, so remove them
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%target")
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
var directives = importedModule.statements.filterIsInstance<Directive>()
importedModule.statements.removeAll(directives)
directives = directives.filter{ it.directive !in moduleLevelDirectives }
importedModule.statements.addAll(0, directives)
}
private fun tryGetModuleFromResource(name: String, compilationTargetName: String): SourceCode? {
// try target speficic first
try {
return SourceCode.fromResources("/prog8lib/$compilationTargetName/$name")
} catch (e: FileSystemException) {
}
try {
return SourceCode.fromResources("/prog8lib/$name")
} catch (e: FileSystemException) {
}
return null
private fun getModuleFromResource(name: String, compilationTargetName: String): Result<SourceCode, NoSuchFileException> {
val result =
runCatching { SourceCode.Resource("/prog8lib/$compilationTargetName/$name") }
.orElse { runCatching { SourceCode.Resource("/prog8lib/$name") } }
return result.mapError { NoSuchFileException(File(name)) }
}
private fun tryGetModuleFromFile(name: String, importingModule: Module?): SourceCode? {
private fun getModuleFromFile(name: String, importingModule: Module?): Result<SourceCode, NoSuchFileException> {
val fileName = "$name.p8"
val locations =
if (importingModule == null) { // <=> imported from library module
libpaths
sourcePaths
} else {
libpaths.drop(1) + // TODO: why drop the first?
// FIXME: won't work until Prog8Parser is fixed s.t. it fully initialzes the modules it returns
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
sourcePaths.drop(dropCurDir) +
listOf(Path(importingModule.position.file).parent ?: Path("")) +
listOf(Path(".", "prog8lib"))
}
locations.forEach {
try {
return SourceCode.fromPath(it.resolve(fileName))
return Ok(SourceCode.File(it.resolve(fileName)))
} catch (e: NoSuchFileException) {
}
}
//throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)")
return null
return Err(NoSuchFileException(File("name")))
}
}
@@ -1,37 +1,32 @@
package prog8.compiler.astprocessing
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
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.ICompilationTarget
import prog8.compilerinterface.*
import java.io.CharConversionException
import java.io.File
import java.util.*
import kotlin.io.path.*
import kotlin.io.path.Path
internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
private val compilerOptions: CompilationOptions
) : IAstVisitor {
override fun visit(program: Program) {
assert(program === this.program)
require(program === this.program)
// there must be a single 'main' block with a 'start' subroutine for the program entry point.
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size>1)
errors.err("more than one 'main' block", mainBlocks[0].position)
if(mainBlocks.isEmpty())
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: Position.DUMMY)
for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScope("start") as? Subroutine
@@ -44,8 +39,8 @@ 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)
if (compilerOptions.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
errors.err("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'", program.toplevelModule.position)
}
super.visit(program)
@@ -152,11 +147,11 @@ internal class AstChecker(private val program: Program,
val to = range.to as? NumericLiteralValue
if(from != null)
checkValueTypeAndRange(loopvar.datatype, from)
else if(!range.from.inferType(program).istype(loopvar.datatype))
else if(range.from.inferType(program) isnot loopvar.datatype)
errors.err("range start value is incompatible with loop variable type", range.position)
if(to != null)
checkValueTypeAndRange(loopvar.datatype, to)
else if(!range.to.inferType(program).istype(loopvar.datatype))
else if(range.to.inferType(program) isnot loopvar.datatype)
errors.err("range end value is incompatible with loop variable type", range.position)
}
}
@@ -166,7 +161,6 @@ internal class AstChecker(private val program: Program,
super.visit(forLoop)
}
override fun visit(jump: Jump) {
val ident = jump.identifier
if(ident!=null) {
@@ -196,8 +190,12 @@ internal class AstChecker(private val program: Program,
is Label,
is VarDecl,
is InlineAssembly,
is INameScope,
is IStatementContainer,
is NopStatement -> true
is Assignment -> {
val target = statement.target.identifier!!.targetStatement(program)
target === statement.previousSibling() // an initializer assignment is okay
}
else -> false
}
if (!ok) {
@@ -217,7 +215,7 @@ internal class AstChecker(private val program: Program,
super.visit(label)
}
private fun hasReturnOrJump(scope: INameScope): Boolean {
private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{
var count=0
@@ -245,8 +243,8 @@ internal class AstChecker(private val program: Program,
if(subroutine.name in BuiltinFunctions)
err("cannot redefine a built-in function")
if(subroutine.parameters.size>16)
err("subroutines are limited to 16 parameters")
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine)
errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size)
@@ -283,14 +281,14 @@ internal class AstChecker(private val program: Program,
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
err("number of return registers is not the isSameAs as number of return values")
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
if(param.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if(param.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
err("parameter '${param.first.name}' should be (u)byte")
}
else if(param.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
err("parameter '${param.first.name}' should be (u)word/address")
err("parameter '${param.first.name}' should be (u)word (an address) or str")
}
else if(param.second.statusflag!=null) {
if (param.first.type != DataType.UBYTE)
@@ -298,11 +296,11 @@ internal class AstChecker(private val program: Program,
}
}
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
if(pair.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if(pair.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
err("return value #${index + 1} should be (u)byte")
}
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
else if(pair.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
err("return value #${index + 1} should be (u)word/address")
@@ -382,10 +380,12 @@ internal class AstChecker(private val program: Program,
err("can only use Carry as status flag parameter")
} else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Non-string Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
for(p in subroutine.parameters) {
if(p.type in PassByReferenceDatatypes && p.type != DataType.STR) {
err("Non-string pass-by-reference types cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
}
}
}
}
@@ -428,12 +428,12 @@ internal class AstChecker(private val program: Program,
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
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("type of value doesn't match target", assignment.value.position)
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
}
if(assignment.value is TypecastExpression) {
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT))
if(assignment.isAugmentable && targetDt istype DataType.FLOAT)
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
}
@@ -453,7 +453,7 @@ internal class AstChecker(private val program: Program,
val targetIdentifier = assignTarget.identifier
if (targetIdentifier != null) {
val targetName = targetIdentifier.nameInSource
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
when (val targetSymbol = assignment.definingScope.lookup(targetName)) {
null -> {
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
return
@@ -505,7 +505,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(decl: VarDecl) {
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
fun err(msg: String) = errors.err(msg, decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
@@ -518,7 +518,7 @@ internal class AstChecker(private val program: Program,
}
// FLOATS enabled?
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
err("floating point used, but that is not enabled via options")
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
@@ -586,18 +586,18 @@ internal class AstChecker(private val program: Program,
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)
err("memory address must be valid integer 0..\$ffff")
}
} else {
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?")
}
}
}
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR) {
if (!declValue.inferType(program).istype(decl.datatype)) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
if (declValue.inferType(program) isnot decl.datatype) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})")
}
}
@@ -626,10 +626,13 @@ internal class AstChecker(private val program: Program,
}
}
// string assignment is not supported in a vard
if(decl.datatype==DataType.STR) {
if(decl.value==null)
err("string var must be initialized with a string literal")
if(decl.value==null) {
// complain about uninitialized str, but only if it's a regular variable
val parameter = (decl.parent as? Subroutine)?.parameters?.singleOrNull{ it.name==decl.name }
if(parameter==null)
err("string var must be initialized with a string literal")
}
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
err("string var can only be initialized with a string literal")
}
@@ -714,7 +717,7 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
err("invalid option directive argument(s)")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
@@ -726,25 +729,21 @@ internal class AstChecker(private val program: Program,
if (File(filename).isFile)
return
var definingModule = directive.parent // TODO: why not just use directive.definingModule() here?
while (definingModule !is Module)
definingModule = definingModule.parent
if (definingModule.isLibrary)
val definingModule = directive.definingModule
if (definingModule.isLibrary || !definingModule.source.isFromFilesystem)
return
val s = definingModule.source?.pathString()
if (s != null) {
val sourceFileCandidate = Path(s).resolveSibling(filename).toFile()
if (sourceFileCandidate.isFile)
return
}
errors.err("included file not found: $filename", directive.position)
val s = definingModule.source.origin
val sourceFileCandidate = Path(s).resolveSibling(filename).toFile()
if (sourceFileCandidate.isFile)
return
else
errors.err("included file not found: $filename", directive.position)
}
override fun visit(array: ArrayLiteralValue) {
if(array.type.isKnown) {
if (!compilerOptions.floats && array.type.getOr(DataType.UNDEFINED) in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
if (!compilerOptions.floats && array.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.err("floating point used, but that is not enabled via options", array.position)
}
val arrayspec = ArrayIndex.forArray(array)
@@ -772,7 +771,7 @@ internal class AstChecker(private val program: Program,
override fun visit(char: CharLiteral) {
try { // just *try* if it can be encoded, don't actually do it
compTarget.encodeString(char.value.toString(), char.altEncoding)
compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding)
} catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode character", char.position)
}
@@ -784,7 +783,7 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRangeString(DataType.STR, string)
try { // just *try* if it can be encoded, don't actually do it
compTarget.encodeString(string.value, string.altEncoding)
compilerOptions.compTarget.encodeString(string.value, string.altEncoding)
} catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode string", string.position)
}
@@ -793,11 +792,10 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: PrefixExpression) {
val idt = expr.inferType(program)
if(!idt.isKnown)
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
if(dt==DataType.UNDEFINED)
return // any error should be reported elsewhere
val dt = idt.getOr(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)
@@ -919,7 +917,7 @@ internal class AstChecker(private val program: Program,
// warn about sgn(unsigned) this is likely a mistake
if(functionCall.target.nameInSource.last()=="sgn") {
val sgnArgType = functionCall.args.first().inferType(program)
if(sgnArgType.istype(DataType.UBYTE) || sgnArgType.istype(DataType.UWORD))
if(sgnArgType istype DataType.UBYTE || sgnArgType istype DataType.UWORD)
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
}
@@ -988,12 +986,12 @@ internal class AstChecker(private val program: Program,
if(functionCallStatement.target.nameInSource.last() == "sort") {
// sort is not supported on float arrays
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
if(idref!=null && idref.inferType(program).istype(DataType.ARRAY_F)) {
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
}
}
if(functionCallStatement.target.nameInSource.last() in setOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
if(functionCallStatement.target.nameInSource.last() in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
// in-place modification, can't be done on literals
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
@@ -1071,7 +1069,7 @@ internal class AstChecker(private val program: Program,
override fun visit(postIncrDecr: PostIncrDecr) {
if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = program.namespace.lookup(targetName, postIncrDecr)
val target = postIncrDecr.definingScope.lookup(targetName)
if(target==null) {
val symbol = postIncrDecr.target.identifier!!
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
@@ -1122,7 +1120,7 @@ internal class AstChecker(private val program: Program,
// check index value 0..255
val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program)
if(!dtxNum.istype(DataType.UBYTE) && !dtxNum.istype(DataType.BYTE))
if(dtxNum isnot DataType.UBYTE && dtxNum isnot DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
super.visit(arrayIndexedExpression)
@@ -1160,7 +1158,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.getOr(DataType.UNDEFINED) -> errors.err("choice value datatype differs from condition value", whenChoice.position)
conditionType isnot constvalue.type -> errors.err("choice value datatype differs from condition value", whenChoice.position)
}
}
} else {
@@ -1204,7 +1202,7 @@ internal class AstChecker(private val program: Program,
DataType.STR -> return err("string value expected")
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type.istype(targetDt)) {
if(value.type istype targetDt) {
if(!checkArrayValues(value, targetDt))
return false
val arraySpecSize = arrayspec.constIndex()
@@ -1223,7 +1221,7 @@ internal class AstChecker(private val program: Program,
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
// value may be either a single word, or a word arraysize, or a range
if(value.type.istype(targetDt)) {
if(value.type istype targetDt) {
if(!checkArrayValues(value, targetDt))
return false
val arraySpecSize = arrayspec.constIndex()
@@ -1242,7 +1240,7 @@ internal class AstChecker(private val program: Program,
}
DataType.ARRAY_F -> {
// value may be either a single float, or a float arraysize
if(value.type.istype(targetDt)) {
if(value.type istype targetDt) {
if(!checkArrayValues(value, targetDt))
return false
val arraySize = value.value.size
@@ -1258,7 +1256,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE })
if(doubles.any { it < compilerOptions.compTarget.machine.FLOAT_MAX_NEGATIVE || it > compilerOptions.compTarget.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow")
return true
}
@@ -3,75 +3,25 @@ package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RangeExpr
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.Directive
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.target.ICompilationTarget
import kotlin.math.abs
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding
fun RangeExpr.size(encoding: IStringEncoding): Int? {
val fromLv = (from as? NumericLiteralValue)
val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange(encoding)?.count()
}
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
val fromVal: Int
val toVal: Int
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over character values
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
// integer range
fromVal = fromLv.number.toInt()
toVal = toLv.number.toInt()
}
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return makeRange(fromVal, toVal, stepVal)
}
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors, compTarget)
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
val checker = AstChecker(this, errors, compilerOptions)
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors)
fixer.visit(this)
while(errors.noErrors() && fixer.applyModifications()>0) {
fixer.visit(this)
@@ -89,7 +39,7 @@ internal fun Program.reorderStatements(errors: IErrorReporter) {
}
}
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, enc: IStringEncoding) {
internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
val walker = object : AstWalker() {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.ReplaceNode(
@@ -114,9 +64,17 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.preprocessAst() {
val transforms = AstPreprocessor()
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)
mods = transforms.applyModifications()
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
val checker2 = AstIdentifiersChecker(errors, options.compTarget)
checker2.visit(this)
if(errors.noErrors()) {
@@ -127,26 +85,6 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
lit2decl.visit(this)
lit2decl.applyModifications()
}
// Check if each module has a unique name.
// If not report those that haven't.
// TODO: move check for unique module names to earlier stage and/or to unit tests
val namesToModules = mapOf<String, MutableList<prog8.ast.Module>>().toMutableMap()
for (m in modules) {
val others = namesToModules[m.name]
if (others == null) {
namesToModules[m.name] = listOf(m).toMutableList()
} else {
others.add(m)
}
}
val nonUniqueNames = namesToModules.keys
.map { Pair(it, namesToModules[it]!!.size) }
.filter { it.second > 1 }
.map { "\"${it.first}\" (x${it.second})"}
if (nonUniqueNames.isNotEmpty()) {
throw FatalAstException("modules must have unique names; of the ttl ${modules.size} these have not: $nonUniqueNames")
}
}
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
@@ -156,7 +94,6 @@ internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
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,
@@ -186,3 +123,11 @@ internal fun Program.moveMainAndStartToFirst() {
modules[0].statements.add(0, directive)
}
}
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
val vardecl = this.targetVarDecl(program)
if(vardecl!=null && vardecl.autogeneratedDontRemove) {
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
}
return false
}
@@ -1,15 +1,14 @@
package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
internal class AstIdentifiersChecker(private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) {
@@ -42,7 +41,7 @@ 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)
val existing = program.namespace.lookup(listOf(decl.name), decl)
val existing = decl.definingScope.lookup(listOf(decl.name))
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
@@ -65,22 +64,19 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val existing = program.namespace.lookup(listOf(subroutine.name), subroutine)
val existing = subroutine.lookup(listOf(subroutine.name))
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. Blocks are okay.
// check that there are no local symbols (variables, labels, subs) that redefine the subroutine's parameters.
val symbolsInSub = subroutine.allDefinedSymbols
val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet()
val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) {
val labelOrVar = subroutine.getLabelOrVariable(name)
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
nameError(name, labelOrVar.position, subroutine)
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, subroutine.position, sub)
val symbol = subroutine.searchSymbol(name)
if(symbol!=null && symbol.position != subroutine.position)
nameError(name, symbol.position, subroutine)
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
@@ -0,0 +1,48 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AstPreprocessor : AstWalker() {
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
// move vardecls in Anonymous scope up to the containing subroutine
// and add initialization assignment in its place if needed
val vars = scope.statements.filterIsInstance<VarDecl>()
val parentscope = scope.definingScope
if(vars.any() && parentscope !== parent) {
val movements = mutableListOf<IAstModification>()
val replacements = mutableListOf<IAstModification>()
for(decl in vars) {
if(decl.type != VarDeclType.VAR) {
movements.add(IAstModification.InsertFirst(decl, parentscope))
replacements.add(IAstModification.Remove(decl, scope))
} else {
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, parentscope))
}
}
return movements + replacements
}
return noModifications
}
}
@@ -67,7 +67,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
}
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
@@ -99,12 +99,12 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
internal fun replacePointerVarIndexWithMemreadOrMemwrite(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)
val add = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", 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)
@@ -1,5 +1,6 @@
package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
@@ -29,7 +30,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
if(arrayDt isnot vardecl.datatype) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast !== array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
@@ -40,11 +41,13 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
if(litval2!=null) {
if(array.parent !is IStatementContainer)
return noModifications
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope)
IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer)
)
}
}
@@ -1,17 +1,13 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
@@ -38,15 +34,61 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
module.statements.add(0, mainBlock)
}
reorderVardeclsAndDirectives(module.statements)
directivesToTheTop(module.statements)
return noModifications
}
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
if(decl !in declsProcessedWithInitAssignment) {
declsProcessedWithInitAssignment.add(decl)
if (decl.value == null) {
if (!decl.autogeneratedDontRemove && decl.allowInitializeWithZero) {
// A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
// This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal returnvalue intermediates.
return noModifications
}
val nextStmt = decl.nextSibling()
val nextAssign = nextStmt as? Assignment
val nextFor = nextStmt as? ForLoop
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
val hasNextAssignment = nextAssign!=null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)
if (!hasNextAssignment && !hasNextForWithThisLoopvar) {
// Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), decl.position)
return listOf(IAstModification.InsertAfter(
decl, assignzero, parent as IStatementContainer
))
}
}
} else {
// Transform the vardecl with initvalue to a plain vardecl + assignment
// this allows for other optimizations to kick in.
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
decl, assign, parent as IStatementContainer
))
}
}
}
return noModifications
}
private fun directivesToTheTop(statements: MutableList<Statement>) {
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
@@ -61,7 +103,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
)
}
reorderVardeclsAndDirectives(block.statements)
directivesToTheTop(block.statements)
return noModifications
}
@@ -86,7 +128,12 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
if(parent !is VarDecl) {
// don't replace the initializer value in a vardecl - this will be moved to a separate
// assignment statement soon in after(VarDecl)
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
@@ -111,7 +158,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
}
}
is VarDecl -> {
if(!leftDt.istype(parent.datatype)) {
if(leftDt isnot parent.datatype) {
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
@@ -122,8 +169,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
is Subroutine -> {
val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) {
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is BuiltinFunctionStatementPlaceholder -> {
@@ -131,8 +179,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
val paramTypes = func.parameters[argnum].possibleDatatypes
for(type in paramTypes) {
if(leftDt isAssignableTo type) {
val cast = TypecastExpression(expr.left, type, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
}
@@ -8,8 +8,8 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.IErrorReporter
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
@@ -22,7 +22,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val declValue = decl.value
if(decl.type==VarDeclType.VAR && declValue!=null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
if(valueDt isnot decl.datatype) {
// don't add a typecast on an array initializer value
if(valueDt.isInteger && decl.datatype in ArrayDatatypes)
@@ -47,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.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right)
if(toFix!=null) {
return when {
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
@@ -78,10 +78,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
} else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
val cast = cvalue.cast(targettype)
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
val cast = cvalue2.cast(targettype)
return if(cast.isValid)
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
else
emptyList()
}
@@ -135,11 +135,13 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
if(pair.second is IdentifierReference) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position),
AddressOf(identifier, pair.second.position),
call as Node)
}
} else if(pair.second is NumericLiteralValue) {
@@ -182,7 +184,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
if(typecast.implicit && typecast.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
}
return noModifications
@@ -217,7 +219,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val subroutine = returnStmt.definingSubroutine!!
if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program).istype(subReturnType))
if (returnValue.inferType(program) istype subReturnType)
return noModifications
if (returnValue is NumericLiteralValue) {
val cast = returnValue.cast(subroutine.returntypes.single())
@@ -1,7 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
@@ -10,23 +10,23 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compilerinterface.IErrorReporter
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))
return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
return if(parent is IStatementContainer)
listOf(ScopeFlatten(scope, parent as IStatementContainer))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
@@ -71,9 +71,17 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
}
val sourceDt = typecast.expression.inferType(program)
if(sourceDt.istype(typecast.type))
if(sourceDt istype typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(parent is Assignment) {
val targetDt = (parent).target.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
if(sourceDt istype targetDt) {
// we can get rid of this typecast because the type is already
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
return noModifications
}
@@ -8,21 +8,21 @@ import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException
import prog8.compiler.functions.BuiltinFunctions
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.InternalCompilerException
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, program)
if(error!=null)
throw CompilerException(error)
throw InternalCompilerException(error)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null)
throw CompilerException(error)
throw InternalCompilerException(error)
}
companion object {
@@ -83,8 +83,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
if (!anyCompatible) {
val actual = pair.first.toString()
val expected = pair.second.toString()
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
return if(pair.second.size==1) {
val expected = pair.second[0].toString()
"argument ${index + 1} type mismatch, was: $actual expected: $expected"
} else {
val expected = pair.second.toList().toString()
"argument ${index + 1} type mismatch, was: $actual expected one of: $expected"
}
}
}
}
@@ -1,17 +0,0 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
internal interface IAssemblyProgram {
val valid: Boolean
val name: String
fun assemble(options: CompilationOptions): Int
}
@@ -1,140 +0,0 @@
package prog8.compiler.target
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
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
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
// TODO: rename param target, and also AST node AssignTarget - *different meaning of "target"!*
// TODO: remove param program - can be obtained from AST node
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
val memAddr = target.memoryAddress
val arrayIdx = target.arrayindexed
val ident = target.identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}
}
internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
{ throw it },
{ it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
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) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
{ throw it },
{ it}
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
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) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
// at the moment we only have one code generation backend (for 6502 and 65c02)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,86 +0,0 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
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
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
// 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...:
// if(decl.type==VarDeclType.VAR ) {
// val binExpr = decl.value as? BinaryExpression
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
// val assign = Assignment(target, augExpr, binExpr.position)
// println("SPLIT VARDECL $decl")
// return listOf(
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
// IAstModification.InsertAfter(decl, assign, parent)
// )
// }
// }
// return noModifications
// }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN expression ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
X RightExpr.
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
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.ReplaceNode(binExpr, augExpr, assignment),
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope)
)
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also service
// the expressiontrees that are not used in an assignment statement...
}
return noModifications
}
private fun isSimpleTarget(target: AssignTarget, program: Program) =
if (target.identifier!=null || target.memoryAddress!=null)
compTarget.isInRegularRAM(target, program)
else
false
}
+143 -184
View File
@@ -1,131 +1,104 @@
package prog8tests
import kotlin.test.*
import com.github.michaelbull.result.getErrorOrElse
import com.github.michaelbull.result.getOrElse
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.*
import org.hamcrest.core.Is
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.Disabled
import prog8.ast.Program
import prog8.compiler.IErrorReporter
import prog8.ast.internedStringsModuleName
import prog8.compiler.ModuleImporter
import prog8.compilerinterface.IErrorReporter
import prog8.parser.ParseError
import prog8tests.helpers.*
import prog8.parser.SourceCode
import prog8tests.ast.helpers.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import kotlin.io.path.*
import io.kotest.assertions.fail
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeIn
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestModuleImporter {
private val count = listOf("1st", "2nd", "3rd", "4th", "5th")
class TestModuleImporter: FunSpec({
val count = listOf("1st", "2nd", "3rd", "4th", "5th")
private lateinit var program: Program
@BeforeEach
fun beforeEach() {
program = Program("foo", DummyFunctions, DummyMemsizer)
lateinit var program: Program
beforeTest {
program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
}
private fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(false), searchIn.toList())
fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
return makeImporter(errors, searchIn.asList())
}
private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(), searchIn.toList())
context("ImportModule") {
@Nested
inner class Constructor {
@Test
@Disabled("TODO: invalid entries in search list")
fun testInvalidEntriesInSearchList() {}
@Test
@Disabled("TODO: literal duplicates in search list")
fun testLiteralDuplicatesInSearchList() {}
@Test
@Disabled("TODO: factual duplicates in search list")
fun testFactualDuplicatesInSearchList() {}
}
@Nested
inner class ImportModule {
@Nested
inner class WithInvalidPath {
@Test
fun testNonexisting() {
context("WithInvalidPath") {
test("testNonexisting") {
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
val srcPathAbs = srcPathRel.absolute()
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
assertThat(
".file should be normalized",
"${error1.file}", equalTo("${error1.file.normalize()}")
)
assertThat(
".file should point to specified path",
error1.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
)
assertThat(program.modules.size, equalTo(1))
withClue(".file should be normalized") {
"${error1.file}" shouldBe "${error1.file.normalize()}"
}
withClue(".file should point to specified path") {
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
assertThat(
".file should be normalized",
"${error2.file}", equalTo("${error2.file.normalize()}")
)
assertThat(
".file should point to specified path",
error2.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
)
assertThat(program.modules.size, equalTo(1))
withClue(".file should be normalized") {
"${error2.file}" shouldBe "${error2.file.normalize()}"
}
withClue(".file should point to specified path") {
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
}
@Test
fun testDirectory() {
test("testDirectory") {
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
val srcPathAbs = srcPathRel.absolute()
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
val importer = makeImporter(null, searchIn)
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathRel) }
shouldThrow<AccessDeniedException> { importer.importModule(srcPathRel) }
.let {
assertThat(
".file should be normalized",
"${it.file}", equalTo("${it.file.normalize()}")
)
assertThat(
".file should point to specified path",
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
)
withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}"
}
withClue(".file should point to specified path") {
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
}
assertThat(program.modules.size, equalTo(1))
program.modules.size shouldBe 1
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathAbs) }
shouldThrow<AccessDeniedException> { importer.importModule(srcPathAbs) }
.let {
assertThat(
".file should be normalized",
"${it.file}", equalTo("${it.file.normalize()}")
)
assertThat(
".file should point to specified path",
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
)
withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}"
}
withClue(".file should point to specified path") {
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
}
assertThat(program.modules.size, equalTo(1))
program.modules.size shouldBe 1
}
}
@Nested
inner class WithValidPath {
context("WithValidPath") {
@Test
fun testAbsolute() {
test("testAbsolute") {
val searchIn = listOf(
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
).map { it.invariantSeparatorsPathString }
@@ -134,29 +107,29 @@ class TestModuleImporter {
val path = assumeReadableFile(searchIn[0], fileName)
val module = importer.importModule(path.absolute()).getOrElse { throw it }
assertThat(program.modules.size, equalTo(2))
assertContains(program.modules, module)
assertThat(module.program, equalTo(program))
program.modules.size shouldBe 2
module shouldBeIn program.modules
module.program shouldBe program
}
@Test
fun testRelativeToWorkingDir() {
test("testRelativeToWorkingDir") {
val searchIn = listOf(
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
).map { it.invariantSeparatorsPathString }
val importer = makeImporter(null, searchIn)
val fileName = "simple_main.p8"
val path = assumeReadableFile(searchIn[0], fileName)
assertThat("sanity check: path should NOT be absolute", path.isAbsolute, equalTo(false))
withClue("sanity check: path should NOT be absolute") {
path.isAbsolute shouldBe false
}
val module = importer.importModule(path).getOrElse { throw it }
assertThat(program.modules.size, equalTo(2))
assertContains(program.modules, module)
assertThat(module.program, equalTo(program))
program.modules.size shouldBe 2
module shouldBeIn program.modules
module.program shouldBe program
}
@Test
fun testRelativeTo1stDirInSearchList() {
test("testRelativeTo1stDirInSearchList") {
val searchIn = Path(".")
.div(workingDir.relativize(fixturesDir))
.invariantSeparatorsPathString
@@ -166,52 +139,32 @@ class TestModuleImporter {
assumeReadableFile(searchIn, path)
val module = importer.importModule(path).getOrElse { throw it }
assertThat(program.modules.size, equalTo(2))
assertContains(program.modules, module)
assertThat(module.program, equalTo(program))
program.modules.size shouldBe 2
module shouldBeIn program.modules
module.program shouldBe program
}
@Test
@Disabled("TODO: relative to 2nd in search list")
fun testRelativeTo2ndDirInSearchList() {}
@Test
@Disabled("TODO: ambiguous - 2 or more really different candidates")
fun testAmbiguousCandidates() {}
@Nested
inner class WithBadFile {
@Test
fun testWithSyntaxError() {
context("WithBadFile") {
test("testWithSyntaxError") {
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
val act = { importer.importModule(srcPath) }
repeat(2) { n ->
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
assertThat(it.position.file, equalTo(srcPath.absolutePathString()))
assertThat("line; should be 1-based", it.position.line, equalTo(2))
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
repeat(2) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError>() { act() }.let {
it.position.file shouldBe SourceCode.relative(srcPath).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
assertThat(program.modules.size, equalTo(1))
program.modules.size shouldBe 1
}
}
@Test
fun testImportingFileWithSyntaxError_once() {
doTestImportingFileWithSyntaxError(1)
}
@Test
@Disabled("TODO: module that imports faulty module should not be kept in Program.modules")
fun testImportingFileWithSyntaxError_twice() {
doTestImportingFileWithSyntaxError(2)
}
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
@@ -219,75 +172,80 @@ class TestModuleImporter {
val act = { importer.importModule(importing) }
repeat(repetitions) { n ->
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
assertThat(it.position.file, equalTo(imported.absolutePathString()))
assertThat("line; should be 1-based", it.position.line, equalTo(2))
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
repeat(repetitions) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError>() { act() }.let {
it.position.file shouldBe SourceCode.relative(imported).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
assertThat(program.modules.size, equalTo(2))
}
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
program.modules[0].name shouldBe internedStringsModuleName
}
}
test("testImportingFileWithSyntaxError_once") {
doTestImportingFileWithSyntaxError(1)
}
test("testImportingFileWithSyntaxError_twice") {
doTestImportingFileWithSyntaxError(2)
}
}
}
}
@Nested
inner class ImportLibraryModule {
@Nested
inner class WithInvalidName {
@Test
fun testWithNonExistingName() {
context("ImportLibraryModule") {
context("WithInvalidName") {
test("testWithNonExistingName") {
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
val errors = ErrorReporterForTests()
val errors = ErrorReporterForTests(false)
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
repeat(2) { n ->
val result = importer.importLibraryModule(filenameNoExt)
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
assertEquals(errors.errors.single(), "imported file not found: i_do_not_exist.p8")
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
errors.report()
assertThat(program.modules.size, equalTo(1))
program.modules.size shouldBe 1
val result2 = importer.importLibraryModule(filenameWithExt)
assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue()))
assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension")
assertEquals(errors.errors.single(), "imported file not found: i_do_not_exist.p8.p8") // TODO don't duplicate the p8 extension in the import logic...
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
errors.report()
assertThat(program.modules.size, equalTo(1))
program.modules.size shouldBe 1
}
}
}
@Nested
inner class WithValidName {
@Nested
inner class WithBadFile {
@Test
fun testWithSyntaxError() {
context("WithValidName") {
context("WithBadFile") {
test("testWithSyntaxError") {
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
repeat(2) { n ->
assertFailsWith<ParseError>(count[n] + " call")
{ importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
assertThat(it.position.file, equalTo(srcPath.absolutePathString()))
assertThat("line; should be 1-based", it.position.line, equalTo(2))
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
repeat(2) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError>()
{
importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
it.position.file shouldBe SourceCode.relative(srcPath).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
assertThat(program.modules.size, equalTo(1))
}
program.modules.size shouldBe 1
}
}
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
@@ -295,28 +253,29 @@ class TestModuleImporter {
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
repeat(repetitions) { n ->
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
assertThat(it.position.file, equalTo(imported.normalize().absolutePathString()))
assertThat("line; should be 1-based", it.position.line, equalTo(2))
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
repeat(repetitions) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError>() {
act() }.let {
it.position.file shouldBe SourceCode.relative(imported).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
assertThat(program.modules.size, equalTo(2))
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
program.modules[0].name shouldBe internedStringsModuleName
importer.errors.report()
}
}
@Test
fun testImportingFileWithSyntaxError_once() {
test("testImportingFileWithSyntaxError_once") {
doTestImportingFileWithSyntaxError(1)
}
@Test
@Disabled("TODO: module that imports faulty module should not be kept in Program.modules")
fun testImportingFileWithSyntaxError_twice() {
test("testImportingFileWithSyntaxError_twice") {
doTestImportingFileWithSyntaxError(2)
}
}
}
}
}
})
+58
View File
@@ -0,0 +1,58 @@
package prog8tests
import io.kotest.core.config.AbstractProjectConfig
import io.kotest.core.listeners.Listener
import io.kotest.core.listeners.TestListener
import io.kotest.core.spec.Spec
import io.kotest.extensions.system.NoSystemErrListener
import io.kotest.extensions.system.NoSystemOutListener
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import kotlin.math.max
object ProjectConfig : AbstractProjectConfig() {
override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2)
// override fun listeners() = listOf(SystemOutToNullListener)
}
//object SystemOutToNullListener: TestListener {
// override suspend fun beforeSpec(spec: Spec) = setup()
//
// private fun setup() {
// System.setOut(object: PrintStream(object: ByteArrayOutputStream(){
// override fun write(p0: Int) {
// // do nothing
// }
//
// override fun write(b: ByteArray, off: Int, len: Int) {
// // do nothing
// }
//
// override fun write(b: ByteArray) {
// // do nothing
// }
// }){}
// )
// }
//}
//
//object SystemErrToNullListener: TestListener {
// override suspend fun beforeSpec(spec: Spec) = setup()
//
// private fun setup() {
// System.setErr(object: PrintStream(object: ByteArrayOutputStream(){
// override fun write(p0: Int) {
// // do nothing
// }
//
// override fun write(b: ByteArray, off: Int, len: Int) {
// // do nothing
// }
//
// override fun write(b: ByteArray) {
// // do nothing
// }
// }){}
// )
// }
//}
+98
View File
@@ -0,0 +1,98 @@
package prog8tests
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.maps.shouldContainKey
import io.kotest.matchers.maps.shouldNotContainKey
import io.kotest.matchers.shouldBe
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.compiler.target.C64Target
import prog8.compilerinterface.CallGraph
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestCallgraph: FunSpec({
test("testGraphForEmptySubs") {
val sourcecode = """
%import string
main {
sub start() {
}
sub empty() {
}
}
"""
val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.program)
graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single()
importedBy.name.startsWith("on_the_fly_test") shouldBe true
graph.unused(toplevelModule) shouldBe false
graph.unused(importedModule) shouldBe false
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
for(stmt in mainBlock.statements) {
val sub = stmt as Subroutine
graph.calls shouldNotContainKey sub
graph.calledBy shouldNotContainKey sub
if(sub === result.program.entrypoint)
withClue("start() should always be marked as used to avoid having it removed") {
graph.unused(sub) shouldBe false
}
else
graph.unused(sub) shouldBe true
}
}
test("testGraphForEmptyButReferencedSub") {
val sourcecode = """
%import string
main {
sub start() {
uword xx = &empty
xx++
}
sub empty() {
}
}
"""
val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.program)
graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single()
importedBy.name.startsWith("on_the_fly_test") shouldBe true
graph.unused(toplevelModule) shouldBe false
graph.unused(importedModule) shouldBe false
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
withClue("start 'calls' (references) empty") {
graph.calls shouldContainKey startSub
}
withClue("empty doesn't call anything") {
graph.calls shouldNotContainKey emptySub
}
withClue("empty gets 'called'") {
graph.calledBy shouldContainKey emptySub
}
withClue( "start doesn't get called (except as entrypoint ofc.)") {
graph.calledBy shouldNotContainKey startSub
}
}
})
+38 -38
View File
@@ -1,17 +1,19 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.assertions.fail
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import prog8.ast.IFunctionCall
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Assignment
import prog8.compiler.target.Cx16Target
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.assertEquals
import kotlin.test.assertIs
/**
@@ -19,11 +21,9 @@ import kotlin.test.assertIs
* They are not really unit tests, but rather tests of the whole process,
* from source file loading all the way through to running 64tass.
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompilerOnCharLit {
class TestCompilerOnCharLit: FunSpec({
@Test
fun testCharLitAsRomsubArg() {
test("testCharLitAsRomsubArg") {
val platform = Cx16Target
val result = compileText(platform, false, """
main {
@@ -34,19 +34,19 @@ class TestCompilerOnCharLit {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
assertIs<NumericLiteralValue>(funCall.args[0],
"char literal should have been replaced by ubyte literal")
withClue("char literal should have been replaced by ubyte literal") {
funCall.args[0] shouldBe instanceOf<NumericLiteralValue>()
}
val arg = funCall.args[0] as NumericLiteralValue
assertEquals(DataType.UBYTE, arg.type)
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort()) // TODO: short/int/UBYTE - which should it be?
arg.type shouldBe DataType.UBYTE
arg.number.toShort() shouldBe platform.encodeString("\n", false)[0]
}
@Test
fun testCharVarAsRomsubArg() {
test("testCharVarAsRomsubArg") {
val platform = Cx16Target
val result = compileText(platform, false, """
main {
@@ -58,30 +58,35 @@ class TestCompilerOnCharLit {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
assertIs<IdentifierReference>(funCall.args[0])
funCall.args[0] shouldBe instanceOf<IdentifierReference>()
val arg = funCall.args[0] as IdentifierReference
val decl = arg.targetVarDecl(program)!!
assertEquals(VarDeclType.VAR, decl.type)
assertEquals(DataType.UBYTE, decl.datatype)
decl.type shouldBe VarDeclType.VAR
decl.datatype shouldBe DataType.UBYTE
// TODO: assertIs<CharLiteral>(decl.value,
// "char literals should be kept until code gen")
// val initializerValue = decl.value as CharLiteral
// assertEquals('\n', (initializerValue as CharLiteral).value)
assertIs<NumericLiteralValue>(decl.value,
"char literal should have been replaced by ubyte literal")
val initializerValue = decl.value as NumericLiteralValue
assertEquals(DataType.UBYTE, initializerValue.type)
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort()) // TODO: short/int/UBYTE - which should it be?
withClue("initializer value should have been moved to separate assignment"){
decl.value shouldBe null
}
val assignInitialValue = decl.nextSibling() as Assignment
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
withClue("char literal should have been replaced by ubyte literal") {
assignInitialValue.value shouldBe instanceOf<NumericLiteralValue>()
}
val initializerValue = assignInitialValue.value as NumericLiteralValue
initializerValue.type shouldBe DataType.UBYTE
initializerValue.number.toShort() shouldBe platform.encodeString("\n", false)[0]
}
@Test
fun testCharConstAsRomsubArg() {
test("testCharConstAsRomsubArg") {
val platform = Cx16Target
val result = compileText(platform, false, """
main {
@@ -93,7 +98,7 @@ class TestCompilerOnCharLit {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@@ -101,20 +106,15 @@ class TestCompilerOnCharLit {
when (val arg = funCall.args[0]) {
is IdentifierReference -> {
val decl = arg.targetVarDecl(program)!!
assertEquals(VarDeclType.CONST, decl.type)
assertEquals(DataType.UBYTE, decl.datatype)
assertEquals(
platform.encodeString("\n", false)[0],
(decl.value as NumericLiteralValue).number.toShort()) // TODO: short/int/UBYTE - which should it be?
decl.type shouldBe VarDeclType.CONST
decl.datatype shouldBe DataType.UBYTE
(decl.value as NumericLiteralValue).number.toShort() shouldBe platform.encodeString("\n", false)[0]
}
is NumericLiteralValue -> {
assertEquals(
platform.encodeString("\n", false)[0],
arg.number.toShort()) // TODO: short/int/UBYTE - which should it be?
arg.number.toShort() shouldBe platform.encodeString("\n", false)[0]
}
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
}
}
}
})
+105 -78
View File
@@ -1,14 +1,13 @@
package prog8tests
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.TestInstance
import io.kotest.core.spec.style.FunSpec
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8tests.helpers.*
import prog8.compilerinterface.ICompilationTarget
import prog8tests.ast.helpers.*
import prog8tests.helpers.assertSuccess
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.exists
@@ -18,39 +17,97 @@ import kotlin.io.path.exists
* They are not really unit tests, but rather tests of the whole process,
* from source file loading all the way through to running 64tass.
*/
//@Disabled("to save some time")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompilerOnExamples {
private val examplesDir = assumeDirectory(workingDir, "../examples")
private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest {
val searchIn = mutableListOf(examplesDir)
if (platform == Cx16Target) {
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
}
val filepath = searchIn
.map { it.resolve("$name.p8") }
.map { it.normalize().absolute() }
.map { workingDir.relativize(it) }
.first { it.exists() }
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${platform.name}, optimize=$optimize"
return dynamicTest(displayName) {
compileProgram(
filepath,
optimize,
writeAssembly = true,
slowCodegenWarnings = false,
compilationTarget = platform.name,
libdirs = listOf(),
outputDir
).assertSuccess("; $displayName")
private val examplesDir = assumeDirectory(workingDir, "../examples")
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget) = compileProgram(
filepath,
optimize,
optimizeFloatExpressions = true,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = target.name,
sourceDirs = listOf(),
outputDir
)
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
val searchIn = mutableListOf(examplesDir)
if (target == Cx16Target) {
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
}
val filepath = searchIn
.map { it.resolve("$source.p8") }
.map { it.normalize().absolute() }
.map { workingDir.relativize(it) }
.first { it.exists() }
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${target.name}, optimize=$optimize"
return Pair(displayName, filepath)
}
class TestCompilerOnExamplesC64: FunSpec({
val onlyC64 = cartesianProduct(
listOf(
"balloonflight",
"bdmusic",
"bdmusic-irq",
"charset",
"cube3d-sprites",
"plasma",
"sprites",
"turtle-gfx",
"wizzine",
),
listOf(false, true)
)
onlyC64.forEach {
val (source, optimize) = it
val (displayName, filepath) = prepareTestFiles(source, optimize, C64Target)
test(displayName) {
compileTheThing(filepath, optimize, C64Target).assertSuccess()
}
}
})
@TestFactory
// @Disabled
fun bothCx16AndC64() = mapCombinations(
dim1 = listOf(
class TestCompilerOnExamplesCx16: FunSpec({
val onlyCx16 = cartesianProduct(
listOf(
"vtui/testvtui",
"amiga",
"bobs",
"cobramk3-gfx",
"colorbars",
"datetime",
"highresbitmap",
"kefrenbars",
"mandelbrot-gfx-colors",
"multipalette",
"rasterbars",
"sincos",
"tehtriz",
"testgfx2",
),
listOf(false, true)
)
onlyCx16.forEach {
val (source, optimize) = it
val (displayName, filepath) = prepareTestFiles(source, optimize, Cx16Target)
test(displayName) {
compileTheThing(filepath, optimize, Cx16Target).assertSuccess()
}
}
})
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
val bothCx16AndC64 = cartesianProduct(
listOf(
"animals",
"balls",
"cube3d",
@@ -73,48 +130,18 @@ class TestCompilerOnExamples {
"tehtriz",
"textelite",
),
dim2 = listOf(Cx16Target, C64Target),
dim3 = listOf(false, true),
combine3 = ::makeDynamicCompilerTest
listOf(false, true)
)
@TestFactory
// @Disabled
fun onlyC64() = mapCombinations(
dim1 = listOf(
"balloonflight",
"bdmusic",
"bdmusic-irq",
"charset",
"cube3d-sprites",
"plasma",
"sprites",
"turtle-gfx",
"wizzine",
),
dim2 = listOf(C64Target),
dim3 = listOf(false, true),
combine3 = ::makeDynamicCompilerTest
)
@TestFactory
// @Disabled
fun onlyCx16() = mapCombinations(
dim1 = listOf(
"vtui/testvtui",
"amiga",
"bobs",
"cobramk3-gfx",
"colorbars",
"datetime",
"highresbitmap",
"kefrenbars",
"mandelbrot-gfx-colors",
"multipalette",
"testgfx2",
),
dim2 = listOf(Cx16Target),
dim3 = listOf(false, true),
combine3 = ::makeDynamicCompilerTest
)
}
bothCx16AndC64.forEach {
val (source, optimize) = it
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, C64Target)
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, Cx16Target)
test(displayNameC64) {
compileTheThing(filepathC64, optimize, C64Target).assertSuccess()
}
test(displayNameCx16) {
compileTheThing(filepathCx16, optimize, Cx16Target).assertSuccess()
}
}
})
@@ -1,17 +1,20 @@
package prog8tests
import org.junit.jupiter.api.*
import org.junit.jupiter.api.DynamicTest.dynamicTest
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Label
import prog8.compiler.target.Cx16Target
import prog8tests.helpers.*
import prog8tests.ast.helpers.*
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileFile
import kotlin.io.path.name
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
/**
@@ -19,14 +22,11 @@ import kotlin.test.assertNotEquals
* They are not really unit tests, but rather tests of the whole process,
* from source file loading all the way through to running 64tass.
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompilerOnImportsAndIncludes {
class TestCompilerOnImportsAndIncludes: FunSpec({
@Nested
inner class Import {
context("Import") {
@Test
fun testImportFromSameFolder() {
test("testImportFromSameFolder") {
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
assumeReadableFile(fixturesDir, "foo_bar.p8")
@@ -34,47 +34,22 @@ class TestCompilerOnImportsAndIncludes {
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>()
.map { it.args[0] as IdentifierReference }
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
assertEquals("main.bar", strLits[0].value)
assertEquals("foo.bar", strLits[1].value)
assertEquals("main", strLits[0].definingScope.name)
assertEquals("foo", strLits[1].definingScope.name)
}
@Test
@Disabled("TODO: why would we not accept string literals as argument to %import?")
fun testImportFromSameFolder_strLit() {
val filepath = assumeReadableFile(fixturesDir,"importFromSameFolder_strLit.p8")
val imported = assumeReadableFile(fixturesDir, "foo_bar.p8")
val platform = Cx16Target
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess()
val program = result.programAst
val startSub = program.entrypoint
val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>()
.map { it.args[0] as IdentifierReference }
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
assertEquals("main.bar", strLits[0].value)
assertEquals("foo.bar", strLits[1].value)
assertEquals("main", strLits[0].definingScope.name)
assertEquals("foo", strLits[1].definingScope.name)
strLits[0].value shouldBe "main.bar"
strLits[1].value shouldBe "foo.bar"
strLits[0].definingScope.name shouldBe "main"
strLits[1].definingScope.name shouldBe "foo"
}
}
@Nested
inner class AsmInclude {
@Test
fun testAsmIncludeFromSameFolder() {
context("AsmInclude") {
test("testAsmIncludeFromSameFolder") {
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
assumeReadableFile(fixturesDir, "foo_bar.asm")
@@ -82,27 +57,25 @@ class TestCompilerOnImportsAndIncludes {
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val args = startSub.statements
.filterIsInstance<FunctionCallStatement>()
.map { it.args[0] }
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
assertEquals("main.bar", str0.value)
assertEquals("main", str0.definingScope.name)
str0.value shouldBe "main.bar"
str0.definingScope.name shouldBe "main"
val id1 = (args[1] as AddressOf).identifier
val lbl1 = id1.targetStatement(program) as Label
assertEquals("foo_bar", lbl1.name)
assertEquals("start", lbl1.definingScope.name)
lbl1.name shouldBe "foo_bar"
lbl1.definingScope.name shouldBe "start"
}
}
@Nested
inner class Asmbinary {
@Test
fun testAsmbinaryDirectiveWithNonExistingFile() {
context("Asmbinary") {
test("testAsmbinaryDirectiveWithNonExistingFile") {
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
@@ -110,8 +83,7 @@ class TestCompilerOnImportsAndIncludes {
.assertFailure()
}
@Test
fun testAsmbinaryDirectiveWithNonReadableFile() {
test("testAsmbinaryDirectiveWithNonReadableFile") {
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
assumeDirectory(fixturesDir, "subFolder")
@@ -119,31 +91,30 @@ class TestCompilerOnImportsAndIncludes {
.assertFailure()
}
@TestFactory
fun asmbinaryDirectiveWithExistingBinFile(): Iterable<DynamicTest> =
listOf(
val tests = listOf(
Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"),
Triple("sub", "asmBinaryFromSubFolder.p8", "subFolder/do_nothing2.bin"),
).map {
val (where, p8Str, binStr) = it
dynamicTest("%asmbinary from ${where}folder") {
val p8Path = assumeReadableFile(fixturesDir, p8Str)
val binPath = assumeReadableFile(fixturesDir, binStr)
assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workinDir
workingDir.normalize().toAbsolutePath(),
outputDir.normalize().toAbsolutePath(),
"sanity check: workingDir and outputDir should not be the same folder"
)
)
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
.assertSuccess(
"argument to assembler directive .binary " +
"should be relative to the generated .asm file (in output dir), " +
"NOT relative to .p8 neither current working dir"
)
tests.forEach {
val (where, p8Str, binStr) = it
test("%asmbinary from ${where}folder") {
val p8Path = assumeReadableFile(fixturesDir, p8Str)
// val binPath = assumeReadableFile(fixturesDir, binStr)
// the bug we're testing for (#54) was hidden if outputDir == workingDir
withClue("sanity check: workingDir and outputDir should not be the same folder") {
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
}
}
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
.assertSuccess(
"argument to assembler directive .binary " +
"should be relative to the generated .asm file (in output dir), " +
"NOT relative to .p8 neither current working dir"
)
}
}
}
}
})
+83 -82
View File
@@ -1,23 +1,23 @@
package prog8tests
import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.TestInstance
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.ForLoop
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.astprocessing.size
import prog8.compiler.astprocessing.toConstantIntegerRange
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
import prog8tests.ast.helpers.cartesianProduct
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import prog8tests.helpers.mapCombinations
import kotlin.test.assertEquals
/**
@@ -25,13 +25,11 @@ import kotlin.test.assertEquals
* They are not really unit tests, but rather tests of the whole process,
* from source file loading all the way through to running 64tass.
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompilerOnRanges {
class TestCompilerOnRanges: FunSpec({
@Test
fun testUByteArrayInitializerWithRange_char_to_char() {
test("testUByteArrayInitializerWithRange_char_to_char") {
val platform = Cx16Target
val result = compileText(platform, true, """
val result = compileText(platform, false, """
main {
sub start() {
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
@@ -40,7 +38,7 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val decl = startSub
.statements.filterIsInstance<VarDecl>()[0]
@@ -52,12 +50,15 @@ class TestCompilerOnRanges {
val expectedStr = "$expectedStart .. $expectedEnd"
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
assertEquals(expectedStr, actualStr,".first .. .last")
assertEquals(expectedEnd - expectedStart + 1, rhsValues.last() - rhsValues.first() + 1, "rangeExpr.size()")
withClue(".first .. .last") {
actualStr shouldBe expectedStr
}
withClue("rangeExpr.size()") {
(rhsValues.last() - rhsValues.first() + 1) shouldBe (expectedEnd - expectedStart + 1)
}
}
@Test
fun testFloatArrayInitializerWithRange_char_to_char() {
test("testFloatArrayInitializerWithRange_char_to_char") {
val platform = C64Target
val result = compileText(platform, optimize = false, """
%option enable_floats
@@ -69,7 +70,7 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val decl = startSub
.statements.filterIsInstance<VarDecl>()[0]
@@ -81,50 +82,39 @@ class TestCompilerOnRanges {
val expectedStr = "$expectedStart .. $expectedEnd"
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
assertEquals(expectedStr, actualStr,".first .. .last")
assertEquals(expectedEnd - expectedStart + 1, rhsValues.size, "rangeExpr.size()")
withClue(".first .. .last") {
actualStr shouldBe expectedStr
}
withClue("rangeExpr.size()") {
rhsValues.size shouldBe (expectedEnd - expectedStart + 1)
}
}
fun Subroutine.decl(varName: String): VarDecl {
return statements.filterIsInstance<VarDecl>()
.first { it.name == varName }
}
inline fun <reified T : Expression> VarDecl.rhs() : T {
return value as T
}
inline fun <reified T : Expression> ArrayLiteralValue.elements() : List<T> {
return value.map { it as T }
}
context("floatArrayInitializerWithRange") {
val combos = cartesianProduct(
listOf("", "42", "41"), // sizeInDecl
listOf("%option enable_floats", ""), // optEnableFloats
listOf(Cx16Target, C64Target), // platform
listOf(false, true) // optimize
)
fun <N : Number> assertEndpoints(expFirst: N, expLast: N, actual: Iterable<N>, msg: String = ".first .. .last") {
val expectedStr = "$expFirst .. $expLast"
val actualStr = "${actual.first()} .. ${actual.last()}"
assertEquals(expectedStr, actualStr,".first .. .last")
}
@TestFactory
fun floatArrayInitializerWithRange() = mapCombinations(
dim1 = listOf("", "42", "41"), // sizeInDecl
dim2 = listOf("%option enable_floats", ""), // optEnableFloats
dim3 = listOf(Cx16Target, C64Target), // platform
dim4 = listOf(false, true), // optimize
combine4 = { sizeInDecl, optEnableFloats, platform, optimize ->
combos.forEach {
val (sizeInDecl, optEnableFloats, platform, optimize) = it
val displayName =
"test failed for: " +
when (sizeInDecl) {
"" -> "no"
"42" -> "correct"
else -> "wrong"
} + " array size given" +
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
", ${platform.name}, optimize: $optimize"
dynamicTest(displayName) {
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
", ${platform.name}, optimize: $optimize"
test(displayName) {
val result = compileText(platform, optimize, """
$optEnableFloats
main {
sub start() {
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
cs[0] = 23 ; keep optimizer from removing it
}
}
@@ -133,12 +123,12 @@ class TestCompilerOnRanges {
result.assertSuccess()
else
result.assertFailure()
}
}
)
}
@Test
fun testForLoopWithRange_char_to_char() {
test("testForLoopWithRange_char_to_char") {
val platform = Cx16Target
val result = compileText(platform, optimize = true, """
main {
@@ -151,7 +141,7 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val iterable = startSub
.statements.filterIsInstance<ForLoop>()
@@ -162,14 +152,17 @@ class TestCompilerOnRanges {
val expectedEnd = platform.encodeString("f", false)[0].toInt()
val expectedStr = "$expectedStart .. $expectedEnd"
val intProgression = rangeExpr.toConstantIntegerRange(platform)
val intProgression = rangeExpr.toConstantIntegerRange()
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
assertEquals(expectedStr, actualStr,".first .. .last")
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(platform), "rangeExpr.size()")
withClue(".first .. .last") {
actualStr shouldBe expectedStr
}
withClue("rangeExpr.size()") {
rangeExpr.size() shouldBe (expectedEnd - expectedStart + 1)
}
}
@Test
fun testForLoopWithRange_bool_to_bool() {
test("testForLoopWithRange_bool_to_bool") {
val platform = Cx16Target
val result = compileText(platform, optimize = true, """
main {
@@ -182,21 +175,20 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>()
.map { it.iterable }
.filterIsInstance<RangeExpr>()[0]
assertEquals(2, rangeExpr.size(platform))
val intProgression = rangeExpr.toConstantIntegerRange(platform)
assertEquals(0, intProgression?.first)
assertEquals(1, intProgression?.last)
rangeExpr.size() shouldBe 2
val intProgression = rangeExpr.toConstantIntegerRange()
intProgression?.first shouldBe 0
intProgression?.last shouldBe 1
}
@Test
fun testForLoopWithRange_ubyte_to_ubyte() {
test("testForLoopWithRange_ubyte_to_ubyte") {
val platform = Cx16Target
val result = compileText(platform, optimize = true, """
main {
@@ -209,21 +201,21 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>()
.map { it.iterable }
.filterIsInstance<RangeExpr>()[0]
assertEquals(9, rangeExpr.size(platform))
val intProgression = rangeExpr.toConstantIntegerRange(platform)
assertEquals(1, intProgression?.first)
assertEquals(9, intProgression?.last)
rangeExpr.size() shouldBe 9
val intProgression = rangeExpr.toConstantIntegerRange()
intProgression?.first shouldBe 1
intProgression?.last shouldBe 9
}
@Test
fun testForLoopWithRange_str_downto_str() {
test("testForLoopWithRange_str_downto_str") {
val errors = ErrorReporterForTests()
compileText(Cx16Target, true, """
main {
sub start() {
@@ -233,12 +225,13 @@ class TestCompilerOnRanges {
}
}
}
""").assertFailure()
//TODO("test exact compile error(s)")
""", errors, false).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain ".p8:5:29: range expression from value must be integer"
errors.errors[1] shouldContain ".p8:5:44: range expression to value must be integer"
}
@Test
fun testForLoopWithIterable_str() {
test("testForLoopWithIterable_str") {
val result = compileText(Cx16Target, false, """
main {
sub start() {
@@ -250,15 +243,23 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val iterable = startSub
.statements.filterIsInstance<ForLoop>()
.map { it.iterable }
.filterIsInstance<IdentifierReference>()[0]
assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED))
iterable.inferType(program).getOr(DataType.UNDEFINED) shouldBe DataType.STR
}
}
test("testRangeExprNumericSize") {
val expr = RangeExpr(
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
Position.DUMMY)
expr.size() shouldBe 6
expr.toConstantIntegerRange()
}
})
+23 -29
View File
@@ -1,12 +1,13 @@
package prog8tests
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.core.spec.style.FunSpec
import prog8.compiler.compileProgram
import prog8.compiler.target.Cx16Target
import prog8tests.helpers.*
import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.fixturesDir
import prog8tests.ast.helpers.outputDir
import prog8tests.ast.helpers.workingDir
import prog8tests.helpers.assertSuccess
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.createTempFile
@@ -18,13 +19,11 @@ import kotlin.io.path.writeText
* They are not really unit tests, but rather tests of the whole process,
* from source file loading all the way through to running 64tass.
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompilerOptionLibdirs {
class TestCompilerOptionSourcedirs: FunSpec({
private lateinit var tempFileInWorkingDir: Path
lateinit var tempFileInWorkingDir: Path
@BeforeAll
fun setUp() {
beforeSpec {
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
.also { it.writeText("""
main {
@@ -34,63 +33,58 @@ class TestCompilerOptionLibdirs {
""")}
}
@AfterAll
fun tearDown() {
afterSpec {
tempFileInWorkingDir.deleteExisting()
}
private fun compileFile(filePath: Path, libdirs: List<String>) =
fun compileFile(filePath: Path, sourceDirs: List<String>) =
compileProgram(
filepath = filePath,
optimize = false,
optimizeFloatExpressions = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = Cx16Target.name,
libdirs,
sourceDirs,
outputDir
)
@Test
fun testAbsoluteFilePathInWorkingDir() {
test("testAbsoluteFilePathInWorkingDir") {
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
compileFile(filepath, listOf())
.assertSuccess()
}
@Test
fun testFilePathInWorkingDirRelativeToWorkingDir() {
test("testFilePathInWorkingDirRelativeToWorkingDir") {
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
compileFile(filepath, listOf())
.assertSuccess()
}
@Test
fun testFilePathInWorkingDirRelativeTo1stInLibdirs() {
test("testFilePathInWorkingDirRelativeTo1stInSourcedirs") {
val filepath = assumeReadableFile(tempFileInWorkingDir)
compileFile(filepath.fileName, listOf(workingDir.toString()))
.assertSuccess()
}
@Test
fun testAbsoluteFilePathOutsideWorkingDir() {
test("testAbsoluteFilePathOutsideWorkingDir") {
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
compileFile(filepath.absolute(), listOf())
.assertSuccess()
}
@Test
fun testFilePathOutsideWorkingDirRelativeToWorkingDir() {
test("testFilePathOutsideWorkingDirRelativeToWorkingDir") {
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
compileFile(filepath, listOf())
.assertSuccess()
}
@Test
fun testFilePathOutsideWorkingDirRelativeTo1stInLibdirs() {
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
val libdirs = listOf("$fixturesDir")
compileFile(filepath.fileName, libdirs)
val sourcedirs = listOf("$fixturesDir")
compileFile(filepath.fileName, sourcedirs)
.assertSuccess()
}
}
})
@@ -1,25 +1,23 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import prog8.ast.internedStringsModuleName
import prog8.compiler.ErrorReporter
import prog8.compiler.ZeropageType
import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseImports
import prog8.compiler.target.C64Target
import prog8.compilerinterface.ZeropageType
import prog8tests.ast.helpers.outputDir
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import prog8tests.helpers.outputDir
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestImportedModulesOrderAndOptions {
class TestImportedModulesOrderAndOptions: FunSpec({
@Test
fun testImportedModuleOrderCorrect() {
test("testImportedModuleOrderAndMainModuleCorrect") {
val result = compileText(C64Target, false, """
%import textio
%import floats
@@ -30,23 +28,27 @@ main {
}
}
""").assertSuccess()
assertTrue(result.programAst.mainModule.name.startsWith("on_the_fly_test"))
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
val moduleNames = result.programAst.modules.map { it.name }
assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first")
assertEquals(listOf(
"prog8_interned_strings",
"textio",
"syslib",
"conv",
"floats",
"math",
"prog8_lib"
), moduleNames.drop(1), "module order in parse tree")
val moduleNames = result.program.modules.map { it.name }
withClue("main module must be first") {
moduleNames[0] shouldStartWith "on_the_fly_test"
}
withClue("module order in parse tree") {
moduleNames.drop(1) shouldBe listOf(
"prog8_interned_strings",
"textio",
"syslib",
"conv",
"floats",
"math",
"prog8_lib"
)
}
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
}
@Test
fun testCompilationOptionsCorrectFromMain() {
test("testCompilationOptionsCorrectFromMain") {
val result = compileText(C64Target, false, """
%import textio
%import floats
@@ -59,16 +61,15 @@ main {
}
}
""").assertSuccess()
assertTrue(result.programAst.mainModule.name.startsWith("on_the_fly_test"))
val options = determineCompilationOptions(result.programAst, C64Target)
assertTrue(options.floats)
assertEquals(ZeropageType.DONTUSE, options.zeropage)
assertTrue(options.noSysInit)
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
val options = determineCompilationOptions(result.program, C64Target)
options.floats shouldBe true
options.zeropage shouldBe ZeropageType.DONTUSE
options.noSysInit shouldBe true
}
@Test
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
val errors = ErrorReporter()
test("testModuleOrderAndCompilationOptionsCorrectWithJustImports") {
val errors = ErrorReporterForTests()
val sourceText = """
%import textio
%import floats
@@ -86,17 +87,23 @@ main {
filepath.toFile().writeText(sourceText)
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
assertEquals(filenameBase, program.mainModule.name)
assertEquals(1, importedfiles.size, "all imports other than the test source must have been internal resources library files")
assertEquals(listOf(
internedStringsModuleName,
filenameBase,
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
), program.modules.map {it.name}, "module order in parse tree")
assertTrue(options.floats)
assertEquals(ZeropageType.DONTUSE, options.zeropage, "zeropage option must be correctly taken from main module, not from float module import logic")
assertTrue(options.noSysInit)
program.toplevelModule.name shouldBe filenameBase
withClue("all imports other than the test source must have been internal resources library files") {
importedfiles.size shouldBe 1
}
withClue("module order in parse tree") {
program.modules.map { it.name } shouldBe
listOf(
internedStringsModuleName,
filenameBase,
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
)
}
options.floats shouldBe true
options.noSysInit shouldBe true
withClue("zeropage option must be correctly taken from main module, not from float module import logic") {
options.zeropage shouldBe ZeropageType.DONTUSE
}
}
}
})
+76 -91
View File
@@ -1,11 +1,10 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
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.ArrayIndexedExpression
@@ -14,178 +13,164 @@ import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8.compilerinterface.isInRegularRAMof
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import prog8tests.helpers.DummyStringEncoder
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestMemory {
class TestMemory: FunSpec({
@Test
fun testInValidRamC64_memory_addresses() {
test("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", DummyFunctions, DummyMemsizer)
assertTrue(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testNotInValidRamC64_memory_addresses() {
test("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", DummyFunctions, DummyMemsizer)
assertFalse(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_memory_identifiers() {
val program = Program("test", DummyFunctions, DummyMemsizer)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
assertFalse(C64Target.isInRegularRAM(target, program))
}
private fun createTestProgramForMemoryRefViaVar(program: Program, address: Int, vartype: VarDeclType): AssignTarget {
fun createTestProgramForMemoryRefViaVar(program: Program, 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, null)
module.linkParents(program.namespace)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
module.linkIntoProgram(program)
return target
}
test("testInValidRamC64_memory_identifiers") {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
@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", DummyFunctions, DummyMemsizer)
assertFalse(C64Target.isInRegularRAM(target, program))
target.isInRegularRAMof(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_variable() {
test("testInValidRamC64_memory_expression") {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
test("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, null)
val program = Program("test", DummyFunctions, DummyMemsizer)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testInValidRamC64_memmap_variable() {
test("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, null)
val program = Program("test", DummyFunctions, DummyMemsizer)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testNotInValidRamC64_memmap_variable() {
test("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, null)
val program = Program("test", DummyFunctions, DummyMemsizer)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkParents(program.namespace)
assertFalse(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_array() {
test("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, null)
val program = Program("test", DummyFunctions, DummyMemsizer)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testInValidRamC64_array_memmapped() {
test("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, null)
val program = Program("test", DummyFunctions, DummyMemsizer)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testNotValidRamC64_array_memmapped() {
test("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, null)
val program = Program("test", DummyFunctions, DummyMemsizer)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkParents(program.namespace)
assertFalse(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
}
})
+95 -101
View File
@@ -1,116 +1,110 @@
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 io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.doubles.plusOrMinus
import io.kotest.matchers.shouldBe
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
import prog8.compilerinterface.InternalCompilerException
@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() }
class TestNumbers: FunSpec({
test("testToHex") {
0.toHex() shouldBe "0"
1.toHex() shouldBe "1"
1.234.toHex() shouldBe "1"
10.toHex() shouldBe "10"
10.99.toHex() shouldBe "10"
15.toHex() shouldBe "15"
16.toHex() shouldBe "\$10"
255.toHex() shouldBe "\$ff"
256.toHex() shouldBe "\$0100"
20060.toHex() shouldBe "\$4e5c"
50050.toHex() shouldBe "\$c382"
65535.toHex() shouldBe "\$ffff"
65535L.toHex() shouldBe "\$ffff"
0.toHex() shouldBe "0"
(-1).toHex() shouldBe "-1"
(-1.234).toHex() shouldBe "-1"
(-10).toHex() shouldBe "-10"
(-10.99).toHex() shouldBe "-10"
(-15).toHex() shouldBe "-15"
(-16).toHex() shouldBe "-\$10"
(-255).toHex() shouldBe "-\$ff"
(-256).toHex() shouldBe "-\$0100"
(-20060).toHex() shouldBe "-\$4e5c"
(-50050).toHex() shouldBe "-\$c382"
(-65535).toHex() shouldBe "-\$ffff"
(-65535L).toHex() shouldBe "-\$ffff"
shouldThrow<IllegalArgumentException> { 65536.toHex() }
shouldThrow<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("testFloatToMflpt5") {
Mflpt5.fromNumber(0) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)
Mflpt5.fromNumber(-123.45678e-22) shouldBe 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("testFloatRange") {
Mflpt5.fromNumber(FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
}
@Test
fun testMflpt5ToFloat() {
test("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))
Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 0.0
Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 32768.0
Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble() shouldBe -32768.0
Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 1.0
Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble() shouldBe -.5
Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble() shouldBe 10.0
Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble() shouldBe 1000000000.0
Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe .5
Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe .25
Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble() shouldBe(123.45678e22 plusOrMinus 1.0e15)
Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble() shouldBe(-123.45678e-22 plusOrMinus epsilon)
}
}
})
+86 -96
View File
@@ -1,92 +1,85 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestNumericLiteralValue {
class TestNumericLiteralValue: FunSpec({
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
return lv1.type==lv2.type && lv1==lv2
}
private val dummyPos = Position("test", 0, 0, 0)
val dummyPos = Position("test", 0, 0, 0)
@Test
fun testIdentity() {
test("testIdentity") {
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v <= v)
assertTrue(v >= v)
assertFalse(v < v)
assertFalse(v > v)
(v==v) shouldBe true
(v != v) shouldBe false
(v <= v) shouldBe true
(v >= v) shouldBe true
(v < v ) shouldBe false
(v > v ) shouldBe false
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
}
@Test
fun testEqualsAndNotEquals() {
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos))
assertEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos))
test("testEqualsAndNotEquals") {
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.UWORD, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 22239, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) == NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)))
assertTrue(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)))
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.UWORD, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 245, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe true
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)))
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe false
}
@Test
fun testEqualsRef() {
assertEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("hello", false, dummyPos))
assertNotEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("bye", false, dummyPos))
assertEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", true, dummyPos))
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("bye", true, dummyPos))
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", false, dummyPos))
test("testEqualsRef") {
(StringLiteralValue("hello", false, dummyPos) == StringLiteralValue("hello", false, dummyPos)) shouldBe true
(StringLiteralValue("hello", false, dummyPos) != StringLiteralValue("bye", false, dummyPos)) shouldBe true
(StringLiteralValue("hello", true, dummyPos) == StringLiteralValue("hello", true, dummyPos)) shouldBe true
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("bye", true, dummyPos)) shouldBe true
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("hello", false, dummyPos)) shouldBe true
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
@@ -98,47 +91,44 @@ class TestNumericLiteralValue {
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3)
lv1 shouldBe lv2
lv1 shouldNotBe lv3
}
@Test
fun testGreaterThan(){
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos))
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
test("testGreaterThan") {
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe true
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe false
}
@Test
fun testLessThan() {
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos))
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
test("testLessThan") {
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe true
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
}
}
})
+324
View File
@@ -0,0 +1,324 @@
package prog8tests
import io.kotest.assertions.fail
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.printAst
import prog8.compiler.target.C64Target
import prog8.compilerinterface.*
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import prog8tests.helpers.generateAssembly
class TestOptimization: FunSpec({
test("testRemoveEmptySubroutineExceptStart") {
val sourcecode = """
main {
sub start() {
}
sub empty() {
; going to be removed
}
}
"""
val result = compileText(C64Target, true, sourcecode).assertSuccess()
val toplevelModule = result.program.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements.single() as Subroutine
result.program.entrypoint shouldBeSameInstanceAs startSub
withClue("only start sub should remain") {
startSub.name shouldBe "start"
}
withClue("compiler has inserted return in empty subroutines") {
startSub.statements.single() shouldBe instanceOf<Return>()
}
}
test("testDontRemoveEmptySubroutineIfItsReferenced") {
val sourcecode = """
main {
sub start() {
uword xx = &empty
xx++
}
sub empty() {
; should not be removed
}
}
"""
val result = compileText(C64Target, true, sourcecode).assertSuccess()
val toplevelModule = result.program.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements[0] as Subroutine
val emptySub = mainBlock.statements[1] as Subroutine
result.program.entrypoint shouldBeSameInstanceAs startSub
startSub.name shouldBe "start"
emptySub.name shouldBe "empty"
withClue("compiler has inserted return in empty subroutines") {
emptySub.statements.single() shouldBe instanceOf<Return>()
}
}
test("testGeneratedConstvalueInheritsProperParentLinkage") {
val number = NumericLiteralValue(DataType.UBYTE, 11, Position.DUMMY)
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
tc.linkParents(ParentSentinel)
tc.parent shouldNotBe null
number.parent shouldNotBe null
tc shouldBeSameInstanceAs number.parent
val constvalue = tc.constValue(program)!!
constvalue shouldBe instanceOf<NumericLiteralValue>()
constvalue.number.toInt() shouldBe 11
constvalue.type shouldBe DataType.BYTE
tc shouldBeSameInstanceAs constvalue.parent
}
test("testConstantFoldedAndSilentlyTypecastedForInitializerValues") {
val sourcecode = """
main {
sub start() {
const ubyte TEST = 10
byte @shared x1 = TEST as byte + 1
byte @shared x2 = 1 + TEST as byte
ubyte @shared y1 = TEST + 1 as byte
ubyte @shared y2 = 1 as byte + TEST
}
}
"""
val result = compileText(C64Target, true, sourcecode).assertSuccess()
val mainsub = result.program.entrypoint
mainsub.statements.size shouldBe 10
val declTest = mainsub.statements[0] as VarDecl
val declX1 = mainsub.statements[1] as VarDecl
val initX1 = mainsub.statements[2] as Assignment
val declX2 = mainsub.statements[3] as VarDecl
val initX2 = mainsub.statements[4] as Assignment
val declY1 = mainsub.statements[5] as VarDecl
val initY1 = mainsub.statements[6] as Assignment
val declY2 = mainsub.statements[7] as VarDecl
val initY2 = mainsub.statements[8] as Assignment
mainsub.statements[9] shouldBe instanceOf<Return>()
(declTest.value as NumericLiteralValue).number.toDouble() shouldBe 10.0
declX1.value shouldBe null
declX2.value shouldBe null
declY1.value shouldBe null
declY2.value shouldBe null
(initX1.value as NumericLiteralValue).type shouldBe DataType.BYTE
(initX1.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initX2.value as NumericLiteralValue).type shouldBe DataType.BYTE
(initX2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initY1.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY1.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
}
test("typecasted assignment from ubyte logical expressoin to uword var") {
val src = """
main {
sub start() {
ubyte bb
uword ww
ww = not bb or not ww ; expression combining ubyte and uword
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
// ww = ((( not bb as uword) or not ww) as uword)
val wwAssign = result.program.entrypoint.statements.last() as Assignment
val expr = wwAssign.value as TypecastExpression
wwAssign.target.identifier?.nameInSource shouldBe listOf("ww")
expr.type shouldBe DataType.UWORD
expr.expression.inferType(result.program).istype(DataType.UBYTE) shouldBe true
}
test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") {
val src = """
main {
sub start() {
ubyte bb
uword ww
bb = not bb or not ww ; expression combining ubyte and uword
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
// bb = (( not bb as uword) or not ww)
val bbAssign = result.program.entrypoint.statements.last() as Assignment
val expr = bbAssign.value as BinaryExpression
expr.operator shouldBe "or"
expr.left shouldBe instanceOf<TypecastExpression>() // casted to word
expr.right shouldBe instanceOf<PrefixExpression>()
expr.left.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target)
val changer = BeforeAsmGenerationAstChanger(result.program,
options,
ErrorReporterForTests()
)
changer.visit(result.program)
while(changer.applyModifications()>0) {
changer.visit(result.program)
}
// assignment is now split into:
// bb = not bb
// bb = (bb or not ww)
val assigns = result.program.entrypoint.statements.filterIsInstance<Assignment>()
val bbAssigns = assigns.filter { it.value !is NumericLiteralValue }
bbAssigns.size shouldBe 2
bbAssigns[0].target.identifier!!.nameInSource shouldBe listOf("bb")
bbAssigns[0].value shouldBe instanceOf<PrefixExpression>()
(bbAssigns[0].value as PrefixExpression).operator shouldBe "not"
(bbAssigns[0].value as PrefixExpression).expression shouldBe IdentifierReference(listOf("bb"), Position.DUMMY)
bbAssigns[0].value.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
bbAssigns[1].target.identifier!!.nameInSource shouldBe listOf("bb")
val bbAssigns1expr = bbAssigns[1].value as BinaryExpression
bbAssigns1expr.operator shouldBe "or"
bbAssigns1expr.left shouldBe IdentifierReference(listOf("bb"), Position.DUMMY)
bbAssigns1expr.right shouldBe instanceOf<PrefixExpression>()
(bbAssigns1expr.right as PrefixExpression).operator shouldBe "not"
(bbAssigns1expr.right as PrefixExpression).expression shouldBe IdentifierReference(listOf("ww"), Position.DUMMY)
bbAssigns1expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
val asm = generateAssembly(result.program, options)
asm.valid shouldBe true
}
test("intermediate assignment steps generated for typecasted expression") {
val src = """
main {
sub start() {
ubyte r
ubyte @shared bb = (cos8(r)/2 + 100) as ubyte
}
}
"""
val result = compileText(C64Target, true, src, writeAssembly = true).assertSuccess()
/* turned into:
ubyte r
r = 0
ubyte bb
prog8_lib.retval_interm_b = cos8(r)
prog8_lib.retval_interm_b >>= 1
prog8_lib.retval_interm_b += 100
bb = prog8_lib.retval_interm_b
return
*/
val st = result.program.entrypoint.statements
st.size shouldBe 8
st.last() shouldBe instanceOf<Return>()
var assign = st[3] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
assign = st[4] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
assign = st[5] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
assign = st[6] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("bb")
}
test("asmgen correctly deals with float typecasting in augmented assignment") {
val src="""
%option enable_floats
main {
sub start() {
ubyte ub
float ff
ff += (ub as float) ; operator doesn't matter
}
}
"""
val result1 = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
val assignYY = result1.program.entrypoint.statements.last() as Assignment
assignYY.isAugmentable shouldBe true
assignYY.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignYY.value as BinaryExpression
value.operator shouldBe "+"
value.left shouldBe IdentifierReference(listOf("ff"), Position.DUMMY)
value.right shouldBe instanceOf<TypecastExpression>()
val asm = generateAssembly(result1.program)
asm.valid shouldBe true
}
test("unused variable removal") {
val src="""
main {
sub start() {
ubyte unused
ubyte @shared unused_but_shared ; this one should remain
ubyte usedvar_only_written
usedvar_only_written=2
usedvar_only_written++
ubyte usedvar ; and this one too
usedvar = msb(usedvar)
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
result.program.entrypoint.statements.size shouldBe 4 // unused_but_shared decl, unused_but_shared=0, usedvar decl, usedvar assign
val (decl, assign, decl2, assign2) = result.program.entrypoint.statements
decl shouldBe instanceOf<VarDecl>()
(decl as VarDecl).name shouldBe "unused_but_shared"
assign shouldBe instanceOf<Assignment>()
decl2 shouldBe instanceOf<VarDecl>()
(decl2 as VarDecl).name shouldBe "usedvar"
assign2 shouldBe instanceOf<Assignment>()
}
test("unused variable removal from subscope") {
val src="""
main {
sub start() {
if cx16.r0 {
uword xx = 42 ; to be removed
xx=99 ; to be removed
cx16.r0 = 0
}
func2()
sub func2() {
uword yy = 33 ; to be removed
yy=99 ; to be removed
cx16.r0 = 0
}
}
}"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
result.program.entrypoint.statements.size shouldBe 3
val ifstmt = result.program.entrypoint.statements[0] as IfStatement
ifstmt.truepart.statements.size shouldBe 1
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
val func2 = result.program.entrypoint.statements[2] as Subroutine
func2.statements.size shouldBe 1
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
}
})
+170 -87
View File
@@ -1,110 +1,193 @@
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 com.github.michaelbull.result.Ok
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrElse
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.compiler.target.cbm.Petscii
import prog8.left
import prog8.right
import kotlin.test.*
import java.io.CharConversionException
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestPetscii {
class TestPetscii: FunSpec({
@Test
fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(right(listOf<Short>(0))))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(right(listOf<Short>(0))))
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
test("testZero") {
Petscii.encodePetscii("\u0000", true) shouldBe Ok(listOf<Short>(0))
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<Short>(0))
Petscii.decodePetscii(listOf(0), true) shouldBe "\u0000"
Petscii.decodePetscii(listOf(0), false) shouldBe "\u0000"
}
@Test
fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
right(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(right(listOf<Short>(0x12)))) // reverse vid
assertThat(Petscii.encodePetscii("✓", true), equalTo(right(listOf<Short>(0xfa))))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(right(listOf<Short>(255))))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(right(listOf<Short>(0xd3))))
test("testLowercase") {
Petscii.encodePetscii("hello WORLD 123 @!£", true) shouldBe
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<Short>(0x12)) // reverse vid
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<Short>(0xfa))
withClue("expect lowercase error fallback") {
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<Short>(255))
Petscii.encodePetscii("♥", true) shouldBe Ok(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) }
Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true) shouldBe "hW£✓\uF11A"
}
@Test
fun testUppercase() {
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
right(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(right(listOf<Short>(0x12)))) // reverse vid
assertThat(Petscii.encodePetscii("♥"), equalTo(right(listOf<Short>(0xd3))))
assertThat(Petscii.encodePetscii("π"), equalTo(right(listOf<Short>(0xff))))
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(right(listOf<Short>(250))))
test("testUppercase") {
Petscii.encodePetscii("HELLO 123 @!£") shouldBe
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<Short>(0x12)) // reverse vid
Petscii.encodePetscii("♥") shouldBe Ok(listOf<Short>(0xd3))
Petscii.encodePetscii("π") shouldBe Ok(listOf<Short>(0xff))
withClue("expecting fallback") {
Petscii.encodePetscii("✓") shouldBe Ok(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)) }
Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)) shouldBe "H£♥π"
}
@Test
fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
right(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(right(listOf<Short>(0x7a))))
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(right(listOf<Short>(83))))
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(right(listOf<Short>(94))))
test("testScreencodeLowercase") {
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
Ok(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<Short>(0x7a))
withClue("expect fallback") {
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<Short>(83))
Petscii.encodeScreencode("π", true) shouldBe Ok(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) }
Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true) shouldBe "hW£✓"
}
@Test
fun testScreencodeUppercase() {
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
right(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
assertThat(Petscii.encodeScreencode("♥"), equalTo(right(listOf<Short>(0x53))))
assertThat(Petscii.encodeScreencode("π"), equalTo(right(listOf<Short>(0x5e))))
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(right(listOf<Short>(8, 5, 12, 12, 15))))
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(right(listOf<Short>(8, 5, 12, 12, 15))))
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(right(listOf<Short>(122))))
test("testScreencodeUppercase") {
Petscii.encodeScreencode("WORLD 123 @!£") shouldBe
Ok(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<Short>(0x53))
Petscii.encodeScreencode("π") shouldBe Ok(listOf<Short>(0x5e))
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<Short>(8, 5, 12, 12, 15))
withClue("expecting fallback") {
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<Short>(8, 5, 12, 12, 15))
Petscii.encodeScreencode("✓") shouldBe Ok(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)) }
Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)) shouldBe "W£♥π"
}
@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)
test("testErrorCases") {
Petscii.encodePetscii("~", true).expectError { "shouldn't be able to encode tilde" }
Petscii.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
Petscii.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
Petscii.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), true) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), true) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), false) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), false) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), true) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), true) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), false) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), false) }
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)
Petscii.scr2petscii(-1).expectError { "-1 should error" }
Petscii.scr2petscii(256).expectError { "256 should error" }
Petscii.petscii2scr(-1, true).expectError { "-1 should error" }
Petscii.petscii2scr(256, true).expectError { "256 should error" }
Petscii.petscii2scr(-1, false).expectError { "-1 should error" }
Petscii.petscii2scr(256, false).expectError { "256 should error" }
}
}
test("testSpecialReplacements") {
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
Petscii.encodePetscii("`", false).expectError { "shouldn't have translation for backtick" }
Petscii.encodePetscii("`", true).expectError { "shouldn't have translation for backtick" }
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
encodeP('^', false) shouldBe 94
encodeP('^', true) shouldBe 94
encodeS('^', false) shouldBe 30
encodeS('^', true) shouldBe 30
encodeP('_', false) shouldBe 228
encodeP('_', true) shouldBe 228
encodeS('_', false) shouldBe 100
encodeS('_', true) shouldBe 100
encodeP('{', false) shouldBe 243
encodeP('{', true) shouldBe 243
encodeS('{', false) shouldBe 115
encodeS('{', true) shouldBe 115
encodeP('}', false) shouldBe 235
encodeP('}', true) shouldBe 235
encodeS('}', false) shouldBe 107
encodeS('}', true) shouldBe 107
encodeP('|', false) shouldBe 221
encodeP('|', true) shouldBe 221
encodeS('|', false) shouldBe 93
encodeS('|', true) shouldBe 93
encodeP('\\', false) shouldBe 205
encodeP('\\', true) shouldBe 205
encodeS('\\', false) shouldBe 77
encodeS('\\', true) shouldBe 77
}
test("testBoxDrawingCharsEncoding") {
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
// pipe char
encodeP('|', false) shouldBe 221
encodeP('|', true) shouldBe 221
encodeS('|', false) shouldBe 93
encodeS('|', true) shouldBe 93
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
encodeP('│', false) shouldBe 221
encodeP('│', true) shouldBe 221
encodeS('│', false) shouldBe 93
encodeS('│', true) shouldBe 93
// underscore
encodeP('_', false) shouldBe 228
encodeP('_', true) shouldBe 228
encodeS('_', false) shouldBe 100
encodeS('_', true) shouldBe 100
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
encodeP('▁', false) shouldBe 228
encodeP('▁', true) shouldBe 228
encodeS('▁', false) shouldBe 100
encodeS('▁', true) shouldBe 100
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
encodeP('─', false) shouldBe 192
encodeP('─', true) shouldBe 192
encodeS('─', false) shouldBe 64
encodeS('─', true) shouldBe 64
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
encodeP('│', false) shouldBe 221
encodeP('│', true) shouldBe 221
encodeS('│', false) shouldBe 93
encodeS('│', true) shouldBe 93
}
test("testBoxDrawingCharsDecoding") {
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
Petscii.decodePetscii(listOf(195), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodePetscii(listOf(195), true).single() shouldBe 'C'
Petscii.decodePetscii(listOf(192), false).single() shouldBe '─'
Petscii.decodePetscii(listOf(192), true).single() shouldBe '─'
Petscii.decodeScreencode(listOf(67), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodeScreencode(listOf(67), true).single() shouldBe 'C'
Petscii.decodeScreencode(listOf(64), false).single() shouldBe '─'
Petscii.decodeScreencode(listOf(64), true).single() shouldBe '─'
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
Petscii.decodePetscii(listOf(125), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(125), true).single() shouldBe '│'
Petscii.decodePetscii(listOf(221), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(221), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93), false).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(66), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
Petscii.decodeScreencode(listOf(66), true).single() shouldBe 'B'
}
})
+136
View File
@@ -0,0 +1,136 @@
package prog8tests
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.GlobalNamespace
import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestScoping: FunSpec({
test("testModulesParentIsGlobalNamespace") {
val src = """
main {
sub start() {
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
module.parent shouldBe instanceOf<GlobalNamespace>()
module.program shouldBeSameInstanceAs result.program
module.parent.parent shouldBe instanceOf<ParentSentinel>()
}
test("testAnonScopeVarsMovedIntoSubroutineScope") {
val src = """
main {
sub start() {
repeat {
ubyte xx = 99
xx++
}
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
withClue("no vars moved to main block") {
mainBlock.statements.any { it is VarDecl } shouldBe false
}
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
withClue("var from repeat anonscope must be moved up to subroutine") {
subroutineVars.size shouldBe 1
}
subroutineVars[0].name shouldBe "xx"
withClue("var should have been removed from repeat anonscope") {
repeatbody.statements.any { it is VarDecl } shouldBe false
}
val initassign = repeatbody.statements[0] as? Assignment
withClue("vardecl in repeat should be replaced by init assignment") {
initassign?.target?.identifier?.nameInSource shouldBe listOf("xx")
}
withClue("vardecl in repeat should be replaced by init assignment") {
(initassign?.value as? NumericLiteralValue)?.number?.toInt() shouldBe 99
}
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
}
test("testLabelsWithAnonScopes") {
val src = """
main {
sub start() {
uword addr
goto labeloutside
if true {
if true {
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto labeloutside
goto iflabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
iflabel:
}
repeat {
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto iflabel
goto labelinside
goto main.start.nested.nestedlabel
goto nested.nestedlabel
labelinside:
}
sub nested () {
nestedlabel:
addr = &nestedlabel
goto nestedlabel
goto main.start.nested.nestedlabel
}
labeloutside:
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>()
withClue("only one label in subroutine scope") {
labels.size shouldBe 1
}
}
})
+264
View File
@@ -0,0 +1,264 @@
package prog8tests
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.base.DataType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestSubroutines: FunSpec({
test("stringParameter") {
val text = """
main {
sub start() {
str text = "test"
asmfunc("text")
asmfunc(text)
asmfunc($2000)
func("text")
func(text)
func($2000)
}
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
uword t2 = thing as uword
asmfunc(thing)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.STR
asmfunc.statements.isEmpty() shouldBe true
func.isAsmSubroutine shouldBe false
func.parameters.single().type shouldBe DataType.STR
func.statements.size shouldBe 4
val paramvar = func.statements[0] as VarDecl
paramvar.name shouldBe "thing"
paramvar.datatype shouldBe DataType.STR
val assign = func.statements[2] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
withClue("str param in function body should not be transformed by normal compiler steps") {
assign.value shouldBe instanceOf<TypecastExpression>()
}
(assign.value as TypecastExpression).type shouldBe DataType.UWORD
val call = func.statements[3] as FunctionCallStatement
call.target.nameInSource.single() shouldBe "asmfunc"
withClue("str param in function body should not be transformed by normal compiler steps") {
call.args.single() shouldBe instanceOf<IdentifierReference>()
}
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
}
test("stringParameterAsmGen") {
val text = """
main {
sub start() {
str text = "test"
asmfunc("text")
asmfunc(text)
asmfunc($2000)
func("text")
func(text)
func($2000)
}
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
uword t2 = thing as uword
asmfunc(thing)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.STR
asmfunc.statements.single() shouldBe instanceOf<Return>()
func.isAsmSubroutine shouldBe false
withClue("asmgen should have changed str to uword type") {
func.parameters.single().type shouldBe DataType.UWORD
}
asmfunc.statements.last() shouldBe instanceOf<Return>()
func.statements.size shouldBe 5
func.statements[4] shouldBe instanceOf<Return>()
val paramvar = func.statements[0] as VarDecl
paramvar.name shouldBe "thing"
withClue("pre-asmgen should have changed str to uword type") {
paramvar.datatype shouldBe DataType.UWORD
}
val assign = func.statements[2] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
withClue("str param in function body should be treated as plain uword before asmgen") {
assign.value shouldBe instanceOf<IdentifierReference>()
}
(assign.value as IdentifierReference).nameInSource.single() shouldBe "thing"
val call = func.statements[3] as FunctionCallStatement
call.target.nameInSource.single() shouldBe "asmfunc"
withClue("str param in function body should be treated as plain uword and not been transformed") {
call.args.single() shouldBe instanceOf<IdentifierReference>()
}
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
}
test("arrayParameterNotYetAllowed_ButShouldPerhapsBe") {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
main {
sub start() {
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
errors.warnings.size shouldBe 0
errors.errors.single() shouldContain ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly"
}
// TODO allow this?
xtest("arrayParameter") {
val text = """
main {
sub start() {
ubyte[] array = [1,2,3]
asmfunc(array)
asmfunc([4,5,6])
asmfunc($2000)
asmfunc(12.345)
func(array)
func([4,5,6])
func($2000)
func(12.345)
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.ARRAY_UB
asmfunc.statements.isEmpty() shouldBe true
func.isAsmSubroutine shouldBe false
func.parameters.single().type shouldBe DataType.ARRAY_UB
func.statements.isEmpty() shouldBe true
}
test("testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead") {
val text="""
main {
sub thing(uword rr) {
ubyte xx = rr[1] ; should still work as var initializer that will be rewritten
ubyte yy
yy = rr[2]
uword other
ubyte zz = other[3]
}
sub start() {
ubyte[] array=[1,2,3]
thing(array)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val block = module.statements.single() as Block
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
block.name shouldBe "main"
thing.statements.size shouldBe 10 // rr, xx, xx assign, yy, yy assign, other, other assign 0, zz, zz assign, return
val xx = thing.statements[1] as VarDecl
withClue("vardecl init values must have been moved to separate assignments") {
xx.value shouldBe null
}
val assignXX = thing.statements[2] as Assignment
val assignYY = thing.statements[4] as Assignment
val assignZZ = thing.statements[8] as Assignment
assignXX.target.identifier!!.nameInSource shouldBe listOf("xx")
assignYY.target.identifier!!.nameInSource shouldBe listOf("yy")
assignZZ.target.identifier!!.nameInSource shouldBe listOf("zz")
val valueXXexpr = (assignXX.value as DirectMemoryRead).addressExpression as BinaryExpression
val valueYYexpr = (assignYY.value as DirectMemoryRead).addressExpression as BinaryExpression
val valueZZexpr = (assignZZ.value as DirectMemoryRead).addressExpression as BinaryExpression
(valueXXexpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
(valueYYexpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
(valueZZexpr.left as IdentifierReference).nameInSource shouldBe listOf("other")
(valueXXexpr.right as NumericLiteralValue).number.toInt() shouldBe 1
(valueYYexpr.right as NumericLiteralValue).number.toInt() shouldBe 2
(valueZZexpr.right as NumericLiteralValue).number.toInt() shouldBe 3
}
test("testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite") {
val text="""
main {
sub thing(uword rr) {
rr[10] = 42
}
sub start() {
ubyte[] array=[1,2,3]
thing(array)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val block = module.statements.single() as Block
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
block.name shouldBe "main"
thing.statements.size shouldBe 3 // "rr, rr assign, return void"
val assignRR = thing.statements[1] as Assignment
(assignRR.value as NumericLiteralValue).number.toInt() shouldBe 42
val memwrite = assignRR.target.memoryAddress
memwrite shouldNotBe null
val addressExpr = memwrite!!.addressExpression as BinaryExpression
(addressExpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
addressExpr.operator shouldBe "+"
(addressExpr.right as NumericLiteralValue).number.toInt() shouldBe 10
}
})
+176 -133
View File
@@ -1,175 +1,221 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeIn
import io.kotest.matchers.collections.shouldNotBeIn
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
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
import prog8.compilerinterface.*
import prog8tests.helpers.ErrorReporterForTests
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage {
class TestAbstractZeropage: FunSpec({
private val errors = ErrorReporter()
class DummyCompilationTarget: ICompilationTarget {
override val name: String = "dummy"
override val machine: IMachineDefinition
get() = throw NotImplementedError("dummy")
@Test
fun testNames() {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
throw NotImplementedError("dummy")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
throw NotImplementedError("dummy")
}
override fun memorySize(dt: DataType): Int {
throw NotImplementedError("dummy")
}
}
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1: Int = 0x10
override val SCRATCH_REG: Int = 0x11
override val SCRATCH_W1: Int= 0x20
override val SCRATCH_W2: Int = 0x30
init {
free.addAll(0..255)
removeReservedFromFreePool()
}
}
test("testAbstractZeropage") {
val compTarget = DummyCompilationTarget()
val zp = DummyZeropage(
CompilationOptions(
OutputType.RAW,
LauncherType.NONE,
ZeropageType.FULL,
listOf((0x50..0x5f)),
false,
false,
compTarget
)
)
zp.free.size shouldBe 256-6-16
}
})
class TestC64Zeropage: FunSpec({
val errors = ErrorReporterForTests()
test("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> {
shouldThrow<IllegalArgumentException> {
zp.allocate("varname", DataType.UBYTE, null, errors)
}
zp.allocate("varname2", DataType.UBYTE, null, errors)
}
@Test
fun testZpFloatEnable() {
test("testZpFloatEnable") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
shouldThrow<InternalCompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
shouldThrow<InternalCompilerException> {
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() {
test("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> {
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@Test
fun testZpDontuse() {
test("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.availableBytes() shouldBe 0
shouldThrow<InternalCompilerException> {
zp.allocate("", DataType.BYTE, null, errors)
}
}
@Test
fun testFreeSpacesBytes() {
test("testFreeSpacesBytes") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.availableBytes())
zp1.availableBytes() shouldBe 18
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(85, zp2.availableBytes())
zp2.availableBytes() shouldBe 85
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.availableBytes())
zp3.availableBytes() shouldBe 125
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.availableBytes())
zp4.availableBytes() shouldBe 238
zp4.allocate("test", DataType.UBYTE, null, errors)
assertEquals(237, zp4.availableBytes())
zp4.availableBytes() shouldBe 237
zp4.allocate("test2", DataType.UBYTE, null, errors)
assertEquals(236, zp4.availableBytes())
zp4.availableBytes() shouldBe 236
}
@Test
fun testFreeSpacesWords() {
test("testFreeSpacesWords") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(6, zp1.availableWords())
zp1.availableWords() shouldBe 6
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(38, zp2.availableWords())
zp2.availableWords() shouldBe 38
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(57, zp3.availableWords())
zp3.availableWords() shouldBe 57
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(116, zp4.availableWords())
zp4.availableWords() shouldBe 116
zp4.allocate("test", DataType.UWORD, null, errors)
assertEquals(115, zp4.availableWords())
zp4.availableWords() shouldBe 115
zp4.allocate("test2", DataType.UWORD, null, errors)
assertEquals(114, zp4.availableWords())
zp4.availableWords() shouldBe 114
}
@Test
fun testReservedSpace() {
test("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)
zp1.availableBytes() shouldBe 238
50 shouldBeIn zp1.free
100 shouldBeIn zp1.free
49 shouldBeIn zp1.free
101 shouldBeIn zp1.free
200 shouldBeIn zp1.free
255 shouldBeIn zp1.free
199 shouldBeIn 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)
zp2.availableBytes() shouldBe 139
50 shouldNotBeIn zp2.free
100 shouldNotBeIn zp2.free
49 shouldBeIn zp2.free
101 shouldBeIn zp2.free
200 shouldNotBeIn zp2.free
255 shouldNotBeIn zp2.free
199 shouldBeIn zp2.free
}
@Test
fun testBasicsafeAllocation() {
test("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())
zp.availableBytes() shouldBe 18
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
assertFailsWith<ZeropageDepletedError> {
shouldThrow<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)
loc shouldBeGreaterThan 0
}
assertEquals(0, zp.availableBytes())
assertFalse(zp.hasByteAvailable())
assertFalse(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
zp.availableBytes() shouldBe 0
zp.hasByteAvailable() shouldBe false
zp.hasWordAvailable() shouldBe false
shouldThrow<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertFailsWith<ZeropageDepletedError> {
shouldThrow<ZeropageDepletedError> {
zp.allocate("", DataType.UWORD, null, errors)
}
}
@Test
fun testFullAllocation() {
test("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())
zp.availableBytes() shouldBe 238
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
assertFalse(loc in zp.free)
loc shouldBeGreaterThan 3
loc shouldNotBeIn zp.free
val num = zp.availableBytes() / 2
for(i in 0..num-4) {
zp.allocate("", DataType.UWORD, null, errors)
}
assertEquals(6,zp.availableBytes())
zp.availableBytes() shouldBe 6
assertFailsWith<ZeropageDepletedError> {
shouldThrow<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null, errors)
}
@@ -178,88 +224,85 @@ class TestC64Zeropage {
zp.allocate("", DataType.UBYTE, null, errors)
}
assertEquals(0, zp.availableBytes())
assertFalse(zp.hasByteAvailable())
assertFalse(zp.hasWordAvailable())
assertFailsWith<ZeropageDepletedError> {
zp.availableBytes() shouldBe 0
zp.hasByteAvailable() shouldBe false
zp.hasWordAvailable() shouldBe false
shouldThrow<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null, errors)
}
}
@Test
fun testEfficientAllocation() {
test("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())
zp.availableBytes() shouldBe 18
zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0a
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9b
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9e
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbe
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0e
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9
zp.availableBytes() shouldBe 0
}
@Test
fun testReservedLocations() {
test("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")
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1 shouldBe zp.SCRATCH_REG
}
}
}
})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
private val errors = ErrorReporter()
class TestCx16Zeropage: FunSpec({
val errors = ErrorReporterForTests()
@Test
fun testReservedLocations() {
test("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")
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1 shouldBe zp.SCRATCH_REG
}
}
@Test
fun testFreeSpacesBytes() {
test("testFreeSpacesBytes") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.availableBytes())
zp1.availableBytes() shouldBe 88
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp2.availableBytes())
zp2.availableBytes() shouldBe 175
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp3.availableBytes())
zp3.availableBytes() shouldBe 216
zp3.allocate("test", DataType.UBYTE, null, errors)
assertEquals(215, zp3.availableBytes())
zp3.availableBytes() shouldBe 215
zp3.allocate("test2", DataType.UBYTE, null, errors)
assertEquals(214, zp3.availableBytes())
zp3.availableBytes() shouldBe 214
}
@Test
fun testFreeSpacesWords() {
test("testFreeSpacesWords") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(108, zp1.availableWords())
zp1.availableWords() shouldBe 108
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(87, zp2.availableWords())
zp2.availableWords() shouldBe 87
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(44, zp3.availableWords())
zp3.availableWords() shouldBe 44
zp3.allocate("test", DataType.UWORD, null, errors)
assertEquals(43, zp3.availableWords())
zp3.availableWords() shouldBe 43
zp3.allocate("test2", DataType.UWORD, null, errors)
assertEquals(42, zp3.availableWords())
zp3.availableWords() shouldBe 42
}
@Test
fun testReservedSpace() {
test("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)
zp1.availableBytes() shouldBe 216
0x22 shouldBeIn zp1.free
0x80 shouldBeIn zp1.free
0xff shouldBeIn zp1.free
0x02 shouldNotBeIn zp1.free
0x21 shouldNotBeIn zp1.free
}
}
})
+1 -1
View File
@@ -2,7 +2,7 @@
main {
str myBar = "main.bar"
;foo_bar:
; %asminclude "foo_bar.asm" ; FIXME: should be accessible from inside start() but give assembler error. See github issue #62
; %asminclude "foo_bar.asm" ; TODO: should be accessible from inside start() but give assembler error. See github issue #62
sub start() {
txt.print(myBar)
txt.print(&foo_bar)
-9
View File
@@ -1,9 +0,0 @@
%import textio
%import "foo_bar.p8"
main {
str myBar = "main.bar"
sub start() {
txt.print(myBar)
txt.print(foo.bar)
}
}
@@ -1,21 +1,36 @@
package prog8tests.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
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.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
val DummyFunctions = object : IBuiltinFunctions {
internal val DummyFunctions = object : 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()
}
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}
@@ -1,25 +1,28 @@
package prog8tests.helpers
import prog8.ast.base.Position
import prog8.compiler.IErrorReporter
class ErrorReporterForTests: IErrorReporter {
import prog8.compilerinterface.IErrorReporter
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add(msg)
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add(msg)
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
errors.clear()
warnings.clear()
}
+42 -11
View File
@@ -1,21 +1,31 @@
package prog8tests.helpers
import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import prog8.ast.Program
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.C64Target
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.outputDir
import java.nio.file.Path
import kotlin.io.path.name
import kotlin.test.assertFalse
import kotlin.test.assertTrue
internal fun CompilationResult.assertSuccess(description: String = ""): CompilationResult {
assertTrue(success, "expected successful compilation but failed $description")
withClue("expected successful compilation but failed $description") {
success shouldBe true
}
return this
}
internal fun CompilationResult.assertFailure(description: String = ""): CompilationResult {
assertFalse(success, "expected failure to compile but succeeded $description")
withClue("expected failure to compile but succeeded $description") {
success shouldBe false
}
return this
}
@@ -28,18 +38,24 @@ internal fun compileFile(
optimize: Boolean,
fileDir: Path,
fileName: String,
outputDir: Path = prog8tests.helpers.outputDir
outputDir: Path = prog8tests.ast.helpers.outputDir,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
optFloatExpr: Boolean = true
) : CompilationResult {
val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath)
return compileProgram(
filepath,
optimize,
writeAssembly = true,
optimizeFloatExpressions = optFloatExpr,
writeAssembly = writeAssembly,
slowCodegenWarnings = false,
quietAssembler = true,
platform.name,
libdirs = listOf(),
outputDir
sourceDirs = listOf(),
outputDir,
errors = errors ?: ErrorReporterForTests()
)
}
@@ -51,10 +67,25 @@ internal fun compileFile(
internal fun compileText(
platform: ICompilationTarget,
optimize: Boolean,
sourceText: String
sourceText: String,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
optFloatExpr: Boolean = true
) : CompilationResult {
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
filePath.toFile().writeText(sourceText)
return compileFile(platform, optimize, filePath.parent, filePath.name)
return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
}
internal fun generateAssembly(
program: Program,
options: CompilationOptions? = null
): IAssemblyProgram {
val coptions = options ?: CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, true, C64Target)
val zp = C64MachineDefinition.C64Zeropage(coptions)
coptions.compTarget.machine.zeropage=zp
val asmgen = AsmGen(program, ErrorReporterForTests(), zp, coptions, C64Target, outputDir)
return asmgen.compileToAssembly()
}
+7 -28
View File
@@ -1,47 +1,27 @@
plugins {
id 'java'
id "java"
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.8"
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenCentral()
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation 'org.antlr:antlr4-runtime:4.9.2'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets {
main {
java {
@@ -59,7 +39,6 @@ sourceSets {
}
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
+2 -2
View File
@@ -13,8 +13,8 @@
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="library" name="hamcrest" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
</component>
</module>
@@ -12,9 +12,8 @@ import prog8.ast.walk.IAstVisitor
/**
* Produces Prog8 source text from a [Program] (AST node),
* passing it as a String to the specified receiver function.
* TODO: rename/refactor to make proper sense in the presence of class [prog8.parser.SourceCode]
*/
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0
private fun indent(s: String) = " ".repeat(scopelevel) + s
@@ -98,7 +97,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again
val paramNames = (decl.definingScope as? Subroutine)?.parameters?.map { it.name }
val paramNames = decl.definingSubroutine?.parameters?.map { it.name }
if(paramNames!=null && decl.name in paramNames)
return
@@ -304,7 +303,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(assignment: Assignment) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.left isSameAs assignment.target
&& binExpr.operator !in setOf("and", "or", "xor")
&& binExpr.operator !in arrayOf("and", "or", "xor")
&& binExpr.operator !in comparisonOperators) {
// we only support the inplace assignments of the form A = A <operator> <value>
assignment.target.accept(this)
+175 -267
View File
@@ -3,7 +3,6 @@ package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
@@ -21,117 +20,12 @@ interface IFunctionCall {
var args: MutableList<Expression>
}
interface INameScope {
val name: String
interface IStatementContainer {
val position: Position
val statements: MutableList<Statement>
val parent: Node
val statements: MutableList<Statement>
fun linkParents(parent: Node)
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> if(stmt.body.name==name) return stmt.body
is UntilLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return null
}
fun getLabelOrVariable(name: String): Statement? {
// this is called A LOT and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
if (stmt is AnonymousScope) {
val sub = stmt.getLabelOrVariable(name)
if(sub!=null)
return sub
}
}
return null
}
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
if(scopedName.size>1) {
// 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
for(name in scopedName.dropLast(1)) {
scope = scope?.subScope(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
} else {
// unqualified name
// 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.getLabelOrVariable(scopedName[0])
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
fun remove(stmt: Statement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
@@ -140,11 +34,11 @@ interface INameScope {
fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>()
fun find(scope: INameScope) {
fun find(scope: IStatementContainer) {
scope.statements.forEach {
when(it) {
is Label -> result.add(it)
is INameScope -> find(it)
is IStatementContainer -> find(it)
is IfStatement -> {
find(it.truepart)
find(it.elsepart)
@@ -162,22 +56,6 @@ interface INameScope {
return result
}
fun nextSibling(stmt: Statement): Statement? {
val nextIdx = statements.indexOfFirst { it===stmt } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
fun previousSibling(stmt: Statement): Statement? {
val previousIdx = statements.indexOfFirst { it===stmt } - 1
return if(previousIdx>=0)
statements[previousIdx]
else
null
}
fun indexOfChild(stmt: Statement): Int {
val idx = statements.indexOfFirst { it===stmt }
if(idx>=0)
@@ -185,6 +63,159 @@ interface INameScope {
else
throw FatalAstException("attempt to find a non-child")
}
fun isEmpty(): Boolean = statements.isEmpty()
fun isNotEmpty(): Boolean = statements.isNotEmpty()
fun searchSymbol(name: String): Statement? {
// this is called quite a lot and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference...
for (stmt in statements) {
when(stmt) {
// is INamedStatement -> {
// if(stmt.name==name) return stmt
// }
is VarDecl -> {
if(stmt.name==name) return stmt
}
is Label -> {
if(stmt.name==name) return stmt
}
is Subroutine -> {
if(stmt.name==name)
return stmt
}
is AnonymousScope -> {
val found = stmt.searchSymbol(name)
if(found!=null)
return found
}
is IfStatement -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
}
is BranchStatement -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
}
is ForLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is WhileLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is RepeatLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is UntilLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is WhenStatement -> {
stmt.choices.forEach {
val found = it.statements.searchSymbol(name)
if(found!=null)
return found
}
}
else -> {
// NOTE: when other nodes containing AnonymousScope are introduced,
// these should be added here as well to look into!
}
}
}
return null
}
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }
}
}
interface INameScope: IStatementContainer, INamedStatement {
fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope
fun lookup(scopedName: List<String>) : Statement? {
return if(scopedName.size>1)
lookupQualified(scopedName)
else {
lookupUnqualified(scopedName[0])
}
}
private fun lookupQualified(scopedName: List<String>): Statement? {
// a scoped name refers to a name in another namespace.
// look "up" from our current scope to search for the correct one.
val localScope = this.subScope(scopedName[0])
if(localScope!=null)
return resolveLocally(localScope, scopedName.drop(1))
var statementScope = this
while(statementScope !is GlobalNamespace) {
if(statementScope !is Module && statementScope.name==scopedName[0]) {
return resolveLocally(statementScope, scopedName.drop(1))
} else {
statementScope = (statementScope as Node).definingScope
}
}
// not found, try again but now assume it's a globally scoped name starting with the name of a block
for(module in (this as Node).definingModule.program.modules) {
module.statements.forEach {
if(it is Block && it.name==scopedName[0])
return it.lookup(scopedName)
}
}
return null
}
private fun resolveLocally(startScope: INameScope, name: List<String>): Statement? {
var scope: INameScope? = startScope
for(part in name.dropLast(1)) {
scope = scope!!.subScope(part)
if(scope==null)
return null
}
return scope!!.searchSymbol(name.last())
}
private fun lookupUnqualified(name: String): Statement? {
val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names
if(name in builtinFunctionsNames) {
// builtin functions always exist, return a dummy placeholder for them
val builtinPlaceholder = Label("builtin::$name", this.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// search for the unqualified name in the current scope (and possibly in any anonymousscopes it may contain)
// if it's not found there, jump up one higher in the namespaces and try again.
var statementScope = this
while(statementScope !is GlobalNamespace) {
val symbol = statementScope.searchSymbol(name)
if(symbol!=null)
return symbol
else
statementScope = (statementScope as Node).definingScope
}
return null
}
// private fun getNamedSymbol(name: String): Statement? =
// statements.singleOrNull { it is INamedStatement && it.name==name }
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
}
@@ -234,127 +265,18 @@ interface Node {
}
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer): Node {
private val _modules = mutableListOf<Module>()
val modules: List<Module> = _modules
val namespace: GlobalNamespace = GlobalNamespace(modules, builtinFunctions.names)
init {
// insert a container module for all interned strings later
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, null)
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
internedStringsModule.statements.add(block)
_modules.add(0, internedStringsModule)
internedStringsModule.linkParents(namespace)
internedStringsModule.program = this
}
fun addModule(module: Module): Program {
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkParents(namespace)
module.program = this
return this
}
fun moveModuleToFront(module: Module): Program {
require(_modules.contains(module))
{ "Not a module of this program: '${module.name}'"}
_modules.remove(module)
_modules.add(0, module)
return this
}
val allBlocks: List<Block>
get() = modules.flatMap { it.statements.filterIsInstance<Block>() }
val entrypoint: Subroutine
get() {
val mainBlocks = allBlocks.filter { it.name == "main" }
return when (mainBlocks.size) {
0 -> throw FatalAstException("no 'main' block")
1 -> mainBlocks[0].subScope("start") as Subroutine
else -> throw FatalAstException("more than one 'main' block")
}
}
val mainModule: Module // TODO: rename Program.mainModule - it's NOT necessarily the one containing the main *block*!
get() = modules.first { it.name!=internedStringsModuleName }
val definedLoadAddress: Int
get() = mainModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
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 = internedStringsUnique[key]
if (existing != null)
return existing
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
}
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(_) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(namespace)
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module)
val idx = _modules.indexOfFirst { it===node }
_modules[idx] = replacement
replacement.parent = this // TODO: why not replacement.program = this; replacement.linkParents(namespace)?!
}
}
open class Module(override val name: String,
override var statements: MutableList<Statement>,
open class Module(final override var statements: MutableList<Statement>,
final override val position: Position,
val source: SourceCode?) : Node, INameScope {
val source: SourceCode) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
override val name = source.origin
.substringBeforeLast(".")
.substringAfterLast("/")
.substringAfterLast("\\")
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
address
@@ -366,6 +288,11 @@ open class Module(override val name: String,
statements.forEach {it.linkParents(this)}
}
fun linkIntoProgram(program: Program) {
this.program = program
linkParents(program.namespace)
}
override val definingScope: INameScope
get() = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) {
@@ -380,16 +307,20 @@ open class Module(override val name: String,
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
val isLibrary get() = (source == null) || source.isFromResources
val isLibrary get() = source.isFromResources
}
class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope {
class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override fun lookup(scopedName: List<String>): Statement? {
throw NotImplementedError("use lookup on actual ast node instead")
}
override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) }
}
@@ -397,29 +328,6 @@ class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunction
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("cannot replace anything in the namespace")
}
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// 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)
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// lookup something from the module.
return when (val stmt = localContext.definingModule.lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw SyntaxError("invalid identifier target type", stmt.position)
}
}
}
object BuiltinFunctionScopePlaceholder : INameScope {
Loaded 100 of 162 files, more files were not shown because too many files have changed in this diff. Show more