Compare commits

...

120 Commits
v7.2 ... v7.4

Author SHA1 Message Date
1f346230e3 release 7.4 2021-11-30 22:50:12 +01:00
a2860a7c8c todo 2021-11-30 22:45:43 +01:00
df997e5d3b don't write the asm file twice 2021-11-30 03:47:57 +01:00
a67a82c921 tweak 2021-11-30 03:05:25 +01:00
ea0fe8d3d2 PrefixExpression doesn't cause clobber risk 2021-11-30 02:32:37 +01:00
2560042ac7 fix compiler crashes on in-place operations on cx16 registers or invalid signed types 2021-11-30 02:27:37 +01:00
3d1d0696b9 refactor compiler arguments passing 2021-11-30 01:40:21 +01:00
83f893f50b doc 2021-11-30 00:54:03 +01:00
9ecf95b075 fix syntaxerror in const processing of ranges if it contained variables 2021-11-29 23:36:41 +01:00
7748c261da rsave/rrestore moved from sys to builtin function to solve the stack related problem when calling it as a regular subroutine 2021-11-29 23:13:04 +01:00
a2db44f80c also consider Y register for clobber check for functioncall arguments 2021-11-29 22:09:05 +01:00
b438d8aec0 fix invalid range size check when stepval is not a positive integer 2021-11-29 02:01:19 +01:00
4ac169b210 formatting 2021-11-29 01:25:21 +01:00
56dc6d7f1e comment 2021-11-29 01:10:11 +01:00
45b8762188 use inc/ina instead of adc 2021-11-29 00:07:15 +01:00
cafab98d10 correction 2021-11-28 18:59:36 +01:00
9256f910f0 rollback binexpr splitting, caused slowdowns 2021-11-28 18:50:05 +01:00
32068a832a split some additional binary expressions to avoid stack-based evaluation 2021-11-28 18:27:28 +01:00
47c2c0376a added some cpu stack related assembly-level optimizations 2021-11-28 17:27:01 +01:00
f0dadc4a43 optimize 1-arg functioncalls 2021-11-28 16:55:10 +01:00
960b60cd2d tweak 2021-11-28 14:06:12 +01:00
d6abd72e55 fix push() of signed values 2021-11-28 13:01:46 +01:00
0a568f2530 fix the check of double-defined subroutine variables 2021-11-28 12:52:32 +01:00
c52aa648c0 use an AnonymousScope to contain GoSub changes instead of adding separate statements 2021-11-28 12:09:13 +01:00
3d23b39f4c moved A to the end of the param list to avoid having to store its value 2021-11-28 04:03:18 +01:00
f3a4048ebf improved setting Carry bit as asmsub parameter 2021-11-28 03:31:32 +01:00
1b07637cc4 better error checking for wrong pop() 2021-11-28 02:49:18 +01:00
68b75fd558 fix: also allow pass-by-reference arguments to builtin functions that accept UWORD (adds implicit type cast) 2021-11-28 02:34:53 +01:00
7c5ec1853d nice error message if pop() argument is wrong 2021-11-28 02:20:35 +01:00
e8f4686430 undid failed attempt of using sys.push/sys.pop for stack args - now using new push(), pushw(), pop(), popw() builtin functions 2021-11-28 01:22:40 +01:00
02348924d0 failed attempt of using sys.push/pop for stack args 2021-11-27 23:52:47 +01:00
69dcb4dbda fix reporting of (not) unused code after GoSub jump 2021-11-27 21:22:34 +01:00
c838821615 refactor fuction arguments codegen a bit 2021-11-27 21:14:21 +01:00
8b4ac7801f fix sys.push() signature for c64 2021-11-27 20:18:41 +01:00
64a411628d doc fixes 2021-11-27 19:58:08 +01:00
e8e25c6fd6 added sys.push() and sys.pop() to put values on cpu stack. Added missing builtin functions to syntax-files. 2021-11-27 18:09:15 +01:00
62485b6851 allow assigns to asmsub parameters (registers), but this is not very useful in practice. 2021-11-27 15:41:44 +01:00
54025d2bf5 small refactor and spelling fixes 2021-11-27 14:49:18 +01:00
f5ebf79e71 make sure X register is also saved if needed when GoSub is used 2021-11-26 22:11:52 +01:00
66d5490702 just added missing FAC2 assign possibility 2021-11-26 21:34:00 +01:00
42fe052f9f got rid of old getScopedSymbolNameForTarget routine 2021-11-26 21:09:29 +01:00
58d9c46a9b got rid of old makeScopedName routine 2021-11-26 20:56:30 +01:00
e4648e2138 proper rounding of builtin functions that return int from float 2021-11-26 20:32:12 +01:00
110e047681 replace subroutine calls (statement) by GoSub 2021-11-26 19:47:01 +01:00
17d403d812 Merge branch 'ref-subroutine-param' into v7.4-dev
# Conflicts:
#	compilerAst/src/prog8/ast/AstToplevel.kt
2021-11-26 01:12:14 +01:00
0a53bd4956 fix parameter name conflict 2021-11-26 01:01:59 +01:00
e52d05c7db fix some scoping related symbol lookup issues, clarified scoping rules in docs 2021-11-23 23:43:23 +01:00
b00db4f8a2 no longer report unknown type errors as well for unknown symbols,
added a bunch more unit tests for symbol scoping rules
2021-11-23 22:45:57 +01:00
0c2f30fd45 links to 6502 bresenham line algorithms 2021-11-23 21:51:18 +01:00
e08871c637 oops! replace phx/plx 65C02 (cx16) instructions by 6502 (c64) compatible alternative.
Couldn't assemble code that used some of the routines in conv on c64 before...
2021-11-22 21:02:43 +01:00
ff715881bc allow scoped identifiers to reference a subroutine parameter directly.
also for asmsubroutines, but the asm generation for that is not yet done.
2021-11-21 23:21:39 +01:00
0e2e5ffa52 fix parameter name conflict 2021-11-21 22:12:35 +01:00
8095c4c155 added GoSub node (internal use only later for calling subroutines) 2021-11-21 16:23:48 +01:00
e86246a985 todo 2021-11-21 14:00:19 +01:00
625aaa02eb documented the compiler's command line options in more detail 2021-11-21 13:53:22 +01:00
787e35c9f3 asm optimizer can now also see of a symbol reference if it is in IO space or not (to a certain extent), so that these instructions are no longer optimized away 2021-11-21 13:12:51 +01:00
8887e6af91 fix substituting 0 only if its actually the same variable that's substituted 2021-11-21 12:34:57 +01:00
dde4c751da version 7.4-dev 2021-11-21 03:28:13 +01:00
3c39baf1d6 don't optimize seemingly redundant assembly instructions away that manipulate IO memory space 2021-11-21 03:24:03 +01:00
b292124f3c replaced many short/int values by unsigned types if appropriate 2021-11-21 00:55:56 +01:00
c0035ba1a2 char encodings now use UByte type instead of short 2021-11-21 00:07:17 +01:00
2491509c6a add assignment optimization X=value-X --> X=-X ; X+=value (to avoid need of stack-evaluation) 2021-11-20 23:43:10 +01:00
107935ed31 add some more const folding patterns 2021-11-20 22:47:49 +01:00
31491c62c5 add some more const folding patterns 2021-11-20 22:40:12 +01:00
eacf8b896a fix augmentable check to align with what the asmgen understands 2021-11-20 22:06:51 +01:00
7936fc5bd8 tiny optimization of negating a register 2021-11-20 21:42:55 +01:00
adfaddbcf4 give a nicer error when given a wrong compilation target. 2021-11-20 18:30:55 +01:00
74db5c6be7 fix referencesIdentifier() and better removal of unnecessary assignments 2021-11-20 17:41:41 +01:00
f9399bcce7 r=(q+r)-c and r=q+(r-c) are now both also 'augmentable', and BinExprSplitter doesn't check for associativeOperator anymore 2021-11-20 02:03:32 +01:00
87600b23db fix constvalue parent linkage for prefix and typecast 2021-11-20 00:20:35 +01:00
cedfb17b18 fix too aggressive removal of vars that weren't completely unused 2021-11-19 22:49:35 +01:00
fa4c83df6b added 3 tests for discovered problems 2021-11-18 23:55:20 +01:00
42c8720e8b fix float rounding tests 2021-11-18 22:54:49 +01:00
b334d89715 refactor and fix the way memory addresses are checked to be in IO space or regular ram 2021-11-18 22:47:58 +01:00
4f5d36a84d optimization added: bitwise operations with a negative constant number -> replace the number by its positive 2 complement 2021-11-18 02:51:42 +01:00
8f379e2262 give an error when initializing an integer var with a float value instead of silently rounding 2021-11-18 01:56:11 +01:00
fa11a6e18b removed faulty and too aggressive assembly optimization of double-store 2021-11-18 01:43:22 +01:00
52bedce8f4 added test for assignment.isAugmented 2021-11-18 01:05:16 +01:00
4c82af36e6 fix improperly changed behavior about =0 initializer 2021-11-18 00:17:22 +01:00
dafa0d9138 fix compiler crash bug due to reused ast expression nodes. Now all (relevant) Nodes have a copy() function to make a clone. 2021-11-17 23:05:59 +01:00
2e0450d7ed fix bug where variable=0 initializer was forgotten if vardecl is followed by an augmented assignment 2021-11-17 22:31:43 +01:00
6af3209d4d add more const foldings 2021-11-17 00:57:00 +01:00
5d362047e2 add some more comparison expression optimizations to compare against 0 if possible 2021-11-17 00:04:52 +01:00
f48d6ca9f8 simplified NumericLiteral to always just contain a Double instead of a Number for the value 2021-11-16 23:52:54 +01:00
964e8e0a17 update to Kotlin 1.6.0 2021-11-16 22:36:23 +01:00
1f60a2d8b9 comments 2021-11-15 01:30:12 +01:00
5fd83f2757 version 7.3 2021-11-14 22:55:13 +01:00
c80df4140b until-loop condition now also simplified to avoid stack-eval 2021-11-14 22:51:02 +01:00
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
ab2d1122a9 conditional expressions are optimized more intelligently (simple ones are not split off in separate assignments) 2021-11-14 12:38:56 +01:00
5190594c8a added several more assembly-level optimizations to remove redundant instructions 2021-11-14 12:23:46 +01:00
c858ceeb58 compiler shouldn't use cx16.r15 as temp var 2021-11-14 02:38:59 +01:00
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
00c6f74481 tweak temp float 2021-11-13 12:56:59 +01:00
2177ba0ed2 added signed versions of the cx16 virtual registers 2021-11-13 02:42:21 +01:00
3483515346 preparing for more optimizations 2021-11-12 23:23:51 +01:00
75a06d2a40 preparing for more optimizations 2021-11-12 02:17:37 +01:00
53ac11983b better unused variable removal 2021-11-11 03:03:21 +01:00
69f4a4d4f8 tweak expr.typecastTo() a bit 2021-11-11 00:15:09 +01:00
222bcb808f optimize load-store-load combo in output asm 2021-11-10 23:47:35 +01:00
686483f51a fixed division of signed byte number by 2. (!) 2021-11-10 00:17:56 +01:00
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
84dafda0e4 fix error message for type mismatch on builtin-function parameter 2021-11-09 22:19:07 +01:00
b909facfe5 fix compiler stackoverflow crash on certain typecasted expressions containing floats. 2021-11-09 19:31:19 +01:00
7780d94de1 discovered crash related to float typecasting in asm assignment codegen 2021-11-09 03:45:07 +01:00
f2c440e466 new sin/cos idea 2021-11-09 02:38:43 +01:00
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
4cb383dccb discovered crash about storage size mismatch 2021-11-08 21:44:06 +01:00
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
857724c7e6 attempt to make if-statement not use stack eval anymore 2021-11-08 19:07:36 +01:00
a9b0400d13 fixed 'not' operator priority: it now has higher priority as or/and/xor. 2021-11-08 18:38:04 +01:00
2d1e5bbc7e remove unimportant empty tests 2021-11-08 17:00:10 +01:00
60627ce756 kotest migration done, fixes #70 2021-11-08 16:19:24 +01:00
7961a09d16 converting compiler module's testcases to kotest assertions 2021-11-08 16:14:22 +01:00
613efcacc7 converting compiler module's testcases to kotest (ongoing) 2021-11-08 15:08:48 +01:00
7e8db16e18 moved to kotest assertions in compilerAst module tests 2021-11-07 21:18:18 +01:00
1fbbed7e23 remove unittest machinery from modules that don't have tests 2021-11-07 17:34:14 +01:00
984272beb4 migrated compilerAst module to KoTest (but not finished with the assertions yet) 2021-11-07 17:25:53 +01:00
b9ce94bb68 migrated codeGeneration module to KoTest 2021-11-07 15:40:05 +01:00
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
137 changed files with 7785 additions and 5120 deletions

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>

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>

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>

5
.idea/misc.xml generated
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>

View File

@ -3,6 +3,7 @@ plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.8"
}
java {
@ -18,11 +19,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
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'
}
sourceSets {
@ -43,7 +40,6 @@ sourceSets {
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.

View File

@ -13,7 +13,7 @@
<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" scope="TEST" name="hamcrest" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
</component>
</module>

View File

@ -1,35 +1,41 @@
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.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
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) =
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
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
else -> Int.MIN_VALUE
}
}
}

View File

@ -5,7 +5,12 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
@ -13,23 +18,28 @@ import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
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) =
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
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
else -> Int.MIN_VALUE
}
}
}

View File

@ -16,12 +16,12 @@ object C64MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801
override val RAW_LOAD_ADDRESS = 0xc000
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0xc000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
@ -56,7 +56,7 @@ object C64MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
@ -76,10 +76,10 @@ object C64MachineDefinition: IMachineDefinition {
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
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fb+$fc
init {
@ -87,9 +87,9 @@ object C64MachineDefinition: IMachineDefinition {
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(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
free.addAll(0x04u..0xf9u)
free.add(0xffu)
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(
@ -106,7 +106,7 @@ object C64MachineDefinition: IMachineDefinition {
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernal work storage area'
))
).map{it.toUInt()})
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
@ -118,7 +118,7 @@ object C64MachineDefinition: IMachineDefinition {
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
))
).map{it.toUInt()})
}
if(options.zeropage!= ZeropageType.DONTUSE) {
@ -126,7 +126,7 @@ object C64MachineDefinition: IMachineDefinition {
// 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,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
free.clear()
@ -137,11 +137,11 @@ object C64MachineDefinition: IMachineDefinition {
}
}
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte):
IMachineFloat {
companion object {
val zero = Mflpt5(0, 0, 0, 0, 0)
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
@ -175,11 +175,11 @@ object C64MachineDefinition: IMachineDefinition {
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
exponent.toUByte(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
(mantLong.and(0x000000ffL)).toUByte())
}
}
}
@ -187,7 +187,7 @@ object C64MachineDefinition: IMachineDefinition {
override fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0 - 128
val exp = b0.toInt() - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000

View File

@ -1065,15 +1065,15 @@ object Petscii {
else -> chr
}
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
return screencode?.toShort() ?: when (chr) {
'\u0000' -> 0.toShort()
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toShort()
(chr.code - 0x8000).toUByte()
}
else -> {
val case = if (lowercase) "lower" else "upper"
@ -1095,7 +1095,7 @@ object Petscii {
}
}
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
return petscii.map {
val code = it.toInt()
if(code<0 || code>= decodingPetsciiLowercase.size)
@ -1104,15 +1104,15 @@ object Petscii {
}.joinToString("")
}
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toShort() ?: when (chr) {
'\u0000' -> 0.toShort()
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toShort()
(chr.code - 0x8000).toUByte()
}
else -> {
val case = if (lowercase) "lower" else "upper"
@ -1134,7 +1134,7 @@ object Petscii {
}
}
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
return screencode.map {
val code = it.toInt()
if(code<0 || code>= decodingScreencodeLowercase.size)
@ -1143,38 +1143,37 @@ object Petscii {
}.joinToString("")
}
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> {
val code = when {
petscii_code < 0 -> return Err(CharConversionException("petscii code out of range"))
petscii_code <= 0x1f -> petscii_code + 128
petscii_code <= 0x3f -> petscii_code.toInt()
petscii_code <= 0x5f -> petscii_code - 64
petscii_code <= 0x7f -> petscii_code - 32
petscii_code <= 0x9f -> petscii_code + 64
petscii_code <= 0xbf -> petscii_code - 64
petscii_code <= 0xfe -> petscii_code - 128
petscii_code == 255.toShort() -> 95
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {
val code: UInt = when {
petscii_code <= 0x1fu -> petscii_code + 128u
petscii_code <= 0x3fu -> petscii_code.toUInt()
petscii_code <= 0x5fu -> petscii_code - 64u
petscii_code <= 0x7fu -> petscii_code - 32u
petscii_code <= 0x9fu -> petscii_code + 64u
petscii_code <= 0xbfu -> petscii_code - 64u
petscii_code <= 0xfeu -> petscii_code - 128u
petscii_code == 255.toUByte() -> 95u
else -> return Err(CharConversionException("petscii code out of range"))
}
if(inverseVideo)
return Ok((code or 0x80).toShort())
return Ok(code.toShort())
if(inverseVideo) {
return Ok((code or 0x80u).toUByte())
}
return Ok(code.toUByte())
}
fun scr2petscii(screencode: Short): Result<Short, CharConversionException> {
val petscii = when {
screencode < 0 -> return Err(CharConversionException("screencode out of range"))
screencode <= 0x1f -> screencode + 64
screencode <= 0x3f -> screencode.toInt()
screencode <= 0x5d -> screencode +123
screencode == 0x5e.toShort() -> 255
screencode == 0x5f.toShort() -> 223
screencode <= 0x7f -> screencode + 64
screencode <= 0xbf -> screencode - 128
screencode <= 0xfe -> screencode - 64
screencode == 255.toShort() -> 191
fun scr2petscii(screencode: UByte): Result<UByte, CharConversionException> {
val petscii: UInt = when {
screencode <= 0x1fu -> screencode + 64u
screencode <= 0x3fu -> screencode.toUInt()
screencode <= 0x5du -> screencode +123u
screencode == 0x5e.toUByte() -> 255u
screencode == 0x5f.toUByte() -> 223u
screencode <= 0x7fu -> screencode + 64u
screencode <= 0xbfu -> screencode - 128u
screencode <= 0xfeu -> screencode - 64u
screencode == 255.toUByte() -> 191u
else -> return Err(CharConversionException("screencode out of range"))
}
return Ok(petscii.toShort())
return Ok(petscii.toUByte())
}
}

View File

@ -1,10 +1,16 @@
package prog8.compiler.target.cpu6502.codegen
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
import prog8.ast.Program
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.VarDecl
import prog8.compilerinterface.IMachineDefinition
fun optimizeAssembly(lines: MutableList<String>): Int {
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
var numberOfOptimizations = 0
@ -31,7 +37,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
numberOfOptimizations++
}
mods = optimizeStoreLoadSame(linesByFour)
mods = optimizeStoreLoadSame(linesByFour, machine, program)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@ -46,7 +52,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen)
mods = optimizeSameAssignments(linesByFourteen, machine, program)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
@ -111,22 +117,22 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
return mods
}
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
// The better place to do this is in the Compiler instead and never create these types of assembly, but hey
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")) {
@ -135,9 +141,13 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val thirdvalue = fifth.substring(4)
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))
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
val address1 = getAddressArg(first, program)
val address2 = getAddressArg(second, program)
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null))
}
}
}
@ -145,8 +155,10 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val firstvalue = first.substring(4)
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))
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address))
mods.add(Modification(lines[2].index, true, null))
}
}
@ -154,12 +166,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 +179,147 @@ 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) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
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) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
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) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
val reg2 = second[2]
mods.add(Modification(lines[1].index, false, " ta$reg2"))
mods.add(Modification(lines[2].index, true, null))
}
}
}
/*
sta A
sta A
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
if(first[2]==second[2]) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
if(firstvalue==secondvalue) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[1].index, true, null))
}
}
}
}
}
return mods
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): 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) 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,32 +331,101 @@ 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
val address = getAddressArg(lines[2].value, program)
address==null || !machine.isIOAddress(address)
}
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))
}
}
else if(first=="pha" && second=="pla" ||
first=="phx" && second=="plx" ||
first=="phy" && second=="ply" ||
first=="php" && second=="plp") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
} else if(first=="pha" && second=="plx") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tax"))
} else if(first=="pha" && second=="ply") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tay"))
} else if(first=="phx" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txa"))
} else if(first=="phx" && second=="ply") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txy"))
} else if(first=="phy" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tya"))
} else if(first=="phy" && second=="plx") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tyx"))
}
}
return mods
}
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
private fun getAddressArg(line: String, program: Program): UInt? {
val loadArg = line.trimStart().substring(3).trim()
return when {
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
loadArg.startsWith('%') -> loadArg.substring(1).toUIntOrNull(2)
loadArg.startsWith('#') -> null
loadArg.startsWith('(') -> null
loadArg[0].isLetter() -> {
val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) {
val identifier = identMatch.value
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
if(decl!=null) {
when(decl.type){
VarDeclType.VAR -> null
VarDeclType.CONST,
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
}
}
else null
} else null
}
else -> loadArg.substring(1).toUIntOrNull()
}
}
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)
|| (" ina" in first || "\tina" in first) && (" dea" in second || "\tdea" in second)
|| (" inc a" in first || "\tinc a" in first) && (" dec a" in second || "\tdec a" 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))
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)
|| (" dea" in first || "\tdea" in first) && (" ina" in second || "\tina" in second)
|| (" dec a" in first || "\tdec a" in first) && (" inc a" in second || "\tinc a" in second)) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
}
}
return mods
@ -230,12 +434,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

View File

@ -0,0 +1,59 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (X last), except A,
// 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
regsWithoutA.forEach {
if(it.value.second.registerOrPair != RegisterOrPair.X)
order += it.index
}
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
rest.forEach { order += it.index }
regA.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean {
fun isClobberRisk(expr: Expression): Boolean {
when (expr) {
is ArrayIndexedExpression -> {
return paramRegisters.any {
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is FunctionCall -> {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword"))
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple
}
else -> return !expr.isSimple
}
}
return args.size>1 && args.any { isClobberRisk(it) }
}

View File

@ -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",
@ -65,6 +67,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"push", "pushw" -> funcPush(fcall, func)
"pop", "popw" -> funcPop(fcall, func)
"rsave" -> funcRsave()
"rsavex" -> funcRsaveX()
"rrestore" -> funcRrestore()
"rrestorex" -> funcRrestoreX()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"callrom" -> funcCallRom(fcall)
@ -72,6 +80,168 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun funcRsave() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
php
pha
phy
phx""")
else
// see http://6502.org/tutorials/register_preservation.html
asmgen.out("""
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG""")
}
private fun funcRsaveX() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" phx")
else
asmgen.out(" txa | pha")
}
private fun funcRrestore() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
plx
ply
pla
plp""")
else
asmgen.out("""
pla
tay
pla
tax
pla
plp""")
}
private fun funcRrestoreX() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" plx")
else
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
}
private fun funcPop(fcall: IFunctionCall, func: FSignature) {
// note: because A is pushed first so popped last, saving A is often not required here.
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
val target = (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!
val parameter = target.subroutineParameter
if(parameter!=null) {
val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) {
"push/pop arg passing only supported on asmsubs ${(fcall as Node).position}"
}
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG")
asmgen.out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
asmgen.out(" lda P8ZP_SCRATCH_REG")
}
else {
if (func.name == "pop") {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> asmgen.out(" plx")
RegisterOrPair.Y -> asmgen.out(" ply")
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tay")
}
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (asmgen.isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" plx | pla")
RegisterOrPair.AY -> asmgen.out(" ply | pla")
RegisterOrPair.XY -> asmgen.out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" pla | tax | pla")
RegisterOrPair.AY -> asmgen.out(" pla | tay | pla")
RegisterOrPair.XY -> asmgen.out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
} else {
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, target.datatype, (fcall as Node).definingSubroutine, variableAsmName = asmgen.asmVariableName(target.name))
if (func.name == "pop") {
asmgen.out(" pla")
asmgen.assignRegister(RegisterOrPair.A, tgt)
} else {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" ply | pla")
else
asmgen.out(" pla | tay | pla")
asmgen.assignRegister(RegisterOrPair.AY, tgt)
}
}
}
private fun funcPush(fcall: IFunctionCall, func: FSignature) {
val signed = fcall.args[0].inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(func.name=="push") {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed)
asmgen.out(" pha")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" pha | phy")
else
asmgen.out(" pha | tya | pha")
}
}
private fun funcCallFar(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
throw AssemblyError("callfar only works on cx16 target at this time")
@ -87,7 +257,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0) {
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
jsr cx16.jsrfar
.word ${address.toHex()}
@ -132,7 +302,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("callrom bank must be <32")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0) {
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
lda $01
pha
@ -248,7 +418,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("should not discard result of memory allocation at $fcall")
val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
val existingSize = asmgen.slabs[name]
if(existingSize!=null && existingSize!=size)
@ -283,11 +453,11 @@ 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, 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, false, scope, program, asmgen), RegisterOrPair.AY)
}
@ -1229,6 +1399,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
}
else -> throw AssemblyError("wrong pokew arg type")
}
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)

View File

@ -43,7 +43,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val stepsize=range.step.constValue(program)!!.number.toInt()
if(stepsize < -1) {
val limit = range.to.constValue(program)?.number?.toDouble()
val limit = range.to.constValue(program)?.number
if(limit==0.0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}

View File

@ -5,10 +5,7 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.ast.statements.*
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
@ -21,7 +18,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt)
translateFunctionCall(stmt, false)
restoreXafterCall(stmt)
// just ignore any result values from the function call.
}
@ -37,6 +34,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun saveXbeforeCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
}
}
internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
@ -48,90 +56,76 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun translateFunctionCall(stmt: IFunctionCall) {
internal fun restoreXafterCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
if(!isExpression && !sub.isAsmSubroutine)
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
if(sub.isAsmSubroutine) {
argumentsViaRegisters(sub, call)
if (sub.inline && asmgen.options.optimize) {
// inline the subroutine.
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}")
} else {
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])
} else {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
when {
stmt.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
asmgen.out(" jsr $subAsmName")
}
}
if(!sub.inline || !asmgen.options.optimize) {
asmgen.out(" jsr $subName")
} else {
// inline the subroutine.
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
// note: for now, this is only reliably supported for asmsubs.
if(!sub.isAsmSubroutine)
else {
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}")
argumentsViaVariables(sub, call)
asmgen.out(" jsr $subAsmName")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first, arg.second)
}
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
} else {
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
registerArgsViaStackEvaluation(call, sub)
} else {
asmgen.asmsubArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param), arg)
}
}
}
}
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.
@ -220,11 +214,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(argForCarry!=null) {
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
asmgen.out("""
clc
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
@ -267,7 +260,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
@ -303,11 +296,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
clc
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
@ -335,8 +327,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val target: AsmAssignTarget =
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, false, sub, program, asmgen)
else {
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
AsmAssignTarget.fromRegisters(register, signed, sub, program, asmgen)
}
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)

View File

@ -39,8 +39,8 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val origAstTarget: AssignTarget? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
@ -56,16 +56,31 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.getOr(DataType.UNDEFINED)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
with(assign.target) {
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.getOr(DataType.UNDEFINED)
when {
identifier != null -> {
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
if (parameter!=null) {
val sub = parameter.definingSubroutine!!
if (sub.isAsmSubroutine) {
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null)
throw AssemblyError("can't assign value to processor statusflag directly")
else
return AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
}
}
return AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
}
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
}
@ -111,8 +126,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val expression: Expression? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
val asmVarname: String
get() = if(array==null)
@ -133,6 +148,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val parameter = value.targetVarDecl(program)?.subroutineParameter
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
@ -209,7 +227,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
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"
}
}
}

View File

@ -7,17 +7,14 @@ import prog8.ast.statements.*
import prog8.ast.toHex
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)
@ -32,6 +29,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, origtarget.datatype, origtarget.scope,
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
} else origtarget
}
fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
@ -43,9 +47,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// simple case: assign a constant number
val num = assign.source.number!!.number
when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toInt())
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
DataType.FLOAT -> assignConstantFloat(assign.target, num)
else -> throw AssemblyError("weird numval type")
}
}
@ -131,7 +135,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val value = assign.source.memory!!
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
assignMemoryByte(assign.target, address, null)
}
is IdentifierReference -> {
@ -162,7 +166,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value)
asmgen.translateFunctionCall(value, true)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
@ -261,11 +265,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
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)
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
@ -273,7 +278,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// Everything else just evaluate via the stack.
// (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...)
// TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
// TODO DON'T STACK-EVAL 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)
@ -345,7 +350,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
assignMemoryByteIntoWord(target, address, null)
return
}
@ -445,9 +450,35 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
// No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
asmgen.assignExpressionTo(origTypeCastExpression, 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) {
@ -779,7 +810,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
@ -989,7 +1020,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> {
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
@ -1044,7 +1075,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.copy_float""")
+ jsr floats.copy_float""")
}
else -> throw AssemblyError("weird dt")
}
@ -1078,6 +1109,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFAC2float(target: AsmAssignTarget) {
asmgen.out(" jsr floats.MOVFA") // fac2 -> fac1
assignFAC1float(target)
}
internal fun assignFAC1float(target: AsmAssignTarget) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1213,7 +1249,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
@ -1266,7 +1302,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(DataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
@ -1334,7 +1370,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
@ -1494,7 +1530,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val idx = target.constArrayIndexValue!! * 2
val idx = target.constArrayIndexValue!! * 2u
when (regs) {
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
@ -1715,8 +1751,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
private fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
if(byte==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this cpu
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1949,7 +1985,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignMemoryByte(target: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
private fun assignMemoryByte(target: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
if (address != null) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -2033,7 +2069,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
if (address != null) {
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
@ -2125,7 +2161,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
val scopedName = vardecl.scopedName.joinToString(".")
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy

View File

@ -7,12 +7,11 @@ import prog8.ast.statements.Subroutine
import prog8.ast.toHex
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) {
@ -22,15 +21,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
val itype = value.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.getOr(DataType.UNDEFINED)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign.target, type)
"~" -> inplaceInvert(assign.target, type)
"not" -> inplaceBooleanNot(assign.target, type)
"-" -> inplaceNegate(target, type)
"~" -> inplaceInvert(target, type)
"not" -> inplaceBooleanNot(target, type)
else -> throw AssemblyError("invalid prefix operator")
}
}
@ -102,10 +102,66 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
throw FatalAstException("assignment should be augmentable $binExpr")
val leftBinExpr = binExpr.left as? BinaryExpression
val rightBinExpr = binExpr.right as? BinaryExpression
if(leftBinExpr!=null && rightBinExpr==null) {
if(leftBinExpr.left isSameAs astTarget) {
// X = (X <oper> Right) <oper> Something
inplaceModification(target, leftBinExpr.operator, leftBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
if(leftBinExpr.right isSameAs astTarget) {
// X = (Left <oper> X) <oper> Something
if(leftBinExpr.operator in associativeOperators) {
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs astTarget) {
// X = Something <oper> (X <oper> Right)
if(binExpr.operator in associativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
if(rightBinExpr.right isSameAs astTarget) {
// X = Something <oper> (Left <oper> X)
if(binExpr.operator in associativeOperators && rightBinExpr.operator in associativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
throw FatalAstException("assignment should follow augmentable rules $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,9 +237,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
else -> {
// TODO OTHER EVALUATION HERE, don't use the estack
// TODO OTHER EVALUATION HERE, don't use the estack to transfer the address to read/write from
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
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
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)
@ -194,7 +250,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") // TODO don't use estack to transfer the address to read from
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx")
}
}
}
@ -289,8 +345,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
TargetStorageKind.STACK -> throw AssemblyError("no asm gen for stack in-place modification")
}
}
@ -657,14 +713,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
@ -673,15 +729,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
@ -694,7 +750,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
@ -704,7 +760,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
}
"-" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
asmgen.out("""
sta P8ZP_SCRATCH_B1
lda $name
@ -716,11 +772,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))
@ -730,7 +786,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
@ -1103,7 +1159,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta $name
lda P8ZP_SCRATCH_W2+1
sta $name+1
""") }
""")
}
"<<" -> {
asmgen.out("""
ldy $otherName
@ -1790,8 +1847,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
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}")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack not")
else -> throw AssemblyError("no asm gen for in-place not of ubyte ${target.kind}")
}
}
DataType.UWORD -> {
@ -1843,13 +1900,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+ ldx #1
+""")
}
in Cx16VirtualRegisters -> TODO()
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
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}")
TargetStorageKind.STACK -> TODO("no asm gen for word stack not")
else -> throw AssemblyError("no asm gen for in-place not of uword for ${target.kind}")
}
}
else -> throw AssemblyError("boolean-not of invalid type")
@ -1899,8 +1955,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
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}")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
}
}
DataType.UWORD -> {
@ -1919,15 +1975,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
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")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
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}")
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
}
}
else -> throw AssemblyError("invert of invalid type")
@ -1947,15 +2000,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
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")
RegisterOrPair.A -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" eor #255 | ina")
else
asmgen.out(" eor #255 | clc | adc #1")
}
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
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")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
else -> throw AssemblyError("no asm gen for in-place negate byte")
}
}
DataType.WORD -> {
@ -2010,15 +2069,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc P8ZP_SCRATCH_REG+1
tay""")
}
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register negate")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
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")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
else -> throw AssemblyError("no asm gen for in-place negate word")
}
}
DataType.FLOAT -> {
@ -2031,13 +2087,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1
""")
}
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")
TargetStorageKind.STACK -> TODO("no asm gen for stack float negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
}
}
else -> throw AssemblyError("negate of invalid type")
else -> throw AssemblyError("negate of invalid type $dt")
}
}

View File

@ -16,12 +16,12 @@ object CX16MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801
override val RAW_LOAD_ADDRESS = 0x8000
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0x8000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
@ -67,7 +67,7 @@ object CX16MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
@ -89,10 +89,10 @@ object CX16MachineDefinition: IMachineDefinition {
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
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
init {
@ -103,14 +103,14 @@ object CX16MachineDefinition: IMachineDefinition {
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22..0xff)
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22..0x7f)
free.addAll(0xa9..0xff)
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22..0x7f)
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all

View File

@ -1,15 +1,11 @@
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.DataType
import prog8.ast.base.Position
import prog8.ast.base.RegisterOrPair
import prog8.ast.base.VarDeclType
import prog8.ast.base.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
@ -25,22 +21,19 @@ 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
@ -50,11 +43,11 @@ locallabel:
tgt = &main.var_outside
tgt = &main.label_outside
}
}
}
*/
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, null, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, null, Position.DUMMY)
val labelInSub = Label("locallabel", Position.DUMMY)
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
@ -70,17 +63,16 @@ locallabel:
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
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 varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, null, Position.DUMMY)
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
return program
}
private fun createTestAsmGen(program: Program): AsmGen {
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)
@ -88,64 +80,93 @@ locallabel:
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"
}
})

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

View File

@ -23,15 +23,15 @@ internal val DummyFunctions = object : IBuiltinFunctions {
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
override fun memorySize(dt: DataType) = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
return ""
}
}

View File

@ -16,12 +16,6 @@ dependencies {
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
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'
}
sourceSets {
@ -33,22 +27,6 @@ sourceSets {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}
// note: there are no unit tests in this module!

View File

@ -12,7 +12,5 @@
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" />
</component>
</module>

View File

@ -1,2 +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.
for convenience sake, and to not spread the test cases around too much.

View File

@ -4,7 +4,10 @@ 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
@ -12,39 +15,19 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.isInRegularRAMof
import prog8.compilerinterface.isIOAddress
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, 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...: [ IS THIS STILL TRUE AFTER ALL CHANGES? ]
// 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> {
if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
if(binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
@ -63,10 +46,34 @@ X = BinExpr X = LeftExpr
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
if(assignment.target isSameAs binExpr.right)
return noModifications
if(assignment.target isSameAs binExpr.left) {
if(binExpr.right.isSimple)
return noModifications
val leftBx = binExpr.left as? BinaryExpression
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
return noModifications
val rightBx = binExpr.right as? BinaryExpression
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
return noModifications
if(binExpr.right.isSimple && !assignment.isAugmentable) {
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
// val dt = assignment.target.inferType(program)
// if(!dt.isInteger)
// return noModifications
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
// val assignTempVar = Assignment(
// AssignTarget(tempVar, null, null, binExpr.right.position),
// binExpr.right, binExpr.right.position
// )
// return listOf(
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
// )
}
if(binExpr.right.isSimple) {
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)
@ -78,16 +85,43 @@ X = BinExpr X = LeftExpr
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also service
// 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 tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) {
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)
!target.isIOAddress(compTarget.machine)
else
false

View File

@ -46,14 +46,14 @@ class ConstExprEvaluator {
left.number.toInt().ushr(amount.number.toInt())
else
left.number.toInt().shr(amount.number.toInt())
return NumericLiteralValue(left.type, result, left.position)
return NumericLiteralValue(left.type, result.toDouble(), left.position)
}
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.number.toInt().shl(amount.number.toInt())
return NumericLiteralValue(left.type, result, left.position)
return NumericLiteralValue(left.type, result.toDouble(), left.position)
}
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
@ -61,12 +61,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -78,12 +78,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -95,12 +95,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -110,11 +110,11 @@ class ConstExprEvaluator {
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left ^ $right", left.position)
@ -123,11 +123,11 @@ class ConstExprEvaluator {
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left | $right", left.position)
@ -136,11 +136,11 @@ class ConstExprEvaluator {
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)
@ -151,12 +151,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -168,12 +168,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -185,12 +185,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -202,12 +202,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -227,19 +227,19 @@ class ConstExprEvaluator {
NumericLiteralValue.optimalInteger(result, left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
if(right.number==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number.toInt(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
@ -256,19 +256,19 @@ class ConstExprEvaluator {
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number.toInt(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}

View File

@ -40,7 +40,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
}
DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position),
parent))
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
@ -48,29 +48,29 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
"~" -> when (subexpr.type) {
DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
NumericLiteralValue(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
parent))
}
DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
NumericLiteralValue(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
"not" -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position),
parent))
}
else -> throw ExpressionError(expr.operator, subexpr.position)
@ -79,7 +79,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return noModifications
}
/**
/*
* Try to constfold a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
@ -101,23 +101,50 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val rightconst = expr.right.constValue(program)
val modifications = mutableListOf<IAstModification>()
if(expr.operator=="==" && rightconst!=null) {
val leftExpr = expr.left as? BinaryExpression
if(leftExpr!=null) {
val leftRightConst = leftExpr.right.constValue(program)
if(leftRightConst!=null) {
when (leftExpr.operator) {
"+" -> {
// X + С1 == C2 --> X == C2 - C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
)
}
"-" -> {
// X - С1 == C2 --> X == C2 + C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
)
}
}
}
}
}
if(expr.operator == "**" && leftconst!=null) {
// optimize various simple cases of ** :
// optimize away 1 ** x into just 1 and 0 ** x into just 0
// optimize 2 ** x into (1<<x) if both operands are integer.
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
when (leftconst.number.toDouble()) {
when (leftconst.number) {
0.0 -> {
val value = NumericLiteralValue(leftDt, 0, expr.position)
val value = NumericLiteralValue(leftDt, 0.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
1.0 -> {
val value = NumericLiteralValue(leftDt, 1, expr.position)
val value = NumericLiteralValue(leftDt, 1.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
2.0 -> {
if(rightconst!=null) {
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number), expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
} else {
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
@ -128,7 +155,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
is VarDecl -> parent.datatype
else -> leftDt
}
val one = NumericLiteralValue(targetDt, 1, expr.position)
val one = NumericLiteralValue(targetDt, 1.0, expr.position)
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
modifications += IAstModification.ReplaceNode(expr, shift, parent)
}
@ -159,13 +186,52 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
}
}
val evaluator = ConstExprEvaluator()
// const fold when both operands are a const
if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator()
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
modifications += IAstModification.ReplaceNode(expr, result, parent)
}
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(expr.operator=="+" || expr.operator=="-") {
if(leftBinExpr!=null && rightBinExpr!=null) {
val c1 = leftBinExpr.right.constValue(program)
val c2 = rightBinExpr.right.constValue(program)
if(leftBinExpr.operator=="+" && rightBinExpr.operator=="+") {
if (c1 != null && c2 != null) {
// (X + C1) <plusmin> (Y + C2) => (X <plusmin> Y) + (C1 <plusmin> C2)
val c3 = evaluator.evaluate(c1, expr.operator, c2)
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "+", c3, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
else if(leftBinExpr.operator=="-" && rightBinExpr.operator=="-") {
if (c1 != null && c2 != null) {
// (X - C1) <plusmin> (Y - C2) => (X <plusmin> Y) - (C1 <plusmin> C2)
val c3 = evaluator.evaluate(c1, expr.operator, c2)
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "-", c3, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
if (c1 != null && c2 != null && c1==c2) {
//(X * C) <plusmin> (Y * C) => (X <plusmin> Y) * C
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
}
}
return modifications
}

View File

@ -24,11 +24,16 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& 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)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
&& declConstValue.type != decl.datatype) {
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
@ -38,9 +43,9 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
}
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
val from = range.from.constValue(program)?.number?.toDouble()
val to = range.to.constValue(program)?.number?.toDouble()
val step = range.step.constValue(program)?.number?.toDouble()
val from = range.from.constValue(program)?.number
val to = range.to.constValue(program)?.number
val step = range.step.constValue(program)?.number
if(from==null) {
if(!range.from.inferType(program).isInteger)
@ -105,8 +110,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
@ -132,7 +136,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number, litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
@ -142,17 +146,17 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
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 eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
@ -185,7 +189,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
@ -210,7 +214,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
val size = decl.arraysize?.constIndex() ?: return noModifications
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number.toDouble()
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {

View File

@ -19,17 +19,6 @@ import kotlin.math.pow
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))
*/
@ -160,7 +149,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
@ -170,7 +159,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
@ -190,14 +179,26 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
}
if(expr.operator == ">=" && rightVal?.number == 0) {
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
// for integers: x >= 1 --> x > 0
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == ">=" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
}
}
if(expr.operator == "<" && rightVal?.number == 0) {
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
// for integers: x < 1 --> x <= 0
expr.operator = "<="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == "<" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
@ -231,52 +232,62 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) {
"or" -> {
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
constTrue
else if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
when {
leftVal != null && leftVal.asBooleanValue || rightVal != null && rightVal.asBooleanValue -> constTrue
leftVal != null && !leftVal.asBooleanValue -> expr.right
rightVal != null && !rightVal.asBooleanValue -> expr.left
else -> null
}
}
"and" -> {
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
constFalse
else if (leftVal != null && leftVal.asBooleanValue)
expr.right
else if (rightVal != null && rightVal.asBooleanValue)
expr.left
else
null
when {
leftVal != null && !leftVal.asBooleanValue || rightVal != null && !rightVal.asBooleanValue -> constFalse
leftVal != null && leftVal.asBooleanValue -> expr.right
rightVal != null && rightVal.asBooleanValue -> expr.left
else -> null
}
}
"xor" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else if (leftVal != null && leftVal.asBooleanValue)
PrefixExpression("not", expr.right, expr.right.position)
else if (rightVal != null && rightVal.asBooleanValue)
PrefixExpression("not", expr.left, expr.left.position)
else
null
when {
leftVal != null && !leftVal.asBooleanValue -> expr.right
rightVal != null && !rightVal.asBooleanValue -> expr.left
leftVal != null && leftVal.asBooleanValue -> PrefixExpression("not", expr.right, expr.right.position)
rightVal != null && rightVal.asBooleanValue -> PrefixExpression("not", expr.left, expr.left.position)
else -> null
}
}
"|", "^" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
"|" -> {
when {
leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position)
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position)
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, leftVal.position)
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, leftVal.position)
else -> null
}
}
"^" -> {
when {
leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
else -> null
}
}
"&" -> {
if (leftVal != null && !leftVal.asBooleanValue)
constFalse
else if (rightVal != null && !rightVal.asBooleanValue)
constFalse
else
null
when {
leftVal?.number==0.0 -> constFalse
rightVal?.number==0.0 -> constFalse
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
else -> null
}
}
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
"/" -> optimizeDivision(expr, leftVal, rightVal)
@ -320,7 +331,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0, arg.expression.position),
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
parent))
}
} else {
@ -329,7 +340,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
parent))
}
}
@ -362,7 +373,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal2
when (rightConst.number.toDouble()) {
when (rightConst.number) {
0.0 -> {
// left
return expr2.left
@ -371,7 +382,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
// no need to check for left val constant (because of associativity)
val rnum = rightVal?.number?.toDouble()
val rnum = rightVal?.number
if(rnum!=null && rnum<0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
@ -392,7 +403,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rnum = rightVal.number.toDouble()
val rnum = rightVal.number
if (rnum == 0.0) {
// left
return expr.left
@ -406,7 +417,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
0.0 -> {
// -right
return PrefixExpression("-", expr.right, expr.position)
@ -425,7 +436,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
when (rightConst.number) {
-3.0 -> {
// -1/(left*left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
@ -445,7 +456,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
0.0 -> {
// 1
return NumericLiteralValue(rightConst.type, 1, expr.position)
return NumericLiteralValue(rightConst.type, 1.0, expr.position)
}
0.5 -> {
// sqrt(left)
@ -467,18 +478,18 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
-1.0 -> {
// -1
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
}
1.0 -> {
//1
return NumericLiteralValue(leftVal.type, 1, expr.position)
return NumericLiteralValue(leftVal.type, 1.0, expr.position)
}
}
@ -500,7 +511,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val idt = expr.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0, expr.position)
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
} else if (cv in powersOfTwo) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
@ -520,7 +531,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble()
val cv = rightConst.number
val leftIDt = expr.left.inferType(program)
if (!leftIDt.isKnown)
return null
@ -555,22 +566,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
if (leftDt == DataType.UBYTE) {
if (abs(rightConst.number.toDouble()) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
if (abs(rightConst.number) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
}
} else if (leftDt == DataType.UWORD) {
if (abs(rightConst.number.toDouble()) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
if (abs(rightConst.number) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
}
}
}
@ -587,14 +598,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left
val rightConst: NumericLiteralValue = rightVal2
when (val cv = rightConst.number.toDouble()) {
when (val cv = rightConst.number) {
-1.0 -> {
// -left
return PrefixExpression("-", leftValue, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(rightConst.type, 0, expr.position)
return NumericLiteralValue(rightConst.type, 0.0, expr.position)
}
1.0 -> {
// left
@ -635,12 +646,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
return NumericLiteralValue(targetDt, 0.0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
return NumericLiteralValue(targetDt, 0.0, expr.position)
} else if (amount >= 8) {
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
@ -687,7 +698,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
// mkword(0, msb(v))
val zero = NumericLiteralValue(DataType.UBYTE, 0, expr.position)
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
}
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)

View File

@ -2,6 +2,9 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.InferredTypes
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
@ -65,3 +68,15 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
opti.visit(this)
return opti.applyModifications()
}
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
return 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")
dt.isPassByReference -> listOf("cx16", "r9")
else -> throw FatalAstException("invalid dt $dt")
}
}

View File

@ -1,9 +1,6 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
@ -15,8 +12,6 @@ import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import kotlin.math.floor
internal const val retvarName = "prog8_retval"
class StatementOptimizer(private val program: Program,
private val errors: IErrorReporter,
@ -24,33 +19,22 @@ class StatementOptimizer(private val program: Program,
private val compTarget: ICompilationTarget
) : AstWalker() {
private val subsThatNeedReturnVariable = mutableSetOf<Triple<IStatementContainer, 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
}
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,
// remove the jump altogeter and inline the returnvalue directly.
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
val target = variable.targetStatement(program) as INamedStatement
return IdentifierReference(target.scopedName, variable.position)
}
val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value?.isSimple==true) {
val copy = when(val orig = first.value!!) {
is AddressOf -> {
val scoped = scopePrefix(orig.identifier, subroutine)
val scoped = scopePrefix(orig.identifier)
AddressOf(scoped, orig.position)
}
is DirectMemoryRead -> {
@ -59,7 +43,7 @@ class StatementOptimizer(private val program: Program,
else -> return noModifications
}
}
is IdentifierReference -> scopePrefix(orig, subroutine)
is IdentifierReference -> scopePrefix(orig)
is NumericLiteralValue -> orig.copy()
is StringLiteralValue -> orig.copy()
else -> return noModifications
@ -70,13 +54,10 @@ class StatementOptimizer(private val program: Program,
return noModifications
}
private fun scopePrefix(variable: IdentifierReference, subroutine: Subroutine): IdentifierReference {
val scoped = subroutine.makeScopedName(variable.nameInSource.last())
return IdentifierReference(scoped.split('.'), variable.position)
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionStatementPlaceholder) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
@ -101,7 +82,7 @@ class StatementOptimizer(private val program: Program,
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
@ -109,12 +90,12 @@ class StatementOptimizer(private val program: Program,
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
functionCallStatement.void, pos
)
return listOf(
@ -134,23 +115,25 @@ class StatementOptimizer(private val program: Program,
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
// see if we can optimize any complex arguments
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
val arg = functionCallStatement.args[0]
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
val name = getTempVarName(arg.inferType(program))
val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position)
return listOf(
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
)
}
}
return noModifications
}
// override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
// val subroutine = functionCall.target.targetSubroutine(program)
// if(subroutine!=null) {
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
// if(first is Return && first.value!=null) {
// val constval = first.value?.constValue(program)
// if(constval!=null)
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
// }
// }
// return noModifications
// }
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
@ -215,7 +198,7 @@ class StatementOptimizer(private val program: Program,
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
@ -296,12 +279,13 @@ 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.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, scope))
if(!jump.isGosub) {
// if the jump is to the next statement, remove the jump
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, scope))
}
return noModifications
}
@ -324,22 +308,22 @@ class StatementOptimizer(private val program: Program,
if(rNum!=null) {
if (op1 == "+" || op1 == "-") {
if (op2 == "+") {
// A = A +/- B + N
// A = A +/- B + N ---> A = A +/- B ; A = A + N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val addConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
} else if (op2 == "-") {
// A = A +/- B - N
// A = A +/- B - N ---> A = A +/- B ; A = A - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val subConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
@ -374,14 +358,26 @@ class StatementOptimizer(private val program: Program,
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
val rightCv = bexpr.right.constValue(program)?.number
if(bexpr.operator=="-" && rightCv==null) {
if(bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position)
return listOf(
IAstModification.ReplaceNode(bexpr, negation, assignment),
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
)
}
}
if (rightCv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
when (bexpr.operator) {
"+" -> {
@ -394,7 +390,7 @@ class StatementOptimizer(private val program: Program,
repeat(rightCv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
}
}
}
@ -439,12 +435,17 @@ 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 IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)

View File

@ -1,18 +1,16 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.PrefixExpression
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CallGraph
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.isInRegularRAMof
import prog8.compilerinterface.isIOAddress
class UnusedCodeRemover(private val program: Program,
@ -30,27 +28,28 @@ class UnusedCodeRemover(private val program: Program,
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as IStatementContainer)
reportUnreachable(breakStmt)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as IStatementContainer)
if(!jump.isGosub)
reportUnreachable(jump)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as IStatementContainer)
reportUnreachable(returnStmt)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as IStatementContainer)
reportUnreachable(functionCallStatement)
return emptyList()
}
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
private fun reportUnreachable(stmt: Statement) {
when(val next = stmt.nextSibling()) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position)
@ -58,8 +57,7 @@ class UnusedCodeRemover(private val program: Program,
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
return deduplicateAssignments(scope.statements, scope)
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
@ -75,8 +73,7 @@ class UnusedCodeRemover(private val program: Program,
}
}
val removeDoubleAssignments = deduplicateAssignments(block.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
return deduplicateAssignments(block.statements, block)
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
@ -99,17 +96,34 @@ class UnusedCodeRemover(private val program: Program,
}
}
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
return deduplicateAssignments(subroutine.statements, subroutine)
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR) {
val forceOutput = "force_output" in decl.definingBlock.options()
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
if (callgraph.unused(decl)) {
val usages = callgraph.usages(decl)
if (usages.isEmpty()) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
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 {
it.parent as? AssignTarget
}.filter {
!it.isIOAddress(compTarget.machine)
}
if(assignTargets.size==usages.size) {
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)
)
}
}
}
}
@ -117,28 +131,117 @@ class UnusedCodeRemover(private val program: Program,
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
// removes 'duplicate' assignments that assign the same target directly after another
val linesToRemove = mutableListOf<Assignment>()
val modifications = mutableListOf<IAstModification>()
for (stmtPairs in statements.windowed(2, step = 1)) {
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) && 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) {
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCall -> { /* don't remove */ }
else -> linesToRemove.add(assign1)
}
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.left isSameAs assign2.target) {
// X = X <oper> Right
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.left, zero, expr
))
}
if(expr.right isSameAs assign2.target) {
// X = Left <oper> X
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.right, zero, expr
))
}
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(leftBinExpr!=null && rightBinExpr==null) {
if(leftBinExpr.left isSameAs assign2.target) {
// X = (X <oper> Right) <oper> Something
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
leftBinExpr.left, zero, leftBinExpr
))
}
if(leftBinExpr.right isSameAs assign2.target) {
// X = (Left <oper> X) <oper> Something
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
leftBinExpr.right, zero, leftBinExpr
))
}
}
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs assign2.target) {
// X = Something <oper> (X <oper> Right)
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
rightBinExpr.left, zero, rightBinExpr
))
}
if(rightBinExpr.right isSameAs assign2.target) {
// X = Something <oper> (Left <oper> X)
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
rightBinExpr.right, zero, rightBinExpr
))
}
}
}
return linesToRemove
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.expression, zero, expr
))
}
}
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.expression, zero, expr
))
}
val subCast = expr.expression as? TypecastExpression
if(subCast!=null && subCast.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
subCast.expression, zero, subCast
))
}
}
for (stmtPairs in statements.windowed(2, step = 1)) {
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null) {
val cvalue1 = assign1.value.constValue(program)
if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) {
val value2 = assign2.value
val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
when(value2) {
is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2)
is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2)
is TypecastExpression -> substituteZeroInTypecast(value2, zero, assign1, assign2)
else -> {}
}
} else {
if (assign1.target.isSameAs(assign2.target, program) && !assign1.target.isIOAddress(compTarget.machine)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(assign2.target.identifier!!.nameInSource))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCall -> { /* don't remove */ }
else -> linesToRemove.add(assign1)
}
}
}
}
}
return modifications + linesToRemove.map { IAstModification.Remove(it, scope) }
}
}

View File

@ -3,6 +3,7 @@ plugins {
id 'application'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.1.0'
id "io.kotest" version "0.3.8"
}
java {
@ -23,10 +24,7 @@ dependencies {
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 {

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,13 +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>

View File

@ -12,7 +12,7 @@ floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
ubyte[5] tempvar_swap_float ; used for some swap() operations
float tempvar_swap_float ; used for some swap() operations
; ---- C64 basic and kernal ROM float constants and functions ----

View File

@ -33,6 +33,9 @@ graphics {
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO implement this as optimized assembly, for instance https://github.com/EgonOlsen71/bresenham/blob/main/src/asm/graphics.asm ??
; or from here https://retro64.altervista.org/blog/an-introduction-to-vector-based-graphics-the-commodore-64-rotating-simple-3d-objects/
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)

View File

@ -597,62 +597,34 @@ _longcopy
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
pla
tay
pla
tax
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
sei
}}
}
@ -699,6 +671,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 +721,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
}

View File

@ -12,14 +12,14 @@ conv {
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
phx
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
plx
ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -27,7 +27,7 @@ asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
phx
stx P8ZP_SCRATCH_REG
ldy #0
sty P8ZP_SCRATCH_B1
jsr conv.ubyte2decimal
@ -53,7 +53,7 @@ _output_byte_digits
iny
lda #0
sta string_out,y
plx
ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -61,7 +61,7 @@ _output_byte_digits
asmsub str_b (byte value @ A) clobbers(A,Y) {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
phx
stx P8ZP_SCRATCH_REG
ldy #0
sty P8ZP_SCRATCH_B1
cmp #0
@ -149,7 +149,7 @@ asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
phx
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
@ -157,7 +157,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
beq +
iny
bne -
+ plx
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -165,7 +165,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
phx
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldx #0
_output_digits
@ -183,7 +183,7 @@ _gotdigit sta string_out,x
bne _gotdigit
_end lda #0
sta string_out,x
plx
ldx P8ZP_SCRATCH_REG
rts
_allzero lda #'0'
@ -198,7 +198,7 @@ asmsub str_w (word value @ AY) clobbers(A,Y) {
%asm {{
cpy #0
bpl str_uw
phx
stx P8ZP_SCRATCH_REG
pha
lda #'-'
sta string_out
@ -516,7 +516,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A

View File

@ -13,7 +13,7 @@ floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
ubyte[5] tempvar_swap_float ; used for some swap() operations
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ----

View File

@ -775,6 +775,7 @@ _done
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
; TODO draw whole horizontal spans using vera auto increment if possible, instead of per-character columns
uword chardataptr
when active_mode {
1, 5 -> {
@ -808,11 +809,8 @@ _done
sta cx16.VERA_ADDR_L
bcc +
inc cx16.VERA_ADDR_M
+ lda x
clc
adc #1
sta x
bcc +
+ inc x
bne +
inc x+1
+ dey
bne -
@ -826,7 +824,6 @@ _done
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y)
y++
%asm {{
@ -855,7 +852,9 @@ _done
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop fully in assembly
; TODO rewrite this inner loop partly in assembly
; requires expanding the charbits to 2-bits per pixel (based on color)
; also it's way more efficient to draw whole horizontal spans instead of per-character
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 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
@ -262,7 +312,7 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; TODO specify the correct clobbers for all functions below, we now assume all 3 regs are clobbered
; high level graphics & fonts
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
@ -317,14 +367,14 @@ romsub $fecc = monitor() clobbers(A,X,Y)
; ---- utilities -----
inline asmsub rombank(ubyte rombank @A) {
inline asmsub rombank(ubyte bank @A) {
; -- set the rom banks
%asm {{
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
}}
}
inline asmsub rambank(ubyte rambank @A) {
inline asmsub rambank(ubyte bank @A) {
; -- set the ram bank
%asm {{
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
@ -809,27 +859,6 @@ sys {
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
pha
phy
phx
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
plx
ply
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
@ -839,25 +868,25 @@ sys {
inline asmsub clear_carry() {
%asm {{
clc
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
sei
}}
}

View File

@ -565,9 +565,8 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
tay
pla
eor #255
clc
adc #1
bcc +
ina
bne +
iny
+ bra print_uw
}}

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

View File

@ -6,12 +6,24 @@ prog8_lib {
%asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm"
; TODO these retval variables are no longer used???
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)
; NOTE: these variables are checked in the StatementReorderer (in fun after(decl: VarDecl)), for these exact names!
; 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 {{

View File

@ -1 +1 @@
7.2
7.4

View File

@ -3,6 +3,7 @@ package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
@ -66,6 +67,11 @@ private fun compileMain(args: Array<String>): Boolean {
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if (compilationTarget != C64Target.name && compilationTarget != Cx16Target.name) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
if(watchMode==true) {
val watchservice = FileSystems.getDefault().newWatchService()
val allImportedFiles = mutableSetOf<Path>()
@ -75,10 +81,18 @@ 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, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
val args = CompilerArguments(
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
compilationTarget,
srcdirs,
outputPath
)
val compilationResult = compileProgram(args)
results.add(compilationResult)
}
@ -115,11 +129,19 @@ private fun compileMain(args: Array<String>): Boolean {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
if(!compilationResult.success)
val args = CompilerArguments(
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
compilationTarget,
srcdirs,
outputPath
)
compilationResult = compileProgram(args)
if (!compilationResult.success)
return false
} catch (x: AstException) {
return false

View File

@ -11,17 +11,30 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.*
import prog8.optimizer.getTempVarName
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
private val errors: IErrorReporter) : AstWalker() {
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>()
private fun rememberSubroutineVar(decl: VarDecl) {
val sub = decl.definingSubroutine ?: return
var varsList = subroutineVariables[sub]
if(varsList==null) {
varsList = mutableListOf()
subroutineVariables[sub] = varsList
}
varsList.add(decl.name to decl)
}
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)
rememberSubroutineVar(decl)
return noModifications
}
@ -31,7 +44,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& assignment.target.isInRegularRAMof(options.compTarget.machine)) {
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
@ -39,20 +52,24 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
// the right part of the expression contains the target variable itself.
// we can't 'split' it trivially because the variable will be changed halfway through.
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, 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, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
@ -63,47 +80,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
return noModifications
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
addedIfConditionVars.clear()
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 })
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val firstDeclarations = mutableMapOf<String, VarDecl>()
for(decl in subroutineVariables) {
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
for(decl in rememberedSubroutineVars) {
val existing = firstDeclarations[decl.first]
if(existing!=null && existing !== decl.second) {
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
@ -111,7 +97,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
firstDeclarations[decl.first] = decl.second
}
}
rememberedSubroutineVars.clear()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
@ -141,7 +127,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
}
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,
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
@ -201,12 +187,76 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
(binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
throw InternalCompilerException("if 0==X should have been swapped to if X==0")
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
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 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 name = getTempVarName(expr.left.inferType(program))
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")
@ -224,7 +274,26 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
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.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.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")
@ -242,6 +311,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
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.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.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
}
@ -255,13 +336,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
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
@ -325,10 +408,10 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
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.parent as IStatementContainer))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))

View File

@ -28,24 +28,27 @@ class CompilationResult(val success: Boolean,
val compTarget: ICompilationTarget,
val importedFiles: List<Path>)
class CompilerArguments(val filepath: Path,
val optimize: Boolean,
val optimizeFloatExpressions: Boolean,
val writeAssembly: Boolean,
val slowCodegenWarnings: Boolean,
val quietAssembler: Boolean,
val compilationTarget: String,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
val errors: IErrorReporter = ErrorReporter())
// TODO refactor the gigantic list of parameters
fun compileProgram(filepath: Path,
optimize: Boolean,
optimizeFloatExpressions: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
quietAssembler: Boolean,
compilationTarget: String,
sourceDirs: List<String>,
outputDir: Path,
errors: IErrorReporter = ErrorReporter()): CompilationResult {
fun compileProgram(args: CompilerArguments): CompilationResult {
var programName = ""
lateinit var program: Program
lateinit var importedFiles: List<Path>
val optimizeFloatExpr = if(args.optimize) args.optimizeFloatExpressions else false
val compTarget =
when(compilationTarget) {
when(args.compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> throw IllegalArgumentException("invalid compilation target")
@ -54,30 +57,30 @@ fun compileProgram(filepath: Path,
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
with(compilationOptions) {
this.slowCodegenWarnings = slowCodegenWarnings
this.optimize = optimize
this.optimizeFloatExpressions = optimizeFloatExpressions
this.slowCodegenWarnings = args.slowCodegenWarnings
this.optimize = args.optimize
this.optimizeFloatExpressions = optimizeFloatExpr
}
program = programresult
importedFiles = imported
processAst(program, errors, compilationOptions)
processAst(program, args.errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(
program,
compilationOptions,
errors,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
compTarget
)
postprocessAst(program, errors, compilationOptions)
postprocessAst(program, args.errors, compilationOptions)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// printAst(program)
// printProgram(program)
if (writeAssembly) {
val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
if (args.writeAssembly) {
val result = writeAssembly(program, args.errors, args.outputDir, args.quietAssembler, compilationOptions)
when (result) {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
@ -253,16 +256,17 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
program.preprocessAst()
program.preprocessAst(program)
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
// ...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.
// Yes this is different from strings that are only encoded in the code gen phase.
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.constantFold(errors, compilerOptions.compTarget)
errors.report()
program.reorderStatements(errors)
program.reorderStatements(errors, compilerOptions)
errors.report()
program.addTypecasts(errors)
errors.report()
@ -325,7 +329,7 @@ private fun writeAssembly(program: Program,
errors.report()
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printAst(program)
// printProgram(program)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
@ -348,7 +352,7 @@ private fun writeAssembly(program: Program,
}
}
fun printAst(program: Program) {
fun printProgram(program: Program) {
println()
val printer = AstToSourceTextConverter(::print, program)
printer.visit(program)

View File

@ -51,7 +51,7 @@ class ModuleImporter(private val program: Program,
fun importLibraryModule(name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(import, null)
}

View File

@ -20,7 +20,7 @@ internal class AstChecker(private val program: Program,
) : 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)
@ -92,9 +92,9 @@ internal class AstChecker(private val program: Program,
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
if(range==null)
return
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0
val step = range.step.constValue(program)?.number ?: 1.0
if(step < -1.0) {
val limit = range.to.constValue(program)?.number?.toDouble()
val limit = range.to.constValue(program)?.number
if(limit==0.0 && range.from.constValue(program)==null)
errors.err("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping", forLoop.position)
}
@ -161,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) {
@ -170,17 +169,20 @@ internal class AstChecker(private val program: Program,
if(targetStatement is BuiltinFunctionStatementPlaceholder)
errors.err("can't jump to a builtin function", jump.position)
}
if(!jump.isGosub && targetStatement is Subroutine && targetStatement.parameters.any()) {
errors.err("can't jump to a subroutine that takes parameters", jump.position)
}
}
val addr = jump.address
if(addr!=null && (addr < 0 || addr > 65535))
if(addr!=null && addr > 65535u)
errors.err("jump address must be valid integer 0..\$ffff", jump.position)
super.visit(jump)
}
override fun visit(block: Block) {
val addr = block.address
if(addr!=null && (addr<0 || addr>65535)) {
if(addr!=null && addr>65535u) {
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
}
@ -225,7 +227,8 @@ internal class AstChecker(private val program: Program,
count++
}
override fun visit(jump: Jump) {
count++
if(!jump.isGosub)
count++
}
}
@ -415,11 +418,10 @@ internal class AstChecker(private val program: Program,
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
if (stmt is Subroutine) {
val idt = assignment.target.inferType(program)
if(!idt.isKnown) {
errors.err("return type mismatch", assignment.value.position)
}
if(!idt.isKnown)
throw FatalAstException("assignment target invalid dt")
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.getOr(DataType.BYTE))) {
errors.err("return type mismatch", assignment.value.position)
errors.err("return type mismatch: ${stmt.returntypes.single()} expected $idt", assignment.value.position)
}
}
}
@ -429,8 +431,15 @@ 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)) {
if(targetDt.isUnknown) {
if(assignment.target.identifier?.targetStatement(program)!=null)
errors.err("target datatype is unknown", assignment.target.position)
// otherwise, another error about missing symbol is already reported.
} else {
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
}
}
}
if(assignment.value is TypecastExpression) {
@ -490,7 +499,7 @@ internal class AstChecker(private val program: Program,
if (assignment.value !is FunctionCall)
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget,
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
}
}
@ -509,7 +518,7 @@ internal class AstChecker(private val program: Program,
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)
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true)
err("recursive var declaration")
// CONST can only occur on simple types (byte, word, float)
@ -827,7 +836,7 @@ internal class AstChecker(private val program: Program,
when(expr.operator){
"/", "%" -> {
val constvalRight = expr.right.constValue(program)
val divisor = constvalRight?.number?.toDouble()
val divisor = constvalRight?.number
if(divisor==0.0)
errors.err("division by zero", expr.right.position)
if(expr.operator=="%") {
@ -967,36 +976,43 @@ internal class AstChecker(private val program: Program,
override fun visit(functionCallStatement: FunctionCallStatement) {
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null)
if(targetStatement!=null) {
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
if (!functionCallStatement.void) {
// check for unused return values
if (targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
if(targetStatement.returntypes.size==1)
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
else
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
}
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
val rt = builtinFunctionReturnType(targetStatement.name, functionCallStatement.args, program)
if(rt.isKnown)
errors.warn("result value of a function call is discarded (use void?)", functionCallStatement.position)
}
checkUnusedReturnValues(functionCallStatement, targetStatement, program, errors)
}
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) {
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
}
}
val funcName = functionCallStatement.target.nameInSource
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)
if(funcName.size==1) {
// check some builtin function calls
if(funcName[0] == "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) {
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
}
}
else if(funcName[0] in arrayOf("pop", "popw")) {
// can only pop into a variable, that has to have the correct type
val idref = functionCallStatement.args[0]
if(idref !is IdentifierReference) {
if(idref is TypecastExpression) {
val passByRef = idref.expression.inferType(program).isPassByReference
if(idref.type!=DataType.UWORD || !passByRef)
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
} else {
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
}
}
}
if(funcName[0] 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)
}
}
}
val error =
@ -1054,7 +1070,12 @@ internal class AstChecker(private val program: Program,
ident = fcall.args[0] as? IdentifierReference
}
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].uppercase())
var regname = ident.nameInSource[1].uppercase()
if(regname.endsWith('L'))
regname=regname.substring(0, regname.length-1)
if(regname.endsWith('s'))
regname=regname.substring(0, regname.length-1)
val reg = RegisterOrPair.valueOf(regname)
val same = params.filter { it.value.registerOrPair==reg }
for(s in same) {
if(s.index!=arg.index) {
@ -1274,7 +1295,7 @@ internal class AstChecker(private val program: Program,
}
when (targetDt) {
DataType.FLOAT -> {
val number=value.number.toDouble()
val number=value.number
if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
return err("value '$number' out of range for MFLPT format")
}
@ -1350,7 +1371,6 @@ internal class AstChecker(private val program: Program,
}
private fun checkAssignmentCompatible(targetDatatype: DataType,
target: AssignTarget,
sourceDatatype: DataType,
sourceValue: Expression,
position: Position) : Boolean {
@ -1393,3 +1413,19 @@ internal class AstChecker(private val program: Program,
return false
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, program: Program, errors: IErrorReporter) {
if (!call.void) {
// check for unused return values
if (target is Subroutine && target.returntypes.isNotEmpty()) {
if (target.returntypes.size == 1)
errors.warn("result value of subroutine call is discarded (use void?)", call.position)
else
errors.warn("result values of subroutine call are discarded (use void?)", call.position)
} else if (target is BuiltinFunctionStatementPlaceholder) {
val rt = builtinFunctionReturnType(target.name, call.args, program)
if (rt.isKnown)
errors.warn("result value of a function call is discarded (use void?)", call.position)
}
}
}

View File

@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding
@ -28,8 +29,8 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
val reorder = StatementReorderer(this, errors, options)
reorder.visit(this)
if(errors.noErrors()) {
reorder.applyModifications()
@ -44,7 +45,7 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.ReplaceNode(
char,
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toDouble(), char.position),
parent
))
}
@ -64,8 +65,8 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.preprocessAst() {
val transforms = AstPreprocessor()
internal fun Program.preprocessAst(program: Program) {
val transforms = AstPreprocessor(program)
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)

View File

@ -75,7 +75,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, private
val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) {
val symbol = subroutine.searchSymbol(name)
if(symbol!=null && symbol.position != subroutine.position)
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
nameError(name, symbol.position, subroutine)
}

View File

@ -1,9 +1,13 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.SyntaxError
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
@ -12,7 +16,40 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AstPreprocessor : AstWalker() {
class AstPreprocessor(val program: Program) : AstWalker() {
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
val modifications = mutableListOf<IAstModification>()
if(range.from !is NumericLiteralValue) {
try {
val constval = range.from.constValue(program)
if (constval != null)
modifications += IAstModification.ReplaceNode(range.from, constval, range)
} catch (x: SyntaxError) {
// syntax errors will be reported later
}
}
if(range.to !is NumericLiteralValue) {
try {
val constval = range.to.constValue(program)
if(constval!=null)
modifications += IAstModification.ReplaceNode(range.to, constval, range)
} catch (x: SyntaxError) {
// syntax errors will be reported later
}
}
if(range.step !is NumericLiteralValue) {
try {
val constval = range.step.constValue(program)
if(constval!=null)
modifications += IAstModification.ReplaceNode(range.step, constval, range)
} catch (x: SyntaxError) {
// syntax errors will be reported later
}
}
return modifications
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {

View File

@ -20,13 +20,13 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
val symbolsInSub = subroutine.allDefinedSymbols
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) {
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
val vardecl = VarDecl.fromParameter(it)
IAstModification.InsertFirst(vardecl, subroutine)
}
}

View File

@ -7,10 +7,14 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
internal class StatementReorderer(val program: Program,
val errors: IErrorReporter,
private val options: CompilationOptions) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
@ -21,12 +25,13 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
// - replace subroutine calls (statement) by just assigning the arguments to the parameters and then a GoSub to the routine.
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList()
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
@ -53,15 +58,12 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
decl.value = null
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal returnvalue intermediates.
// TODO these variables are no longer used???
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) {
if (!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)
@ -125,6 +127,30 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
subs.map { IAstModification.InsertLast(it, subroutine) }
}
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 stringParamsByNames = stringParams.associateBy { it.name }
val varsChanges =
if(stringParamsByNames.isNotEmpty()) {
subroutine.statements
.filterIsInstance<VarDecl>()
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
.map {
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, stringParamsByNames.getValue(it.name), it.position)
IAstModification.ReplaceNode(it, newvar, subroutine)
}
}
else emptyList()
return parameterChanges + varsChanges
}
return noModifications
}
@ -170,8 +196,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 -> {
@ -179,8 +206,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))
}
}
}
@ -196,7 +224,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
// generating the wrong results later
fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0.0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
@ -334,4 +362,110 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)!!
checkUnusedReturnValues(functionCallStatement, function, program, errors)
if(function is Subroutine) {
if(function.inline)
return noModifications
return if(function.isAsmSubroutine)
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent)
else
replaceCallSubStatementWithGosub(function, functionCallStatement, parent)
}
return noModifications
}
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(function.parameters.isEmpty()) {
// 0 params -> just GoSub
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
}
val assignParams =
function.parameters.zip(call.args).map {
var argumentValue = it.second
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
if(argDt in ArrayDatatypes) {
// pass the address of the array instead
argumentValue = AddressOf(argumentValue as IdentifierReference, argumentValue.position)
}
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
}
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
scope.statements += GoSub(null, call.target, null, call.position)
return listOf(IAstModification.ReplaceNode(call, scope, parent))
}
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(function.parameters.isEmpty()) {
// 0 params -> just GoSub
val scope = AnonymousScope(mutableListOf(), call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
}
scope.statements += GoSub(null, call.target, null, call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
}
return listOf(IAstModification.ReplaceNode(call, scope, parent))
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
// No register clobber risk, let the asmgen assign values to the registers directly.
// this is more efficient than first evaluating them to the stack.
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
return noModifications
} else {
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
if (options.slowCodegenWarnings)
errors.warn("slow argument passing used to avoid register clobbering", call.position)
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
val scope = AnonymousScope(mutableListOf(), call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
}
argOrder.reversed().forEach {
val arg = call.args[it]
val param = function.parameters[it]
scope.statements += pushCall(arg, param.type, arg.position)
}
// ... and pop them off again into the registers.
argOrder.forEach {
val param = function.parameters[it]
val targetName = function.scopedName + param.name
scope.statements += popCall(targetName, param.type, call.position)
}
scope.statements += GoSub(null, call.target, null, call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
}
return listOf(IAstModification.ReplaceNode(call, scope, parent))
}
}
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
mutableListOf(IdentifierReference(targetName, position)),
true, position
)
}
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
val pushvalue = when(dt) {
DataType.UBYTE, DataType.UWORD -> value
in PassByReferenceDatatypes -> value
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
else -> throw FatalAstException("invalid dt $dt $value")
}
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
mutableListOf(pushvalue),
true, position
)
}
}

View File

@ -46,8 +46,29 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// convert a negative operand for bitwise operator to the 2's complement positive number instead
if(expr.operator in bitwiseOperators && leftDt.isInteger && rightDt.isInteger) {
val leftCv = expr.left.constValue(program)
if(leftCv!=null && leftCv.number<0) {
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
return listOf(IAstModification.ReplaceNode(
expr.left,
NumericLiteralValue(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
expr))
}
val rightCv = expr.right.constValue(program)
if(rightCv!=null && rightCv.number<0) {
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
return listOf(IAstModification.ReplaceNode(
expr.right,
NumericLiteralValue(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
expr))
}
}
// 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(
@ -171,6 +192,18 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
call as Node)
break
}
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
// 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(identifier, pair.second.position),
call as Node)
break
}
}
}
}
}

View File

@ -74,6 +74,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
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
}
@ -86,6 +94,13 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.parent!==parent)
throw FatalAstException("parent node mismatch at $assignment")
val nextAssign = assignment.nextSibling() as? Assignment
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
if(nextAssign.value isSameAs assignment.value)
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
return noModifications
}

View File

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

View File

@ -2,11 +2,6 @@ package prog8tests
import com.github.michaelbull.result.getErrorOrElse
import com.github.michaelbull.result.getOrElse
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.hamcrest.core.Is
import org.junit.jupiter.api.*
import prog8.ast.Program
import prog8.ast.internedStringsModuleName
import prog8.compiler.ModuleImporter
@ -19,119 +14,91 @@ import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import kotlin.io.path.*
import kotlin.test.assertContains
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.fail
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() {
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(false), 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 }
@ -140,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
@ -172,51 +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(SourceCode.relative(srcPath).toString()))
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
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")
@ -224,28 +172,33 @@ class TestModuleImporter {
val act = { importer.importModule(importing) }
repeat(repetitions) { n ->
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
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("imported module with error in it should not be present", program.modules.size, equalTo(1))
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
}
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(false)
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
@ -254,46 +207,45 @@ class TestModuleImporter {
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")
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist")
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")
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist.p8")
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(SourceCode.relative(srcPath).toString()))
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")
@ -301,29 +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(SourceCode.relative(imported).toString()))
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("imported module with error in it should not be present", program.modules.size, equalTo(1))
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
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
fun testImportingFileWithSyntaxError_twice() {
test("testImportingFileWithSyntaxError_twice") {
doTestImportingFileWithSyntaxError(2)
}
}
}
}
}
})

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

View File

@ -1,21 +1,19 @@
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.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
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCallgraph {
@Test
fun testGraphForEmptySubs() {
class TestCallgraph: FunSpec({
test("testGraphForEmptySubs") {
val sourcecode = """
%import string
main {
@ -28,32 +26,33 @@ class TestCallgraph {
val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.program)
assertEquals(1, graph.imports.size)
assertEquals(1, graph.importedBy.size)
graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
assertEquals("string", importedModule.name)
importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single()
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
importedBy.name.startsWith("on_the_fly_test") shouldBe true
assertFalse(graph.unused(toplevelModule))
assertFalse(graph.unused(importedModule))
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
assertFalse(sub in graph.calls)
assertFalse(sub in graph.calledBy)
graph.calls shouldNotContainKey sub
graph.calledBy shouldNotContainKey sub
if(sub === result.program.entrypoint)
assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed")
withClue("start() should always be marked as used to avoid having it removed") {
graph.unused(sub) shouldBe false
}
else
assertTrue(graph.unused(sub))
graph.unused(sub) shouldBe true
}
}
@Test
fun testGraphForEmptyButReferencedSub() {
test("testGraphForEmptyButReferencedSub") {
val sourcecode = """
%import string
main {
@ -68,24 +67,32 @@ class TestCallgraph {
val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.program)
assertEquals(1, graph.imports.size)
assertEquals(1, graph.importedBy.size)
graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
assertEquals("string", importedModule.name)
importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single()
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
importedBy.name.startsWith("on_the_fly_test") shouldBe true
assertFalse(graph.unused(toplevelModule))
assertFalse(graph.unused(importedModule))
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"}
assertTrue(startSub in graph.calls, "start 'calls' (references) empty")
assertFalse(emptySub in graph.calls, "empty doesn't call anything")
assertTrue(emptySub in graph.calledBy, "empty gets 'called'")
assertFalse(startSub in graph.calledBy, "start doesn't get called (except as entrypoint ofc.)")
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
}
}
}
})

View File

@ -1,7 +1,10 @@
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
@ -11,9 +14,6 @@ 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
import kotlin.test.assertNull
/**
@ -21,11 +21,9 @@ import kotlin.test.assertNull
* 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 {
@ -40,15 +38,15 @@ class TestCompilerOnCharLit {
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())
arg.type shouldBe DataType.UBYTE
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
}
@Test
fun testCharVarAsRomsubArg() {
test("testCharVarAsRomsubArg") {
val platform = Cx16Target
val result = compileText(platform, false, """
main {
@ -64,28 +62,31 @@ class TestCompilerOnCharLit {
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)
assertNull(decl.value, "initializer value should have been moved to separate assignment")
withClue("initializer value should have been moved to separate assignment"){
decl.value shouldBe null
}
val assignInitialValue = decl.nextSibling() as Assignment
assertEquals(listOf("ch"), assignInitialValue.target.identifier!!.nameInSource)
assertIs<NumericLiteralValue>(assignInitialValue.value, "char literal should have been replaced by ubyte literal")
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
assertEquals(DataType.UBYTE, initializerValue.type)
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
initializerValue.type shouldBe DataType.UBYTE
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble()
}
@Test
fun testCharConstAsRomsubArg() {
test("testCharConstAsRomsubArg") {
val platform = Cx16Target
val result = compileText(platform, false, """
main {
@ -105,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())
decl.type shouldBe VarDeclType.CONST
decl.datatype shouldBe DataType.UBYTE
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0]
}
is NumericLiteralValue -> {
assertEquals(
platform.encodeString("\n", false)[0],
arg.number.toShort())
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
}
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
}
}
}
})

View File

@ -1,18 +1,15 @@
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.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compilerinterface.ICompilationTarget
import prog8tests.ast.helpers.assumeDirectory
import prog8tests.ast.helpers.mapCombinations
import prog8tests.ast.helpers.outputDir
import prog8tests.ast.helpers.workingDir
import prog8tests.ast.helpers.*
import prog8tests.helpers.assertSuccess
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.exists
@ -22,41 +19,99 @@ 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("disable 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,
optimizeFloatExpressions = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = platform.name,
sourceDirs = listOf(),
outputDir
).assertSuccess("; $displayName")
private val examplesDir = assumeDirectory(workingDir, "../examples")
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget): CompilationResult {
val args = CompilerArguments(
filepath,
optimize,
optimizeFloatExpressions = true,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = target.name,
outputDir = outputDir
)
return compileProgram(args)
}
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("disable to save some time")
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",
@ -79,48 +134,18 @@ class TestCompilerOnExamples {
"tehtriz",
"textelite",
),
dim2 = listOf(Cx16Target, C64Target),
dim3 = listOf(false, true),
combine3 = ::makeDynamicCompilerTest
listOf(false, true)
)
@TestFactory
// @Disabled("disable to save some time")
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("disable to save some time")
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()
}
}
})

View File

@ -1,7 +1,9 @@
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
@ -13,8 +15,6 @@ 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
/**
@ -22,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")
@ -44,17 +41,15 @@ class TestCompilerOnImportsAndIncludes {
.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")
@ -69,20 +64,18 @@ class TestCompilerOnImportsAndIncludes {
.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")
@ -90,8 +83,7 @@ class TestCompilerOnImportsAndIncludes {
.assertFailure()
}
@Test
fun testAsmbinaryDirectiveWithNonReadableFile() {
test("testAsmbinaryDirectiveWithNonReadableFile") {
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
assumeDirectory(fixturesDir, "subFolder")
@ -99,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 == workingDir
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"
)
}
}
}
}
})

View File

@ -1,26 +1,25 @@
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 io.kotest.matchers.types.instanceOf
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.printProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
import prog8tests.ast.helpers.mapCombinations
import prog8tests.ast.helpers.cartesianProduct
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.assertContains
import kotlin.test.assertEquals
/**
@ -28,13 +27,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
@ -55,12 +52,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
@ -84,50 +84,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
}
}
@ -136,12 +125,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 {
@ -167,12 +156,15 @@ class TestCompilerOnRanges {
val intProgression = rangeExpr.toConstantIntegerRange()
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
assertEquals(expectedStr, actualStr,".first .. .last")
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(), "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 {
@ -192,14 +184,13 @@ class TestCompilerOnRanges {
.map { it.iterable }
.filterIsInstance<RangeExpr>()[0]
assertEquals(2, rangeExpr.size())
rangeExpr.size() shouldBe 2
val intProgression = rangeExpr.toConstantIntegerRange()
assertEquals(0, intProgression?.first)
assertEquals(1, intProgression?.last)
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 {
@ -219,14 +210,13 @@ class TestCompilerOnRanges {
.map { it.iterable }
.filterIsInstance<RangeExpr>()[0]
assertEquals(9, rangeExpr.size())
rangeExpr.size() shouldBe 9
val intProgression = rangeExpr.toConstantIntegerRange()
assertEquals(1, intProgression?.first)
assertEquals(9, intProgression?.last)
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 {
@ -238,13 +228,12 @@ class TestCompilerOnRanges {
}
}
""", errors, false).assertFailure()
assertEquals(2, errors.errors.size)
assertContains(errors.errors[0], ".p8:5:29: range expression from value must be integer")
assertContains(errors.errors[1], ".p8:5:44: range expression to value must be integer")
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() {
@ -263,18 +252,54 @@ class TestCompilerOnRanges {
.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
fun testRangeExprNumericSize() {
test("testRangeExprNumericSize") {
val expr = RangeExpr(
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
Position.DUMMY)
assertEquals(6, expr.size())
expr.size() shouldBe 6
expr.toConstantIntegerRange()
}
}
test("range with negative step should be constvalue") {
val result = compileText(C64Target, false, """
main {
sub start() {
ubyte[] array = 100 to 50 step -2
ubyte xx
for xx in 100 to 50 step -2 {
}
}
}
""").assertSuccess()
val statements = result.program.entrypoint.statements
val array = (statements[0] as VarDecl).value
array shouldBe instanceOf<ArrayLiteralValue>()
(array as ArrayLiteralValue).value.size shouldBe 26
val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpr>()
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
}
test("range with start/end variables should be ok") {
val result = compileText(C64Target, false, """
main {
sub start() {
byte from = 100
byte end = 50
byte xx
for xx in from to end step -2 {
}
}
}
""").assertSuccess()
val statements = result.program.entrypoint.statements
val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpr>()
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
}
})

View File

@ -1,9 +1,8 @@
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.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.Cx16Target
import prog8tests.ast.helpers.assumeReadableFile
@ -22,13 +21,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 TestCompilerOptionSourcedirs {
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 {
@ -38,13 +35,12 @@ class TestCompilerOptionSourcedirs {
""")}
}
@AfterAll
fun tearDown() {
afterSpec {
tempFileInWorkingDir.deleteExisting()
}
private fun compileFile(filePath: Path, sourceDirs: List<String>) =
compileProgram(
fun compileFile(filePath: Path, sourceDirs: List<String>): CompilationResult {
val args = CompilerArguments(
filepath = filePath,
optimize = false,
optimizeFloatExpressions = false,
@ -55,48 +51,44 @@ class TestCompilerOptionSourcedirs {
sourceDirs,
outputDir
)
return compileProgram(args)
}
@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 testFilePathInWorkingDirRelativeTo1stInSourcedirs() {
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 testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs() {
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
val sourcedirs = listOf("$fixturesDir")
compileFile(filepath.fileName, sourcedirs)
.assertSuccess()
}
}
})

View File

@ -1,7 +1,9 @@
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.determineCompilationOptions
import prog8.compiler.parseImports
@ -11,15 +13,11 @@ import prog8tests.ast.helpers.outputDir
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestImportedModulesOrderAndOptions {
class TestImportedModulesOrderAndOptions: FunSpec({
@Test
fun testImportedModuleOrderAndMainModuleCorrect() {
test("testImportedModuleOrderAndMainModuleCorrect") {
val result = compileText(C64Target, false, """
%import textio
%import floats
@ -30,25 +28,27 @@ main {
}
}
""").assertSuccess()
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
val moduleNames = result.program.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")
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
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
@ -61,15 +61,14 @@ main {
}
}
""").assertSuccess()
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
val options = determineCompilationOptions(result.program, C64Target)
assertTrue(options.floats)
assertEquals(ZeropageType.DONTUSE, options.zeropage)
assertTrue(options.noSysInit)
options.floats shouldBe true
options.zeropage shouldBe ZeropageType.DONTUSE
options.noSysInit shouldBe true
}
@Test
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
test("testModuleOrderAndCompilationOptionsCorrectWithJustImports") {
val errors = ErrorReporterForTests()
val sourceText = """
%import textio
@ -88,17 +87,23 @@ main {
filepath.toFile().writeText(sourceText)
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
assertEquals(filenameBase, program.toplevelModule.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
}
}
}
})

View File

@ -1,7 +1,7 @@
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
@ -12,179 +12,212 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.*
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.compilerinterface.isInRegularRAMof
import prog8.compilerinterface.isIOAddress
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestMemory {
class TestMemory: FunSpec({
@Test
fun testInValidRamC64_memory_addresses() {
fun wrapWithProgram(statements: List<Statement>): Program {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), statements.toMutableList(), false, Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
program.addModule(module)
return program
}
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
test("assignment target not in mapped IO space C64") {
var memexpr = NumericLiteralValue.optimalInteger(0x0002, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
}
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
@Test
fun testNotInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
memexpr = NumericLiteralValue.optimalInteger(0xeeee, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_memory_identifiers() {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
test("assign target in mapped IO space C64") {
assertTrue(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0x0001, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xdfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
}
private 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)
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, 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", 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)
wrapWithProgram(listOf(decl, assignment))
return target
}
@Test
fun testInValidRamC64_memory_expression() {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
test("identifier mapped to IO memory on C64") {
var target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.VAR)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.VAR)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.CONST)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.CONST)
target.isIOAddress(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.MEMORY)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.MEMORY)
target.isIOAddress(C64Target.machine) shouldBe true
}
@Test
fun testInValidRamC64_variable() {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
test("memory expression mapped to IO memory on C64") {
var memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
printProgram(target.definingModule.program)
target.isIOAddress(C64Target.machine) shouldBe true
}
test("regular variable not in mapped IO ram on C64") {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, 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", 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)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isIOAddress(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_memmap_variable() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped variable not in mapped IO ram on C64") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, 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", 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)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isIOAddress(C64Target.machine) shouldBe false
}
@Test
fun testNotInValidRamC64_memmap_variable() {
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped variable in mapped IO ram on C64") {
val address = 0xd020u
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, 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", 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)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target.isIOAddress(C64Target.machine) shouldBe true
}
@Test
fun testInValidRamC64_array() {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
test("array not in mapped IO ram") {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, 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", 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)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isIOAddress(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_array_memmapped() {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped array not in mapped IO ram") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, 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", 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)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isIOAddress(C64Target.machine) shouldBe false
}
@Test
fun testNotValidRamC64_array_memmapped() {
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped array in mapped IO ram") {
val address = 0xd800u
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, 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", 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)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target.isIOAddress(C64Target.machine) shouldBe true
}
}
})

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.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compilerinterface.InternalCompilerException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestNumbers {
@Test
fun testToHex() {
assertEquals("0", 0.toHex())
assertEquals("1", 1.toHex())
assertEquals("1", 1.234.toHex())
assertEquals("10", 10.toHex())
assertEquals("10", 10.99.toHex())
assertEquals("15", 15.toHex())
assertEquals("\$10", 16.toHex())
assertEquals("\$ff", 255.toHex())
assertEquals("\$0100", 256.toHex())
assertEquals("\$4e5c", 20060.toHex())
assertEquals("\$c382", 50050.toHex())
assertEquals("\$ffff", 65535.toHex())
assertEquals("\$ffff", 65535L.toHex())
assertEquals("0", 0.toHex())
assertEquals("-1", (-1).toHex())
assertEquals("-1", (-1.234).toHex())
assertEquals("-10", (-10).toHex())
assertEquals("-10", (-10.99).toHex())
assertEquals("-15", (-15).toHex())
assertEquals("-\$10", (-16).toHex())
assertEquals("-\$ff", (-255).toHex())
assertEquals("-\$0100", (-256).toHex())
assertEquals("-\$4e5c", (-20060).toHex())
assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
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(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u)
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u)
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u)
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u)
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u)
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u)
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u)
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u)
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u)
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu)
Mflpt5.fromNumber(-123.45678e-22) shouldBe Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu)
}
@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<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
test("testFloatRange") {
Mflpt5.fromNumber(FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xffu, 0x7fu, 0xffu, 0xffu, 0xffu)
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xffu, 0xffu, 0xffu, 0xffu, 0xffu)
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03u, 0x39u, 0x1du, 0x15u, 0x63u)
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03u, 0xb9u, 0x1du, 0x15u, 0x63u)
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
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(0x00u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 0.0
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 32768.0
Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -32768.0
Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 1.0
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -.5
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 10.0
Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u).toDouble() shouldBe 1000000000.0
Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .5
Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .25
Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu).toDouble() shouldBe(123.45678e22 plusOrMinus 1.0e15)
Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu).toDouble() shouldBe(-123.45678e-22 plusOrMinus epsilon)
}
}
})

View File

@ -1,144 +1,153 @@
package prog8tests
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.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.base.DataType
import prog8.ast.base.ExpressionError
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() {
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)
test("testIdentity") {
val v = NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)
(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.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, 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("test rounding") {
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.BYTE, -2.345, dummyPos)
}.message shouldContain "refused silent rounding"
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.BYTE, -2.6, dummyPos)
}.message shouldContain "refused silent rounding"
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.UWORD, 2222.345, dummyPos)
}.message shouldContain "refused silent rounding"
NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
NumericLiteralValue(DataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
NumericLiteralValue(DataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
NumericLiteralValue(DataType.FLOAT, 123.456, dummyPos)
}
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)))
test("testEqualsAndNotEquals") {
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe true
(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))
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, 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)))
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9.0, 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)
val lvThree = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
val lvOne = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
val lvThree = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4.0, dummyPos)
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3)
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.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 253.0, 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.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254.0, 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.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 254.0, 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.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255.0, 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.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 255.0, 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.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254.0, 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.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 254.0, 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.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
}
}
})

View File

@ -1,26 +1,34 @@
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.shouldNotBe
import io.kotest.matchers.string.shouldContain
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.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.compilerinterface.*
import prog8tests.helpers.*
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 kotlin.test.*
import prog8tests.helpers.generateAssembly
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestOptimization {
@Test
fun testRemoveEmptySubroutineExceptStart() {
class TestOptimization: FunSpec({
test("remove empty subroutine except start") {
val sourcecode = """
main {
sub start() {
@ -34,13 +42,16 @@ class TestOptimization {
val toplevelModule = result.program.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements.single() as Subroutine
assertSame(result.program.entrypoint, startSub)
assertEquals("start", startSub.name, "only start sub should remain")
assertTrue(startSub.statements.single() is Return, "compiler has inserted return in empty subroutines")
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
fun testDontRemoveEmptySubroutineIfItsReferenced() {
test("don't remove empty subroutine if it's referenced") {
val sourcecode = """
main {
sub start() {
@ -57,45 +68,59 @@ class TestOptimization {
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements[0] as Subroutine
val emptySub = mainBlock.statements[1] as Subroutine
assertSame(result.program.entrypoint, startSub)
assertEquals("start", startSub.name)
assertEquals("empty", emptySub.name)
assertTrue(emptySub.statements.single() is Return, "compiler has inserted return in empty subroutines")
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
fun testGeneratedConstvalueInheritsProperParentLinkage()
{
val number = NumericLiteralValue(DataType.UBYTE, 11, Position.DUMMY)
test("generated constvalue from typecast inherits proper parent linkage") {
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
tc.linkParents(ParentSentinel)
assertNotNull(tc.parent)
assertNotNull(number.parent)
assertSame(tc, number.parent)
tc.parent shouldNotBe null
number.parent shouldNotBe null
tc shouldBeSameInstanceAs number.parent
val constvalue = tc.constValue(program)!!
assertIs<NumericLiteralValue>(constvalue)
assertEquals(11, constvalue.number.toInt())
assertEquals(DataType.BYTE, constvalue.type)
assertSame(tc, constvalue.parent)
constvalue shouldBe instanceOf<NumericLiteralValue>()
constvalue.number shouldBe 11.0
constvalue.type shouldBe DataType.BYTE
constvalue.parent shouldBeSameInstanceAs tc.parent
}
@Test
fun testConstantFoldedAndSilentlyTypecastedForInitializerValues() {
test("generated constvalue from prefixexpr inherits proper parent linkage") {
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
val pfx = PrefixExpression("-", number, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
pfx.linkParents(ParentSentinel)
pfx.parent shouldNotBe null
number.parent shouldNotBe null
pfx shouldBeSameInstanceAs number.parent
val constvalue = pfx.constValue(program)!!
constvalue shouldBe instanceOf<NumericLiteralValue>()
constvalue.number shouldBe -11.0
constvalue.type shouldBe DataType.BYTE
constvalue.parent shouldBeSameInstanceAs pfx.parent
}
test("constantfolded and silently typecasted for initializervalues") {
val sourcecode = """
main {
sub start() {
const ubyte TEST = 10
byte x1 = TEST as byte + 1
byte x2 = 1 + TEST as byte
ubyte y1 = TEST + 1 as byte
ubyte y2 = 1 as byte + TEST
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
assertEquals(10, mainsub.statements.size)
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
@ -105,19 +130,440 @@ class TestOptimization {
val initY1 = mainsub.statements[6] as Assignment
val declY2 = mainsub.statements[7] as VarDecl
val initY2 = mainsub.statements[8] as Assignment
assertIs<Return>(mainsub.statements[9])
assertEquals(10.0, (declTest.value as NumericLiteralValue).number.toDouble())
assertNull(declX1.value)
assertNull(declX2.value)
assertNull(declY1.value)
assertNull(declY2.value)
assertEquals(DataType.BYTE, (initX1.value as NumericLiteralValue).type)
assertEquals(11.0, (initX1.value as NumericLiteralValue).number.toDouble())
assertEquals(DataType.BYTE, (initX2.value as NumericLiteralValue).type)
assertEquals(11.0, (initX2.value as NumericLiteralValue).number.toDouble())
assertEquals(DataType.UBYTE, (initY1.value as NumericLiteralValue).type)
assertEquals(11.0, (initY1.value as NumericLiteralValue).number.toDouble())
assertEquals(DataType.UBYTE, (initY2.value as NumericLiteralValue).type)
assertEquals(11.0, (initY2.value as NumericLiteralValue).number.toDouble())
mainsub.statements[9] shouldBe instanceOf<Return>()
(declTest.value as NumericLiteralValue).number 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 shouldBe 11.0
(initX2.value as NumericLiteralValue).type shouldBe DataType.BYTE
(initX2.value as NumericLiteralValue).number shouldBe 11.0
(initY1.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY1.value as NumericLiteralValue).number shouldBe 11.0
(initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY2.value as NumericLiteralValue).number 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 = 1.0
ff += (ub as float) ; operator doesn't matter
}
}
"""
val result = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
val assignFF = result.program.entrypoint.statements.last() as Assignment
assignFF.isAugmentable shouldBe true
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignFF.value as BinaryExpression
value.operator shouldBe "+"
value.left shouldBe IdentifierReference(listOf("ff"), Position.DUMMY)
value.right shouldBe instanceOf<TypecastExpression>()
val asm = generateAssembly(result.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")
}
test("test simple augmented assignment optimization correctly initializes all variables") {
val src="""
main {
sub start() {
ubyte @shared z1
z1 = 10
ubyte @shared z2
z2 = ~z2
ubyte @shared z3
z3 = not z3
uword @shared z4
z4 = (z4 as ubyte)
ubyte @shared z5
z5 = z1+z5+5
ubyte @shared z6
z6 = z1+z6-5
}
}"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
/* expected:
ubyte z1
z1 = 10
ubyte z2
z2 = 255
ubyte z3
z3 = 1
uword z4
z4 = 0
ubyte z5
z5 = z1
z5 += 5
ubyte z6
z6 = z1
z6 -= 5
*/
val statements = result.program.entrypoint.statements
statements.size shouldBe 14
val z1decl = statements[0] as VarDecl
val z1init = statements[1] as Assignment
val z2decl = statements[2] as VarDecl
val z2init = statements[3] as Assignment
val z3decl = statements[4] as VarDecl
val z3init = statements[5] as Assignment
val z4decl = statements[6] as VarDecl
val z4init = statements[7] as Assignment
val z5decl = statements[8] as VarDecl
val z5init = statements[9] as Assignment
val z5plus = statements[10] as Assignment
val z6decl = statements[11] as VarDecl
val z6init = statements[12] as Assignment
val z6plus = statements[13] as Assignment
z1decl.name shouldBe "z1"
z1init.value shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
z2decl.name shouldBe "z2"
z2init.value shouldBe NumericLiteralValue(DataType.UBYTE, 255.0, Position.DUMMY)
z3decl.name shouldBe "z3"
z3init.value shouldBe NumericLiteralValue(DataType.UBYTE, 1.0, Position.DUMMY)
z4decl.name shouldBe "z4"
z4init.value shouldBe NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
z5decl.name shouldBe "z5"
z5init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
z5plus.isAugmentable shouldBe true
(z5plus.value as BinaryExpression).operator shouldBe "+"
(z5plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
z6decl.name shouldBe "z6"
z6init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
z6plus.isAugmentable shouldBe true
(z6plus.value as BinaryExpression).operator shouldBe "-"
(z6plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
}
test("force_output option should work with optimizing memwrite assignment") {
val src="""
main {
%option force_output
sub start() {
uword aa
ubyte zz
@(aa) = zz + 32
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 6
val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
test("don't optimize memory writes away") {
val src="""
main {
sub start() {
uword aa
ubyte zz
@(aa) = zz + 32 ; do not optimize this away!
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 6
val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
test("correctly process constant prefix numbers") {
val src="""
main {
sub start() {
ubyte @shared z1 = 1
ubyte @shared z2 = + 1
ubyte @shared z3 = ~ 1
ubyte @shared z4 = not 1
byte @shared z5 = - 1
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 10
stmts.filterIsInstance<VarDecl>().size shouldBe 5
stmts.filterIsInstance<Assignment>().size shouldBe 5
}
test("correctly process constant prefix numbers with type mismatch and give error") {
val src="""
main {
sub start() {
ubyte @shared z1 = - 1
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, optimize=true, src, writeAssembly=false, errors = errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "type of value BYTE doesn't match target UBYTE"
errors.errors[1] shouldContain "value '-1' out of range for unsigned byte"
}
test("test augmented expression asmgen") {
val src = """
main {
sub start() {
ubyte c
ubyte r
ubyte q
r = (q+r)-c
q=r
r = q+(r-c)
q=r
}
}"""
val result = compileText(C64Target, optimize=false, src, writeAssembly=true).assertSuccess()
result.program.entrypoint.statements.size shouldBe 11
result.program.entrypoint.statements.last() shouldBe instanceOf<Return>()
}
test("keep the value initializer assignment if the next one depends on it") {
val src="""
main {
sub start() {
uword @shared yy
yy = 20 ; ok to remove =0 initializer before this
uword @shared zz
zz += 60 ; NOT ok to remove initializer, should evaluate to 60
ubyte @shared xx
xx = 6+sin8u(xx) ; NOT ok to remove initializer
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
/* expected result:
uword yy
yy = 20
uword zz
zz = 60
ubyte xx
xx = 0
xx = sin8u(xx)
xx += 6
*/
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8
stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 5
}
test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {
val src="""
main {
sub start() {
uword @shared xx
xx = xx + 20 ; is same var so can be changed just fine into xx=20
uword @shared yy
xx = 20
yy = 0 ; is other var..
xx = xx+10 ; so this should not be changed into xx=10
}
}"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
/*
expected result:
uword xx
xx = 20
uword yy
yy = 0
xx = 20
yy = 0
xx += 10
*/
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
stmts.filterIsInstance<VarDecl>().size shouldBe 2
stmts.filterIsInstance<Assignment>().size shouldBe 5
val assignXX1 = stmts[1] as Assignment
assignXX1.target.identifier!!.nameInSource shouldBe listOf("xx")
assignXX1.value shouldBe NumericLiteralValue(DataType.UBYTE, 20.0, Position.DUMMY)
val assignXX2 = stmts.last() as Assignment
assignXX2.target.identifier!!.nameInSource shouldBe listOf("xx")
val xxValue = assignXX2.value as BinaryExpression
xxValue.operator shouldBe "+"
xxValue.left shouldBe IdentifierReference(listOf("xx"), Position.DUMMY)
xxValue.right shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
}
})

View File

@ -1,106 +1,83 @@
package prog8tests
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.expect
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrElse
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.FunSpec
import io.kotest.matchers.shouldBe
import prog8.compiler.target.cbm.Petscii
import java.io.CharConversionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestPetscii {
class TestPetscii: FunSpec({
@Test
fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(Ok(listOf<Short>(0))))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(Ok(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<UByte>(0u))
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<UByte>(0u))
Petscii.decodePetscii(listOf(0u), true) shouldBe "\u0000"
Petscii.decodePetscii(listOf(0u), false) shouldBe "\u0000"
}
@Test
fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
Ok(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(Ok(listOf<Short>(0x12)))) // reverse vid
assertThat(Petscii.encodePetscii("", true), equalTo(Ok(listOf<Short>(0xfa))))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(Ok(listOf<Short>(255))))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("", true), equalTo(Ok(listOf<Short>(0xd3))))
test("testLowercase") {
Petscii.encodePetscii("hello WORLD 123 @!£", true) shouldBe
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 0xd7u, 0xcfu, 0xd2u, 0xccu, 0xc4u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
Petscii.encodePetscii("", true) shouldBe Ok(listOf<UByte>(0xfau))
withClue("expect lowercase error fallback") {
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<UByte>(255u))
Petscii.encodePetscii("", true) shouldBe Ok(listOf<UByte>(0xd3u))
}
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
Petscii.decodePetscii(listOf(72u, 0xd7u, 0x5cu, 0xfau, 0x12u), true) shouldBe "hW£✓\uF11A"
}
@Test
fun testUppercase() {
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
assertThat(Petscii.encodePetscii(""), equalTo(Ok(listOf<Short>(0xd3))))
assertThat(Petscii.encodePetscii("π"), equalTo(Ok(listOf<Short>(0xff))))
assertThat("expecting fallback", Petscii.encodePetscii(""), equalTo(Ok(listOf<Short>(250))))
test("testUppercase") {
Petscii.encodePetscii("HELLO 123 @!£") shouldBe
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
Petscii.encodePetscii("") shouldBe Ok(listOf<UByte>(0xd3u))
Petscii.encodePetscii("π") shouldBe Ok(listOf<UByte>(0xffu))
withClue("expecting fallback") {
Petscii.encodePetscii("") shouldBe Ok(listOf<UByte>(250u))
}
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
Petscii.decodePetscii(listOf(72u, 0x5cu, 0xd3u, 0xffu)) shouldBe "H£♥π"
}
@Test
fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
Ok(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(Ok(listOf<Short>(0x7a))))
assertThat("expect fallback", Petscii.encodeScreencode("", true), equalTo(Ok(listOf<Short>(83))))
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94))))
test("testScreencodeLowercase") {
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
Ok(listOf<UByte>(0x08u, 0x05u, 0x0cu, 0x0cu, 0x0fu, 0x20u, 0x57u, 0x4fu, 0x52u, 0x4cu, 0x44u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
Petscii.encodeScreencode("", true) shouldBe Ok(listOf<UByte>(0x7au))
withClue("expect fallback") {
Petscii.encodeScreencode("", true) shouldBe Ok(listOf<UByte>(83u))
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<UByte>(94u))
}
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
Petscii.decodeScreencode(listOf(0x08u, 0x57u, 0x1cu, 0x7au), true) shouldBe "hW£✓"
}
@Test
fun testScreencodeUppercase() {
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
Ok(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
assertThat(Petscii.encodeScreencode(""), equalTo(Ok(listOf<Short>(0x53))))
assertThat(Petscii.encodeScreencode("π"), equalTo(Ok(listOf<Short>(0x5e))))
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
assertThat("expecting fallback", Petscii.encodeScreencode(""), equalTo(Ok(listOf<Short>(122))))
test("testScreencodeUppercase") {
Petscii.encodeScreencode("WORLD 123 @!£") shouldBe
Ok(listOf<UByte>(0x17u, 0x0fu, 0x12u, 0x0cu, 0x04u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
Petscii.encodeScreencode("") shouldBe Ok(listOf<UByte>(0x53u))
Petscii.encodeScreencode("π") shouldBe Ok(listOf<UByte>(0x5eu))
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
withClue("expecting fallback") {
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
Petscii.encodeScreencode("") shouldBe Ok(listOf<UByte>(122u))
}
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
Petscii.decodeScreencode(listOf(0x17u, 0x1cu, 0x53u, 0x5eu)) shouldBe "W£♥π"
}
@Test
fun testErrorCases() {
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" }
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), true) }
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), true) }
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), false) }
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), false) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), true) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), true) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), false) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), false) }
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
fun testSpecialReplacements()
{
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()
@ -109,92 +86,90 @@ class TestPetscii {
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
assertEquals(94, encodeP('^', false))
assertEquals(94, encodeP('^', true))
assertEquals(30, encodeS('^', false))
assertEquals(30, encodeS('^', true))
assertEquals(228, encodeP('_', false))
assertEquals(228, encodeP('_', true))
assertEquals(100, encodeS('_', false))
assertEquals(100, encodeS('_', true))
assertEquals(243, encodeP('{', false))
assertEquals(243, encodeP('{', true))
assertEquals(115, encodeS('{', false))
assertEquals(115, encodeS('{', true))
assertEquals(235, encodeP('}', false))
assertEquals(235, encodeP('}', true))
assertEquals(107, encodeS('}', false))
assertEquals(107, encodeS('}', true))
assertEquals(221, encodeP('|', false))
assertEquals(221, encodeP('|', true))
assertEquals(93, encodeS('|', false))
assertEquals(93, encodeS('|', true))
assertEquals(205, encodeP('\\', false))
assertEquals(205, encodeP('\\', true))
assertEquals(77, encodeS('\\', false))
assertEquals(77, encodeS('\\', true))
encodeP('^', false) shouldBe 94u
encodeP('^', true) shouldBe 94u
encodeS('^', false) shouldBe 30u
encodeS('^', true) shouldBe 30u
encodeP('_', false) shouldBe 228u
encodeP('_', true) shouldBe 228u
encodeS('_', false) shouldBe 100u
encodeS('_', true) shouldBe 100u
encodeP('{', false) shouldBe 243u
encodeP('{', true) shouldBe 243u
encodeS('{', false) shouldBe 115u
encodeS('{', true) shouldBe 115u
encodeP('}', false) shouldBe 235u
encodeP('}', true) shouldBe 235u
encodeS('}', false) shouldBe 107u
encodeS('}', true) shouldBe 107u
encodeP('|', false) shouldBe 221u
encodeP('|', true) shouldBe 221u
encodeS('|', false) shouldBe 93u
encodeS('|', true) shouldBe 93u
encodeP('\\', false) shouldBe 205u
encodeP('\\', true) shouldBe 205u
encodeS('\\', false) shouldBe 77u
encodeS('\\', true) shouldBe 77u
}
@Test
fun testBoxDrawingCharsEncoding() {
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
assertEquals(221, encodeP('|', false))
assertEquals(221, encodeP('|', true))
assertEquals(93, encodeS('|', false))
assertEquals(93, encodeS('|', true))
encodeP('|', false) shouldBe 221u
encodeP('|', true) shouldBe 221u
encodeS('|', false) shouldBe 93u
encodeS('|', true) shouldBe 93u
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
assertEquals(221, encodeP('│', false))
assertEquals(221, encodeP('│', true))
assertEquals(93, encodeS('│', false))
assertEquals(93, encodeS('│', true))
encodeP('│', false) shouldBe 221u
encodeP('│', true) shouldBe 221u
encodeS('│', false) shouldBe 93u
encodeS('│', true) shouldBe 93u
// underscore
assertEquals(228, encodeP('_', false))
assertEquals(228, encodeP('_', true))
assertEquals(100, encodeS('_', false))
assertEquals(100, encodeS('_', true))
encodeP('_', false) shouldBe 228u
encodeP('_', true) shouldBe 228u
encodeS('_', false) shouldBe 100u
encodeS('_', true) shouldBe 100u
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
assertEquals(228, encodeP('▁', false))
assertEquals(228, encodeP('▁', true))
assertEquals(100, encodeS('▁', false))
assertEquals(100, encodeS('▁', true))
encodeP('▁', false) shouldBe 228u
encodeP('▁', true) shouldBe 228u
encodeS('▁', false) shouldBe 100u
encodeS('▁', true) shouldBe 100u
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
assertEquals(192, encodeP('─', false))
assertEquals(192, encodeP('─', true))
assertEquals(64, encodeS('─', false))
assertEquals(64, encodeS('─', true))
encodeP('─', false) shouldBe 192u
encodeP('─', true) shouldBe 192u
encodeS('─', false) shouldBe 64u
encodeS('─', true) shouldBe 64u
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
assertEquals(221, encodeP('│', false))
assertEquals(221, encodeP('│', true))
assertEquals(93, encodeS('│', false))
assertEquals(93, encodeS('│', true))
encodeP('│', false) shouldBe 221u
encodeP('│', true) shouldBe 221u
encodeS('│', false) shouldBe 93u
encodeS('│', true) shouldBe 93u
}
@Test
fun testBoxDrawingCharsDecoding() {
test("testBoxDrawingCharsDecoding") {
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
assertEquals('\uf13b', Petscii.decodePetscii(listOf(195), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
assertEquals('C', Petscii.decodePetscii(listOf(195), true).single())
assertEquals('─', Petscii.decodePetscii(listOf(192), false).single())
assertEquals('─', Petscii.decodePetscii(listOf(192), true).single())
assertEquals('\uf13b', Petscii.decodeScreencode(listOf(67), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
assertEquals('C', Petscii.decodeScreencode(listOf(67), true).single())
assertEquals('─', Petscii.decodeScreencode(listOf(64), false).single())
assertEquals('─', Petscii.decodeScreencode(listOf(64), true).single())
Petscii.decodePetscii(listOf(195u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodePetscii(listOf(195u), true).single() shouldBe 'C'
Petscii.decodePetscii(listOf(192u), false).single() shouldBe '─'
Petscii.decodePetscii(listOf(192u), true).single() shouldBe '─'
Petscii.decodeScreencode(listOf(67u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodeScreencode(listOf(67u), true).single() shouldBe 'C'
Petscii.decodeScreencode(listOf(64u), false).single() shouldBe '─'
Petscii.decodeScreencode(listOf(64u), true).single() shouldBe '─'
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
assertEquals('│', Petscii.decodePetscii(listOf(125), false).single())
assertEquals('│', Petscii.decodePetscii(listOf(125), true).single())
assertEquals('│', Petscii.decodePetscii(listOf(221), false).single())
assertEquals('│', Petscii.decodePetscii(listOf(221), true).single())
assertEquals('│', Petscii.decodeScreencode(listOf(93), false).single())
assertEquals('│', Petscii.decodeScreencode(listOf(93), true).single())
assertEquals('\uf13c', Petscii.decodeScreencode(listOf(66), false).single(), "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)")
assertEquals('B', Petscii.decodeScreencode(listOf(66), true).single())
Petscii.decodePetscii(listOf(125u), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(125u), true).single() shouldBe '│'
Petscii.decodePetscii(listOf(221u), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(221u), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93u), false).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93u), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(66u), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
Petscii.decodeScreencode(listOf(66u), true).single() shouldBe 'B'
}
}
})

View File

@ -1,22 +1,25 @@
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.shouldContain
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.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestScoping {
class TestScoping: FunSpec({
@Test
fun testModulesParentIsGlobalNamespace() {
test("modules parent is global namespace") {
val src = """
main {
sub start() {
@ -26,13 +29,12 @@ class TestScoping {
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
assertIs<GlobalNamespace>(module.parent)
assertSame(result.program, module.program)
assertIs<ParentSentinel>(module.parent.parent)
module.parent shouldBe instanceOf<GlobalNamespace>()
module.program shouldBeSameInstanceAs result.program
module.parent.parent shouldBe instanceOf<ParentSentinel>()
}
@Test
fun testAnonScopeVarsMovedIntoSubroutineScope() {
test("anon scope vars moved into subroutine scope") {
val src = """
main {
sub start() {
@ -49,19 +51,28 @@ class TestScoping {
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
withClue("no vars moved to main block") {
mainBlock.statements.any { it is VarDecl } shouldBe false
}
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
assertEquals(1, subroutineVars.size, "var from repeat anonscope must be moved up to subroutine")
assertEquals("xx", subroutineVars[0].name)
assertFalse(repeatbody.statements.any { it is VarDecl }, "var should have been removed from repeat anonscope")
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
assertEquals(listOf("xx"), initassign?.target?.identifier?.nameInSource, "vardecl in repeat should be replaced by init assignment")
assertEquals(99, (initassign?.value as? NumericLiteralValue)?.number?.toInt(), "vardecl in repeat should be replaced by init assignment")
assertTrue(repeatbody.statements[1] is PostIncrDecr)
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
fun testLabelsWithAnonScopes() {
test("labels with anon scopes") {
val src = """
main {
sub start() {
@ -74,11 +85,9 @@ class TestScoping {
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:
}
@ -88,11 +97,9 @@ class TestScoping {
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:
}
@ -108,9 +115,7 @@ class TestScoping {
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
}
"""
@ -120,7 +125,211 @@ class TestScoping {
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>()
assertEquals(1, labels.size, "only one label in subroutine scope")
withClue("only one label in subroutine scope") {
labels.size shouldBe 1
}
}
}
test("good subroutine call without qualified names") {
val text="""
main {
sub start() {
routine()
routine2()
sub routine2() {
}
}
sub routine() {
start()
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong subroutine call without qualified names") {
val text="""
main {
sub start() {
sub routine2() {
}
}
sub routine() {
routine2()
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure()
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "undefined symbol: routine2"
}
test("good subroutine calls with qualified names (from root)") {
val text="""
main {
sub start() {
main.routine()
main.start.routine2()
sub routine2() {
}
}
sub routine() {
main.start.routine2()
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong subroutine calls with qualified names (not from root)") {
val text="""
main {
sub start() {
start.routine2()
wrong.start.routine2()
sub routine2() {
}
}
sub routine() {
start.routine2()
wrong.start.routine2()
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "undefined symbol: start.routine2"
errors.errors[1] shouldContain "undefined symbol: wrong.start.routine2"
errors.errors[2] shouldContain "undefined symbol: start.routine2"
errors.errors[3] shouldContain "undefined symbol: wrong.start.routine2"
}
test("good variables without qualified names") {
val text="""
main {
ubyte v1
sub start() {
ubyte v2
v1=1
v2=2
sub routine2() {
ubyte v3
v1=1
v2=2
v3=3
}
}
sub routine() {
ubyte v4
v1=1
v4=4
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong variables without qualified names") {
val text="""
main {
ubyte v1
sub start() {
ubyte v2
v1=1
v2=2
v3=3 ; can't access
v4=4 ; can't access
sub routine2() {
ubyte v3
v1=1
v2=2
v3=3
v4=3 ;can't access
}
}
sub routine() {
ubyte v4
v1=1
v2=2 ; can't access
v3=3 ; can't access
v4=4
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 5
errors.errors[0] shouldContain "undefined symbol: v3"
errors.errors[1] shouldContain "undefined symbol: v4"
errors.errors[2] shouldContain "undefined symbol: v4"
errors.errors[3] shouldContain "undefined symbol: v2"
errors.errors[4] shouldContain "undefined symbol: v3"
}
test("good variable refs with qualified names (from root)") {
val text="""
main {
sub start() {
uword xx
xx = &main.routine
main.routine(5)
main.routine.value = 5
main.routine.arg = 5
xx = &main.routine.nested
main.routine.nested(5)
main.routine.nested.nestedvalue = 5
main.routine.nested.arg2 = 5
}
sub routine(ubyte arg) {
ubyte value
sub nested(ubyte arg2) {
ubyte nestedvalue
}
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong variable refs with qualified names 1 (not from root)") {
val text="""
main {
sub start() {
uword xx
xx = &routine
routine(5)
routine.value = 5
routine.arg = 5
routine.nested.arg2 = 5
routine.nested.nestedvalue = 5
nested.nestedvalue = 5
}
sub routine(ubyte arg) {
ubyte value
sub nested(ubyte arg2) {
ubyte nestedvalue
}
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 5
errors.errors[0] shouldContain "undefined symbol: routine.value"
errors.errors[1] shouldContain "undefined symbol: routine.arg"
errors.errors[2] shouldContain "undefined symbol: routine.nested.arg2"
errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue"
errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue"
}
})

View File

@ -1,8 +1,11 @@
package prog8tests
import org.junit.jupiter.api.Disabled
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.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.*
@ -11,14 +14,11 @@ import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSubroutines {
class TestSubroutines: FunSpec({
@Test
fun stringParameter() {
test("stringParameter") {
val text = """
main {
sub start() {
@ -46,27 +46,31 @@ class TestSubroutines {
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"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.STR, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.STR, func.parameters.single().type)
assertEquals(4, func.statements.size)
val paramvar = func.statements[0] as VarDecl
assertEquals("thing", paramvar.name)
assertEquals(DataType.STR, paramvar.datatype)
asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.STR
asmfunc.statements.isEmpty() shouldBe true
func.isAsmSubroutine shouldBe false
withClue("str param for normal subroutine should be changed into UWORD") {
func.parameters.single().type shouldBe DataType.UWORD
func.statements.size shouldBe 4
val paramvar = func.statements[0] as VarDecl
paramvar.name shouldBe "thing"
paramvar.datatype shouldBe DataType.UWORD
}
val assign = func.statements[2] as Assignment
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
assertTrue(assign.value is TypecastExpression, "str param in function body should not be transformed by normal compiler steps")
assertEquals(DataType.UWORD, (assign.value as TypecastExpression).type)
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
withClue("str param in function body should have been transformed into just uword assignment") {
assign.value shouldBe instanceOf<IdentifierReference>()
}
val call = func.statements[3] as FunctionCallStatement
assertEquals("asmfunc", call.target.nameInSource.single())
assertTrue(call.args.single() is IdentifierReference, "str param in function body should not be transformed by normal compiler steps")
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
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
fun stringParameterAsmGen() {
test("stringParameterAsmGen") {
val text = """
main {
sub start() {
@ -94,30 +98,37 @@ class TestSubroutines {
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"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.STR, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.single() is Return)
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.UWORD, func.parameters.single().type, "asmgen should have changed str to uword type")
assertTrue(asmfunc.statements.last() is Return)
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>()
assertEquals(5, func.statements.size)
assertTrue(func.statements[4] is Return)
func.statements.size shouldBe 5
func.statements[4] shouldBe instanceOf<Return>()
val paramvar = func.statements[0] as VarDecl
assertEquals("thing", paramvar.name)
assertEquals(DataType.UWORD, paramvar.datatype, "pre-asmgen should have changed str to uword type")
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
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
assertTrue(assign.value is IdentifierReference, "str param in function body should be treated as plain uword before asmgen")
assertEquals("thing", (assign.value as IdentifierReference).nameInSource.single())
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
assertEquals("asmfunc", call.target.nameInSource.single())
assertTrue(call.args.single() is IdentifierReference, "str param in function body should be treated as plain uword and not been transformed")
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
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
fun arrayParameterNotYetAllowed_ButShouldPerhapsBe() {
test("array param not yet allowd (but should perhaps be?)") {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
@ -135,13 +146,12 @@ class TestSubroutines {
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
assertEquals(0, errors.warnings.size)
assertContains(errors.errors.single(), ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly")
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"
}
@Test
@Disabled("TODO: allow array parameter in signature") // TODO allow this?
fun arrayParameter() {
// TODO allow this?
xtest("arrayParameter") {
val text = """
main {
sub start() {
@ -170,23 +180,22 @@ class TestSubroutines {
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"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
assertTrue(func.statements.isEmpty())
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
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead() {
test("uword param and normal varindexed as array work as DirectMemoryRead") {
val text="""
main {
sub thing(uword rr) {
ubyte xx = rr[1] ; should still work as var initializer that will be rewritten
ubyte yy
ubyte @shared xx = rr[1] ; should still work as var initializer that will be rewritten
ubyte @shared yy
yy = rr[2]
uword other
uword @shared other
ubyte zz = other[3]
}
@ -201,29 +210,30 @@ class TestSubroutines {
val module = result.program.toplevelModule
val block = module.statements.single() as Block
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
assertEquals("main", block.name)
assertEquals(10, thing.statements.size, "rr, xx, xx assign, yy, yy assign, other, other assign 0, zz, zz assign, return")
block.name shouldBe "main"
thing.statements.size shouldBe 11 // rr paramdecl, xx, xx assign, yy decl, yy init 0, yy assign, other, other assign 0, zz, zz assign, return
val xx = thing.statements[1] as VarDecl
assertNull(xx.value, "vardecl init values must have been moved to separate assignments")
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
assertEquals(listOf("xx"), assignXX.target.identifier!!.nameInSource)
assertEquals(listOf("yy"), assignYY.target.identifier!!.nameInSource)
assertEquals(listOf("zz"), assignZZ.target.identifier!!.nameInSource)
val assignYY = thing.statements[5] as Assignment
val assignZZ = thing.statements[9] 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
assertEquals(listOf("rr"), (valueXXexpr.left as IdentifierReference).nameInSource)
assertEquals(listOf("rr"), (valueYYexpr.left as IdentifierReference).nameInSource)
assertEquals(listOf("other"), (valueZZexpr.left as IdentifierReference).nameInSource)
assertEquals(1, (valueXXexpr.right as NumericLiteralValue).number.toInt())
assertEquals(2, (valueYYexpr.right as NumericLiteralValue).number.toInt())
assertEquals(3, (valueZZexpr.right as NumericLiteralValue).number.toInt())
(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
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite() {
test("uword param and normal varindexed as array work as MemoryWrite") {
val text="""
main {
sub thing(uword rr) {
@ -241,15 +251,15 @@ class TestSubroutines {
val module = result.program.toplevelModule
val block = module.statements.single() as Block
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
assertEquals("main", block.name)
assertEquals(3, thing.statements.size, "rr, rr assign, return void")
block.name shouldBe "main"
thing.statements.size shouldBe 3 // "rr, rr assign, return void"
val assignRR = thing.statements[1] as Assignment
assertEquals(42, (assignRR.value as NumericLiteralValue).number.toInt())
(assignRR.value as NumericLiteralValue).number.toInt() shouldBe 42
val memwrite = assignRR.target.memoryAddress
assertNotNull(memwrite, "memwrite to set byte array value")
val addressExpr = memwrite.addressExpression as BinaryExpression
assertEquals(listOf("rr"), (addressExpr.left as IdentifierReference).nameInSource)
assertEquals("+", addressExpr.operator)
assertEquals(10, (addressExpr.right as NumericLiteralValue).number.toInt())
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
}
}
})

View File

@ -1,50 +1,45 @@
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.comparables.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import prog8.ast.base.DataType
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
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 prog8.compilerinterface.*
import prog8tests.helpers.ErrorReporterForTests
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAbstractZeropage {
@Test
fun testAbstractZeropage() {
val compTarget = DummyCompilationTarget()
val zp = DummyZeropage(
CompilationOptions(
OutputType.RAW,
LauncherType.NONE,
ZeropageType.FULL,
listOf((0x50..0x5f)),
false,
false,
compTarget
)
)
assertEquals(256-6-16, zp.free.size)
}
class TestAbstractZeropage: FunSpec({
class DummyCompilationTarget: ICompilationTarget {
override val name: String = "dummy"
override val machine: IMachineDefinition
get() = throw NotImplementedError("dummy")
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
throw NotImplementedError("dummy")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
throw NotImplementedError("dummy")
}
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> {
throw NotImplementedError("dummy")
}
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean {
throw NotImplementedError("dummy")
}
@ -55,177 +50,184 @@ class TestAbstractZeropage {
}
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
override val SCRATCH_B1 = 0x10u
override val SCRATCH_REG = 0x11u
override val SCRATCH_W1 = 0x20u
override val SCRATCH_W2 = 0x30u
init {
free.addAll(0..255)
free.addAll(0u..255u)
removeReservedFromFreePool()
}
}
}
test("testAbstractZeropage") {
val compTarget = DummyCompilationTarget()
val zp = DummyZeropage(
CompilationOptions(
OutputType.RAW,
LauncherType.NONE,
ZeropageType.FULL,
listOf((0x50u..0x5fu)),
false,
false,
compTarget
)
)
zp.free.size shouldBe 256-6-16
}
})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage {
class TestC64Zeropage: FunSpec({
private val errors = ErrorReporterForTests()
val errors = ErrorReporterForTests()
@Test
fun testNames() {
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<InternalCompilerException> {
shouldThrow<InternalCompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<InternalCompilerException> {
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<InternalCompilerException> {
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<InternalCompilerException> {
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<InternalCompilerException> {
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)
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)
zp1.availableBytes() shouldBe 238
50u shouldBeIn zp1.free
100u shouldBeIn zp1.free
49u shouldBeIn zp1.free
101u shouldBeIn zp1.free
200u shouldBeIn zp1.free
255u shouldBeIn zp1.free
199u shouldBeIn zp1.free
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, C64Target))
zp2.availableBytes() shouldBe 139
50u shouldNotBeIn zp2.free
100u shouldNotBeIn zp2.free
49u shouldBeIn zp2.free
101u shouldBeIn zp2.free
200u shouldNotBeIn zp2.free
255u shouldNotBeIn zp2.free
199u 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 0u
}
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 3u
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)
}
@ -234,88 +236,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 0x04u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0au
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9bu
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9eu
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5u
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0u
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbeu
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0eu
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9u
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 + 1u shouldBe zp.SCRATCH_REG
}
}
}
})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
private val errors = ErrorReporterForTests()
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 + 1u 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
0x22u shouldBeIn zp1.free
0x80u shouldBeIn zp1.free
0xffu shouldBeIn zp1.free
0x02u shouldNotBeIn zp1.free
0x21u shouldNotBeIn zp1.free
}
}
})

View File

@ -22,15 +22,15 @@ internal val DummyFunctions = object : IBuiltinFunctions {
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
override fun memorySize(dt: DataType) = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
return ""
}
}

View File

@ -5,7 +5,6 @@ import prog8.compilerinterface.IErrorReporter
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()

View File

@ -1,24 +1,32 @@
package prog8tests.helpers
import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import prog8.ast.Program
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
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
}
@ -33,22 +41,23 @@ internal fun compileFile(
fileName: String,
outputDir: Path = prog8tests.ast.helpers.outputDir,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true
writeAssembly: Boolean = true,
optFloatExpr: Boolean = true
) : CompilationResult {
val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath)
return compileProgram(
val args = CompilerArguments(
filepath,
optimize,
optimizeFloatExpressions = false,
optimizeFloatExpressions = optFloatExpr,
writeAssembly = writeAssembly,
slowCodegenWarnings = false,
quietAssembler = true,
platform.name,
sourceDirs = listOf(),
outputDir,
outputDir = outputDir,
errors = errors ?: ErrorReporterForTests()
)
return compileProgram(args)
}
/**
@ -61,10 +70,23 @@ internal fun compileText(
optimize: Boolean,
sourceText: String,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true
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, errors=errors, writeAssembly=writeAssembly)
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()
}

View File

@ -1,9 +1,9 @@
plugins {
id 'java'
id "java"
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.8"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
@ -15,10 +15,7 @@ dependencies {
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 {
@ -42,7 +39,6 @@ sourceSets {
}
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.

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>

View File

@ -219,7 +219,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(jump: Jump) {
output("goto ")
if(jump.isGosub)
output("gosub ")
else
output("goto ")
when {
jump.address!=null -> output(jump.address.toHex())
jump.generatedLabel!=null -> output(jump.generatedLabel)
@ -262,7 +265,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(numLiteral: NumericLiteralValue) {
output(numLiteral.number.toString())
if(numLiteral.type==DataType.FLOAT)
output(numLiteral.number.toString())
else
output(numLiteral.number.toInt().toString())
}
override fun visit(char: CharLiteral) {

View File

@ -68,6 +68,9 @@ interface IStatementContainer {
fun isNotEmpty(): Boolean = statements.isNotEmpty()
fun searchSymbol(name: String): Statement? {
if(this is Subroutine && isAsmSubroutine)
return searchAsmParameter(name)
// 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) {
@ -75,16 +78,10 @@ interface IStatementContainer {
// 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 VarDecl -> if(stmt.name==name) return stmt
is Label -> if(stmt.name==name) return stmt
is Subroutine -> if(stmt.name==name) return stmt
is Block -> if(stmt.name==name) return stmt
is AnonymousScope -> {
val found = stmt.searchSymbol(name)
if(found!=null)
@ -154,41 +151,22 @@ interface INameScope: IStatementContainer, INamedStatement {
}
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
// a scoped name refers to a name in another namespace, and stars from the root.
for(module in (this as Node).definingModule.program.modules) {
module.statements.forEach {
if(it is Block && it.name==scopedName[0])
return it.lookup(scopedName)
val block = module.searchSymbol(scopedName[0])
if(block!=null) {
var statement = block
for(name in scopedName.drop(1)) {
statement = (statement as? IStatementContainer)?.searchSymbol(name)
if(statement==null)
return null
}
return statement
}
}
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) {
@ -199,7 +177,6 @@ interface INameScope: IStatementContainer, INamedStatement {
}
// 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)
@ -262,6 +239,7 @@ interface Node {
}
fun replaceChildNode(node: Node, replacement: Node)
fun copy(): Node
}
@ -277,8 +255,8 @@ open class Module(final override var statements: MutableList<Statement>,
.substringAfterLast("/")
.substringAfterLast("\\")
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
val loadAddress: UInt by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0u
address
}
@ -288,11 +266,6 @@ open class Module(final override var statements: MutableList<Statement>,
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) {
@ -302,6 +275,8 @@ open class Module(final override var statements: MutableList<Statement>,
replacement.parent = this
}
override fun copy(): Node = throw NotImplementedError("no support for duplicating a Module")
override fun toString() = "Module(name=$name, pos=$position, lib=${isLibrary})"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -317,6 +292,8 @@ class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override fun copy(): Node = throw NotImplementedError("no support for duplicating a GlobalNamespace")
override fun lookup(scopedName: List<String>): Statement? {
throw NotImplementedError("use lookup on actual ast node instead")
}
@ -330,12 +307,10 @@ class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
}
}
object BuiltinFunctionScopePlaceholder : INameScope {
internal object BuiltinFunctionScopePlaceholder : INameScope {
override val name = "<<builtin-functions-scope-placeholder>>"
override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
}

View File

@ -16,4 +16,16 @@ fun Number.toHex(): String {
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}
}
fun UInt.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
return when (this) {
in 0u until 16u -> this.toString()
in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0')
in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}

View File

@ -41,7 +41,8 @@ class Program(val name: String,
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkIntoProgram(this)
module.program = this
module.linkParents(namespace)
return this
}
@ -71,10 +72,10 @@ class Program(val name: String,
val toplevelModule: Module
get() = modules.first { it.name!= internedStringsModuleName }
val definedLoadAddress: Int
val definedLoadAddress: UInt
get() = toplevelModule.loadAddress
var actualLoadAddress: Int = 0
var actualLoadAddress = 0u
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
fun internString(string: StringLiteralValue): List<String> {
@ -93,7 +94,7 @@ class Program(val name: String,
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
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)

View File

@ -13,7 +13,7 @@ import kotlin.io.path.isRegularFile
/***************** Antlr Extension methods to create AST ****************/
private data class NumericLiteral(val number: Number, val datatype: DataType)
private data class NumericLiteral(val number: Double, val datatype: DataType)
private fun ParserRuleContext.toPosition() : Position {
@ -43,7 +43,7 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
else -> throw FatalAstException("weird block node $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
return Block(identifier().text, integerliteral()?.toAst()?.number?.toUInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Statement> =
@ -64,6 +64,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
null,
it.toPosition()
)
}
@ -81,6 +82,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED() != null,
null,
cvarinit.toPosition()
)
}
@ -98,6 +100,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
null,
mvarinit.toPosition()
)
}
@ -193,7 +196,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt()
val address = integerliteral().toAst().number.toUInt()
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
@ -293,7 +296,7 @@ private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
}
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
val address = integerliteral()?.toAst()?.number?.toInt()
val address = integerliteral()?.toAst()?.number?.toUInt()
val identifier = scoped_identifier()?.toAst()
return Jump(address, identifier, null, toPosition())
}
@ -354,7 +357,7 @@ private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
if(str?.ALT_STRING_ENCODING() != null)
throw SyntaxError("can't use alternate string s for directive arguments", toPosition())
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
}
private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
@ -396,7 +399,7 @@ private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteral(integer, datatype)
return NumericLiteral(integer.toDouble(), datatype)
}
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
@ -420,11 +423,11 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
val intLit = litval.integerliteral()?.toAst()
when {
intLit!=null -> when(intLit.datatype) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number, litval.toPosition())
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number, litval.toPosition())
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number, litval.toPosition())
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number, litval.toPosition())
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number, litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
@ -455,7 +458,7 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
if (rangefrom!=null && rangeto!=null) {
val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep.toDouble(), toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
@ -600,6 +603,7 @@ private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
ARRAYSIG() != null || arrayindex() != null,
false,
SHARED()!=null,
null,
toPosition()
)
}

View File

@ -29,7 +29,7 @@ enum class DataType {
UWORD -> targetType.oneOf(UWORD, FLOAT)
WORD -> targetType.oneOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR
STR -> targetType.oneOf(STR, UWORD)
in ArrayDatatypes -> targetType == this
else -> false
}
@ -184,6 +184,8 @@ object ParentSentinel : Node {
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override fun copy(): Node = throw FatalAstException("should never duplicate a ParentSentinel")
}
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {

View File

@ -6,21 +6,23 @@ import prog8.ast.base.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.IMemSizer
import java.util.*
import kotlin.math.round
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val logicalOperators = setOf("and", "or", "xor", "not")
val bitwiseOperators = setOf("&", "|", "^")
sealed class Expression: Node {
abstract override fun copy(): Expression
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
abstract fun referencesIdentifier(nameInSource: List<String>): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
abstract val isSimple: Boolean
@ -62,6 +64,18 @@ sealed class Expression: Node {
else -> other==this
}
}
fun typecastTo(targetDt: DataType, sourceDt: DataType, implicit: Boolean=false): Pair<Boolean, Expression> {
require(sourceDt!=DataType.UNDEFINED && targetDt!=DataType.UNDEFINED)
if(sourceDt==targetDt)
return Pair(false, this)
if(this is TypecastExpression) {
this.type = targetDt
return Pair(false, this)
}
val typecast = TypecastExpression(this, targetDt, implicit, this.position)
return Pair(true, typecast)
}
}
@ -79,11 +93,33 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun copy() = PrefixExpression(operator, expression.copy(), position)
override fun constValue(program: Program): NumericLiteralValue? {
val constval = expression.constValue(program) ?: return null
val converted = when(operator) {
"+" -> constval
"-" -> when (constval.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(-constval.number.toInt(), constval.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, -constval.number, constval.position)
else -> throw ExpressionError("can only take negative of int or float", constval.position)
}
"~" -> when (constval.type) {
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, constval.number.toInt().inv().toDouble(), constval.position)
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
}
"not" -> NumericLiteralValue.fromBoolean(constval.number == 0.0, constval.position)
else -> throw FatalAstException("invalid operator")
}
converted.linkParents(this.parent)
return converted
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program)
return when(operator) {
@ -106,7 +142,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
}
}
override val isSimple = false
override val isSimple = true
override fun toString(): String {
return "Prefix($operator $expression)"
@ -132,9 +168,8 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
replacement.parent = this
}
override fun toString(): String {
return "[$left $operator $right]"
}
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position)
override fun toString() = "[$left $operator $right]"
override val isSimple = false
@ -144,7 +179,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
@ -158,7 +193,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
commonDatatype(
leftDt.getOr(DataType.BYTE),
rightDt.getOr(DataType.BYTE),
null, null
null, "", null
).first
)
} catch (x: FatalAstException) {
@ -180,7 +215,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
companion object {
fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: Expression?, right: Expression?): Pair<DataType, Expression?> {
left: Expression?, operator: String, right: Expression?): Pair<DataType, Expression?> {
// byte + byte -> byte
// byte + word -> word
// word + byte -> word
@ -261,13 +296,13 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = arrayvar.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = arrayvar.targetStatement(program)
if (target is VarDecl) {
return when (target.datatype) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
DataType.STR, DataType.UWORD -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(target.datatype))
else -> InferredTypes.unknown()
}
@ -279,7 +314,7 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
}
fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
override fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
}
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
@ -298,16 +333,20 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
replacement.parent = this
}
override fun copy() = TypecastExpression(expression.copy(), type, implicit, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
override fun inferType(program: Program) = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null
val cast = cv.cast(type)
return if(cast.isValid)
cast.valueOrZero()
return if(cast.isValid) {
val newval = cast.valueOrZero()
newval.linkParents(parent)
return newval
}
else
null
}
@ -333,8 +372,9 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
replacement.parent = this
}
override fun copy() = AddressOf(identifier.copy(), position)
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = identifier.nameInSource==nameInSource
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -356,10 +396,11 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
replacement.parent = this
}
override fun copy() = DirectMemoryRead(addressExpression.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = addressExpression.referencesIdentifier(nameInSource)
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null
@ -369,43 +410,63 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
}
class NumericLiteralValue(val type: DataType, // only numerical types allowed
val number: Number, // can be byte, word or float depending on the type
numbervalue: Double, // can be byte, word or float depending on the type
override val position: Position) : Expression() {
override lateinit var parent: Node
val number: Double by lazy {
if(type==DataType.FLOAT)
numbervalue
else {
val rounded = round(numbervalue)
if(rounded != numbervalue)
throw ExpressionError("refused silent rounding of float to avoid loss of precision", position)
rounded
}
}
override val isSimple = true
fun copy() = NumericLiteralValue(type, number, position)
override fun copy() = NumericLiteralValue(type, number, position)
companion object {
fun fromBoolean(bool: Boolean, position: Position) =
NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
NumericLiteralValue(DataType.UBYTE, if (bool) 1.0 else 0.0, position)
fun optimalNumeric(value: Number, position: Position): NumericLiteralValue {
return if(value is Double) {
NumericLiteralValue(DataType.FLOAT, value, position)
} else {
when (val intval = value.toInt()) {
in 0..255 -> NumericLiteralValue(DataType.UBYTE, intval, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, intval, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, intval, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, intval, position)
else -> NumericLiteralValue(DataType.FLOAT, intval.toDouble(), position)
val dvalue = value.toDouble()
when (value.toInt()) {
in 0..255 -> NumericLiteralValue(DataType.UBYTE, dvalue, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, dvalue, position)
else -> NumericLiteralValue(DataType.FLOAT, dvalue, position)
}
}
}
fun optimalInteger(value: Int, position: Position): NumericLiteralValue {
val dvalue = value.toDouble()
return when (value) {
in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, value, position)
else -> throw FatalAstException("integer overflow: $value")
in 0..255 -> NumericLiteralValue(DataType.UBYTE, dvalue, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, dvalue, position)
else -> throw FatalAstException("integer overflow: $dvalue")
}
}
fun optimalInteger(value: UInt, position: Position): NumericLiteralValue {
return when (value) {
in 0u..255u -> NumericLiteralValue(DataType.UBYTE, value.toDouble(), position)
in 0u..65535u -> NumericLiteralValue(DataType.UWORD, value.toDouble(), position)
else -> throw FatalAstException("unsigned integer overflow: $value")
}
}
}
val asBooleanValue: Boolean = number.toDouble() != 0.0
val asBooleanValue: Boolean = number != 0.0
override fun linkParents(parent: Node) {
this.parent = parent
@ -415,7 +476,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program) = this
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -430,13 +491,13 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteralValue)
return false
return number.toDouble()==other.number.toDouble()
return number==other.number
}
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
operator fun compareTo(other: NumericLiteralValue): Int = number.compareTo(other.number)
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
fun linkParent(parent: Node) {
value?.linkParents(parent)
}
@ -451,55 +512,54 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
private fun internalCast(targettype: DataType): CastValue {
if(type==targettype)
return CastValue(true, this)
val numval = number.toDouble()
when(type) {
DataType.UBYTE -> {
if(targettype== DataType.BYTE && numval <= 127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.BYTE && number <= 127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.WORD || targettype== DataType.UWORD)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.BYTE -> {
if(targettype== DataType.UBYTE && numval >= 0)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >= 0)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.UBYTE && number >= 0)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UWORD && number >= 0)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.WORD)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.UWORD -> {
if(targettype== DataType.BYTE && numval <= 127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval <= 255)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.WORD && numval <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.BYTE && number <= 127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UBYTE && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.WORD && number <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.WORD -> {
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >=0)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.BYTE && number >= -128 && number <=127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UBYTE && number >= 0 && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UWORD && number >=0)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.FLOAT -> {
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if (targettype == DataType.BYTE && number >= -128 && number <=127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UBYTE && number >=0 && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UWORD && number >=0 && number <= 65535)
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
else -> {}
}
@ -522,10 +582,11 @@ class CharLiteral(val value: Char,
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun copy() = CharLiteral(value, altEncoding, position)
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteralValue {
val bytevalue = program.encoding.encodeString(value.toString(), altEncoding).single()
return NumericLiteralValue(DataType.UBYTE, bytevalue, position)
return NumericLiteralValue(DataType.UBYTE, bytevalue.toDouble(), position)
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -551,13 +612,13 @@ class StringLiteralValue(val value: String,
}
override val isSimple = true
fun copy() = StringLiteralValue(value, altEncoding, position)
override fun copy() = StringLiteralValue(value, altEncoding, position)
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -583,6 +644,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
value.forEach {it.linkParents(this)}
}
override fun copy() = throw NotImplementedError("no support for duplicating a ArrayLiteralValue")
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
@ -592,7 +654,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
replacement.parent = this
}
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
override fun referencesIdentifier(nameInSource: List<String>) = value.any { it.referencesIdentifier(nameInSource) }
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -608,14 +670,6 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
return type==other.type && value.contentEquals(other.value)
}
fun memsize(memsizer: IMemSizer): Int {
if(type.isKnown) {
val eltType = ArrayToElementTypes.getValue(type.getOr(DataType.UNDEFINED))
return memsizer.memorySize(eltType) * value.size
}
else throw IllegalArgumentException("array datatype is not yet known")
}
fun guessDatatype(program: Program): InferredTypes.InferredType {
// Educated guess of the desired array literal's datatype.
// If it's inside a for loop, assume the data type of the loop variable is what we want.
@ -702,11 +756,12 @@ class RangeExpr(var from: Expression,
replacement.parent = this
}
override fun copy() = RangeExpr(from.copy(), to.copy(), step.copy(), position)
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = from.referencesIdentifier(nameInSource) || to.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
@ -759,6 +814,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
throw FatalAstException("can't replace here")
}
override fun copy() = IdentifierReference(nameInSource, position)
override fun constValue(program: Program): NumericLiteralValue? {
val node = definingScope.lookup(nameInSource) ?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl
@ -777,8 +833,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean =
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = this.nameInSource==nameInSource
override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program)) {
@ -809,6 +864,7 @@ class FunctionCall(override var target: IdentifierReference,
args.forEach { it.linkParents(this) }
}
override fun copy() = FunctionCall(target.copy(), args.map { it.copy() }.toMutableList(), position)
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in arrayOf("msb", "lsb", "peek", "peekw"))
override fun replaceChildNode(node: Node, replacement: Node) {
@ -846,7 +902,7 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false)

View File

@ -9,30 +9,26 @@ import prog8.ast.walk.IAstVisitor
interface INamedStatement {
val name: String
val scopedName: List<String>
get() {
val scopedName = mutableListOf(name)
var node: Node = this as Node
while (node !is Block) {
node = node.parent
if(node is INameScope) {
scopedName.add(0, node.name)
}
}
return scopedName
}
}
sealed class Statement : Node {
abstract override fun copy(): Statement
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
val scope = mutableListOf<String>()
var statementScope = this.parent
while(statementScope !is ParentSentinel && statementScope !is Module) {
if(statementScope is INameScope) {
scope.add(0, statementScope.name)
}
statementScope = statementScope.parent
}
if(name.isNotEmpty())
scope.add(name)
return scope.joinToString(".")
}
fun nextSibling(): Statement? {
val statements = (parent as? IStatementContainer)?.statements ?: return null
val nextIdx = statements.indexOfFirst { it===this } + 1
@ -62,17 +58,21 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override fun copy() = throw NotImplementedError("no support for duplicating a BuiltinFunctionStatementPlaceholder")
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)
class Block(override val name: String,
val address: Int?,
val address: UInt?,
override var statements: MutableList<Statement>,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override fun copy() = throw NotImplementedError("no support for duplicating a Block")
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach {it.linkParents(this)}
@ -87,10 +87,7 @@ class Block(override val name: String,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Block(name=$name, address=$address, ${statements.size} statements)"
}
override fun toString() = "Block(name=$name, address=$address, ${statements.size} statements)"
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
}
@ -104,17 +101,19 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Directive(directive, args.map { it.copy() }, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
data class DirectiveArg(val str: String?, val name: String?, val int: UInt?, override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = DirectiveArg(str, name, int, position)
}
data class Label(override val name: String, override val position: Position) : Statement(), INamedStatement {
@ -125,12 +124,10 @@ data class Label(override val name: String, override val position: Position) : S
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Label(name, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Label(name=$name, pos=$position)"
}
override fun toString()= "Label(name=$name, pos=$position)"
}
open class Return(var value: Expression?, final override val position: Position) : Statement() {
@ -147,12 +144,10 @@ open class Return(var value: Expression?, final override val position: Position)
replacement.parent = this
}
override fun copy() = Return(value?.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Return($value, pos=$position)"
}
override fun toString() = "Return($value, pos=$position)"
}
class Break(override val position: Position) : Statement() {
@ -163,6 +158,7 @@ class Break(override val position: Position) : Statement() {
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Break(position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@ -184,6 +180,7 @@ open class VarDecl(val type: VarDeclType,
val isArray: Boolean,
val autogeneratedDontRemove: Boolean,
val sharedWithAsm: Boolean,
val subroutineParameter: SubroutineParameter?,
final override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@ -193,6 +190,16 @@ open class VarDecl(val type: VarDeclType,
companion object {
private var autoHeapValueSequenceNumber = 0
fun fromParameter(param: SubroutineParameter): VarDecl {
return VarDecl(VarDeclType.VAR, param.type, ZeropageWish.DONTCARE, null, param.name, null,
isArray = false,
autogeneratedDontRemove = true,
sharedWithAsm = false,
subroutineParameter = param,
position = param.position
)
}
fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val arrayDt =
@ -203,14 +210,14 @@ open class VarDecl(val type: VarDeclType,
val declaredType = ArrayToElementTypes.getValue(arrayDt)
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, position = array.position)
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = array.position)
}
fun defaultZero(dt: DataType, position: Position) = when(dt) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0.0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0.0, position)
DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0.0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0.0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only determine default zero value for a numeric type")
}
@ -246,10 +253,8 @@ open class VarDecl(val type: VarDeclType,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
}
override fun toString() =
"VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
fun zeroElementValue(): NumericLiteralValue {
if(allowInitializeWithZero)
@ -258,17 +263,14 @@ open class VarDecl(val type: VarDeclType,
throw IllegalArgumentException("attempt to get zero value for vardecl that shouldn't get it")
}
fun copy(): VarDecl {
val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, sharedWithAsm, position)
override fun copy(): VarDecl {
val c = VarDecl(type, declaredDatatype, zeropage, arraysize?.copy(), name, value?.copy(),
isArray, autogeneratedDontRemove, sharedWithAsm, subroutineParameter, position)
c.allowInitializeWithZero = this.allowInitializeWithZero
return c
}
}
// a vardecl used only for subroutine parameters
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, false, position)
class ArrayIndex(var indexExpr: Expression,
override val position: Position) : Node {
override lateinit var parent: Node
@ -292,16 +294,13 @@ class ArrayIndex(var indexExpr: Expression,
}
fun accept(visitor: IAstVisitor) = indexExpr.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = indexExpr.accept(visitor, this)
override fun toString(): String {
return("ArrayIndex($indexExpr, pos=$position)")
}
fun accept(visitor: AstWalker) = indexExpr.accept(visitor, this)
override fun toString() = "ArrayIndex($indexExpr, pos=$position)"
fun constIndex() = (indexExpr as? NumericLiteralValue)?.number?.toInt()
infix fun isSameAs(other: ArrayIndex): Boolean = indexExpr isSameAs other.indexExpr
fun copy() = ArrayIndex(indexExpr, position)
override fun copy() = ArrayIndex(indexExpr.copy(), position)
}
open class Assignment(var target: AssignTarget, var value: Expression, final override val position: Position) : Statement() {
@ -322,12 +321,10 @@ open class Assignment(var target: AssignTarget, var value: Expression, final ove
replacement.parent = this
}
override fun copy()= Assignment(target.copy(), value.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return("Assignment(target: $target, value: $value, pos=$position)")
}
override fun toString() = "Assignment(target: $target, value: $value, pos=$position)"
/**
* Is the assigment value an expression that references the assignment target itself?
@ -340,20 +337,35 @@ open class Assignment(var target: AssignTarget, var value: Expression, final ove
if(binExpr.left isSameAs target)
return true // A = A <operator> Something
if(binExpr.operator in "+-") {
val leftBinExpr = binExpr.left as? BinaryExpression
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr==null && leftBinExpr!=null && leftBinExpr.operator in "+-") {
// A = (A +- x) +- y
if(leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target)
return true
}
if(leftBinExpr==null && rightBinExpr!=null && rightBinExpr.operator in "+-") {
// A = y +- (A +- x)
if(rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target || binExpr.left isSameAs target)
return true
}
}
if(binExpr.operator in associativeOperators) {
if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
return true // A = v <associative-operator> A
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
val rightBinExpr = binExpr.right as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator && rightBinExpr==null) {
// one of these?
// A = (A <associative-operator> x) <same-operator> y
// A = (x <associative-operator> A) <same-operator> y
// A = (x <associative-operator> y) <same-operator> A
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
if(rightBinExpr?.operator == binExpr.operator && leftBinExpr==null) {
// one of these?
// A = y <associative-operator> (A <same-operator> x)
// A = y <associative-operator> (x <same-operator> y)
@ -423,7 +435,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
return when {
identifier != null -> identifier!!.copy()
arrayindexed != null -> arrayindexed!!.copy()
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression.copy(), memoryAddress.position)
else -> throw FatalAstException("invalid assignmenttarget $this")
}
}
@ -468,7 +480,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
return false
}
fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
override fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
}
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
@ -485,19 +497,18 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
replacement.parent = this
}
override fun copy() = PostIncrDecr(target.copy(), operator, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
}
override fun toString() = "PostIncrDecr(op: $operator, target: $target, pos=$position)"
}
class Jump(val address: Int?,
val identifier: IdentifierReference?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : Statement() {
open class Jump(val address: UInt?,
val identifier: IdentifierReference?,
val generatedLabel: String?, // can be used in code generation scenarios
override val position: Position) : Statement() {
override lateinit var parent: Node
open val isGosub = false
override fun linkParents(parent: Node) {
this.parent = parent
@ -505,12 +516,22 @@ class Jump(val address: Int?,
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Jump(address, identifier?.copy(), generatedLabel, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
override fun toString() =
"Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
// a GoSub is ONLY created internally for calling subroutines
class GoSub(address: UInt?, identifier: IdentifierReference?, generatedLabel: String?, position: Position) :
Jump(address, identifier, generatedLabel, position) {
override val isGosub = true
override fun copy() = GoSub(address, identifier?.copy(), generatedLabel, position)
override fun toString() =
"GoSub(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
class FunctionCallStatement(override var target: IdentifierReference,
@ -525,6 +546,8 @@ class FunctionCallStatement(override var target: IdentifierReference,
args.forEach { it.linkParents(this) }
}
override fun copy() = throw NotImplementedError("no support for duplicating a FunctionCallStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target = replacement as IdentifierReference
@ -537,10 +560,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "FunctionCallStatement(target=$target, pos=$position)"
}
override fun toString() = "FunctionCallStatement(target=$target, pos=$position)"
}
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
@ -550,6 +570,9 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
this.parent = parent
}
override fun copy() = throw NotImplementedError("no support for duplicating a InlineAssembly")
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -571,6 +594,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
replacement.parent = this
}
override fun copy() = AnonymousScope(statements.map { it.copy() }.toMutableList(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@ -583,6 +607,7 @@ class NopStatement(override val position: Position): Statement() {
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = NopStatement(position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@ -598,7 +623,7 @@ class AsmGenInfo {
var usedFloatEvalResultVar1 = false
var usedFloatEvalResultVar2 = false
val extraVars = mutableListOf<Triple<DataType, String, Int?>>()
val extraVars = mutableListOf<Triple<DataType, String, UInt?>>()
}
// the subroutine class covers both the normal user-defined subroutines,
@ -610,7 +635,7 @@ class Subroutine(override val name: String,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>,
val asmAddress: Int?,
val asmAddress: UInt?,
val isAsmSubroutine: Boolean,
val inline: Boolean,
override var statements: MutableList<Statement>,
@ -634,7 +659,8 @@ class Subroutine(override val name: String,
override lateinit var parent: Node
val asmGenInfo = AsmGenInfo()
val scopedname: String by lazy { makeScopedName(name) }
override fun copy() = throw NotImplementedError("no support for duplicating a Subroutine")
override fun linkParents(parent: Node) {
this.parent = parent
@ -660,10 +686,8 @@ class Subroutine(override val name: String,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
}
override fun toString() =
"Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
@ -687,6 +711,24 @@ class Subroutine(override val name: String,
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it}
// code to provide the ability to reference asmsub parameters via qualified name:
private val asmParamsDecls = mutableMapOf<String, VarDecl>()
fun searchAsmParameter(name: String): VarDecl? {
require(isAsmSubroutine)
val existingDecl = asmParamsDecls[name]
if(existingDecl!=null)
return existingDecl
val param = parameters.firstOrNull {it.name==name} ?: return null
val decl = VarDecl.fromParameter(param)
decl.linkParents(this)
asmParamsDecls[name] = decl
return decl
}
}
open class SubroutineParameter(val name: String,
@ -701,6 +743,9 @@ open class SubroutineParameter(val name: String,
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace anything in a subroutineparameter node")
}
override fun copy() = SubroutineParameter(name, type, position)
override fun toString() = "Param($type:$name)"
}
class IfStatement(var condition: Expression,
@ -716,6 +761,8 @@ class IfStatement(var condition: Expression,
elsepart.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a IfStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
@ -743,6 +790,8 @@ class BranchStatement(var condition: BranchCondition,
elsepart.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a BranchStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===truepart -> truepart = replacement as AnonymousScope
@ -770,6 +819,8 @@ class ForLoop(var loopVar: IdentifierReference,
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a ForLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===loopVar -> loopVar = replacement as IdentifierReference
@ -782,10 +833,7 @@ class ForLoop(var loopVar: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
}
override fun toString() = "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
fun loopVarDt(program: Program) = loopVar.inferType(program)
}
@ -801,6 +849,8 @@ class WhileLoop(var condition: Expression,
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a WhileLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
@ -823,6 +873,8 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a RepeatLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===iterations -> iterations = replacement as Expression
@ -846,6 +898,7 @@ class UntilLoop(var body: AnonymousScope,
condition.linkParents(this)
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a UntilLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
@ -871,6 +924,8 @@ class WhenStatement(var condition: Expression,
choices.forEach { it.linkParents(this) }
}
override fun copy() = throw NotImplementedError("no support for duplicating a WhenStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===condition)
condition = replacement as Expression
@ -927,9 +982,8 @@ class WhenChoice(var values: MutableList<Expression>?, // if null, th
}
}
override fun toString(): String {
return "Choice($values at $position)"
}
override fun copy() = WhenChoice(values?.map{ it.copy() }?.toMutableList(), statements.copy(), position)
override fun toString() = "Choice($values at $position)"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -949,11 +1003,8 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
replacement.parent = this
}
override fun toString(): String {
return "DirectMemoryWrite($addressExpression)"
}
override fun toString() = "DirectMemoryWrite($addressExpression)"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun copy() = DirectMemoryWrite(addressExpression, position)
override fun copy() = DirectMemoryWrite(addressExpression.copy(), position)
}

View File

@ -235,7 +235,7 @@ abstract class AstWalker {
fun visit(decl: VarDecl, parent: Node) {
track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl)
decl.arraysize?.accept(this)
track(after(decl, parent), decl, parent)
}
@ -375,7 +375,7 @@ abstract class AstWalker {
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
}

View File

@ -1,6 +1,6 @@
package prog8.compilerinterface
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun encodeString(str: String, altEncoding: Boolean): List<UByte>
fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String
}

View File

@ -132,7 +132,7 @@ sealed class SourceCode {
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
val channel = Channels.newChannel(inpStr)
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1);
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1)
}
override fun readText(): String {

View File

@ -0,0 +1,8 @@
package prog8tests.ast
import io.kotest.core.config.AbstractProjectConfig
import kotlin.math.max
object ProjectConfig : AbstractProjectConfig() {
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
}

View File

@ -1,7 +1,8 @@
package prog8tests.ast
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.assertions.fail
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.matchers.string.shouldContain
import prog8.ast.AstToSourceTextConverter
import prog8.ast.Module
import prog8.ast.Program
@ -12,11 +13,9 @@ import prog8.parser.SourceCode
import prog8tests.ast.helpers.DummyFunctions
import prog8tests.ast.helpers.DummyMemsizer
import prog8tests.ast.helpers.DummyStringEncoder
import kotlin.test.assertContains
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAstToSourceText {
class TestAstToSourceText: AnnotationSpec() {
private fun generateP8(module: Module) : String {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
@ -35,8 +34,7 @@ class TestAstToSourceText {
val parsedAgain = parseModule(SourceCode.Text(generatedText))
return Pair(generatedText, parsedAgain)
} catch (e: ParseError) {
assert(false) { "should produce valid Prog8 but threw $e" }
throw e
fail("should produce valid Prog8 but threw $e")
}
}
@ -44,24 +42,21 @@ class TestAstToSourceText {
fun testMentionsInternedStringsModule() {
val orig = SourceCode.Text("\n")
val (txt, _) = roundTrip(parseModule(orig))
// assertContains has *actual* first!
assertContains(txt, Regex(";.*$internedStringsModuleName"))
txt shouldContain Regex(";.*$internedStringsModuleName")
}
@Test
fun testImportDirectiveWithLib() {
val orig = SourceCode.Text("%import textio\n")
val (txt, _) = roundTrip(parseModule(orig))
// assertContains has *actual* first!
assertContains(txt, Regex("%import +textio"))
txt shouldContain Regex("%import +textio")
}
@Test
fun testImportDirectiveWithUserModule() {
val orig = SourceCode.Text("%import my_own_stuff\n")
val (txt, _) = roundTrip(parseModule(orig))
// assertContains has *actual* first!
assertContains(txt, Regex("%import +my_own_stuff"))
txt shouldContain Regex("%import +my_own_stuff")
}
@ -73,8 +68,7 @@ class TestAstToSourceText {
}
""")
val (txt, _) = roundTrip(parseModule(orig))
// assertContains has *actual* first!
assertContains(txt, Regex("str +s += +\"fooBar\\\\n\""))
txt shouldContain Regex("str +s += +\"fooBar\\\\n\"")
}
@Test
@ -85,8 +79,7 @@ class TestAstToSourceText {
}
""")
val (txt, _) = roundTrip(parseModule(orig))
// assertContains has *actual* first!
assertContains(txt, Regex("str +sAlt += +@\"fooBar\\\\n\""))
txt shouldContain Regex("str +sAlt += +@\"fooBar\\\\n\"")
}
@Test
@ -97,8 +90,7 @@ class TestAstToSourceText {
}
""")
val (txt, _) = roundTrip(parseModule(orig))
// assertContains has *actual* first!
assertContains(txt, Regex("ubyte +c += +'x'"), "char literal")
txt shouldContain Regex("ubyte +c += +'x'")
}
@Test
@ -109,8 +101,7 @@ class TestAstToSourceText {
}
""")
val (txt, _) = roundTrip(parseModule(orig))
// assertContains has *actual* first!
assertContains(txt, Regex("ubyte +cAlt += +@'x'"), "alt char literal")
txt shouldContain Regex("ubyte +cAlt += +@'x'")
}
}

View File

@ -1,18 +1,22 @@
package prog8tests.ast
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
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.or
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.types.instanceOf
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.Position
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RangeExpr
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule
@ -24,39 +28,30 @@ import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
import kotlin.io.path.nameWithoutExtension
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestProg8Parser {
class TestProg8Parser: FunSpec( {
@Nested
inner class Newline {
context("Newline at end") {
test("is not required - #40, fixed by #45") {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val src = SourceCode.Text("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
@Nested
inner class AtEnd {
@Test
fun `is not required - #40, fixed by #45`() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val src = SourceCode.Text("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
val module = parseModule(src)
assertEquals(1, module.statements.size)
}
@Test
fun `is still accepted - #40, fixed by #45`() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
val module = parseModule(SourceCode.Text(srcText))
assertEquals(1, module.statements.size)
}
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
val module = parseModule(src)
module.statements.size shouldBe 1
}
@Test
fun `is required after each block except the last`() {
test("is still accepted - #40, fixed by #45") {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
val module = parseModule(SourceCode.Text(srcText))
module.statements.size shouldBe 1
}
}
context("Newline") {
test("is required after each block except the last") {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
@ -65,22 +60,21 @@ class TestProg8Parser {
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcBad)) }
shouldThrow<ParseError> { parseModule(SourceCode.Text(srcBad)) }
val module = parseModule(SourceCode.Text(srcGood))
assertEquals(2, module.statements.size)
module.statements.size shouldBe 2
}
@Test
fun `is required between two Blocks or Directives - #47`() {
test("is required between two Blocks or Directives - #47") {
// block and block
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
blockA {
} blockB {
}
""")) }
// block and directive
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
blockB {
} %import textio
""")) }
@ -89,18 +83,17 @@ class TestProg8Parser {
// Leaving them in anyways.
// dir and block
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
%import textio blockB {
}
""")) }
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
%import textio %import syslib
""")) }
}
@Test
fun `can be Win, Unix or mixed, even mixed`() {
test("can be Win, Unix or mixed, even mixed") {
val nlWin = "\r\n"
val nlUnix = "\n"
val nlMac = "\r"
@ -124,15 +117,13 @@ class TestProg8Parser {
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
val module = parseModule(SourceCode.Text(srcText))
assertEquals(2, module.statements.size)
module.statements.size shouldBe 2
}
}
@Nested
inner class EOLsInterleavedWithComments {
context("EOLsInterleavedWithComments") {
@Test
fun `are ok before first block - #47`() {
test("are ok before first block - #47") {
// issue: #47
val srcText = """
; comment
@ -143,11 +134,10 @@ class TestProg8Parser {
}
"""
val module = parseModule(SourceCode.Text(srcText))
assertEquals(1, module.statements.size)
module.statements.size shouldBe 1
}
@Test
fun `are ok between blocks - #47`() {
test("are ok between blocks - #47") {
// issue: #47
val srcText = """
blockA {
@ -160,11 +150,10 @@ class TestProg8Parser {
}
"""
val module = parseModule(SourceCode.Text(srcText))
assertEquals(2, module.statements.size)
module.statements.size shouldBe 2
}
@Test
fun `are ok after last block - #47`() {
test("are ok after last block - #47") {
// issue: #47
val srcText = """
blockA {
@ -175,45 +164,36 @@ class TestProg8Parser {
"""
val module = parseModule(SourceCode.Text(srcText))
assertEquals(1, module.statements.size)
module.statements.size shouldBe 1
}
}
@Nested
inner class ImportDirectives {
@Test
fun `should not be looked into by the parser`() {
context("ImportDirectives") {
test("should not be looked into by the parser") {
val importedNoExt = assumeNotExists(fixturesDir, "i_do_not_exist")
assumeNotExists(fixturesDir, "i_do_not_exist.p8")
val text = "%import ${importedNoExt.name}"
val module = parseModule(SourceCode.Text(text))
assertEquals(1, module.statements.size)
module.statements.size shouldBe 1
}
}
@Nested
inner class EmptySourcecode {
@Test
fun `from an empty string should result in empty Module`() {
context("EmptySourcecode") {
test("from an empty string should result in empty Module") {
val module = parseModule(SourceCode.Text(""))
assertEquals(0, module.statements.size)
module.statements.size shouldBe 0
}
@Test
fun `from an empty file should result in empty Module`() {
test("from an empty file should result in empty Module") {
val path = assumeReadableFile(fixturesDir, "empty.p8")
val module = parseModule(SourceCode.File(path))
assertEquals(0, module.statements.size)
module.statements.size shouldBe 0
}
}
@Nested
inner class NameOfModule {
@Test
fun `parsed from a string`() {
context("NameOfModule") {
test("parsed from a string") {
val srcText = """
main {
}
@ -221,21 +201,19 @@ class TestProg8Parser {
val module = parseModule(SourceCode.Text(srcText))
// Note: assertContains has *actual* as first param
assertContains(module.name, Regex("^<String@[0-9a-f\\-]+>$"))
module.name shouldContain Regex("^<String@[0-9a-f\\-]+>$")
}
@Test
fun `parsed from a file`() {
test("parsed from a file") {
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
val module = parseModule(SourceCode.File(path))
assertEquals(path.nameWithoutExtension, module.name)
module.name shouldBe path.nameWithoutExtension
}
}
@Nested
inner class PositionOfAstNodesAndParseErrors {
context("PositionOfAstNodesAndParseErrors") {
private fun assertPosition(
fun assertPosition(
actual: Position,
expFile: String? = null,
expLine: Int? = null,
@ -243,13 +221,13 @@ class TestProg8Parser {
expEndCol: Int? = null
) {
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)")
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
if (expFile != null) assertEquals(expFile, actual.file, ".position.file")
if (expLine != null) actual.line shouldBe expLine
if (expStartCol != null) actual.startCol shouldBe expStartCol
if (expEndCol != null) actual.endCol shouldBe expEndCol
if (expFile != null) actual.file shouldBe expFile
}
private fun assertPosition(
fun assertPosition(
actual: Position,
expFile: Regex? = null,
expLine: Int? = null,
@ -257,14 +235,13 @@ class TestProg8Parser {
expEndCol: Int? = null
) {
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)")
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
// Note: assertContains expects *actual* value first
if (expFile != null) assertContains(actual.file, expFile, ".position.file")
if (expLine != null) actual.line shouldBe expLine
if (expStartCol != null) actual.startCol shouldBe expStartCol
if (expEndCol != null) actual.endCol shouldBe expEndCol
if (expFile != null) actual.file shouldContain expFile
}
private fun assertPositionOf(
fun assertPositionOf(
actual: Node,
expFile: String? = null,
expLine: Int? = null,
@ -273,7 +250,7 @@ class TestProg8Parser {
) =
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
private fun assertPositionOf(
fun assertPositionOf(
actual: Node,
expFile: Regex? = null,
expLine: Int? = null,
@ -283,24 +260,21 @@ class TestProg8Parser {
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
@Test
fun `in ParseError from bad string source code`() {
test("in ParseError from bad string source code") {
val srcText = "bad * { }\n"
val e = assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcText)) }
val e = shouldThrow<ParseError> { parseModule(SourceCode.Text(srcText)) }
assertPosition(e.position, Regex("^<String@[0-9a-f\\-]+>$"), 1, 4, 4)
}
@Test
fun `in ParseError from bad file source code`() {
test("in ParseError from bad file source code") {
val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
val e = assertFailsWith<ParseError> { parseModule(SourceCode.File(path)) }
val e = shouldThrow<ParseError> { parseModule(SourceCode.File(path)) }
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
}
@Test
fun `of Module parsed from a string`() {
test("of Module parsed from a string") {
val srcText = """
main {
}
@ -309,15 +283,13 @@ class TestProg8Parser {
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0)
}
@Test
fun `of Module parsed from a file`() {
test("of Module parsed from a file") {
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
val module = parseModule(SourceCode.File(path))
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
}
@Test
fun `of non-root Nodes parsed from file`() {
test("of non-root Nodes parsed from file") {
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
val module = parseModule(SourceCode.File(path))
@ -329,9 +301,7 @@ class TestProg8Parser {
assertPositionOf(startSub, mpf, 3, 4, 6)
}
@Test
fun `of non-root Nodes parsed from a string`() {
test("of non-root Nodes parsed from a string") {
val srcText = """
%zeropage basicsafe
main {
@ -369,20 +339,30 @@ class TestProg8Parser {
}
}
@Nested
inner class PositionFile {
@Test
fun `isn't absolute for filesystem paths`() {
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
val module = parseModule(SourceCode.File(path))
assertSomethingForAllNodes(module) {
assertFalse(Path(it.position.file).isAbsolute)
assertTrue(Path(it.position.file).isRegularFile())
context("PositionFile") {
fun assertSomethingForAllNodes(module: Module, asserter: (Node) -> Unit) {
asserter(module)
module.statements.forEach(asserter)
module.statements.filterIsInstance<Block>().forEach { b ->
asserter(b)
b.statements.forEach(asserter)
b.statements.filterIsInstance<Subroutine>().forEach { s ->
asserter(s)
s.statements.forEach(asserter)
}
}
}
@Test
fun `is mangled string id for string sources`()
test("isn't absolute for filesystem paths") {
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
val module = parseModule(SourceCode.File(path))
assertSomethingForAllNodes(module) {
Path(it.position.file).isAbsolute shouldBe false
Path(it.position.file).isRegularFile() shouldBe true
}
}
test("is mangled string id for string sources")
{
val srcText="""
%zeropage basicsafe
@ -395,38 +375,23 @@ class TestProg8Parser {
""".trimIndent()
val module = parseModule(SourceCode.Text(srcText))
assertSomethingForAllNodes(module) {
assertTrue(it.position.file.startsWith(SourceCode.stringSourcePrefix))
it.position.file shouldStartWith SourceCode.stringSourcePrefix
}
}
@Test
fun `is library prefixed path for resources`()
test("is library prefixed path for resources")
{
val resource = SourceCode.Resource("prog8lib/math.p8")
val module = parseModule(resource)
assertSomethingForAllNodes(module) {
assertTrue(it.position.file.startsWith(SourceCode.libraryFilePrefix))
it.position.file shouldStartWith SourceCode.libraryFilePrefix
}
}
}
private fun assertSomethingForAllNodes(module: Module, asserter: (Node) -> Unit) {
asserter(module)
module.statements.forEach(asserter)
module.statements.filterIsInstance<Block>().forEach { b ->
asserter(b)
b.statements.forEach(asserter)
b.statements.filterIsInstance<Subroutine>().forEach { s ->
asserter(s)
s.statements.forEach(asserter)
}
}
} }
context("CharLiterals") {
@Nested
inner class CharLiterals {
@Test
fun `in argument position, no altEnc`() {
test("in argument position, no altEnc") {
val src = SourceCode.Text("""
main {
sub start() {
@ -441,13 +406,12 @@ class TestProg8Parser {
.statements.filterIsInstance<Subroutine>()[0]
val funCall = startSub.statements.filterIsInstance<IFunctionCall>().first()
assertIs<CharLiteral>(funCall.args[0])
funCall.args[0] shouldBe(instanceOf<CharLiteral>())
val char = funCall.args[0] as CharLiteral
assertEquals('\n', char.value)
char.value shouldBe '\n'
}
@Test
fun `on rhs of block-level var decl, no AltEnc`() {
test("on rhs of block-level var decl, no AltEnc") {
val src = SourceCode.Text("""
main {
ubyte c = 'x'
@ -459,12 +423,11 @@ class TestProg8Parser {
.statements.filterIsInstance<VarDecl>()[0]
val rhs = decl.value as CharLiteral
assertEquals('x', rhs.value, "char literal's .value")
assertEquals(false, rhs.altEncoding, "char literal's .altEncoding")
rhs.value shouldBe 'x'
rhs.altEncoding shouldBe false
}
@Test
fun `on rhs of block-level const decl, with AltEnc`() {
test("on rhs of block-level const decl, with AltEnc") {
val src = SourceCode.Text("""
main {
const ubyte c = @'x'
@ -476,12 +439,11 @@ class TestProg8Parser {
.statements.filterIsInstance<VarDecl>()[0]
val rhs = decl.value as CharLiteral
assertEquals('x', rhs.value, "char literal's .value")
assertEquals(true, rhs.altEncoding, "char literal's .altEncoding")
rhs.value shouldBe 'x'
rhs.altEncoding shouldBe true
}
@Test
fun `on rhs of subroutine-level var decl, no AltEnc`() {
test("on rhs of subroutine-level var decl, no AltEnc") {
val src = SourceCode.Text("""
main {
sub start() {
@ -496,12 +458,11 @@ class TestProg8Parser {
.statements.filterIsInstance<VarDecl>()[0]
val rhs = decl.value as CharLiteral
assertEquals('x', rhs.value, "char literal's .value")
assertEquals(false, rhs.altEncoding, "char literal's .altEncoding")
rhs.value shouldBe 'x'
rhs.altEncoding shouldBe false
}
@Test
fun `on rhs of subroutine-level const decl, with AltEnc`() {
test("on rhs of subroutine-level const decl, with AltEnc") {
val src = SourceCode.Text("""
main {
sub start() {
@ -516,16 +477,14 @@ class TestProg8Parser {
.statements.filterIsInstance<VarDecl>()[0]
val rhs = decl.value as CharLiteral
assertEquals('x', rhs.value, "char literal's .value")
assertEquals(true, rhs.altEncoding, "char literal's .altEncoding")
rhs.value shouldBe 'x'
rhs.altEncoding shouldBe true
}
}
@Nested
inner class Ranges {
context("Ranges") {
@Test
fun `in for-loops`() {
test("in for-loops") {
val module = parseModule(SourceCode.Text("""
main {
sub start() {
@ -549,67 +508,64 @@ class TestProg8Parser {
.statements.filterIsInstance<ForLoop>()
.map { it.iterable }
assertEquals(5, iterables.size)
iterables.size shouldBe 5
val it0 = iterables[0] as RangeExpr
assertIs<StringLiteralValue>(it0.from, "parser should leave it as is")
assertIs<StringLiteralValue>(it0.to, "parser should leave it as is")
it0.from shouldBe instanceOf<StringLiteralValue>()
it0.to shouldBe instanceOf<StringLiteralValue>()
val it1 = iterables[1] as StringLiteralValue
assertEquals("something", it1.value, "parser should leave it as is")
it1.value shouldBe "something"
val it2 = iterables[2] as RangeExpr
assertIs<CharLiteral>(it2.from, "parser should leave it as is")
assertIs<CharLiteral>(it2.to, "parser should leave it as is")
it2.from shouldBe instanceOf<CharLiteral>()
it2.to shouldBe instanceOf<CharLiteral>()
val it3 = iterables[3] as RangeExpr
assertIs<NumericLiteralValue>(it3.from, "parser should leave it as is")
assertIs<NumericLiteralValue>(it3.to, "parser should leave it as is")
it3.from shouldBe instanceOf<NumericLiteralValue>()
it3.to shouldBe instanceOf<NumericLiteralValue>()
val it4 = iterables[4] as RangeExpr
assertIs<NumericLiteralValue>(it4.from, "parser should leave it as is")
assertIs<NumericLiteralValue>(it4.to, "parser should leave it as is")
it4.from shouldBe instanceOf<NumericLiteralValue>()
it4.to shouldBe instanceOf<NumericLiteralValue>()
}
}
@Test
fun testCharLiteralConstValue() {
test("testCharLiteralConstValue") {
val char1 = CharLiteral('A', false, Position.DUMMY)
val char2 = CharLiteral('z', true, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, AsciiStringEncoder)
assertEquals(65, char1.constValue(program).number.toInt())
assertEquals(122, char2.constValue(program).number.toInt())
char1.constValue(program).number.toInt() shouldBe 65
char2.constValue(program).number.toInt() shouldBe 122
}
@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("testLiteralValueComparisons") {
val ten = NumericLiteralValue(DataType.UWORD, 10.0, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9.0, Position.DUMMY)
ten shouldBe ten
nine shouldNotBe ten
(ten != ten) shouldBe false
(ten != nine) shouldBe true
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
(ten > nine) shouldBe true
(ten >= nine) shouldBe true
(ten >= ten) shouldBe true
(ten > ten) shouldBe false
assertFalse(ten < nine)
assertFalse(ten <= nine)
assertTrue(ten <= ten)
assertFalse(ten < ten)
(ten < nine) shouldBe false
(ten <= nine) shouldBe false
(ten <= ten) shouldBe true
(ten < ten) shouldBe false
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
abc shouldBe abc
(abc!=abd) shouldBe true
(abc!=abc) shouldBe false
}
@Test
fun testAnonScopeStillContainsVarsDirectlyAfterParse() {
test("testAnonScopeStillContainsVarsDirectlyAfterParse") {
val src = SourceCode.Text("""
main {
sub start() {
@ -624,17 +580,22 @@ class TestProg8Parser {
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val repeatbody = (start.statements.single() as RepeatLoop).body
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
assertFalse(start.statements.any { it is VarDecl }, "no vars moved to start sub")
assertTrue(repeatbody.statements[0] is VarDecl, "var is still in repeat block (anonymousscope)")
withClue("no vars moved to main block") {
mainBlock.statements.any { it is VarDecl } shouldBe false
}
withClue("no vars moved to start sub") {
start.statements.any { it is VarDecl } shouldBe false
}
withClue("\"var is still in repeat block (anonymousscope") {
repeatbody.statements[0] shouldBe instanceOf<VarDecl>()
}
val initvalue = (repeatbody.statements[0] as VarDecl).value as? NumericLiteralValue
assertEquals(99, initvalue?.number?.toInt())
assertTrue(repeatbody.statements[1] is PostIncrDecr)
initvalue?.number?.toInt() shouldBe 99
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
// the ast processing steps used in the compiler, will eventually move the var up to the containing scope (subroutine).
}
@Test
fun testLabelsWithAnonScopesParsesFine() {
test("testLabelsWithAnonScopesParsesFine") {
val src = SourceCode.Text("""
main {
sub start() {
@ -661,6 +622,151 @@ class TestProg8Parser {
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>()
assertEquals(1, labels.size, "only one label in subroutine scope")
labels.size shouldBe 1
}
}
test("logical operator 'not' priority") {
val src = SourceCode.Text("""
main {
sub start() {
ubyte xx
xx = not 4 and not 5
xx = not 4 or not 5
xx = not 4 xor not 5
}
}
""")
val module = parseModule(src)
val start = (module.statements.single() as Block).statements.single() as Subroutine
val andAssignmentExpr = (start.statements[1] as Assignment).value
val orAssignmentExpr = (start.statements[2] as Assignment).value
val xorAssignmentExpr = (start.statements[3] as Assignment).value
fun correctPrios(expr: Expression, operator: String) {
withClue("not should have higher prio as the other logical operators") {
expr shouldBe instanceOf<BinaryExpression>()
val binExpr = expr as BinaryExpression
binExpr.operator shouldBe operator
(binExpr.left as PrefixExpression).operator shouldBe "not"
(binExpr.right as PrefixExpression).operator shouldBe "not"
}
}
correctPrios(andAssignmentExpr, "and")
correctPrios(orAssignmentExpr, "or")
correctPrios(xorAssignmentExpr, "xor")
}
test("inferred type correct for binaryexpression") {
val src = SourceCode.Text("""
main {
ubyte bb
uword ww
ubyte bb2 = not bb or not ww ; expression combining ubyte and uword
}
""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val bb2 = (module.statements.single() as Block).statements[2] as VarDecl
val expr = bb2.value as BinaryExpression
println(expr)
expr.operator shouldBe "or"
expr.left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
expr.right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
expr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
}
test("inferred type for typecasted expressions with logical operators") {
val src=SourceCode.Text("""
main {
ubyte bb
uword ww
uword qq = (not bb as uword)
uword zz = not bb or not ww
ubyte bb2 = not bb or not ww
uword zz2 = (not bb as uword) or not ww
}
""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val stmts = (module.statements.single() as Block).statements
stmts.size shouldBe 6
val qq = (stmts[2] as VarDecl).value as TypecastExpression
val zz = (stmts[3] as VarDecl).value as BinaryExpression
val bb2 = (stmts[4] as VarDecl).value as BinaryExpression
val zz2 = (stmts[5] as VarDecl).value as BinaryExpression
qq.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
zz.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
bb2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
zz2.operator shouldBe "or"
val left = zz2.left as TypecastExpression
val right = zz2.right as PrefixExpression
left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
zz2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE // 'or' causes UBYTE result
}
test("type cast from byte to ubyte as desired target type") {
val src = SourceCode.Text("""
main {
ubyte r
ubyte ub = (cos8(r)/2 + 100) as ubyte
}""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val stmts = (module.statements.single() as Block).statements
stmts.size shouldBe 2
val ubexpr = (stmts[1] as VarDecl).value as TypecastExpression
ubexpr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
}
test("assignment isAugmented correctness") {
val src = SourceCode.Text("""
main {
sub start() {
ubyte r
ubyte q
r = q*3 ; #1 no
r = r*3 ; #2 yes
r = 3*r ; #3 yes
r = 3*q ; #4 no
r = 5+r ; #5 yes
r = 5-r ; #6 no
r = r-5 ; #7 yes
r = not r ; #8 yes
r = not q ; #9 no
r = (q+r)+5 ; #10 yes
r = q+(r+5) ; #11 yes
r = (q+r)-5 ; #12 yes
r = q+(r-5) ; #13 yes
}
}""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val stmts = program.entrypoint.statements
val expectedResults = listOf(
false, true, true,
false, true, false,
true, true, false,
true, true, true,
true
)
stmts.size shouldBe 15
expectedResults.size shouldBe stmts.size-2
for((idx, pp) in stmts.drop(2).zip(expectedResults).withIndex()) {
val assign = pp.first as Assignment
val expected = pp.second
withClue("#${idx+1}: should${if(expected) "" else "n't"} be augmentable: $assign") {
assign.isAugmentable shouldBe expected
assign.value shouldBe (instanceOf<PrefixExpression>() or instanceOf<BinaryExpression>() or instanceOf<TypecastExpression>())
}
}
}
})

View File

@ -0,0 +1,109 @@
package prog8tests.ast
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
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.internedStringsModuleName
import prog8.parser.SourceCode
import prog8tests.ast.helpers.DummyFunctions
import prog8tests.ast.helpers.DummyMemsizer
import prog8tests.ast.helpers.DummyStringEncoder
class TestProgram: FunSpec({
context("Constructor") {
test("withNameBuiltinsAndMemsizer") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.modules.size shouldBe 1
program.modules[0].name shouldBe internedStringsModuleName
program.modules[0].program shouldBeSameInstanceAs program
program.modules[0].parent shouldBeSameInstanceAs program.namespace
}
}
context("AddModule") {
test("withEmptyModule") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val retVal = program.addModule(m1)
retVal shouldBeSameInstanceAs program
program.modules.size shouldBe 2
m1 shouldBeIn program.modules
m1.program shouldBeSameInstanceAs program
m1.parent shouldBeSameInstanceAs program.namespace
withClue("module may not occur multiple times") {
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m1) }
ex.message shouldContain m1.name
}
val m2 = Module(mutableListOf(), m1.position, m1.source)
withClue("other module but with same name may not occur multiple times") {
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m2) }
ex.message shouldContain m1.name
}
}
}
context("MoveModuleToFront") {
test("withInternedStringsModule") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = program.modules[0]
m.name shouldBe internedStringsModuleName
val retVal = program.moveModuleToFront(m)
retVal shouldBeSameInstanceAs program
program.modules[0] shouldBeSameInstanceAs m
}
test("withForeignModule") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
shouldThrow<IllegalArgumentException> { program.moveModuleToFront(m) }
}
test("withFirstOfPreviouslyAddedModules") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
program.addModule(m1)
program.addModule(m2)
val retVal = program.moveModuleToFront(m1)
retVal shouldBeSameInstanceAs program
program.modules.indexOf(m1) shouldBe 0
}
test("withSecondOfPreviouslyAddedModules") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
program.addModule(m1)
program.addModule(m2)
val retVal = program.moveModuleToFront(m2)
retVal shouldBeSameInstanceAs program
program.modules.indexOf(m2) shouldBe 0
}
}
context("Properties") {
test("modules") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val ms1 = program.modules
val ms2 = program.modules
ms2 shouldBeSameInstanceAs ms1
}
}
})

View File

@ -1,9 +1,9 @@
package prog8tests.ast
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.StringStartsWith
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.parser.SourceCode
import prog8.parser.SourceCode.Companion.libraryFilePrefix
import prog8tests.ast.helpers.assumeNotExists
@ -11,11 +11,9 @@ import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.fixturesDir
import prog8tests.ast.helpers.resourcesDir
import kotlin.io.path.Path
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSourceCode {
class TestSourceCode: AnnotationSpec() {
@Test
fun testFromString() {
@ -25,30 +23,30 @@ class TestSourceCode {
val src = SourceCode.Text(text)
val actualText = src.getCharStream().toString()
assertContains(src.origin, Regex("^<String@[0-9a-f\\-]+>$"))
assertEquals(text, actualText)
assertFalse(src.isFromResources)
assertFalse(src.isFromFilesystem)
assertThat(src.toString(), StringStartsWith("prog8.parser.SourceCode"))
src.origin shouldContain Regex("^<String@[0-9a-f\\-]+>$")
actualText shouldBe text
src.isFromResources shouldBe false
src.isFromFilesystem shouldBe false
src.toString().startsWith("prog8.parser.SourceCode") shouldBe true
}
@Test
fun testFromPathWithNonExistingPath() {
val filename = "i_do_not_exist.p8"
val path = assumeNotExists(fixturesDir, filename)
assertFailsWith<NoSuchFileException> { SourceCode.File(path) }
shouldThrow<NoSuchFileException> { SourceCode.File(path) }
}
@Test
fun testFromPathWithMissingExtension_p8() {
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
assumeReadableFile(fixturesDir,"simple_main.p8")
assertFailsWith<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
shouldThrow<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
}
@Test
fun testFromPathWithDirectory() {
assertFailsWith<AccessDeniedException> { SourceCode.File(fixturesDir) }
shouldThrow<AccessDeniedException> { SourceCode.File(fixturesDir) }
}
@Test
@ -57,10 +55,10 @@ class TestSourceCode {
val path = assumeReadableFile(fixturesDir, filename)
val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString()
assertEquals(expectedOrigin, src.origin)
assertEquals(path.toFile().readText(), src.readText())
assertFalse(src.isFromResources)
assertTrue(src.isFromFilesystem)
src.origin shouldBe expectedOrigin
src.readText() shouldBe path.toFile().readText()
src.isFromResources shouldBe false
src.isFromFilesystem shouldBe true
}
@Test
@ -70,8 +68,8 @@ class TestSourceCode {
val srcFile = assumeReadableFile(path).toFile()
val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString()
assertEquals(expectedOrigin, src.origin)
assertEquals(srcFile.readText(), src.readText())
src.origin shouldBe expectedOrigin
src.readText() shouldBe srcFile.readText()
}
@Test
@ -80,10 +78,10 @@ class TestSourceCode {
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/$pathString", src.origin)
assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources)
assertFalse(src.isFromFilesystem)
src.origin shouldBe "$libraryFilePrefix/$pathString"
src.readText() shouldBe srcFile.readText()
src.isFromResources shouldBe true
src.isFromFilesystem shouldBe false
}
@Test
@ -92,8 +90,8 @@ class TestSourceCode {
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix$pathString", src.origin)
assertEquals(srcFile.readText(), src.readText())
src.origin shouldBe "$libraryFilePrefix$pathString"
src.readText() shouldBe srcFile.readText()
}
@Test
@ -102,9 +100,9 @@ class TestSourceCode {
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/$pathString", src.origin)
assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources, ".isFromResources")
src.origin shouldBe "$libraryFilePrefix/$pathString"
src.readText() shouldBe srcFile.readText()
src.isFromResources shouldBe true
}
@Test
@ -113,8 +111,8 @@ class TestSourceCode {
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix$pathString", src.origin)
assertEquals(srcFile.readText(), src.readText())
src.origin shouldBe "$libraryFilePrefix$pathString"
src.readText() shouldBe srcFile.readText()
}
@Test
@ -123,9 +121,9 @@ class TestSourceCode {
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/prog8lib/math.p8", src.origin)
assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources, ".isFromResources")
src.origin shouldBe "$libraryFilePrefix/prog8lib/math.p8"
src.readText() shouldBe srcFile.readText()
src.isFromResources shouldBe true
}
@ -134,13 +132,13 @@ class TestSourceCode {
val pathString = "/prog8lib/i_do_not_exist"
assumeNotExists(resourcesDir, pathString.substring(1))
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
}
@Test
fun testFromResourcesWithNonExistingFile_withoutLeadingSlash() {
val pathString = "prog8lib/i_do_not_exist"
assumeNotExists(resourcesDir, pathString)
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
}
}

View File

@ -1,19 +1,15 @@
package prog8tests.ast
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.matchers.shouldBe
import prog8.ast.base.DataType
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSubroutines {
class TestSubroutines: AnnotationSpec() {
@Test
fun stringParameterAcceptedInParser() {
@ -33,12 +29,12 @@ class TestSubroutines {
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"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.STR, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.STR, func.parameters.single().type)
assertTrue(func.statements.isEmpty())
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.isEmpty() shouldBe true
}
@Test
@ -59,11 +55,11 @@ class TestSubroutines {
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"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
assertTrue(func.statements.isEmpty())
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
}
}

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