Compare commits

...
143 Commits
Author SHA1 Message Date
Irmen de Jong 8132edbb08 updated some compiled example 2020-11-10 22:51:01 +01:00
Irmen de Jong d29ce78c86 todos and version 2020-11-10 22:44:48 +01:00
Irmen de Jong 94bc9d7a69 string compare in expression no longer via stack args 2020-11-10 21:48:28 +01:00
Irmen de Jong e8faec0932 re-introduced more aggressive binexpr splitting optimization 2020-11-10 21:17:33 +01:00
Irmen de Jong 69ca4fe304 cleanup 2020-11-10 21:02:12 +01:00
Irmen de Jong cd99fe46fd finished call convention change for builtin functions now no longer via stack 2020-11-10 00:43:45 +01:00
Irmen de Jong 4825b4dc68 fix passing address of pass-by-reference assignment to a UWORD 2020-11-10 00:35:24 +01:00
Irmen de Jong 8d0607ef58 fix missing float casts 2020-11-09 23:57:50 +01:00
Irmen de Jong 225295a7d8 fix float casts 2020-11-09 01:18:22 +01:00
Irmen de Jong 4cd74daf53 float eval result var added, but some examples are broken 2020-11-08 18:54:02 +01:00
Irmen de Jong 6eb9118197 example 2020-11-07 01:08:56 +01:00
Irmen de Jong d0bd2f522c rol and ror 2020-11-07 00:56:54 +01:00
Irmen de Jong 661c757236 fix string compare in expressions 2020-11-06 22:59:56 +01:00
Irmen de Jong aaa20093ef cleaning up and correcting cc for builtin functions 2020-11-06 00:56:26 +01:00
Irmen de Jong 1eecdd6fa3 fix error when taking address of struct var 2020-11-05 02:39:04 +01:00
Irmen de Jong 800b5b2a43 cleaning up and correcting cc for builtin functions 2020-11-05 02:29:33 +01:00
Irmen de Jong 9d17421c66 implemented the arithmetic functions with new cc. fixed sgn(). 2020-11-04 02:27:29 +01:00
Irmen de Jong 0edd50e956 implemented cc for abs() 2020-11-03 23:01:23 +01:00
Irmen de Jong 288d4f08b3 implemented cc for integer sin and cos variants 2020-11-03 22:42:59 +01:00
Irmen de Jong 526e4b8bdc fix faulty binexpr splitting 2020-11-03 21:31:08 +01:00
Irmen de Jong e0c5ccc16b begun with converting builtin functions to new call convention 2020-11-02 23:00:20 +01:00
Irmen de Jong ebc2c614d7 use non-stack call conv for builtin functions 2020-11-02 19:59:27 +01:00
Irmen de Jong 29f5a85158 callconv 2020-11-01 19:25:23 +01:00
Irmen de Jong 8af2380a47 pair 2020-11-01 18:00:20 +01:00
Irmen de Jong 431f2a2088 optimized memset and memcopy on CX16, memcopy can deal with any size now 2020-11-01 08:00:32 +01:00
Irmen de Jong e05ea887f6 implement proper returning of float values via FAC1 2020-11-01 06:27:17 +01:00
Irmen de Jong 95c0425151 improved sqrt16 2020-11-01 05:45:49 +01:00
Irmen de Jong 47cbc7b1f9 added a custom-charset example for the c64 2020-10-31 02:26:59 +01:00
Irmen de Jong e7b75d591c assigning float results from functions (from FAC1) 2020-10-31 01:22:19 +01:00
Irmen de Jong 99f7d469f4 assigning string result from subroutine 2020-10-30 22:22:06 +01:00
Irmen de Jong 8a6ef17fbf option 2020-10-30 21:51:15 +01:00
Irmen de Jong 5f337a0bd9 fix typecheck of multiple returnvalues 2020-10-30 21:45:37 +01:00
Irmen de Jong 87862f772a better handling of inferred type errors 2020-10-30 21:24:49 +01:00
Irmen de Jong 3ab641aa21 removed @stack in subroutine args and returnvalues, can only use variables or registers now 2020-10-30 15:02:42 +01:00
Irmen de Jong 3efa8da8e0 made versions of various builtin funcs returning value in registers 2020-10-30 14:35:20 +01:00
Irmen de Jong 3e28ed4fe4 mader versions of abs() and sgn() returning value in register 2020-10-28 22:56:13 +01:00
Irmen de Jong 44949460ed change for subroutine return values via registers instead of stack 2020-10-28 00:29:34 +01:00
Irmen de Jong 83cc19ad6f preparing for subroutine return values via registers instead of stack 2020-10-23 20:56:10 +02:00
Irmen de Jong 66bb98c479 fixed bugs in code assigning values from eval stack 2020-10-23 03:45:09 +02:00
Irmen de Jong ff3f985658 refactoring 2020-10-22 23:41:16 +02:00
Irmen de Jong 2ba6c9ccbe textelite 1.1 finalize load/save, add it to examplesd disk 2020-10-20 21:49:06 +02:00
Irmen de Jong 3eaf111e7d added 'slowwarn' cli option 2020-10-20 21:38:37 +02:00
Irmen de Jong 30da26b9a9 tackling problem of invalid reuse of auto indexer var 2020-10-20 21:23:43 +02:00
Irmen de Jong e35ad0cc8f code cleanups 2020-10-20 17:54:16 +02:00
Irmen de Jong 1a36302cf1 rest of optimizations following simplification of array indexer 2020-10-19 23:57:00 +02:00
Irmen de Jong 82a28bb555 extra attempt to simplify add and subtract with negative numbers 2020-10-19 23:01:32 +02:00
Irmen de Jong c1ce0be451 slightly optimize expression code for most common cases +/- 1 , */div 2 2020-10-19 22:50:38 +02:00
Irmen de Jong c0a5f8fef0 removed double mul code 2020-10-19 21:32:44 +02:00
Irmen de Jong 702cf304d0 implemented missing swap() operations 2020-10-19 21:26:11 +02:00
Irmen de Jong 4dee8b6048 remove superfluous value eval 2020-10-19 02:38:26 +02:00
Irmen de Jong ec665e0cc1 fixed incorrect removal of certain assignments that are NOT double 2020-10-19 02:16:23 +02:00
Irmen de Jong aec3b82476 fixed bitshifting by more than the number of bits in the value 2020-10-19 02:05:01 +02:00
Irmen de Jong e83796b5b9 fixed bit shifting by 0. optimized bitshifting code. 2020-10-18 17:12:52 +02:00
Irmen de Jong 8eb69d6eda vardecl with initializer expression are now optimized again (unless floats) 2020-10-18 16:15:05 +02:00
Irmen de Jong 74b5124a42 removed restriction on array indexer expression again from docs and code... :) 2020-10-18 14:05:26 +02:00
Irmen de Jong b9706a180b fix array indexer bug 2020-10-18 13:49:53 +02:00
Irmen de Jong 8aeb8a9bb7 reintroduce expressions for array indexing 2020-10-18 13:33:11 +02:00
Irmen de Jong 8f2e166a22 annotated some high prio todos 2020-10-17 22:57:54 +02:00
Irmen de Jong fdd91170dc allow simple binary expressions as array indexing too, but not more 2020-10-17 22:43:35 +02:00
Irmen de Jong c40ddb061b example adjustments 2020-10-17 21:00:59 +02:00
Irmen de Jong 353d6cfc55 doc about array index restriction 2020-10-17 20:35:36 +02:00
Irmen de Jong f37564c49c fixed 2020-10-17 19:59:48 +02:00
Irmen de Jong 157484d94b adapted p8 code to restricted array indexing 2020-10-17 19:57:55 +02:00
Irmen de Jong 7626c9fff7 only allow array indexing via a number, or a variable (eliminate complex expression calcs for array indexing, force explicit use of an index variable) 2020-10-17 19:57:55 +02:00
Irmen de Jong 1f55f9fc49 removed 2 problematic ZP locations for the C64 2020-10-17 19:57:10 +02:00
Irmen de Jong 2554bc7ef8 ordered the functions in the docs 2020-10-17 02:14:19 +02:00
Irmen de Jong 7cb4100419 string can be compared directly (uses strcmp() automatically in asm) 2020-10-17 02:01:00 +02:00
Irmen de Jong 2d3b7eb878 started making string compares use strcmp() automatically 2020-10-17 01:11:01 +02:00
Irmen de Jong 4d01a78731 introduced strcmp() builtin function 2020-10-16 19:00:06 +02:00
Irmen de Jong a03e36828a fixed lines in assembly source optimizer 2020-10-16 01:48:03 +02:00
Irmen de Jong 260fb65b06 making strcmp 2020-10-16 00:11:46 +02:00
Irmen de Jong 9fb8526136 added conv.bin and hex string to number 2020-10-15 23:47:10 +02:00
Irmen de Jong 26fc5ff5e2 preparing conv.bin and hex string to number 2020-10-15 23:10:28 +02:00
Irmen de Jong 5060f0bb19 fixed assigning a memory byte from an array 2020-10-15 22:15:00 +02:00
Irmen de Jong beaf6d449b added short overview of the library modules 2020-10-15 21:30:03 +02:00
Irmen de Jong 4d68b508a2 proper error if variable name is the same as its subroutine or block (that would create naming problems in the assembly code) 2020-10-15 20:48:18 +02:00
Irmen de Jong cd825e386d fix invalid address-of error when taking address of struct variable 2020-10-15 20:14:17 +02:00
Irmen de Jong 095c8b2309 corrected name and added cx16logo library module for fun 2020-10-15 00:58:41 +02:00
Irmen de Jong 8b6eb74c58 refactor 2020-10-14 23:43:38 +02:00
Irmen de Jong aba437e5a2 diskio load and save use kernel routines for load and save, and don't bother with SEQ files 2020-10-14 22:33:49 +02:00
Irmen de Jong efe3ed499b starting with load/save in textelite 2020-10-14 02:51:00 +02:00
Irmen de Jong 5595564a1f todo strcmp 2020-10-14 01:22:43 +02:00
Irmen de Jong 439761cb67 fixed C64 ZP addresses to allow disk I/O, introduced diskio library module 2020-10-14 01:17:18 +02:00
Irmen de Jong bee6c65293 fixed several bugs in the repeat assembly for loop sizes like 0 and 256 2020-10-13 21:48:15 +02:00
Irmen de Jong 10145b946b invalid repeat loop code is generated... 2020-10-13 16:27:40 +02:00
Irmen de Jong ebf4b50059 reused existing CallGraph to check for recursion, which is now fixed. It's a warning too now. 2020-10-12 23:04:00 +02:00
Irmen de Jong 07cce3b3fc version 4.5 2020-10-11 21:59:38 +02:00
Irmen de Jong f2c19afd95 version 4.5 2020-10-11 21:47:41 +02:00
Irmen de Jong d159e70e1c textelite travel commands 2020-10-11 21:38:25 +02:00
Irmen de Jong ac693a2541 textelite buy and sell commands 2020-10-11 19:29:18 +02:00
Irmen de Jong 1e988116ce fixed precedence of comparison and bitwise operators 2020-10-11 19:02:53 +02:00
Irmen de Jong ec9e722927 added conv.str2byte and conv.str2ubyte 2020-10-11 18:36:20 +02:00
Irmen de Jong 4cd5e8c378 textelite 2020-10-11 18:19:09 +02:00
Irmen de Jong b759d5e06a fixed X register corruption on Cx16 verions of float.GIVUAYFAY and GIVAYFAY 2020-10-11 17:46:19 +02:00
Irmen de Jong 1469033c1e todo 2020-10-11 16:53:00 +02:00
Irmen de Jong c15fd75df7 asmassignment can now use arbitrary source symbols; optimized byte-word sign extesion with this to not use stack anymore 2020-10-11 15:44:08 +02:00
Irmen de Jong 73524e01a6 really fix byte-word sign extension for function args as expression 2020-10-11 03:07:45 +02:00
Irmen de Jong 9e54e11113 fixed string + string/ string * number 2020-10-11 02:34:04 +02:00
Irmen de Jong 01ac5f29db fix byte-word sign extension for function args as expression 2020-10-11 01:38:34 +02:00
Irmen de Jong 67a2241e32 textelite market start 2020-10-11 00:38:38 +02:00
Irmen de Jong 72b6dc3de7 avoid crash when optimizer has multiple replacements of the same node 2020-10-11 00:37:35 +02:00
Irmen de Jong 6f5b645995 textelite market start 2020-10-10 23:24:15 +02:00
Irmen de Jong 458ad1de57 fix strlen on uword (pointer) instead of str 2020-10-10 23:24:05 +02:00
Irmen de Jong 216f48b7c1 txtelite 2020-10-10 22:45:03 +02:00
Irmen de Jong b2d1757e5a asmgen: byte to word sign extensions 2020-10-10 15:39:48 +02:00
Irmen de Jong 6e53eb9d5c asmgen: only generate storage byte for register saves in subroutine when it's actually needed 2020-10-10 15:02:56 +02:00
Irmen de Jong e5ee5be9c5 textelite 2020-10-10 04:42:17 +02:00
Irmen de Jong bd237b2b95 it's now possible in more places to assign arrays and put array literals without the need to define explicit variable. 2020-10-10 04:30:28 +02:00
Irmen de Jong d31cf766eb added missing doc picture 2020-10-10 02:51:02 +02:00
Irmen de Jong 56d530ff04 txtelite with input loop 2020-10-10 01:46:19 +02:00
Irmen de Jong 0bbb2240f2 txtelite with input loop 2020-10-10 01:35:46 +02:00
Irmen de Jong 1c8e4dba73 added \' escape character 2020-10-10 01:28:57 +02:00
Irmen de Jong 4a9956c4a4 txtelite species and planet naming fix 2020-10-10 01:15:26 +02:00
Irmen de Jong 59c0e6ae32 added some more missing assignment codegens (word * byte etc) 2020-10-09 23:48:33 +02:00
Irmen de Jong 94c30fc21e textelite 2020-10-09 22:47:42 +02:00
Irmen de Jong 8bb3b3be20 fix repeat loop for variables when var == 0 2020-10-09 22:30:21 +02:00
Irmen de Jong 85e3c2c5a2 textelite 2020-10-09 22:25:12 +02:00
Irmen de Jong 4be381c597 fixed compiler optimizer crash because of conflicting expression replacements 2020-10-09 21:51:54 +02:00
Irmen de Jong 6ff5470cf1 txtelite 2020-10-09 21:01:06 +02:00
Irmen de Jong 151dcfdef9 code style 2020-10-08 21:47:07 +02:00
Irmen de Jong c282b4cb9f code style 2020-10-07 23:24:30 +02:00
Irmen de Jong c426f4626c added some more missing aug assign operator code 2020-10-07 22:53:18 +02:00
Irmen de Jong 0e3c92626e fixed handling of main module when importing another. fixed diskdir closedown. 2020-10-07 21:55:00 +02:00
Irmen de Jong 5099525e24 added missing register pair assignments. fixed compiler crashes 2020-10-07 03:43:02 +02:00
Irmen de Jong e22b4cbb67 fixed invalid errormessage about memory mapped strings 2020-10-07 01:35:39 +02:00
Irmen de Jong 2b48828179 examples issues 2020-10-07 01:21:41 +02:00
Irmen de Jong 3e181362dd optimized code for processing return values from asmsubs without intermediate estack. 2020-10-07 00:51:57 +02:00
Irmen de Jong 71fd98e39e allow asmsub routines with multiple return values to be called (special case for return values in status register) 2020-10-07 00:33:42 +02:00
Irmen de Jong 71cd8b6d51 cx16 cross-compile teaser screenshot 2020-10-05 19:59:51 +02:00
Irmen de Jong ad75fcbf7e txtelite 2020-10-05 19:49:13 +02:00
Irmen de Jong f8b04a6357 added status return flags to some kernel i/o operations 2020-10-05 19:48:21 +02:00
Irmen de Jong d8fcbb78d3 txtelite goatsoup 2020-10-04 21:53:16 +02:00
Irmen de Jong 8408bf3789 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:53:08 +02:00
Irmen de Jong 3e1185658e txtelite goatsoup 2020-10-04 21:35:37 +02:00
Irmen de Jong d778cdcd61 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:11:42 +02:00
Irmen de Jong 90b303fc03 fix error message for invalid number of arguments 2020-10-04 19:28:22 +02:00
Irmen de Jong eb86b1270d txtelite 2020-10-04 19:23:36 +02:00
Irmen de Jong a1f0cc878b correct error message for faulty string variable declarations 2020-10-04 19:13:19 +02:00
Irmen de Jong f2e2720b15 compiler crash fixed when dealing with functioncall returning a str 2020-10-04 18:47:47 +02:00
Irmen de Jong ec8cfe1591 make string-assignment actually work (using strcpy) 2020-10-04 18:18:58 +02:00
Irmen de Jong 22eac159e5 txtelite 2020-10-04 17:47:57 +02:00
Irmen de Jong 956b0c3fa7 added \xHH escape character to strings, allow strings of length zero. 2020-10-04 13:05:43 +02:00
Irmen de Jong a6427e0949 added \$HH escape character to strings 2020-10-03 15:11:09 +02:00
96 changed files with 8948 additions and 4710 deletions

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
+31 -307
View File
@@ -61,7 +61,7 @@ stack_b2float .proc
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
stack_w2float .proc
@@ -71,7 +71,7 @@ stack_w2float .proc
lda P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVAYF
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
stack_ub2float .proc
@@ -82,7 +82,7 @@ stack_ub2float .proc
tay
lda #0
jsr GIVAYF
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
stack_uw2float .proc
@@ -92,12 +92,12 @@ stack_uw2float .proc
ldy P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr pop_float_fac1
jsr AYINT
ldx P8ZP_SCRATCH_REG
lda $64
@@ -109,8 +109,8 @@ stack_float2w .proc ; also used for float2b
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr pop_float_fac1
jsr GETADR
ldx P8ZP_SCRATCH_REG
sta P8ESTACK_HI,x
@@ -146,22 +146,6 @@ push_float .proc
rts
.pend
func_rndf .proc
; -- put a random floating point value on the stack
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y
ldx P8ZP_SCRATCH_REG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
@@ -198,27 +182,6 @@ pop_float_fac1 .proc
jmp MOVFM
.pend
pop_float_fac2 .proc
; -- pops float from stack into FAC2
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp CONUPK
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
@@ -284,9 +247,11 @@ pop_2_floats_f2_in_fac1 .proc
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1
push_fac1 .proc
; -- push the float in FAC1 onto the stack
stx P8ZP_SCRATCH_REG
_internal ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
@@ -295,6 +260,7 @@ push_fac1_as_result .proc
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
@@ -310,8 +276,7 @@ pow_f .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
div_f .proc
@@ -321,7 +286,7 @@ div_f .proc
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
add_f .proc
@@ -331,7 +296,7 @@ add_f .proc
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
sub_f .proc
@@ -341,7 +306,7 @@ sub_f .proc
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
mul_f .proc
@@ -351,7 +316,7 @@ mul_f .proc
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1_as_result
jmp push_fac1._internal
.pend
neg_f .proc
@@ -362,14 +327,6 @@ neg_f .proc
rts
.pend
abs_f .proc
; -- strip the sign bit on the stack
lda P8ESTACK_HI+3,x
and #$7f
sta P8ESTACK_HI+3,x
rts
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
@@ -468,252 +425,33 @@ _return_true lda #1
bne _return_result
.pend
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr SIN
jmp push_fac1_as_result
.pend
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr COS
jmp push_fac1_as_result
.pend
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
+ jmp push_fac1_as_result
.pend
func_any_f .proc
inx
lda P8ESTACK_LO,x ; array size
set_array_float_from_fac1 .proc
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1 ; times 5 because of float
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda P8ESTACK_LO,x ; array size
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1 ; times 5 because of float
tay
dey
- lda (P8ZP_SCRATCH_W1),y
clc
dey
adc (P8ZP_SCRATCH_W1),y
dey
adc (P8ZP_SCRATCH_W1),y
dey
adc (P8ZP_SCRATCH_W1),y
dey
adc (P8ZP_SCRATCH_W1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta P8ESTACK_LO+1,x
rts
+ sta P8ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #255
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx floats_store_reg
- sty P8ZP_SCRATCH_REG
lda P8ZP_SCRATCH_W1
adc P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_W1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVFM
+ lda P8ZP_SCRATCH_W1
clc
adc #5
sta P8ZP_SCRATCH_W1
adc P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ ldy P8ZP_SCRATCH_REG
dey
cpy #255
bne -
iny
+ stx floats_store_reg
tax
jsr MOVMF
ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f .proc
lda #1
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<FL_ZERO_const
ldy #>FL_ZERO_const
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx floats_store_reg
- sty P8ZP_SCRATCH_REG
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FADD
ldy P8ZP_SCRATCH_REG
dey
cpy #255
beq +
lda P8ZP_SCRATCH_W1
clc
adc #5
sta P8ZP_SCRATCH_W1
bcc -
inc P8ZP_SCRATCH_W1+1
bne -
+ ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
.pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta P8ESTACK_LO,x
dex
rts
.pend
set_0_array_float .proc
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
inx
lda P8ESTACK_LO,x
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ESTACK_LO,x
adc P8ZP_SCRATCH_B1
tay
lda #0
sta (P8ZP_SCRATCH_W1),y
@@ -730,13 +468,12 @@ set_0_array_float .proc
set_array_float .proc
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
inx
lda P8ESTACK_LO,x
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ESTACK_LO,x
adc P8ZP_SCRATCH_B1
adc P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
bcc +
@@ -747,16 +484,3 @@ set_array_float .proc
.pend
swap_floats .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (P8ZP_SCRATCH_W1),y
pha
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
+1
View File
@@ -213,5 +213,6 @@ sub print_f (float value) {
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
}
+437
View File
@@ -0,0 +1,437 @@
; --- floating point builtin functions
abs_f_stack .proc
; -- push abs(AY) on stack
jsr floats.MOVFM
jsr floats.ABS
jmp push_fac1
.pend
abs_f_fac1 .proc
; -- FAC1 = abs(AY)
jsr floats.MOVFM
jmp floats.ABS
.pend
func_atan_stack .proc
jsr func_atan_fac1
jmp push_fac1
.pend
func_atan_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr ATN
ldx P8ZP_SCRATCH_REG
rts
.pend
func_ceil_stack .proc
jsr func_ceil_fac1
jmp push_fac1
.pend
func_ceil_fac1 .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr MOVFM
stx P8ZP_SCRATCH_REG
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
+ ldx P8ZP_SCRATCH_REG
rts
.pend
func_floor_stack .proc
jsr func_floor_fac1
jmp push_fac1
.pend
func_floor_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr INT
ldx P8ZP_SCRATCH_REG
rts
.pend
func_round_stack .proc
jsr func_round_fac1
jmp push_fac1
.pend
func_round_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr FADDH
jsr INT
ldx P8ZP_SCRATCH_REG
rts
.pend
func_sin_stack .proc
jsr func_sin_fac1
jmp push_fac1
.pend
func_sin_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr SIN
ldx P8ZP_SCRATCH_REG
rts
.pend
func_cos_stack .proc
jsr func_cos_fac1
jmp push_fac1
.pend
func_cos_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr COS
ldx P8ZP_SCRATCH_REG
rts
.pend
func_tan_stack .proc
jsr func_tan_fac1
jmp push_fac1
.pend
func_tan_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr TAN
ldx P8ZP_SCRATCH_REG
rts
.pend
func_rad_stack .proc
jsr func_rad_fac1
jmp push_fac1
.pend
func_rad_fac1 .proc
; -- convert degrees to radians (d * pi / 180)
jsr MOVFM
stx P8ZP_SCRATCH_REG
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
ldx P8ZP_SCRATCH_REG
rts
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg_stack .proc
jsr func_deg_fac1
jmp push_fac1
.pend
func_deg_fac1 .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr MOVFM
stx P8ZP_SCRATCH_REG
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
ldx P8ZP_SCRATCH_REG
rts
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_ln_stack .proc
jsr func_ln_fac1
jmp push_fac1
.pend
func_ln_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr LOG
ldx P8ZP_SCRATCH_REG
rts
.pend
func_log2_stack .proc
jsr func_log2_fac1
jmp push_fac1
.pend
func_log2_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr LOG
jsr MOVEF
lda #<FL_LOG2
ldy #>FL_LOG2
jsr MOVFM
jsr FDIVT
ldx P8ZP_SCRATCH_REG
rts
.pend
func_sign_f_stack .proc
jsr func_sign_f_into_A
sta P8ESTACK_LO,x
dex
rts
.pend
func_sign_f_into_A .proc
jsr MOVFM
jmp SIGN
.pend
func_sqrt_stack .proc
jsr func_sqrt_fac1
jmp push_fac1
.pend
func_sqrt_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr SQR
ldx P8ZP_SCRATCH_REG
rts
.pend
func_rndf_stack .proc
jsr func_rndf_fac1
jmp push_fac1
.pend
func_rndf_fac1 .proc
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx P8ZP_SCRATCH_REG
rts
.pend
func_swap_f .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (P8ZP_SCRATCH_W1),y
pha
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
func_reverse_f .proc
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
_left_index = P8ZP_SCRATCH_W2
_right_index = P8ZP_SCRATCH_W2+1
_loop_count = P8ZP_SCRATCH_REG
pha
jsr a_times_5
sec
sbc #5
sta _right_index
lda #0
sta _left_index
pla
lsr a
sta _loop_count
_loop ; push the left indexed float on the stack
ldy _left_index
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
; copy right index float to left index float
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
; pop the float off the stack into the right index float
ldy _right_index
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
inc _left_index
lda _right_index
sec
sbc #9
sta _right_index
dec _loop_count
bne _loop
rts
.pend
a_times_5 .proc
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
rts
.pend
func_any_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_any_b_into_A
.pend
func_all_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_all_b_into_A
.pend
func_any_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_any_b_stack
.pend
func_all_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_all_b_stack
.pend
func_max_f_stack .proc
jsr func_max_f_fac1
jmp push_fac1
.pend
func_max_f_fac1 .proc
; -- max(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
_loop_count = P8ZP_SCRATCH_REG
stx floats_store_reg
sta _loop_count
lda #255
sta _minmax_cmp+1 ; modifying
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
- lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVFM
+ lda P8ZP_SCRATCH_W1
clc
adc #5
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ dec _loop_count
bne -
ldx floats_store_reg
rts
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f_stack .proc
jsr func_min_f_fac1
jmp push_fac1
.pend
func_min_f_fac1 .proc
; -- min(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
sta func_max_f_fac1._loop_count
lda #1
sta func_max_f_fac1._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f_fac1._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f_stack .proc
jsr func_sum_f_fac1
jmp push_fac1
.pend
func_sum_f_fac1 .proc
; -- sum(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
_loop_count = P8ZP_SCRATCH_REG
stx floats_store_reg
sta _loop_count
lda #<FL_ZERO_const
ldy #>FL_ZERO_const
jsr MOVFM
- lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FADD
lda P8ZP_SCRATCH_W1
clc
adc #5
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ dec _loop_count
bne -
ldx floats_store_reg
rts
.pend
+1 -1
View File
@@ -33,7 +33,7 @@ graphics {
}
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2-x1
word @zp dx = x2-x1 as word
word @zp dy = y2-y1
if dx < 0 {
dx = -dx
+6 -6
View File
@@ -204,23 +204,23 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 ROM kernal routines ----
+96 -2
View File
@@ -249,7 +249,28 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the unsigned byte value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; TODO implement optimized custom version of this instead of simply reusing str2uword
%asm {{
jmp str2uword
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; TODO implement optimized custom version of this instead of simply reusing str2word
%asm {{
jmp str2word
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
@@ -303,7 +324,7 @@ _result_times_10 ; (W*4 + W)*2
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
@@ -358,4 +379,77 @@ _negative .byte 0
}}
}
asmsub hex2uword(str string @ AY) -> uword @AY {
; -- hexadecimal string with or without '$' to uword.
; string may be in petscii or c64-screencode encoding.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
sty P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
beq _skip
cmp #7
bcc _add_nine
cmp #'9'
beq _calc
bcs _add_nine
_calc asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
_skip inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_nine ldy #9
sty P8ZP_SCRATCH_B1
bne _calc
}}
}
asmsub bin2uword(str string @ AY) -> uword @AY {
; -- binary string with or without '%' to uword.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
beq +
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
}
+5 -4
View File
@@ -78,10 +78,10 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
phx
sta P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_W2
jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1
bpl +
@@ -98,9 +98,9 @@ _flt65536 .byte 145,0,0,0,0 ; 65536.0
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
tya
ldy P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_W2
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
@@ -149,5 +149,6 @@ sub print_f (float value) {
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
}
+27 -27
View File
@@ -12,48 +12,48 @@ c64 {
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
; STROUT --> use screen.print
; CLEARSCR -> use screen.clear_screen
; HOMECRSR -> use screen.plot
; STROUT --> use txt.print
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.plot
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> ubyte @A, uword @ XY ; read/set top of memory pointer, returns number of banks in A
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use screen.plot for a 'safe' wrapper that preserves X.
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
}
+6 -3
View File
@@ -134,13 +134,16 @@ _la lda #0 ; modified
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
sub color (ubyte txtcol) {
c64.CHROUT(color_to_charcode[txtcol & 15])
txtcol &= 15
c64.CHROUT(color_to_charcode[txtcol])
}
sub color2 (ubyte txtcol, ubyte bgcol) {
c64.CHROUT(color_to_charcode[bgcol & 15])
txtcol &= 15
bgcol &= 15
c64.CHROUT(color_to_charcode[bgcol])
c64.CHROUT(1) ; switch fg and bg colors
c64.CHROUT(color_to_charcode[txtcol & 15])
c64.CHROUT(color_to_charcode[txtcol])
}
sub lowercase() {
+29
View File
@@ -0,0 +1,29 @@
%import textio
cx16logo {
sub logo_at(ubyte column, ubyte row) {
uword strptr
for strptr in logo_lines {
txt.plot(column, row)
txt.print(strptr)
row++
}
}
sub logo() {
uword strptr
for strptr in logo_lines
txt.print(strptr)
txt.chrout('\n')
}
str[] logo_lines = [
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
"\uf101\uf130\uf13a \uf139▎\uf100"
]
}
+162
View File
@@ -0,0 +1,162 @@
%import textio
%import syslib
; Note: this code is compatible with C64 and CX16.
diskio {
sub directory(ubyte drivenumber) -> byte {
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0)
void c64.OPEN() ; open 1,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(1) ; use #1 as input channel
if_cs
goto io_error
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
; while not key pressed / EOF encountered, read data.
ubyte status = c64.READST()
while not status {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ')
ubyte @zp char
do {
char = c64.CHRIN()
txt.chrout(char)
} until char==0
txt.chrout('\n')
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
status = c64.READST()
void c64.STOP()
if_nz
break
}
io_error:
status = c64.READST()
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(1)
if status and status != 64 { ; 64=end of file
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.chrout('\n')
return false
}
return true
}
sub status(ubyte drivenumber) {
; -- display the disk drive's current status message
c64.SETNAM(0, $0000)
c64.SETLFS(15, drivenumber, 15)
void c64.OPEN() ; open 15,8,15
if_cs
goto io_error
void c64.CHKIN(15) ; use #15 as input channel
if_cs
goto io_error
while not c64.READST()
txt.chrout(c64.CHRIN())
io_error:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15)
}
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
c64.SETNAM(strlen(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size
%asm {{
lda address
sta P8ZP_SCRATCH_W1
lda address+1
sta P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG
lda #<P8ZP_SCRATCH_W1
ldx end_address
ldy end_address+1
jsr c64.SAVE
php
ldx P8ZP_SCRATCH_REG
plp
}}
if_cc
return c64.READST()==0
return false
}
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(strlen(filenameptr), filenameptr)
ubyte secondary = 1
uword end_of_load = 0
if address_override
secondary = 0
c64.SETLFS(1, drivenumber, secondary)
%asm {{
stx P8ZP_SCRATCH_REG
lda #0
ldx address_override
ldy address_override+1
jsr c64.LOAD
bcs +
stx end_of_load
sty end_of_load+1
+ ldx P8ZP_SCRATCH_REG
}}
if end_of_load
return end_of_load - address_override
return 0
}
str filename = "0:??????????????????????????????????????"
sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive
ubyte flen = strlen(filenameptr)
filename[0] = 's'
filename[1] = ':'
memcopy(filenameptr, &filename+2, flen+1)
c64.SETNAM(flen+2, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
c64.CLRCHN()
c64.CLOSE(1)
}
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive
ubyte flen_old = strlen(oldfileptr)
ubyte flen_new = strlen(newfileptr)
filename[0] = 'r'
filename[1] = ':'
memcopy(newfileptr, &filename+2, flen_new)
filename[flen_new+2] = '='
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
c64.SETNAM(3+flen_new+flen_old, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
c64.CLRCHN()
c64.CLOSE(1)
}
}
+20 -40
View File
@@ -781,7 +781,7 @@ mul_byte_3 .proc
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8P_P8ZP_SCRATCH_REG
adc P8ZP_SCRATCH_REG
rts
.pend
@@ -1244,45 +1244,6 @@ mul_word_100 .proc
; ----------- end optimized multiplications -----------
sign_b .proc
lda P8ESTACK_LO+1,x
beq _sign_zero
bmi _sign_neg
_sign_pos lda #1
sta P8ESTACK_LO+1,x
rts
_sign_neg lda #-1
_sign_zero sta P8ESTACK_LO+1,x
rts
.pend
sign_ub .proc
lda P8ESTACK_LO+1,x
beq sign_b._sign_zero
bne sign_b._sign_pos
.pend
sign_w .proc
lda P8ESTACK_HI+1,x
bmi sign_b._sign_neg
beq sign_ub
bne sign_b._sign_pos
.pend
sign_uw .proc
lda P8ESTACK_HI+1,x
beq _sign_possibly_zero
_sign_pos lda #1
sta P8ESTACK_LO+1,x
rts
_sign_possibly_zero lda P8ESTACK_LO+1,x
bne _sign_pos
sta P8ESTACK_LO+1,x
rts
.pend
; bit shifts.
; anything below 3 is done inline. anything above 7 is done via other optimizations.
@@ -1469,3 +1430,22 @@ shift_right_w_3 .proc
jmp shift_right_w_7._shift3
.pend
; support for bit shifting that is too large to be unrolled:
lsr_byte_A .proc
; -- lsr signed byte in A times the value in Y (assume >0)
cmp #0
bmi _negative
- lsr a
dey
bne -
rts
_negative lsr a
ora #$80
dey
bne _negative
rts
.pend
File diff suppressed because it is too large. Load diff
+991
View File
@@ -0,0 +1,991 @@
; Prog8 internal library routines - always included by the compiler
; Generic machine independent 6502 code.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
read_byte_from_address_on_stack .proc
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W2),y
rts
.pend
write_byte_to_address_on_stack .proc
; -- write the byte in A to the memory address on the top of the stack (stack remains unchanged)
ldy P8ESTACK_LO+1,x
sty P8ZP_SCRATCH_W2
ldy P8ESTACK_HI+1,x
sty P8ZP_SCRATCH_W2+1
ldy #0
sta (P8ZP_SCRATCH_W2),y
rts
.pend
neg_b .proc
lda #0
sec
sbc P8ESTACK_LO+1,x
sta P8ESTACK_LO+1,x
rts
.pend
neg_w .proc
sec
lda #0
sbc P8ESTACK_LO+1,x
sta P8ESTACK_LO+1,x
lda #0
sbc P8ESTACK_HI+1,x
sta P8ESTACK_HI+1,x
rts
.pend
inv_word .proc
lda P8ESTACK_LO+1,x
eor #255
sta P8ESTACK_LO+1,x
lda P8ESTACK_HI+1,x
eor #255
sta P8ESTACK_HI+1,x
rts
.pend
not_byte .proc
lda P8ESTACK_LO+1,x
beq +
lda #1
+ eor #1
sta P8ESTACK_LO+1,x
rts
.pend
not_word .proc
lda P8ESTACK_LO + 1,x
ora P8ESTACK_HI + 1,x
beq +
lda #1
+ eor #1
sta P8ESTACK_LO + 1,x
lsr a
sta P8ESTACK_HI + 1,x
rts
.pend
bitand_b .proc
; -- bitwise and (of 2 bytes)
lda P8ESTACK_LO+2,x
and P8ESTACK_LO+1,x
inx
sta P8ESTACK_LO+1,x
rts
.pend
bitor_b .proc
; -- bitwise or (of 2 bytes)
lda P8ESTACK_LO+2,x
ora P8ESTACK_LO+1,x
inx
sta P8ESTACK_LO+1,x
rts
.pend
bitxor_b .proc
; -- bitwise xor (of 2 bytes)
lda P8ESTACK_LO+2,x
eor P8ESTACK_LO+1,x
inx
sta P8ESTACK_LO+1,x
rts
.pend
bitand_w .proc
; -- bitwise and (of 2 words)
lda P8ESTACK_LO+2,x
and P8ESTACK_LO+1,x
sta P8ESTACK_LO+2,x
lda P8ESTACK_HI+2,x
and P8ESTACK_HI+1,x
sta P8ESTACK_HI+2,x
inx
rts
.pend
bitor_w .proc
; -- bitwise or (of 2 words)
lda P8ESTACK_LO+2,x
ora P8ESTACK_LO+1,x
sta P8ESTACK_LO+2,x
lda P8ESTACK_HI+2,x
ora P8ESTACK_HI+1,x
sta P8ESTACK_HI+2,x
inx
rts
.pend
bitxor_w .proc
; -- bitwise xor (of 2 bytes)
lda P8ESTACK_LO+2,x
eor P8ESTACK_LO+1,x
sta P8ESTACK_LO+2,x
lda P8ESTACK_HI+2,x
eor P8ESTACK_HI+1,x
sta P8ESTACK_HI+2,x
inx
rts
.pend
and_b .proc
; -- logical and (of 2 bytes)
lda P8ESTACK_LO+2,x
beq +
lda #1
+ sta P8ZP_SCRATCH_B1
lda P8ESTACK_LO+1,x
beq +
lda #1
+ and P8ZP_SCRATCH_B1
inx
sta P8ESTACK_LO+1,x
rts
.pend
or_b .proc
; -- logical or (of 2 bytes)
lda P8ESTACK_LO+2,x
ora P8ESTACK_LO+1,x
beq +
lda #1
+ inx
sta P8ESTACK_LO+1,x
rts
.pend
xor_b .proc
; -- logical xor (of 2 bytes)
lda P8ESTACK_LO+2,x
beq +
lda #1
+ sta P8ZP_SCRATCH_B1
lda P8ESTACK_LO+1,x
beq +
lda #1
+ eor P8ZP_SCRATCH_B1
inx
sta P8ESTACK_LO+1,x
rts
.pend
and_w .proc
; -- logical and (word and word -> byte)
lda P8ESTACK_LO+2,x
ora P8ESTACK_HI+2,x
beq +
lda #1
+ sta P8ZP_SCRATCH_B1
lda P8ESTACK_LO+1,x
ora P8ESTACK_HI+1,x
beq +
lda #1
+ and P8ZP_SCRATCH_B1
inx
sta P8ESTACK_LO+1,x
sta P8ESTACK_HI+1,x
rts
.pend
or_w .proc
; -- logical or (word or word -> byte)
lda P8ESTACK_LO+2,x
ora P8ESTACK_LO+1,x
ora P8ESTACK_HI+2,x
ora P8ESTACK_HI+1,x
beq +
lda #1
+ inx
sta P8ESTACK_LO+1,x
sta P8ESTACK_HI+1,x
rts
.pend
xor_w .proc
; -- logical xor (word xor word -> byte)
lda P8ESTACK_LO+2,x
ora P8ESTACK_HI+2,x
beq +
lda #1
+ sta P8ZP_SCRATCH_B1
lda P8ESTACK_LO+1,x
ora P8ESTACK_HI+1,x
beq +
lda #1
+ eor P8ZP_SCRATCH_B1
inx
sta P8ESTACK_LO+1,x
sta P8ESTACK_HI+1,x
rts
.pend
add_w .proc
; -- push word+word / uword+uword
inx
clc
lda P8ESTACK_LO,x
adc P8ESTACK_LO+1,x
sta P8ESTACK_LO+1,x
lda P8ESTACK_HI,x
adc P8ESTACK_HI+1,x
sta P8ESTACK_HI+1,x
rts
.pend
sub_w .proc
; -- push word-word
inx
sec
lda P8ESTACK_LO+1,x
sbc P8ESTACK_LO,x
sta P8ESTACK_LO+1,x
lda P8ESTACK_HI+1,x
sbc P8ESTACK_HI,x
sta P8ESTACK_HI+1,x
rts
.pend
mul_byte .proc
; -- b*b->b (signed and unsigned)
inx
lda P8ESTACK_LO,x
ldy P8ESTACK_LO+1,x
jsr math.multiply_bytes
sta P8ESTACK_LO+1,x
rts
.pend
mul_word .proc
inx
lda P8ESTACK_LO,x
sta P8ZP_SCRATCH_W1
lda P8ESTACK_HI,x
sta P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.multiply_words
lda math.multiply_words.result
sta P8ESTACK_LO+1,x
lda math.multiply_words.result+1
sta P8ESTACK_HI+1,x
rts
.pend
idiv_b .proc
; signed division: use unsigned division and fix sign of result afterwards
inx
lda P8ESTACK_LO,x
eor P8ESTACK_LO+1,x
php ; save sign of result
lda P8ESTACK_LO,x
bpl +
eor #$ff
sec
adc #0 ; make num1 positive
+ tay
inx
lda P8ESTACK_LO,x
bpl +
eor #$ff
sec
adc #0 ; make num2 positive
+ jsr math.divmod_ub_asm
sta _remainder
tya
plp ; get sign of result
bpl +
eor #$ff
sec
adc #0 ; negate result
+ sta P8ESTACK_LO,x
dex
rts
_remainder .byte 0
.pend
idiv_ub .proc
inx
ldy P8ESTACK_LO,x
lda P8ESTACK_LO+1,x
jsr math.divmod_ub_asm
tya
sta P8ESTACK_LO+1,x
rts
.pend
idiv_w .proc
; signed division: use unsigned division and fix sign of result afterwards
lda P8ESTACK_HI+2,x
eor P8ESTACK_HI+1,x
php ; save sign of result
lda P8ESTACK_HI+1,x
bpl +
jsr neg_w ; make value positive
+ inx
lda P8ESTACK_HI+1,x
bpl +
jsr neg_w ; make value positive
+ lda P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_W1
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
jsr math.divmod_uw_asm
sta P8ESTACK_LO+1,x
tya
sta P8ESTACK_HI+1,x
plp
bpl +
jmp neg_w ; negate result
+ rts
.pend
idiv_uw .proc
inx
lda P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_W1
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
jsr math.divmod_uw_asm
sta P8ESTACK_LO+1,x
tya
sta P8ESTACK_HI+1,x
rts
.pend
remainder_ub .proc
inx
ldy P8ESTACK_LO,x ; right operand
lda P8ESTACK_LO+1,x ; left operand
jsr math.divmod_ub_asm
sta P8ESTACK_LO+1,x
rts
.pend
remainder_uw .proc
inx
lda P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_W1
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
jsr math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
sta P8ESTACK_LO+1,x
lda P8ZP_SCRATCH_W2+1
sta P8ESTACK_HI+1,x
rts
.pend
equal_w .proc
; -- are the two words on the stack identical?
lda P8ESTACK_LO+1,x
cmp P8ESTACK_LO+2,x
bne equal_b._equal_b_false
lda P8ESTACK_HI+1,x
cmp P8ESTACK_HI+2,x
bne equal_b._equal_b_false
beq equal_b._equal_b_true
.pend
notequal_b .proc
; -- are the two bytes on the stack different?
lda P8ESTACK_LO+1,x
cmp P8ESTACK_LO+2,x
beq equal_b._equal_b_false
bne equal_b._equal_b_true
.pend
notequal_w .proc
; -- are the two words on the stack different?
lda P8ESTACK_HI+1,x
cmp P8ESTACK_HI+2,x
beq notequal_b
bne equal_b._equal_b_true
.pend
less_ub .proc
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
bcc equal_b._equal_b_true
bcs equal_b._equal_b_false
.pend
less_b .proc
; see http://www.6502.org/tutorials/compare_beyond.html
lda P8ESTACK_LO+2,x
sec
sbc P8ESTACK_LO+1,x
bvc +
eor #$80
+ bmi equal_b._equal_b_true
bpl equal_b._equal_b_false
.pend
less_uw .proc
lda P8ESTACK_HI+2,x
cmp P8ESTACK_HI+1,x
bcc equal_b._equal_b_true
bne equal_b._equal_b_false
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
bcc equal_b._equal_b_true
bcs equal_b._equal_b_false
.pend
less_w .proc
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
lda P8ESTACK_HI+2,x
sbc P8ESTACK_HI+1,x
bvc +
eor #$80
+ bmi equal_b._equal_b_true
bpl equal_b._equal_b_false
.pend
equal_b .proc
; -- are the two bytes on the stack identical?
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
bne _equal_b_false
_equal_b_true lda #1
_equal_b_store inx
sta P8ESTACK_LO+1,x
rts
_equal_b_false lda #0
beq _equal_b_store
.pend
lesseq_ub .proc
lda P8ESTACK_LO+1,x
cmp P8ESTACK_LO+2,x
bcs equal_b._equal_b_true
bcc equal_b._equal_b_false
.pend
lesseq_b .proc
; see http://www.6502.org/tutorials/compare_beyond.html
lda P8ESTACK_LO+2,x
clc
sbc P8ESTACK_LO+1,x
bvc +
eor #$80
+ bmi equal_b._equal_b_true
bpl equal_b._equal_b_false
.pend
lesseq_uw .proc
lda P8ESTACK_HI+1,x
cmp P8ESTACK_HI+2,x
bcc equal_b._equal_b_false
bne equal_b._equal_b_true
lda P8ESTACK_LO+1,x
cmp P8ESTACK_LO+2,x
bcs equal_b._equal_b_true
bcc equal_b._equal_b_false
.pend
lesseq_w .proc
lda P8ESTACK_LO+1,x
cmp P8ESTACK_LO+2,x
lda P8ESTACK_HI+1,x
sbc P8ESTACK_HI+2,x
bvc +
eor #$80
+ bpl equal_b._equal_b_true
bmi equal_b._equal_b_false
.pend
greater_ub .proc
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
beq equal_b._equal_b_false
bcs equal_b._equal_b_true
bcc equal_b._equal_b_false
.pend
greater_b .proc
; see http://www.6502.org/tutorials/compare_beyond.html
lda P8ESTACK_LO+2,x
clc
sbc P8ESTACK_LO+1,x
bvc +
eor #$80
+ bpl equal_b._equal_b_true
bmi equal_b._equal_b_false
.pend
greater_uw .proc
lda P8ESTACK_HI+1,x
cmp P8ESTACK_HI+2,x
bcc equal_b._equal_b_true
bne equal_b._equal_b_false
lda P8ESTACK_LO+1,x
cmp P8ESTACK_LO+2,x
bcc equal_b._equal_b_true
bcs equal_b._equal_b_false
.pend
greater_w .proc
lda P8ESTACK_LO+1,x
cmp P8ESTACK_LO+2,x
lda P8ESTACK_HI+1,x
sbc P8ESTACK_HI+2,x
bvc +
eor #$80
+ bmi equal_b._equal_b_true
bpl equal_b._equal_b_false
.pend
greatereq_ub .proc
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
bcs equal_b._equal_b_true
bcc equal_b._equal_b_false
.pend
greatereq_b .proc
; see http://www.6502.org/tutorials/compare_beyond.html
lda P8ESTACK_LO+2,x
sec
sbc P8ESTACK_LO+1,x
bvc +
eor #$80
+ bpl equal_b._equal_b_true
bmi equal_b._equal_b_false
.pend
greatereq_uw .proc
lda P8ESTACK_HI+2,x
cmp P8ESTACK_HI+1,x
bcc equal_b._equal_b_false
bne equal_b._equal_b_true
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
bcs equal_b._equal_b_true
bcc equal_b._equal_b_false
.pend
greatereq_w .proc
lda P8ESTACK_LO+2,x
cmp P8ESTACK_LO+1,x
lda P8ESTACK_HI+2,x
sbc P8ESTACK_HI+1,x
bvc +
eor #$80
+ bpl equal_b._equal_b_true
bmi equal_b._equal_b_false
.pend
shiftleft_b .proc
inx
ldy P8ESTACK_LO,x
bne +
rts
+ lda P8ESTACK_LO+1,x
- asl a
dey
bne -
sta P8ESTACK_LO+1,x
rts
.pend
shiftright_b .proc
inx
ldy P8ESTACK_LO,x
bne +
rts
+ lda P8ESTACK_LO+1,x
- lsr a
dey
bne -
sta P8ESTACK_LO+1,x
rts
.pend
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
memcopy16_up .proc
; -- copy memory UP from (P8ZP_SCRATCH_W1) to (P8ZP_SCRATCH_W2) of length X/Y (16-bit, X=lo, Y=hi)
; clobbers register A,X,Y
source = P8ZP_SCRATCH_W1
dest = P8ZP_SCRATCH_W2
length = P8ZP_SCRATCH_B1 ; (and SCRATCH_ZPREG)
stx length
sty length+1
ldx length ; move low byte of length into X
bne + ; jump to start if X > 0
dec length ; subtract 1 from length
+ ldy #0 ; set Y to 0
- lda (source),y ; set A to whatever (source) points to offset by Y
sta (dest),y ; move A to location pointed to by (dest) offset by Y
iny ; increment Y
bne + ; if Y<>0 then (rolled over) then still moving bytes
inc source+1 ; increment hi byte of source
inc dest+1 ; increment hi byte of dest
+ dex ; decrement X (lo byte counter)
bne - ; if X<>0 then move another byte
dec length ; we've moved 255 bytes, dec length
bpl - ; if length is still positive go back and move more
rts ; done
.pend
memset .proc
; -- fill memory from (P8ZP_SCRATCH_W1), length XY, with value in A.
; clobbers X, Y
stx P8ZP_SCRATCH_B1
sty _save_reg
ldy #0
ldx _save_reg
beq _lastpage
_fullpage sta (P8ZP_SCRATCH_W1),y
iny
bne _fullpage
inc P8ZP_SCRATCH_W1+1 ; next page
dex
bne _fullpage
_lastpage ldy P8ZP_SCRATCH_B1
beq +
- dey
sta (P8ZP_SCRATCH_W1),y
bne -
+ rts
_save_reg .byte 0
.pend
memsetw .proc
; -- fill memory from (P8ZP_SCRATCH_W1) number of words in P8ZP_SCRATCH_W2, with word value in AY.
; clobbers A, X, Y
sta _mod1+1 ; self-modify
sty _mod1b+1 ; self-modify
sta _mod2+1 ; self-modify
sty _mod2b+1 ; self-modify
ldx P8ZP_SCRATCH_W1
stx P8ZP_SCRATCH_B1
ldx P8ZP_SCRATCH_W1+1
inx
stx P8ZP_SCRATCH_REG ; second page
ldy #0
ldx P8ZP_SCRATCH_W2+1
beq _lastpage
_fullpage
_mod1 lda #0 ; self-modified
sta (P8ZP_SCRATCH_W1),y ; first page
sta (P8ZP_SCRATCH_B1),y ; second page
iny
_mod1b lda #0 ; self-modified
sta (P8ZP_SCRATCH_W1),y ; first page
sta (P8ZP_SCRATCH_B1),y ; second page
iny
bne _fullpage
inc P8ZP_SCRATCH_W1+1 ; next page pair
inc P8ZP_SCRATCH_W1+1 ; next page pair
inc P8ZP_SCRATCH_B1+1 ; next page pair
inc P8ZP_SCRATCH_B1+1 ; next page pair
dex
bne _fullpage
_lastpage ldx P8ZP_SCRATCH_W2
beq _done
ldy #0
-
_mod2 lda #0 ; self-modified
sta (P8ZP_SCRATCH_W1), y
inc P8ZP_SCRATCH_W1
bne _mod2b
inc P8ZP_SCRATCH_W1+1
_mod2b lda #0 ; self-modified
sta (P8ZP_SCRATCH_W1), y
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ dex
bne -
_done rts
.pend
ror2_mem_ub .proc
; -- in-place 8-bit ror of byte at memory location in AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
lsr a
bcc +
ora #$80
+ sta (P8ZP_SCRATCH_W1),y
rts
.pend
rol2_mem_ub .proc
; -- in-place 8-bit rol of byte at memory location in AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
cmp #$80
rol a
sta (P8ZP_SCRATCH_W1),y
rts
.pend
rol_array_ub .proc
; -- rol a ubyte in an array
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy _arg_index
lda (P8ZP_SCRATCH_W1),y
rol a
sta (P8ZP_SCRATCH_W1),y
rts
_arg_target .word 0
_arg_index .byte 0
.pend
ror_array_ub .proc
; -- ror a ubyte in an array
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy _arg_index
lda (P8ZP_SCRATCH_W1),y
ror a
sta (P8ZP_SCRATCH_W1),y
rts
_arg_target .word 0
_arg_index .byte 0
.pend
ror2_array_ub .proc
; -- ror2 (8-bit ror) a ubyte in an array
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy _arg_index
lda (P8ZP_SCRATCH_W1),y
lsr a
bcc +
ora #$80
+ sta (P8ZP_SCRATCH_W1),y
rts
_arg_target .word 0
_arg_index .byte 0
.pend
rol2_array_ub .proc
; -- rol2 (8-bit rol) a ubyte in an array
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy _arg_index
lda (P8ZP_SCRATCH_W1),y
cmp #$80
rol a
sta (P8ZP_SCRATCH_W1),y
rts
_arg_target .word 0
_arg_index .byte 0
.pend
ror_array_uw .proc
; -- ror a uword in an array
php
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_index
asl a
tay
iny
lda (P8ZP_SCRATCH_W1),y
plp
ror a
sta (P8ZP_SCRATCH_W1),y
dey
lda (P8ZP_SCRATCH_W1),y
ror a
sta (P8ZP_SCRATCH_W1),y
rts
_arg_target .word 0
_arg_index .byte 0
.pend
rol_array_uw .proc
; -- rol a uword in an array
php
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_index
asl a
tay
lda (P8ZP_SCRATCH_W1),y
plp
rol a
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
rol a
sta (P8ZP_SCRATCH_W1),y
rts
_arg_target .word 0
_arg_index .byte 0
.pend
rol2_array_uw .proc
; -- rol2 (16-bit rol) a uword in an array
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_index
asl a
tay
lda (P8ZP_SCRATCH_W1),y
asl a
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
rol a
sta (P8ZP_SCRATCH_W1),y
bcc +
dey
lda (P8ZP_SCRATCH_W1),y
adc #0
sta (P8ZP_SCRATCH_W1),y
+ rts
_arg_target .word 0
_arg_index .byte 0
.pend
ror2_array_uw .proc
; -- ror2 (16-bit ror) a uword in an array
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_index
asl a
tay
iny
lda (P8ZP_SCRATCH_W1),y
lsr a
sta (P8ZP_SCRATCH_W1),y
dey
lda (P8ZP_SCRATCH_W1),y
ror a
sta (P8ZP_SCRATCH_W1),y
bcc +
iny
lda (P8ZP_SCRATCH_W1),y
ora #$80
sta (P8ZP_SCRATCH_W1),y
+ rts
_arg_target .word 0
_arg_index .byte 0
.pend
strcpy .proc
; copy a string (must be 0-terminated) from A/Y to (P8ZP_SCRATCH_W1)
; it is assumed the target string is large enough.
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #$ff
- iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
bne -
rts
.pend
strcmp_expression .proc
; -- compare strings, result in A
lda _arg_s2
ldy _arg_s2+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda _arg_s1
ldy _arg_s1+1
jmp strcmp_mem
_arg_s1 .word 0
_arg_s2 .word 0
.pend
strcmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ lda (P8ZP_SCRATCH_W2),y
sec
sbc (P8ZP_SCRATCH_W1),y
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_return_one
lda #1
_return rts
_return_minusone
lda #-1
rts
.pend
@@ -5,5 +5,6 @@
; indent format: TABS, size=8
prog8_lib {
%asminclude "library:prog8lib.asm", ""
%asminclude "library:prog8_lib.asm", ""
%asminclude "library:prog8_funcs.asm", ""
}
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
4.4
5.0
+3 -2
View File
@@ -38,6 +38,7 @@ private fun compileMain(args: Array<String>) {
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
@@ -62,7 +63,7 @@ private fun compileMain(args: Array<String>) {
println("Continuous watch mode active. Main module: $filepath")
try {
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
@@ -87,7 +88,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
+5 -4
View File
@@ -121,7 +121,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(datatypeString(decl.datatype))
if(decl.arraysize!=null) {
decl.arraysize!!.index.accept(this)
decl.arraysize!!.indexNum?.accept(this)
decl.arraysize!!.indexVar?.accept(this)
}
if(decl.isArray)
output("]")
@@ -142,7 +143,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
val reg =
when {
param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????"
@@ -352,9 +352,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
output("[")
arrayIndexedExpression.arrayspec.index.accept(this)
arrayIndexedExpression.indexer.indexNum?.accept(this)
arrayIndexedExpression.indexer.indexVar?.accept(this)
output("]")
}
+27 -4
View File
@@ -36,6 +36,18 @@ interface Node {
throw FatalAstException("scope missing from $this")
}
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
fun containingStatement(): Statement {
if(this is Statement)
return this
return findParentNode<Statement>(this)!!
}
fun replaceChildNode(node: Node, replacement: Node)
}
@@ -44,6 +56,7 @@ interface IFunctionCall {
var args: MutableList<Expression>
}
interface INameScope {
val name: String
val position: Position
@@ -163,7 +176,6 @@ interface INameScope {
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoVars() = statements.all { it !is VarDecl }
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: Statement) {
@@ -203,6 +215,14 @@ interface INameScope {
else
null
}
fun indexOfChild(stmt: Statement): Int {
val idx = statements.indexOfFirst { it===stmt }
if(idx>=0)
return idx
else
throw FatalAstException("attempt to find a non-child")
}
}
interface IAssignable {
@@ -236,7 +256,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(value) = throw FatalAstException("can't set parent of program")
set(_) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
@@ -263,7 +283,10 @@ class Module(override val name: String,
val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<Module>()
var loadAddress: Int = 0 // can be set with the %address directive
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
address
}
override fun linkParents(parent: Node) {
this.parent = parent
@@ -288,7 +311,7 @@ class Module(override val name: String,
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>()
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {
+26 -13
View File
@@ -254,8 +254,8 @@ private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, false) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
}
@@ -263,13 +263,11 @@ private class AsmSubroutineParameter(name: String,
type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
// TODO implement: val stack: Boolean,
position: Position) : SubroutineParameter(name, type, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
val position: Position)
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
@@ -288,7 +286,7 @@ private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
it.datatype().toAst(),
registerorpair,
statusregister,
!it.stack?.text.isNullOrEmpty(), toPosition())
toPosition())
}
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
@@ -342,14 +340,11 @@ private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
// non-asm subroutine
val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
sub_return_part()?.toAst() ?: emptyList(),
emptyList(),
emptyList(),
emptySet(),
null,
false,
returntypes,
statement_block()?.toAst() ?: mutableListOf(),
toPosition())
}
@@ -470,7 +465,6 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> {
try {
val cc=litval.charliteral()
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
@@ -647,7 +641,20 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
)
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
internal fun escape(str: String): String {
val es = str.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
@@ -661,9 +668,15 @@ internal fun unescape(str: String, position: Position): String {
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
'x' -> {
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
+14 -9
View File
@@ -30,25 +30,30 @@ enum class DataType {
UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType in setOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR
in ArrayDatatypes -> targetType == this
STR -> targetType == STR || targetType == UWORD
in ArrayDatatypes -> targetType == this || targetType == UWORD
else -> false
}
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun isNotAssignableTo(targetType: DataType) = !this.isAssignableTo(targetType)
infix fun isNotAssignableTo(targetTypes: Set<DataType>) = !this.isAssignableTo(targetTypes)
infix fun largerThan(other: DataType) =
when(this) {
in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes
when {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> false
else -> true
}
infix fun equalsSize(other: DataType) =
when(this) {
in ByteDatatypes -> other in ByteDatatypes
in WordDatatypes -> other in WordDatatypes
when {
this == other -> true
this in ByteDatatypes -> other in ByteDatatypes
this in WordDatatypes -> other in WordDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> true
else -> false
}
+40 -8
View File
@@ -3,6 +3,7 @@ package prog8.ast.base
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger
@@ -18,8 +19,8 @@ internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
fixer.applyModifications()
}
internal fun Program.reorderStatements() {
val reorder = StatementReorderer(this)
internal fun Program.reorderStatements(errors: ErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
reorder.applyModifications()
}
@@ -41,12 +42,6 @@ internal fun Module.checkImportedValid() {
imr.applyModifications()
}
internal fun Program.checkRecursion(errors: ErrorReporter) {
val checker = AstRecursionChecker(namespace, errors)
checker.visit(this)
checker.processMessages(name)
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker2 = AstIdentifiersChecker(this, errors)
@@ -56,6 +51,9 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
lit2decl.visit(this)
lit2decl.applyModifications()
}
if (modules.map { it.name }.toSet().size != modules.size) {
@@ -68,3 +66,37 @@ internal fun Program.variousCleanups() {
process.visit(this)
process.applyModifications()
}
internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint()
if(start!=null) {
val mod = start.definingModule()
val block = start.definingBlock()
if(!modules.remove(mod))
throw FatalAstException("module wrong")
modules.add(0, mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
}
}
@@ -41,8 +41,8 @@ sealed class Expression: Node {
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
&& other.indexer isSameAs indexer)
}
is DirectMemoryRead -> {
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
@@ -232,20 +232,19 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
}
}
class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex,
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
val indexer: ArrayIndex,
override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
arrayspec.linkParents(this)
arrayvar.linkParents(this)
indexer.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayspec.index -> arrayspec.index = replacement as Expression
node===arrayvar -> arrayvar = replacement as IdentifierReference
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
@@ -255,10 +254,10 @@ class ArrayIndexedExpression(var identifier: 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) = identifier.referencesIdentifier(*scopedName)
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace)
val target = arrayvar.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
@@ -270,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
}
override fun toString(): String {
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
}
}
@@ -780,6 +779,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
// if it's just a regular variable, return null.
val struct = memberOfStruct(namespace) ?: return null
val decl = targetVarDecl(namespace)!!
if(decl.datatype!=DataType.STRUCT)
return null
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
@@ -822,7 +824,7 @@ class FunctionCall(override var target: IdentifierReference,
val exprfunc = func.constExpressionFunc
if(exprfunc!=null)
resultValue = exprfunc(args, position, program)
else if(func.returntype==null)
else if(func.known_returntype==null)
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
}
@@ -872,6 +874,12 @@ class FunctionCall(override var target: IdentifierReference,
return InferredTypes.void() // no return value
if(stmt.returntypes.size==1)
return InferredTypes.knownFor(stmt.returntypes[0])
// multiple return values. Can occur for asmsub routines. If there is exactly one register return value, take that.
val numRegisterReturns = stmt.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
if(numRegisterReturns==1)
return InferredTypes.InferredType.known(DataType.UBYTE)
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
}
else -> return InferredTypes.unknown()
@@ -35,6 +35,13 @@ object InferredTypes {
}
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
infix fun isAssignableTo(targetDt: InferredType): Boolean =
isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
infix fun isAssignableTo(targetDt: DataType): Boolean =
isKnown && (datatype!! isAssignableTo targetDt)
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
}
private val unknownInstance = InferredType.unknown()
+98 -72
View File
@@ -120,11 +120,6 @@ internal class AstChecker(private val program: Program,
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position)
} else {
fun checkLoopRangeValues() {
}
when (loopvar.datatype) {
DataType.UBYTE -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
@@ -255,19 +250,19 @@ internal class AstChecker(private val program: Program,
err("parameter '${param.first.name}' should be ubyte")
}
}
for(ret in subroutine.returntypes.withIndex().zip(subroutine.asmReturnvaluesRegisters)) {
if(ret.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if (ret.first.value != DataType.UBYTE && ret.first.value != DataType.BYTE)
err("return value #${ret.first.index + 1} should be (u)byte")
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
if(pair.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
err("return value #${index + 1} should be (u)byte")
}
else if(ret.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (ret.first.value != DataType.UWORD && ret.first.value != DataType.WORD
&& ret.first.value != DataType.STR && ret.first.value !in ArrayDatatypes && ret.first.value != DataType.FLOAT)
err("return value #${ret.first.index + 1} should be (u)word/address")
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
err("return value #${index + 1} should be (u)word/address")
}
else if(ret.second.statusflag!=null) {
if (ret.first.value != DataType.UBYTE)
err("return value #${ret.first.index + 1} should be ubyte")
else if(pair.second.statusflag!=null) {
if (pair.first != DataType.UBYTE)
err("return value #${index + 1} should be ubyte")
}
}
@@ -346,17 +341,15 @@ internal class AstChecker(private val program: Program,
}
override fun visit(assignment: Assignment) {
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (stmt is Subroutine && stmt.isAsmSubroutine) {
if(stmt.returntypes.size>1)
errors.err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)
else {
val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
errors.err("return type mismatch", assignment.value.position)
}
if (stmt is Subroutine) {
val idt = assignment.target.inferType(program)
if(!idt.isKnown) {
errors.err("return type mismatch", assignment.value.position)
}
if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
errors.err("return type mismatch", assignment.value.position)
}
}
}
@@ -385,8 +378,9 @@ internal class AstChecker(private val program: Program,
}
}
val targetDt = assignment.target.inferType(program, assignment)
if(assignment.value.inferType(program) != targetDt) {
val targetDt = assignment.target.inferType(program)
val valueDt = assignment.value.inferType(program)
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
errors.err("cannot assign value to string or array", assignment.value.position)
else
@@ -439,19 +433,15 @@ internal class AstChecker(private val program: Program,
if (assignment is Assignment) {
val targetDatatype = assignTarget.inferType(program, assignment)
val targetDatatype = assignTarget.inferType(program)
if (targetDatatype.isKnown) {
val constVal = assignment.value.constValue(program)
if (constVal != null) {
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
} else {
val sourceDatatype = assignment.value.inferType(program)
if (!sourceDatatype.isKnown) {
if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (targetStmt != null)
errors.err("function call doesn't return a suitable value to use in assignment", assignment.value.position)
} else
if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCall)
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
@@ -469,6 +459,7 @@ internal class AstChecker(private val program: Program,
else {
if(variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY
&& variable.struct == null
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
errors.err("invalid pointer-of operand type", addressOf.position)
}
@@ -479,7 +470,7 @@ internal class AstChecker(private val program: Program,
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
err("recursive var declaration")
// CONST can only occur on simple types (byte, word, float)
@@ -602,8 +593,8 @@ internal class AstChecker(private val program: Program,
if(declValue!=null && decl.type==VarDeclType.VAR) {
if(decl.datatype==DataType.STRUCT) {
val valueIdt = declValue.inferType(program)
if(valueIdt.isUnknown)
throw AstException("invalid value type")
if(!valueIdt.isKnown)
throw AstException("unknown dt")
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
if(valueDt !in ArrayDatatypes)
err("initialisation of struct should be with array value", declValue.position)
@@ -632,6 +623,14 @@ internal class AstChecker(private val program: Program,
}
}
// string assignment is not supported in a vard
if(decl.datatype==DataType.STR) {
if(decl.value==null)
err("string var must be initialized with a string literal")
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
err("string var can only be initialized with a string literal")
}
super.visit(decl)
}
@@ -753,8 +752,13 @@ internal class AstChecker(private val program: Program,
return e is StringLiteralValue
}
if(!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal contains invalid types", array.position)
if(array.parent is VarDecl) {
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal for variable initialization contains invalid types", array.position)
} else if(array.parent is ForLoop) {
if (!array.value.all { it.constValue(program) != null })
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
}
super.visit(array)
}
@@ -765,7 +769,11 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: PrefixExpression) {
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
val idt = expr.inferType(program)
if(!idt.isKnown)
return // any error should be reported elsewhere
val dt = idt.typeOrElse(DataType.STRUCT)
if(expr.operator=="-") {
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position)
@@ -783,6 +791,8 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: BinaryExpression) {
super.visit(expr)
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
@@ -822,13 +832,12 @@ internal class AstChecker(private val program: Program,
}
}
if(leftDt !in NumericDatatypes)
errors.err("left operand is not numeric", expr.left.position)
if(rightDt!in NumericDatatypes)
errors.err("right operand is not numeric", expr.right.position)
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
errors.err("left operand is not numeric or str", expr.left.position)
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
errors.err("right operand is not numeric or str", expr.right.position)
if(leftDt!=rightDt)
errors.err("left and right operands aren't the same type", expr.left.position)
super.visit(expr)
}
override fun visit(typecast: TypecastExpression) {
@@ -888,7 +897,29 @@ internal class AstChecker(private val program: Program,
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
if(error!=null)
errors.err(error, functionCall.args.first().position)
errors.err(error, functionCall.position)
// check the functions that return multiple returnvalues.
val stmt = functionCall.target.targetStatement(program.namespace)
if (stmt is Subroutine) {
if (stmt.returntypes.size > 1) {
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
// asmsub routines can have multiple return values, for instance in 2 different registers.
// It's not (yet) possible to handle these multiple return values because assignments
// are only to a single unique target at the same time.
// EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit,
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
val (returnRegisters, returnStatusflags) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.isEmpty() || returnRegisters.size == 1) {
if (returnStatusflags.any())
errors.warn("this asmsub also has one or more return 'values' in one of the status flags", functionCall.position)
} else {
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCall.position)
}
}
}
super.visit(functionCall)
}
@@ -957,8 +988,8 @@ internal class AstChecker(private val program: Program,
if(target.regXasResult())
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
if(target.isAsmSubroutine) {
for (arg in args.withIndex().zip(target.parameters)) {
val argIDt = arg.first.value.inferType(program)
for (arg in args.zip(target.parameters)) {
val argIDt = arg.first.inferType(program)
if (!argIDt.isKnown)
return
}
@@ -981,7 +1012,7 @@ internal class AstChecker(private val program: Program,
}
}
} else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace)
if(target==null) {
errors.err("undefined symbol", postIncrDecr.position)
}
@@ -996,32 +1027,38 @@ internal class AstChecker(private val program: Program,
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace)
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes)
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
val arraysize = target.arraysize?.constIndex()
if(arraysize!=null) {
// check out of bounds
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
val index = arrayIndexedExpression.indexer.constIndex()
if(index!=null && (index<0 || index>=arraysize))
errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position)
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
} else if(target.datatype == DataType.STR) {
if(target.value is StringLiteralValue) {
// check string lengths for non-memory mapped strings
val stringLen = (target.value as StringLiteralValue).value.length
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
val index = arrayIndexedExpression.indexer.constIndex()
if (index != null && (index < 0 || index >= stringLen))
errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position)
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
}
}
} else
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
// check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
if(arrayIndexedExpression.indexer.origExpression!=null)
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
super.visit(arrayIndexedExpression)
}
@@ -1126,10 +1163,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return true
@@ -1148,10 +1182,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return true
@@ -1170,10 +1201,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51)
return err("float array length must be 1-51")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
} else
@@ -1194,7 +1222,7 @@ internal class AstChecker(private val program: Program,
for(elt in value.value.zip(struct.statements)) {
val vardecl = elt.second as VarDecl
val valuetype = elt.first.inferType(program)
if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) {
if (!valuetype.isKnown || valuetype isNotAssignableTo vardecl.datatype) {
errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)
return false
}
@@ -1326,9 +1354,7 @@ internal class AstChecker(private val program: Program,
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
else {
if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes)
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position)
else
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)
}
@@ -80,6 +80,11 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
if(decl.definingBlock().name==decl.name)
nameError(decl.name, decl.position, decl.definingBlock())
if(decl.definingSubroutine()?.name==decl.name)
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
super.visit(decl)
}
@@ -143,8 +148,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(string: StringLiteralValue) {
if (string.value.length !in 1..255)
errors.err("string literal length must be between 1 and 255", string.position)
if (string.value.length > 255)
errors.err("string literal length max is 255", string.position)
super.visit(string)
}
@@ -1,118 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
internal class AstRecursionChecker(private val namespace: INameScope,
private val errors: ErrorReporter) : IAstVisitor {
private val callGraph = DirectedGraph<INameScope>()
fun processMessages(modulename: String) {
val cycle = callGraph.checkForCycle()
if(cycle.isEmpty())
return
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val scope = functionCallStatement.definingScope()
val targetStatement = functionCallStatement.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall) {
val scope = functionCall.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
super.visit(functionCall)
}
private class DirectedGraph<VT> {
private val graph = mutableMapOf<VT, MutableSet<VT>>()
private var uniqueVertices = mutableSetOf<VT>()
val numVertices : Int
get() = uniqueVertices.size
fun add(from: VT, to: VT) {
var targets = graph[from]
if(targets==null) {
targets = mutableSetOf()
graph[from] = targets
}
targets.add(to)
uniqueVertices.add(from)
uniqueVertices.add(to)
}
fun print() {
println("#vertices: $numVertices")
graph.forEach { (from, to) ->
println("$from CALLS:")
to.forEach { println(" $it") }
}
val cycle = checkForCycle()
if(cycle.isNotEmpty()) {
println("CYCLIC! $cycle")
}
}
fun checkForCycle(): MutableList<VT> {
val visited = uniqueVertices.associateWith { false }.toMutableMap()
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
val cycle = mutableListOf<VT>()
for(node in uniqueVertices) {
if(isCyclicUntil(node, visited, recStack, cycle))
return cycle
}
return mutableListOf()
}
private fun isCyclicUntil(node: VT,
visited: MutableMap<VT, Boolean>,
recStack: MutableMap<VT, Boolean>,
cycleNodes: MutableList<VT>): Boolean {
if(recStack[node]==true) return true
if(visited[node]==true) return false
// mark current node as visited and add to recursion stack
visited[node] = true
recStack[node] = true
// recurse for all neighbours
val neighbors = graph[node]
if(neighbors!=null) {
for (neighbour in neighbors) {
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
cycleNodes.add(node)
return true
}
}
}
// pop node from recursion stack
recStack[node] = false
return false
}
}
}
@@ -47,78 +47,59 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
return noModifications
}
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
when {
expr.left is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
parent
))
expr.right is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
parent
))
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftStr = expr.left as? StringLiteralValue
val rightStr = expr.right as? StringLiteralValue
if(expr.operator == "+") {
val concatenatedString = concatString(expr)
if(concatenatedString!=null)
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
}
return noModifications
}
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
)
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast!=array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
else if(expr.operator == "*") {
if (leftStr!=null) {
val amount = expr.right.constValue(program)
if(amount!=null) {
val string = leftStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
}
}
} else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(litval2!=null && litval2!=array) {
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
)
else if (rightStr!=null) {
val amount = expr.right.constValue(program)
if(amount!=null) {
val string = rightStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
}
}
}
return noModifications
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
val rightStrval = expr.right as? StringLiteralValue
val leftStrval = expr.left as? StringLiteralValue
return when {
expr.operator!="+" -> null
expr.left is BinaryExpression && rightStrval!=null -> {
val subStrVal = concatString(expr.left as BinaryExpression)
if(subStrVal==null)
null
else
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
}
expr.right is BinaryExpression && leftStrval!=null -> {
val subStrVal = concatString(expr.right as BinaryExpression)
if(subStrVal==null)
null
else
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
}
leftStrval!=null && rightStrval!=null -> {
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
}
else -> null
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
}
return expr
}
}
+22 -42
View File
@@ -9,78 +9,58 @@ import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: Node) : IAstModification {
class Remove(val node: Node, val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
} else {
throw FatalAstException("parent of a remove modification is not an INameScope")
}
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
}
}
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
override fun perform() {
setter(newExpr)
newExpr.linkParents(parent)
}
}
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(0, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
parent.statements.add(0, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
parent.statements.add(stmt)
stmt.linkParents(parent as Node)
}
}
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
override fun perform() {
parent.replaceChildNode(node, replacement)
replacement.linkParents(parent)
}
}
class SwapOperands(val expr: BinaryExpression): IAstModification {
class SwapOperands(private val expr: BinaryExpression): IAstModification {
override fun perform() {
require(expr.operator in associativeOperators)
val tmp = expr.left
@@ -363,8 +343,8 @@ abstract class AstWalker {
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
}
@@ -125,8 +125,8 @@ interface IAstVisitor {
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayspec.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
arrayIndexedExpression.indexer.accept(this)
}
fun visit(assignTarget: AssignTarget) {
@@ -1,5 +1,6 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
@@ -14,7 +15,7 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent))
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
return noModifications
}
@@ -0,0 +1,53 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope())
)
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast !== array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
}
} else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(litval2!=null) {
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope())
)
}
}
}
return noModifications
}
}
@@ -6,12 +6,12 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class StatementReorderer(val program: Program) : AstWalker() {
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : 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.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every scope, most directives and vardecls are moved to the top.
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
@@ -71,6 +71,69 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is IdentifierReference -> {
arrayIndexedExpression.indexer.indexVar = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is Expression -> {
// replace complex indexing with a temp variable
return getAutoIndexerVarFor(arrayIndexedExpression)
}
else -> return noModifications
}
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val subroutine = expr.definingSubroutine()!!
val statement = expr.containingStatement()
val indexerVarPrefix = "prog8_autovar_index_"
val indexerVarName: String
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
val freeVar = repo.filter { statement !in it.value }.map { it.key }.firstOrNull()
if(freeVar==null) {
// add another loop index var to be used for this expression
val statementId = expr.hashCode()
indexerVarName = "$indexerVarPrefix$statementId"
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
var statements = repo[indexerVarName]
if(statements==null) {
statements = mutableSetOf()
repo[indexerVarName] = statements
}
statements.add(statement)
} else {
// reuse an already created indexer autovar
repo.getValue(freeVar).add(statement)
indexerVarName = freeVar
}
// assign the indexing expression to the helper variable, and replace the indexer with just the variable
val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVarName), indexerExpression.position), null, null, indexerExpression.position)
val assign = Assignment(target, indexerExpression, indexerExpression.position)
val beforeWhat = expr.containingStatement()
modifications.add(IAstModification.InsertBefore(beforeWhat, assign, beforeWhat.definingScope()))
modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null
expr.indexer.origExpression = null
}, target.identifier!!.copy(), expr.indexer))
return modifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
@@ -81,23 +144,57 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
if(decl.datatype!=DataType.FLOAT) {
decl.value = null
decl.allowInitializeWithZero = false
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope())
)
}
}
}
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
val targetType = assignment.target.inferType(program)
var assignments = emptyList<Assignment>()
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
val assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
} else {
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
modifications.add(IAstModification.Remove(assignment, parent))
return modifications
}
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
} else {
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
}
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
val scope = assignment.definingScope()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
modifications.add(IAstModification.Remove(assignment, scope))
return modifications
}
return noModifications
}
@@ -150,15 +247,55 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val alv = assign.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
return emptyList()
}
val alv = sourceVar.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", position)
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
return alv.value.mapIndexed { index, value ->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
}
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements)
throw FatalAstException("element count mismatch")
if(slv==null || slv.value.size != struct.numberOfElements) {
errors.err("element count mismatch", structAssignment.position)
return emptyList()
}
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
@@ -171,7 +308,8 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
}
}
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
// TODO use memcopy beyond a certain number of elements
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
@@ -23,6 +23,11 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
// don't add a typecast on an array initializer value
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
return noModifications
return listOf(IAstModification.ReplaceNode(
declValue,
TypecastExpression(declValue, decl.datatype, true, declValue.position),
@@ -55,12 +60,15 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assignment.value.inferType(program)
val targetItype = assignment.target.inferType(program, assignment)
val targetItype = assignment.target.inferType(program)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications
return listOf(IAstModification.ReplaceNode(
assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
@@ -111,28 +119,30 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val requiredType = arg.first.type
val requiredType = pair.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
call.args[index],
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node)
} else if(arg.second.value is NumericLiteralValue) {
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
if(pair.second is IdentifierReference) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position),
call as Node)
}
} else if(pair.second is NumericLiteralValue) {
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
if(cast.isValid)
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
call.args[index],
cast.valueOrZero(),
call as Node)
}
@@ -142,19 +152,19 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
for (arg in func.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
func.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
call as Node)
break
if (pair.first.possibleDatatypes.all { argtype != it }) {
for (possibleType in pair.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
modifications += IAstModification.ReplaceNode(
call.args[index],
TypecastExpression(pair.second, possibleType, true, pair.second.position),
call as Node)
break
}
}
}
}
@@ -12,7 +12,7 @@ internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent))
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
@@ -4,10 +4,10 @@ import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
@@ -40,10 +40,12 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
}
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val argITypes = call.args.map { it.inferType(program) }
if(argITypes.any { !it.isKnown })
throw FatalAstException("unknown dt")
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
if (target is Subroutine) {
// asmsub types are not checked specifically at this time
if(call.args.size != target.parameters.size)
return "invalid number of arguments"
val paramtypes = target.parameters.map { it.type }
@@ -53,18 +55,28 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
val expected = paramtypes[mismatch].toString()
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {
// multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
}
}
}
}
else if (target is BuiltinFunctionStatementPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size)
return "invalid number of arguments"
val paramtypes = func.parameters.map { it.possibleDatatypes }
for (x in argtypes.zip(paramtypes).withIndex()) {
val anyCompatible = x.value.second.any { argTypeCompatible(x.value.first, it) }
argtypes.zip(paramtypes).forEachIndexed { index, pair ->
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
if (!anyCompatible) {
val actual = x.value.first.toString()
val expected = x.value.second.toString()
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"
val actual = pair.first.toString()
val expected = pair.second.toString()
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
}
}
}
@@ -5,6 +5,7 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.compiler.CompilerException
import prog8.compiler.target.CompilationTarget
@@ -29,12 +30,6 @@ sealed class Statement : Node {
scope.add(name)
return scope.joinToString(".")
}
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}
@@ -49,7 +44,7 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
}
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)
class Block(override val name: String,
val address: Int?,
@@ -176,6 +171,7 @@ open class VarDecl(val type: VarDeclType,
private set
var structHasBeenFlattened = false // set later
private set
var allowInitializeWithZero = true
// prefix for literal values that are turned into a variable on the heap
@@ -190,7 +186,12 @@ open class VarDecl(val type: VarDeclType,
fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
val arrayDt =
if(!array.type.isKnown)
throw FatalAstException("unknown dt")
else
array.type.typeOrElse(DataType.STRUCT)
val declaredType = ArrayElementTypes.getValue(arrayDt)
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
isArray = true, autogeneratedDontRemove = true, position = array.position)
@@ -234,7 +235,8 @@ open class VarDecl(val type: VarDeclType,
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value)
// TODO the check that node===value is too strict sometimes, but leaving it out allows for bugs to creep through ... :( Perhaps check when adding the replace if there is already a replace on the same node?
require(replacement is Expression)
value = replacement
replacement.parent = this
}
@@ -246,12 +248,17 @@ open class VarDecl(val type: VarDeclType,
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
}
fun zeroElementValue() = defaultZero(declaredDatatype, position)
fun zeroElementValue(): NumericLiteralValue {
if(allowInitializeWithZero)
return defaultZero(declaredDatatype, position)
else
throw CompilerException("attempt to get zero value for vardecl that shouldn't get it")
}
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {
val member = it.value as VarDecl
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
val result = struct!!.statements.mapIndexed { index, statement ->
val member = statement as VarDecl
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[index] else null
VarDecl(
VarDeclType.VAR,
member.datatype,
@@ -275,36 +282,72 @@ class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Posit
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
class ArrayIndex(var index: Expression, override val position: Position) : Node {
class ArrayIndex(var origExpression: Expression?, // will be replaced later by either the number or the identifier
override val position: Position) : Node {
// for code simplicity, either indexed via a constant number or via a variable (no arbitrary expressions)
override lateinit var parent: Node
var indexNum: NumericLiteralValue? = origExpression as? NumericLiteralValue
var indexVar: IdentifierReference? = origExpression as? IdentifierReference
init {
if(indexNum!=null || indexVar!=null)
origExpression = null
}
override fun linkParents(parent: Node) {
this.parent = parent
index.linkParents(this)
origExpression?.linkParents(this)
indexNum?.linkParents(this)
indexVar?.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===index)
index = replacement
replacement.parent = this
require(replacement is Expression)
when {
node===origExpression -> origExpression = replacement
node===indexVar -> {
when (replacement) {
is NumericLiteralValue -> {
indexVar = null
indexNum = replacement
}
is IdentifierReference -> {
indexVar = replacement
}
else -> {
throw FatalAstException("invalid replace")
}
}
}
else -> throw FatalAstException("invalid replace")
}
}
companion object {
fun forArray(v: ArrayLiteralValue): ArrayIndex {
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
val indexnum = NumericLiteralValue.optimalNumeric(v.value.size, v.position)
return ArrayIndex(indexnum, v.position)
}
}
fun accept(visitor: IAstVisitor) = index.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
override fun toString(): String {
return("ArrayIndex($index, pos=$position)")
fun accept(visitor: IAstVisitor) {
origExpression?.accept(visitor)
indexNum?.accept(visitor)
indexVar?.accept(visitor)
}
fun accept(visitor: AstWalker, parent: Node) {
origExpression?.accept(visitor, this)
indexNum?.accept(visitor, this)
indexVar?.accept(visitor, this)
}
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
override fun toString(): String {
return("ArrayIndex($indexNum, $indexVar, pos=$position)")
}
infix fun isSameAs(other: ArrayIndex) = index.isSameAs(other.index)
fun constIndex() = indexNum?.number?.toInt()
infix fun isSameAs(other: ArrayIndex) = indexNum==other.indexNum && indexVar == other.indexVar
}
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
@@ -405,20 +448,9 @@ data class AssignTarget(var identifier: IdentifierReference?,
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
companion object {
fun fromExpr(expr: Expression): AssignTarget {
return when (expr) {
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
else -> throw FatalAstException("invalid expression object $expr")
}
}
}
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
fun inferType(program: Program): InferredTypes.InferredType {
if (identifier != null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
val symbol = program.namespace.lookup(identifier!!.nameInSource, this) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
}
@@ -452,8 +484,8 @@ data class AssignTarget(var identifier: IdentifierReference?,
}
identifier != null -> value is IdentifierReference && value.nameInSource == identifier!!.nameInSource
arrayindexed != null -> {
if(value is ArrayIndexedExpression && value.identifier.nameInSource == arrayindexed!!.identifier.nameInSource)
arrayindexed!!.arrayspec isSameAs value.arrayspec
if(value is ArrayIndexedExpression && value.arrayvar.nameInSource == arrayindexed!!.arrayvar.nameInSource)
arrayindexed!!.indexer isSameAs value.indexer
else
false
}
@@ -472,9 +504,9 @@ data class AssignTarget(var identifier: IdentifierReference?,
return addr1 != null && addr2 != null && addr1 == addr2
}
if (this.arrayindexed != null && other.arrayindexed != null) {
if (this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
if (this.arrayindexed!!.arrayvar.nameInSource == other.arrayindexed!!.arrayvar.nameInSource) {
val x1 = this.arrayindexed!!.indexer.constIndex()
val x2 = other.arrayindexed!!.indexer.constIndex()
return x1 != null && x2 != null && x1 == x2
}
}
@@ -499,7 +531,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
}
}
this.arrayindexed != null -> {
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
val targetStmt = this.arrayindexed!!.arrayvar.targetVarDecl(namespace)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
@@ -647,6 +679,18 @@ class NopStatement(override val position: Position): Statement() {
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class AsmGenInfo {
// This class contains various attributes that influence the assembly code generator.
// Conceptually it should be part of any INameScope.
// But because the resulting code only creates "real" scopes on a subroutine level,
// it's more consistent to only define these attributes on a Subroutine node.
var usedAutoArrayIndexerForStatements = mutableMapOf<String, MutableSet<Statement>>()
var usedRegsaveA = false
var usedRegsaveX = false
var usedRegsaveY = false
var usedFloatEvalResultVar = false
}
// the subroutine class covers both the normal user-defined subroutines,
// and also the predefined/ROM/register-based subroutines.
// (multiple return types can only occur for the latter type)
@@ -661,7 +705,24 @@ class Subroutine(override val name: String,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, statements, position)
companion object {
private fun determineReturnRegisters(returntypes: List<DataType>): List<RegisterOrStatusflag> {
// for non-asm subroutines, determine the return registers based on the type of the return value
return when(returntypes.singleOrNull()) {
in ByteDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null))
in WordDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
DataType.FLOAT -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
null -> emptyList()
else -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
}
}
}
override lateinit var parent: Node
val asmGenInfo = AsmGenInfo()
val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) {
@@ -894,11 +955,11 @@ class WhenStatement(var condition: Expression,
if(choice.values==null)
result.add(null to choice)
else {
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
if(values.contains(null))
result.add(null to choice)
else
result.add(values.filterNotNull() to choice)
val values = choice.values!!.map {
val cv = it.constValue(program)
cv?.number?.toInt() ?: it.hashCode() // the hashcode is a nonsensical number but it avoids weird AST validation errors later
}
result.add(values to choice)
}
}
return result
@@ -920,9 +981,15 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===statements)
statements = replacement
replacement.parent = this
val choiceValues = values
if(replacement is AnonymousScope && node===statements) {
statements = replacement
replacement.parent = this
} else if(choiceValues!=null && node in choiceValues) {
throw FatalAstException("cannot replace choice values")
} else {
throw FatalAstException("invalid replacement")
}
}
override fun toString(): String {
@@ -15,15 +15,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
subroutineVariables.add(Pair(decl.name, decl))
subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if(nextAssign!=null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
}
}
return noModifications
}
@@ -46,14 +49,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent),
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
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)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent),
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
@@ -71,7 +74,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
subroutineVariables.addAll(decls.map { Pair(it.name, it) })
subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine()
if (sub != null) {
@@ -124,7 +127,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
return mods
}
@@ -161,11 +164,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
if(typecast.expression is IdentifierReference) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
}
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
+3 -1
View File
@@ -28,7 +28,9 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean)
val noSysInit: Boolean) {
var slowCodegenWarnings = false
}
class CompilerException(message: String?) : Exception(message)
+12 -8
View File
@@ -29,6 +29,7 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
@@ -49,6 +50,7 @@ fun compileProgram(filepath: Path,
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
@@ -107,9 +109,9 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program,
// depending on the machine and compiler options we may have to include some libraries
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
// always import prog8lib and math
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8lib")
importer.importLibraryModule(programAst, "prog8_lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
@@ -120,8 +122,6 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
as? Directive)?.args?.single()?.int ?: 0
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
@@ -173,7 +173,8 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.reorderStatements()
programAst.reorderStatements(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
@@ -189,10 +190,11 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(errors)
val optsDone2 = programAst.splitBinaryExpressions()
val optsDone3 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
if (optsDone1 + optsDone2 == 0)
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
@@ -208,9 +210,11 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.handle()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
+1 -1
View File
@@ -64,7 +64,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
free.removeAll(address until address+size)
allocations[address] = Pair(name ?: "<unnamed>", datatype)
allocations[address] = (name ?: "<unnamed>") to datatype
return address
}
@@ -110,7 +110,7 @@ internal object C64MachineDefinition: IMachineDefinition {
internal 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
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
@@ -126,8 +126,10 @@ internal object C64MachineDefinition: IMachineDefinition {
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
free.addAll(listOf(
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x16, 0x17, 0x18, 0x19, 0x1a,
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
@@ -148,16 +150,16 @@ internal object C64MachineDefinition: IMachineDefinition {
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
))
}
if(options.zeropage!=ZeropageType.DONTUSE) {
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
} else {
// don't use the zeropage at all
free.clear()
@@ -1054,11 +1054,16 @@ object Petscii {
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
return text.map {
val petscii = lookup[it]
petscii?.toShort() ?: if(it=='\u0000')
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
petscii?.toShort() ?: when (it) {
'\u0000' -> 0.toShort()
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(it.toInt() - 0x8000).toShort()
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
}
}
}
}
@@ -1072,11 +1077,16 @@ object Petscii {
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
return text.map{
val screencode = lookup[it]
screencode?.toShort() ?: if(it=='\u0000')
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
screencode?.toShort() ?: when (it) {
'\u0000' -> 0.toShort()
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(it.toInt() - 0x8000).toShort()
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
}
}
}
}
@@ -22,6 +22,7 @@ import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.target.generatedLabelPrefix
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
import java.io.CharConversionException
import java.nio.file.Path
import java.time.LocalDate
import java.time.LocalDateTime
@@ -66,16 +67,21 @@ internal class AsmGen(private val program: Program,
block2asm(b)
footer()
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
if(optimize) {
assemblyLines.clear()
assemblyLines.addAll(outputFile.readLines())
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
}
return AssemblyProgram(program.name, outputDir)
@@ -194,11 +200,10 @@ internal class AsmGen(private val program: Program,
}
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
val variable = IdentifierReference(variableName, decl.position)
variable.linkParents(decl.parent)
val asmName = asmVariableName(variableName)
val asgn = AsmAssignment(
AsmAssignSource.fromAstSource(decl.value!!, program),
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, variable = variable),
AsmAssignSource.fromAstSource(decl.value!!, program, this),
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, decl.definingSubroutine(), variableAsmName = asmName),
false, decl.position)
assignmentAsmGen.translateNormalAssignment(asgn)
}
@@ -227,8 +232,12 @@ internal class AsmGen(private val program: Program,
}
private fun encode(str: String, altEncoding: Boolean): List<Short> {
val bytes = if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return bytes.plus(0)
try {
val bytes = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return bytes.plus(0)
} catch(x: CharConversionException) {
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
}
}
private fun zeropagevars2asm(statements: List<Statement>) {
@@ -249,7 +258,7 @@ internal class AsmGen(private val program: Program,
errors.handle()
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
allocatedZeropageVariables[fullName] = address to variable.datatype
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
@@ -496,8 +505,14 @@ internal class AsmGen(private val program: Program,
}
}
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
internal fun asmVariableName(name: String) = fixNameSymbols(name)
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
// returns if the pointer is already on the ZP itself or not (in which case SCRATCH_W1 is used as intermediary)
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
@@ -538,35 +553,65 @@ internal class AsmGen(private val program: Program,
}
}
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
private val saveRegisterLabels = Stack<String>();
internal fun saveRegister(register: CpuRegister) {
when(register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else out(" stx _prog8_regsave${register.name}")
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine) {
if(dontUseStack) {
when (register) {
CpuRegister.A -> {
out(" sta _prog8_regsaveA")
scope.asmGenInfo.usedRegsaveA = true
}
CpuRegister.X -> {
out(" stx _prog8_regsaveX")
scope.asmGenInfo.usedRegsaveX = true
}
CpuRegister.Y -> {
out(" sty _prog8_regsaveY")
scope.asmGenInfo.usedRegsaveY = true
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else out(" sty _prog8_regsave${register.name}")
} else {
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else {
out(" stx _prog8_regsaveX")
scope.asmGenInfo.usedRegsaveX = true
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else {
out(" sty _prog8_regsaveY")
scope.asmGenInfo.usedRegsaveY = true
}
}
}
}
}
internal fun restoreRegister(register: CpuRegister) {
when(register) {
CpuRegister.A -> out(" pla")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else out(" ldx _prog8_regsave${register.name}")
internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
if(dontUseStack) {
when (register) {
CpuRegister.A -> out(" sta _prog8_regsaveA")
CpuRegister.X -> out(" ldx _prog8_regsaveX")
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else out(" ldy _prog8_regsave${register.name}")
} else {
when (register) {
CpuRegister.A -> out(" pla")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else out(" ldx _prog8_regsaveX")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else out(" ldy _prog8_regsaveY")
}
}
}
}
@@ -588,16 +633,7 @@ internal class AsmGen(private val program: Program,
if (builtinFunc != null) {
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
} else {
functioncallAsmGen.translateFunctionCall(stmt)
// discard any results from the stack:
val sub = stmt.target.targetSubroutine(program.namespace)!!
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((t, reg) in returns) {
if (reg.stack) {
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
else if (t == DataType.FLOAT) out(" inx | inx | inx")
}
}
functioncallAsmGen.translateFunctionCallStatement(stmt)
}
}
is Assignment -> assignmentAsmGen.translate(stmt)
@@ -628,131 +664,75 @@ internal class AsmGen(private val program: Program,
register: CpuRegister,
addOneExtra: Boolean=false) {
val reg = register.toString().toLowerCase()
val index = expr.arrayspec.index
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * elementDt.memorySize() + if(addOneExtra) 1 else 0
val indexnum = expr.indexer.constIndex()
if(indexnum!=null) {
val indexValue = indexnum * elementDt.memorySize() + if(addOneExtra) 1 else 0
out(" ld$reg #$indexValue")
return
}
val indexName = asmVariableName(expr.indexer.indexVar!!)
if(addOneExtra) {
// add 1 to the result
if (index is IdentifierReference) {
val indexName = asmVariableName(index)
when(elementDt) {
in ByteDatatypes -> {
out(" ldy $indexName | iny")
when(register) {
CpuRegister.A -> out(" tya")
CpuRegister.X -> out(" tyx")
CpuRegister.Y -> {}
}
when(elementDt) {
in ByteDatatypes -> {
out(" ldy $indexName | iny")
when(register) {
CpuRegister.A -> out(" tya")
CpuRegister.X -> out(" tyx")
CpuRegister.Y -> {}
}
in WordDatatypes -> {
out(" lda $indexName | sec | rol a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
sec
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
}
else {
expressionsAsmGen.translateExpression(index)
out("""
inc P8ESTACK_LO,x
bne +
inc P8ESTACK_HI,x
+""")
when(register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
in WordDatatypes -> {
out(" lda $indexName | sec | rol a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
sec
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
} else {
if (index is IdentifierReference) {
val indexName = asmVariableName(index)
when(elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in WordDatatypes -> {
out(" lda $indexName | asl a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
clc
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
}
else {
expressionsAsmGen.translateExpression(index)
when(elementDt) {
in ByteDatatypes -> {
when (register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
}
}
in WordDatatypes -> {
out("""
inx
lda P8ESTACK_LO,x
asl a""")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
inx
lda P8ESTACK_LO,x
asl a
asl a
clc
adc P8ESTACK_LO,x""")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
when(elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in WordDatatypes -> {
out(" lda $indexName | asl a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
clc
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
}
}
@@ -760,11 +740,14 @@ internal class AsmGen(private val program: Program,
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack)
internal fun translateFunctionCall(functionCall: FunctionCall, preserveStatusRegisterAfterCall: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCall, preserveStatusRegisterAfterCall)
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)
@@ -806,10 +789,15 @@ internal class AsmGen(private val program: Program,
out("; statements")
sub.statements.forEach{ translate(it) }
out("; variables")
out("""
; register saves
_prog8_regsaveX .byte 0
_prog8_regsaveY .byte 0""") // TODO only generate these bytes if they're actually used by saveRegister()
out("; register saves")
if(sub.asmGenInfo.usedRegsaveA)
out("_prog8_regsaveA .byte 0")
if(sub.asmGenInfo.usedRegsaveX)
out("_prog8_regsaveX .byte 0")
if(sub.asmGenInfo.usedRegsaveY)
out("_prog8_regsaveY .byte 0")
if(sub.asmGenInfo.usedFloatEvalResultVar)
out("_prog8_float_eval_result .byte 0,0,0,0,0")
vardecls2asm(sub.statements)
out(" .pend\n")
}
@@ -914,9 +902,11 @@ _prog8_regsaveY .byte 0""") // TODO only generate these bytes if the
}
}
else -> {
translateExpression(stmt.iterations!!)
val dt = stmt.iterations!!.inferType(program).typeOrElse(DataType.STRUCT)
when (dt) {
translateExpression(stmt.iterations!!) // todo directly into AY?
val dt = stmt.iterations!!.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> {
out(" inx | lda P8ESTACK_LO,x")
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
@@ -934,6 +924,8 @@ _prog8_regsaveY .byte 0""") // TODO only generate these bytes if the
}
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
if(constIterations==0)
return
// note: A/Y must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
out("""
@@ -963,8 +955,12 @@ $counterVar .word 0""")
}
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
if(constIterations==0)
return
// note: A must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
if(constIterations==null)
out(" beq $endLabel")
out("""
sta $counterVar
$repeatLabel""")
@@ -1012,7 +1008,7 @@ $counterVar .byte 0""")
}
private fun translate(stmt: WhenStatement) {
expressionsAsmGen.translateExpression(stmt.condition)
expressionsAsmGen.translateExpression(stmt.condition) // TODO directly into AY?
val endLabel = makeLabel("choice_end")
val choiceBlocks = mutableListOf<Pair<String, AnonymousScope>>()
val conditionDt = stmt.condition.inferType(program)
@@ -1029,7 +1025,7 @@ $counterVar .byte 0""")
translate(choice.statements)
out(" jmp $endLabel")
} else {
choiceBlocks.add(Pair(choiceLabel, choice.statements))
choiceBlocks.add(choiceLabel to choice.statements)
for (cv in choice.values!!) {
val value = (cv as NumericLiteralValue).number.toInt()
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
@@ -1161,7 +1157,44 @@ $counterVar .byte 0""")
}
private fun translate(ret: Return) {
ret.value?.let { expressionsAsmGen.translateExpression(it) }
ret.value?.let { returnvalue ->
val sub = ret.definingSubroutine()!!
val returnType = sub.returntypes.single()
val returnReg = sub.asmReturnvaluesRegisters.single()
val returnValueTarget =
when {
returnReg.registerOrPair!=null -> AsmAssignTarget.fromRegisters(returnReg.registerOrPair, sub, program, this)
else -> throw AssemblyError("normal subroutines can't return value in status register directly")
}
when (returnType) {
in IntegerDatatypes -> {
val src = AsmAssignSource.fromAstSource(returnvalue, program, this)
assignmentAsmGen.translateNormalAssignment(AsmAssignment(src, returnValueTarget, false, ret.position))
}
DataType.FLOAT -> {
// return the float value via FAC1
when (returnvalue) {
is NumericLiteralValue -> throw AssemblyError("float literal should have been changed to auto var")
is IdentifierReference -> {
val asmVar = asmVariableName(returnvalue)
out(" lda #<${asmVar} | ldy #>${asmVar} | jsr floats.MOVFM")
}
else -> {
// todo evaluate directly into fac1 instead of via stack intermediate
translateExpression(returnvalue)
out(" jsr floats.pop_float_fac1")
}
}
}
else -> {
// all else take its address and assign that also to AY register pair
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
val src = AsmAssignSource.fromAstSource(addrofValue, program, this)
assignmentAsmGen.translateNormalAssignment(AsmAssignment(src, returnValueTarget, false, ret.position))
}
}
}
out(" rts")
}
@@ -1169,4 +1202,40 @@ $counterVar .byte 0""")
val assembly = asm.assembly.trimEnd().trimStart('\n')
assemblyLines.add(assembly)
}
internal fun signExtendStackLsb(valueDt: DataType) {
// sign extend signed byte on stack to signed word
when(valueDt) {
DataType.UBYTE -> {
out(" lda #0 | sta P8ESTACK_HI+1,x")
}
DataType.BYTE -> {
out("""
lda P8ESTACK_LO+1,x
ora #$7f
bmi +
lda #0
+ sta P8ESTACK_HI+1,x""")
}
else -> throw AssemblyError("need byte type")
}
}
internal fun signExtendVariableLsb(asmvar: String, valueDt: DataType) {
// sign extend signed byte in a word variable
when(valueDt) {
DataType.UBYTE -> {
out(" lda #0 | sta $asmvar+1")
}
DataType.BYTE -> {
out("""
lda $asmvar+1
ora #$7f
bmi +
lda #0
+ sta $asmvar+1""")
}
else -> throw AssemblyError("need byte type")
}
}
}
@@ -196,8 +196,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val firstLoc = first.substring(4)
val secondLoc = second.substring(4)
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}
File diff suppressed because it is too large. Load diff
@@ -3,9 +3,16 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
import kotlin.math.absoluteValue
@@ -22,7 +29,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is DirectMemoryRead -> translateDirectMemReadExpression(expression, true)
is NumericLiteralValue -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateExpression(expression)
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
}
@@ -50,7 +57,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
val dt = left.inferType(program).typeOrElse(DataType.STRUCT)
val idt = left.inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
when (operator) {
"==" -> {
// if the left operand is an expression, and the right is 0, we can just evaluate that expression.
@@ -64,7 +74,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is AddressOf,
is RangeExpr,
is FunctionCall -> {
translateExpression(left)
translateExpression(left) // todo directly into AY reg?
if(dt in ByteDatatypes) {
asmgen.out("""
inx
@@ -81,6 +91,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
bne $jumpIfFalseLabel""")
return
}
// TODO ....float?
}
else -> {}
}
@@ -90,6 +101,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
in ByteDatatypes -> translateByteEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> translateFloatEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.STR -> translateStringEquals(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@@ -105,7 +117,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is AddressOf,
is RangeExpr,
is FunctionCall -> {
translateExpression(left)
translateExpression(left) // todo directly into AY?
if(dt in ByteDatatypes) {
asmgen.out("""
inx
@@ -122,6 +134,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
beq $jumpIfFalseLabel""")
return
}
// TODO .... .float?
}
else -> {}
}
@@ -131,6 +144,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
in ByteDatatypes -> translateByteNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> translateFloatNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.STR -> translateStringNotEquals(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@@ -141,10 +155,12 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UWORD -> translateUwordLess(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordLess(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> {
// todo via func args
translateExpression(left)
translateExpression(right)
asmgen.out(" jsr floats.less_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringLess(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@@ -155,10 +171,12 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UWORD -> translateUwordLessOrEqual(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordLessOrEqual(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> {
// todo via func args
translateExpression(left)
translateExpression(right)
asmgen.out(" jsr floats.lesseq_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringLessOrEqual(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@@ -169,10 +187,12 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UWORD -> translateUwordGreater(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordGreater(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> {
// todo via func args
translateExpression(left)
translateExpression(right)
asmgen.out(" jsr floats.greater_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringGreater(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@@ -183,10 +203,12 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UWORD -> translateUwordGreaterOrEqual(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordGreaterOrEqual(left, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> {
// todo via func args
translateExpression(left)
translateExpression(right)
asmgen.out(" jsr floats.greatereq_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringGreaterOrEqual(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@@ -223,6 +245,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.less_ub | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -252,6 +275,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.less_b | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -283,6 +307,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.less_uw | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -313,6 +338,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.less_w | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -348,6 +374,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or registers
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greater_ub | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -378,6 +405,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greater_b | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -413,6 +441,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greater_uw | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -449,6 +478,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greater_w | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -491,6 +521,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.lesseq_ub | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -524,6 +555,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.lesseq_b | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -558,6 +590,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.lesseq_uw | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -594,6 +627,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.lesseq_w | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -624,6 +658,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greatereq_ub | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -656,6 +691,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greatereq_b | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -685,6 +721,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greatereq_uw | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -716,6 +753,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.greatereq_w | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -747,6 +785,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// TODO via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.equal_b | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -778,6 +817,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.notequal_b | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -811,6 +851,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.equal_w | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -846,6 +887,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args or regs
asmgen.translateExpression(left)
asmgen.translateExpression(right)
asmgen.out(" jsr prog8_lib.notequal_w | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -891,6 +933,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args
translateExpression(left)
translateExpression(right)
asmgen.out(" jsr floats.equal_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
@@ -937,66 +980,161 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
// todo via func args
translateExpression(left)
translateExpression(right)
asmgen.out(" jsr floats.notequal_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
private fun translateExpression(expression: FunctionCall) {
val functionName = expression.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc)
private fun translateStringEquals(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
cmp #0
bne $jumpIfFalseLabel""")
}
private fun translateStringNotEquals(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
cmp #0
beq $jumpIfFalseLabel""")
}
private fun translateStringLess(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
bpl $jumpIfFalseLabel""")
}
private fun translateStringGreater(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
beq $jumpIfFalseLabel
bmi $jumpIfFalseLabel""")
}
private fun translateStringLessOrEqual(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
beq +
bpl $jumpIfFalseLabel
+""")
}
private fun translateStringGreaterOrEqual(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
beq +
bmi $jumpIfFalseLabel
+""")
}
private fun translateFunctionCallResultOntoStack(expression: FunctionCall) {
// only for use in nested expression evaluation
val sub = expression.target.targetStatement(program.namespace)
if(sub is BuiltinFunctionStatementPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(expression, builtinFunc, true)
} else {
val sub = expression.target.targetSubroutine(program.namespace)!!
asmgen.translateFunctionCall(expression)
sub as Subroutine
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
asmgen.translateFunctionCall(expression, preserveStatusRegisterAfterCall)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
if (!reg.stack) {
// result value in cpu or status registers, put it on the stack
if (reg.registerOrPair != null) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> {
// return value in X register has been discarded, just push a zero
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_LO,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
asmgen.out(" dex")
}
RegisterOrPair.AX -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_HI,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x")
asmgen.out(" dex")
}
RegisterOrPair.XY -> {
// return value in X register has been discarded, just push a zero in this place
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_LO,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
asmgen.out(" tya | sta P8ESTACK_HI,x | dex")
}
// result value in cpu or status registers, put it on the stack
if (reg.registerOrPair != null) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> {
// return value in X register has been discarded, just push a zero
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_LO,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
asmgen.out(" dex")
}
RegisterOrPair.AX -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_HI,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x")
asmgen.out(" dex")
}
RegisterOrPair.XY -> {
// return value in X register has been discarded, just push a zero in this place
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_LO,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
asmgen.out(" tya | sta P8ESTACK_HI,x | dex")
}
}
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
}
else if(reg.statusflag!=null) {
TODO("statusflag result onto stack")
}
}
}
}
private fun translateExpression(expr: TypecastExpression) {
translateExpression(expr.expression)
when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
private fun translateExpression(typecast: TypecastExpression) {
translateExpression(typecast.expression) // todo avoid stack
when(typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when(expr.type) {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
@@ -1010,24 +1148,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
DataType.BYTE -> {
when(expr.type) {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> {
// sign extend
asmgen.out("""
lda P8ESTACK_LO+1,x
ora #$7f
bmi +
lda #0
+ sta P8ESTACK_HI+1,x""")
}
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(expr.type) {
when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
@@ -1036,7 +1166,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
DataType.WORD -> {
when(expr.type) {
when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
@@ -1045,7 +1175,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
DataType.FLOAT -> {
when(expr.type) {
when(typecast.type) {
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
@@ -1055,6 +1185,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
else -> throw AssemblyError("weird type")
}
}
DataType.STR -> {
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
throw AssemblyError("cannot typecast a string into another incompatitble type")
}
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
else -> throw AssemblyError("weird type")
}
@@ -1080,7 +1214,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" sta P8ESTACK_LO,x | dex")
}
else -> {
translateExpression(expr.addressExpression)
translateExpression(expr.addressExpression) // todo directly into A
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack")
if(pushResultOnEstack)
asmgen.out(" sta P8ESTACK_LO+1,x")
@@ -1128,6 +1262,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(expr: BinaryExpression) {
// TODO needs to use optimized assembly generation like the assignment instructions. But avoid code duplication.... rewrite all expressions into assignment form?
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
@@ -1136,7 +1271,108 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
// see if we can apply some optimized routines
// TODO avoid using evaluation on stack everywhere
when(expr.operator) {
"+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt()
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (leftVal!=null && leftVal in -4..4) {
translateExpression(expr.right)
if(rightDt in ByteDatatypes) {
val incdec = if(leftVal<0) "dec" else "inc"
repeat(leftVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(leftVal<0) {
repeat(leftVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(leftVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
else if (rightVal!=null && rightVal in -4..4)
{
translateExpression(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "dec" else "inc"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal<0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
"-" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (rightVal!=null && rightVal in -4..4)
{
translateExpression(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "inc" else "dec"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal>0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
">>" -> {
translateExpression(expr.left)
val amount = expr.right.constValue(program)?.number?.toInt()
@@ -1218,6 +1454,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount==2) {
// optimize x*2 common case
translateExpression(expr.left)
if(leftDt in ByteDatatypes) {
asmgen.out(" asl P8ESTACK_LO+1,x")
} else {
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
}
return
}
when(rightDt) {
DataType.UBYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
@@ -1262,26 +1508,51 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
}
"/" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal!=null && rightVal==2) {
translateExpression(expr.left)
when(leftDt) {
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror P8ESTACK_LO+1,x")
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
DataType.WORD -> asmgen.out(" asl P8ESTACK_HI+1,x | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
else -> throw AssemblyError("wrong dt")
}
return
}
}
}
}
// the general, non-optimized cases
translateExpression(expr.left)
translateExpression(expr.right)
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
translateCompareStrings(expr.left, expr.operator, expr.right)
}
else {
// the general, non-optimized cases TODO optimize more cases....
translateExpression(expr.left)
translateExpression(expr.right)
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
}
}
}
private fun translateExpression(expr: PrefixExpression) {
// todo avoid using stack
translateExpression(expr.expression)
val type = expr.inferType(program).typeOrElse(DataType.STRUCT)
val itype = expr.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.typeOrElse(DataType.STRUCT)
when(expr.operator) {
"+" -> {}
"-" -> {
@@ -1316,11 +1587,13 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
val index = arrayExpr.arrayspec.index
val elementDt = arrayExpr.inferType(program).typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmVariableName(arrayExpr.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * elementDt.memorySize()
val elementIDt = arrayExpr.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
if(arrayExpr.indexer.indexNum!=null) {
val indexValue = arrayExpr.indexer.constIndex()!! * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
@@ -1359,6 +1632,14 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
fun translateExpression(indexer: ArrayIndex) {
// it is either a number, or a variable
val indexNum = indexer.indexNum
val indexVar = indexer.indexVar
indexNum?.let { asmgen.translateExpression(indexNum) }
indexVar?.let { asmgen.translateExpression(indexVar) }
}
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
@@ -1447,4 +1728,45 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
var src = AsmAssignSource.fromAstSource(s1, program, asmgen)
var tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, null, variableAsmName = "prog8_lib.strcmp_expression._arg_s1")
var assign = AsmAssignment(src, tgt, false, Position.DUMMY)
asmgen.translateNormalAssignment(assign)
src = AsmAssignSource.fromAstSource(s2, program, asmgen)
tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, null, variableAsmName = "prog8_lib.strcmp_expression._arg_s2")
assign = AsmAssignment(src, tgt, false, Position.DUMMY)
asmgen.translateNormalAssignment(assign)
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
when(operator) {
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
"<=" -> asmgen.out("""
bpl +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO,x""")
">=" -> asmgen.out("""
bmi +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO,x""")
"<" -> asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x""")
">" -> asmgen.out("""
bpl +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x""")
}
asmgen.out(" dex")
}
}
@@ -13,12 +13,14 @@ import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.toHex
import kotlin.math.absoluteValue
// todo reduce use of stack eval translateExpression()
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown)
throw AssemblyError("can't determine iterable dt")
throw AssemblyError("unknown dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
@@ -610,8 +612,8 @@ $endLabel""")
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) {
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), variable=stmt.loopVar)
val src = AsmAssignSource.fromAstSource(range.from, program).adjustDataTypeToTarget(target)
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine(), variableAsmName=asmgen.asmVariableName(stmt.loopVar))
val src = AsmAssignSource.fromAstSource(range.from, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(src, target, false, range.position)
asmgen.translateNormalAssignment(assign)
}
@@ -1,6 +1,7 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
@@ -13,13 +14,23 @@ import prog8.compiler.target.c64.codegen.assignment.*
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCall(stmt: IFunctionCall) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
if(preserveStatusRegisterAfterCall)
asmgen.out(" plp\t; restore status flags from call")
}
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
if(saveX)
asmgen.saveRegister(CpuRegister.X)
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
@@ -32,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
@@ -57,8 +68,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
asmgen.out(" jsr $subName")
if(preserveStatusRegisterAfterCall) {
asmgen.out(" php\t; save status flags from call")
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
// must take care of popping this value again at the end!
}
if(saveX)
asmgen.restoreRegister(CpuRegister.X)
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
@@ -80,7 +97,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
when {
argi.value.second.stack -> TODO("asmsub @stack parameter")
argi.value.second.statusflag == Statusflag.Pc -> {
require(argForCarry == null)
argForCarry = argi
@@ -143,16 +159,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".")
val identifier = IdentifierReference(scopedParamVar, sub.position)
identifier.linkParents(value.parent)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, sub, variableAsmName = varName)
val source = AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(tgt)
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
asmgen.translateNormalAssignment(asgn)
}
@@ -161,7 +175,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
@@ -169,14 +183,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
@@ -198,7 +213,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.translateExpression(value) // todo directly into A
asmgen.out("""
inx
pha
@@ -216,15 +231,30 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
else -> {
// via register or register pair
val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
val addr = AddressOf(value as IdentifierReference, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target)
} else {
AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target)
val target = AsmAssignTarget.fromRegisters(register!!, sub, program, asmgen)
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
val scratchTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, sub, scratchVar)
val source = AsmAssignSource.fromAstSource(value, program, asmgen)
asmgen.translateNormalAssignment(AsmAssignment(source, scratchTarget, false, value.position))
asmgen.signExtendVariableLsb(scratchVar, valueDt)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, DataType.UWORD, scratchVar)
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
else {
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
}
}
@@ -15,10 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val scope = stmt.definingSubroutine()
when {
targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
@@ -53,7 +54,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
else -> {
asmgen.translateExpression(addressExpr)
asmgen.translateExpression(addressExpr) // todo directly into AY?
asmgen.out("""
inx
lda P8ESTACK_LO,x
@@ -69,67 +70,64 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
when(index) {
is NumericLiteralValue -> {
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
+ dec $asmArrayvarname+$indexValue
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
else -> {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
asmgen.restoreRegister(CpuRegister.X)
else -> throw AssemblyError("need numeric type")
}
}
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope!!)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.restoreRegister(CpuRegister.X, false)
}
}
else -> throw AssemblyError("weird target type ${stmt.target}")
}
}
}
@@ -3,9 +3,7 @@ package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen
@@ -29,10 +27,11 @@ internal enum class SourceStorageKind {
}
internal class AsmAssignTarget(val kind: TargetStorageKind,
program: Program,
asmgen: AsmGen,
private val program: Program,
private val asmgen: AsmGen,
val datatype: DataType,
val variable: IdentifierReference? = null,
val scope: Subroutine?,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryWrite? = null,
val register: RegisterOrPair? = null,
@@ -40,51 +39,51 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val vardecl by lazy { variable!!.targetVarDecl(program.namespace)!! }
val asmVarname by lazy {
if(variable!=null)
asmgen.asmVariableName(variable)
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array!!.identifier)
}
asmgen.asmVariableName(array.arrayvar)
lateinit var origAssign: AsmAssignment
init {
if(variable!=null && vardecl.type == VarDeclType.CONST)
throw AssemblyError("can't assign to a constant")
if(register!=null && datatype !in IntegerDatatypes)
throw AssemblyError("register must be integer type")
}
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this)
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 fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget =
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers)
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers)
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
}
}
}
internal class AsmAssignSource(val kind: SourceStorageKind,
private val program: Program,
private val asmgen: AsmGen,
val datatype: DataType,
val variable: IdentifierReference? = null,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null,
val register: CpuRegister? = null,
@@ -93,53 +92,78 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
companion object {
fun fromAstSource(value: Expression, program: Program): AsmAssignSource {
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
return when {
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
else -> throw AssemblyError("weird indexer")
}
}
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program)
if(cv!=null)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
return when(value) {
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv)
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value)
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value))
}
is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value)
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value)
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
is BuiltinFunctionStatementPlaceholder -> {
val returnType = value.inferType(program)
if(!returnType.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
}
else -> {
throw AssemblyError("weird call")
}
}
}
else -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value)
val dt = value.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
}
}
}
}
fun getAstValue(): Expression = when(kind) {
SourceStorageKind.LITERALNUMBER -> number!!
SourceStorageKind.VARIABLE -> variable!!
SourceStorageKind.ARRAY -> array!!
SourceStorageKind.MEMORY -> memory!!
SourceStorageKind.EXPRESSION -> expression!!
SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node")
SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node")
}
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression)
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource {
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
// allow some signed/unsigned relaxations
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
if(target.datatype!=datatype) {
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
return withAdjustedDt(target.datatype)
@@ -160,6 +184,9 @@ internal class AsmAssignment(val source: AsmAssignSource,
init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype.memorySize() == target.datatype.memorySize()) { "source and target datatype must be same storage class" }
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
require(source.datatype.memorySize() <= target.datatype.memorySize()) {
"source storage size must be less or equal to target datatype storage size"
}
}
}
File diff suppressed because it is too large. Load diff
@@ -3,13 +3,13 @@ package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
import kotlin.math.absoluteValue
internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen,
@@ -19,11 +19,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
require(assign.isAugmentable)
require(assign.source.kind== SourceStorageKind.EXPRESSION)
val value = assign.source.expression!!
when (value) {
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
val type = value.inferType(program).typeOrElse(DataType.STRUCT)
val itype = value.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.typeOrElse(DataType.STRUCT)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign.target, type)
@@ -137,13 +139,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
DataType.FLOAT -> {
when {
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble())
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident)
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(target.asmVarname, operator, value)
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
}
else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value)
else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
@@ -180,7 +182,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
else -> {
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
asmgen.translateExpression(memory.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
val zp = CompilationTarget.instance.machine.zeropage
@@ -199,7 +202,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
TargetStorageKind.ARRAY -> {
println("*** TODO optimize simple inplace array assignment ${target.array} $operator= $value")
if(asmgen.options.slowCodegenWarnings)
println("*** TODO optimize simple inplace array assignment ${target.array} $operator= $value")
assignmentAsmGen.translateNormalAssignment(target.origAssign) // TODO get rid of this fallback for the most common cases here
}
TargetStorageKind.REGISTER -> TODO("reg in-place modification")
@@ -209,9 +213,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun tryRemoveRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) {
val childDt = value.expression.inferType(program).typeOrElse(DataType.STRUCT)
if (value.type.equalsSize(childDt) || value.type.largerThan(childDt)) {
val childIDt = value.expression.inferType(program)
if(!childIDt.isKnown)
throw AssemblyError("unknown dt")
val childDt = childIDt.typeOrElse(DataType.STRUCT)
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
// (works for integer types, not for float.)
inplaceModification(target, operator, value.expression)
return true
}
@@ -220,125 +228,95 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
private fun inplaceModification_byte_value_to_memory(pointervar: IdentifierReference, operator: String, value: Expression) {
println("warning: slow stack evaluation used (3): @(${pointervar.nameInSource.last()}) $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (3): @(${pointervar.nameInSource.last()}) $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) {
// note: ** (power) operator requires floats.
"+" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" clc | adc P8ESTACK_LO+1,x")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
"+" -> asmgen.out(" clc | adc P8ESTACK_LO+1,x")
"-" -> asmgen.out(" sec | sbc P8ESTACK_LO+1,x")
"*" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.multiply_bytes | ldy #0")
"/" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.divmod_ub_asm | tya | ldy #0")
"%" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.divmod_ub_asm | ldy #0")
"<<" -> {
asmgen.out("""
pha
lda P8ESTACK_LO+1,x
bne +
pla
rts
+ tay
pla
- asl a
dey
bne -
+""")
}
"-" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" sec | sbc P8ESTACK_LO+1,x")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"*" -> {
TODO("mul mem byte")// asmgen.out(" jsr prog8_lib.mul_byte")
}
"/" -> TODO("div mem byte")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
TODO("mem byte remainder")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
}
"<<" -> TODO("mem ubyte asl")
">>" -> TODO("mem ubyte lsr")
"&" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" and P8ESTACK_LO+1,x")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"^" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" eor P8ESTACK_LO+1,x")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"|" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" ora P8ESTACK_LO+1,x")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
">>" -> {
asmgen.out("""
pha
lda P8ESTACK_LO+1,x
bne +
pla
rts
+ tay
pla
- lsr a
dey
bne -
+""")
}
"&" -> asmgen.out(" and P8ESTACK_LO+1,x")
"^" -> asmgen.out(" eor P8ESTACK_LO+1,x")
"|" -> asmgen.out(" ora P8ESTACK_LO+1,x")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
asmgen.out(" inx")
}
private fun inplaceModification_byte_variable_to_memory(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
val otherName = asmgen.asmVariableName(value)
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) {
// note: ** (power) operator requires floats.
"+" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" clc | adc $otherName")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
"+" -> asmgen.out(" clc | adc $otherName")
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes | ldy #0")
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya | ldy #0")
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | ldy #0")
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl a
dey
bne -
+""")
}
"-" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" sec | sbc $otherName")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"*" -> {
TODO("mem mul")// asmgen.out(" jsr prog8_lib.mul_byte")
}
"/" -> TODO("mem div")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
TODO("mem byte remainder")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
}
"<<" -> TODO("mem ubyte asl")
">>" -> TODO("mem ubyte lsr")
"&" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" and $otherName")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"^" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" eor $otherName")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"|" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" ora $otherName")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
">>" -> {
asmgen.out("""
ldy $otherName
beq +
- lsr a
dey
bne -
+""")
}
"&" -> asmgen.out(" and $otherName")
"^" -> asmgen.out(" eor $otherName")
"|" -> asmgen.out(" ora $otherName")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
private fun inplaceModification_byte_litval_to_memory(pointervar: IdentifierReference, operator: String, value: Int) {
@@ -361,26 +339,35 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"*" -> {
if(value in asmgen.optimizedByteMultiplications) {
TODO("optimized mem mul ubyte litval $value")
} else {
TODO("mem mul ubyte litval $value")
// asmgen.out(" jsr prog8_lib.mul_byte")
}
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
if(value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr math.mul_byte_${value}")
else
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"/" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
TODO("mem div byte litval")
// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"%" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
TODO("mem byte remainder litval")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"<<" -> {
if (value > 0) {
@@ -433,28 +420,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
println("warning: slow stack evaluation used (5): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (5): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
when (operator) {
// note: ** (power) operator requires floats.
"+" -> asmgen.out(" lda $name | clc | adc P8ESTACK_LO+1,x | sta $name")
"-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name")
"*" -> {
TODO("var mul byte expr")
// check optimizedByteMultiplications
// asmgen.out(" jsr prog8_lib.mul_byte")
}
"*" -> asmgen.out(" lda P8ESTACK_LO+1,x | ldy $name | jsr math.multiply_bytes | sta $name")
"/" -> {
TODO("var div byte expr")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
if(dt==DataType.UBYTE)
asmgen.out(" lda P8ESTACK_LO+1,x | tay | lda $name | jsr math.divmod_ub_asm | sty $name")
else
asmgen.out(" lda P8ESTACK_LO+1,x | tay | lda $name | jsr math.divmod_b_asm | sty $name")
}
"%" -> {
TODO("var byte remainder expr")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
if(dt==DataType.BYTE)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" lda P8ESTACK_LO+1,x | tay | lda $name | jsr math.divmod_ub_asm | sta $name")
}
"<<" -> {
asmgen.translateExpression(value)
asmgen.translateExpression(value) // todo directly into Y
asmgen.out("""
inx
ldy P8ESTACK_LO,x
@@ -465,7 +451,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
}
">>" -> {
asmgen.translateExpression(value)
asmgen.translateExpression(value) // todo directly into Y
if(dt==DataType.UBYTE) {
asmgen.out("""
inx
@@ -519,25 +505,31 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl $name
dey
bne -""")
bne -
+""")
}
">>" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
ldy $otherName
beq +
- lsr $name
dey
bne -""")
bne -
+""")
} else {
asmgen.out("""
ldy $otherName
beq +
- lda $name
asl a
ror $name
dey
bne -""")
bne -
+""")
}
}
"&" -> asmgen.out(" lda $name | and $otherName | sta $name")
@@ -571,33 +563,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"+" -> asmgen.out(" lda $name | clc | adc #$value | sta $name")
"-" -> asmgen.out(" lda $name | sec | sbc #$value | sta $name")
"*" -> {
if(dt == DataType.UBYTE) {
if(value in asmgen.optimizedByteMultiplications) {
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
} else {
TODO("var mul ubyte litval $value")
// asmgen.out(" jsr prog8_lib.mul_byte")
}
} else {
if(value.absoluteValue in asmgen.optimizedByteMultiplications) {
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
} else {
TODO("var mul sbyte litval $value")
// asmgen.out(" jsr prog8_lib.mul_byte")
}
}
if(value in asmgen.optimizedByteMultiplications)
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
else
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
}
"/" -> {
if (dt == DataType.UBYTE) {
asmgen.out("""
lda $name
ldy #$value
jsr math.divmod_ub_asm
sty $name
""")
} else {
TODO("var BYTE div litval")
}
if (dt == DataType.UBYTE)
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
else
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_b_asm | sty $name")
}
"%" -> {
if(dt==DataType.BYTE)
@@ -609,14 +584,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta $name""")
}
"<<" -> {
repeat(value) { asmgen.out(" asl $name") }
if(value>=8) asmgen.out(" lda #0 | sta $name")
else repeat(value) { asmgen.out(" asl $name") }
}
">>" -> {
if(value>0) {
if (dt == DataType.UBYTE) {
repeat(value) { asmgen.out(" lsr $name") }
if(value>=8) asmgen.out(" lda #0 | sta $name")
else repeat(value) { asmgen.out(" lsr $name") }
} else {
repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
when {
value>=8 -> asmgen.out("""
lda $name
bmi +
lda #0
beq ++
+ lda #-1
+ sta $name""")
value>3 -> asmgen.out("""
lda $name
ldy #$value
jsr math.lsr_byte_A
sta $name""")
else -> repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
}
}
}
}
@@ -644,10 +635,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sec
sbc P8ZP_SCRATCH_B1
sta $name""")
// TODO: more operators
// TODO: tuned code for more operators
}
else -> {
inplaceModification_byte_value_to_variable(name, dt, operator, memread);
inplaceModification_byte_value_to_variable(name, dt, operator, memread)
}
}
}
@@ -675,10 +666,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
bcc +
dec $name+1
+""")
// TODO: more operators
// TODO: tuned code for more operators
}
else -> {
inplaceModification_word_value_to_variable(name, dt, operator, memread);
inplaceModification_word_value_to_variable(name, dt, operator, memread)
}
}
}
@@ -686,7 +677,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_word_litval_to_variable(name: String, dt: DataType, operator: String, value: Int) {
when (operator) {
// note: ** (power) operator requires floats.
// TODO use these + and - optimizations in the expressionAsmGenerator as well.
"+" -> {
when {
value==0 -> {}
@@ -701,6 +691,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
value==0x0100 -> asmgen.out(" inc $name+1")
value==0x0200 -> asmgen.out(" inc $name+1 | inc $name+1")
value==0x0300 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1")
value==0x0400 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1 | inc $name+1")
value and 255==0 -> asmgen.out(" lda $name+1 | clc | adc #>$value | sta $name+1")
else -> asmgen.out("""
lda $name
@@ -726,6 +717,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
value==0x0100 -> asmgen.out(" dec $name+1")
value==0x0200 -> asmgen.out(" dec $name+1 | dec $name+1")
value==0x0300 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1")
value==0x0400 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1 | dec $name+1")
value and 255==0 -> asmgen.out(" lda $name+1 | sec | sbc #>$value | sta $name+1")
else -> asmgen.out("""
lda $name
@@ -738,41 +730,22 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
"*" -> {
if(dt == DataType.UWORD){
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
// the mul code works for both signed and unsigned
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
if(value.absoluteValue in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
// TODO does this work for signed words? if so the uword/word distinction can be removed altogether
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
}
"/" -> {
@@ -825,14 +798,63 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
}
"<<" -> {
repeat(value) { asmgen.out(" asl $name | rol $name+1") }
when {
value>=16 -> asmgen.out(" lda #0 | sta $name | sta $name+1")
value==8 -> asmgen.out(" lda $name | sta $name+1 | lda #0 | sta $name")
value>2 -> asmgen.out("""
ldy #$value
- asl $name
rol $name+1
dey
bne -
""")
else -> repeat(value) { asmgen.out(" asl $name | rol $name+1") }
}
}
">>" -> {
if (value > 0) {
if(dt==DataType.UWORD) {
repeat(value) { asmgen.out(" lsr $name+1 | ror $name")}
when {
value>=16 -> asmgen.out(" lda #0 | sta $name | sta $name+1")
value==8 -> asmgen.out(" lda $name+1 | sta $name | lda #0 | sta $name+1")
value>2 -> asmgen.out("""
ldy #$value
- lsr $name+1
ror $name
dey
bne -""")
else -> repeat(value) { asmgen.out(" lsr $name+1 | ror $name")}
}
} else {
repeat(value) { asmgen.out(" lda $name+1 | asl a | ror $name+1 | ror $name") }
when {
value>=16 -> asmgen.out("""
lda $name+1
bmi +
lda #0
beq ++
+ lda #-1
+ sta $name
sta $name+1""")
value==8 -> asmgen.out("""
lda $name+1
sta $name
bmi +
lda #0
- sta $name+1
beq ++
+ lda #-1
sta $name+1
+""")
value>2 -> asmgen.out("""
ldy #$value
- lda $name+1
asl a
ror $name+1
ror $name
dey
bne -""")
else -> repeat(value) { asmgen.out(" lda $name+1 | asl a | ror $name+1 | ror $name") }
}
}
}
}
@@ -956,33 +978,43 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl $name
rol $name+1
dey
bne -""")
bne -
+""")
}
">>" -> {
if(dt==DataType.UWORD) {
asmgen.out("""
ldy $otherName
beq +
- lsr $name+1
ror $name
dey
bne -""")
bne -
+""")
} else {
asmgen.out("""
ldy $otherName
beq +
- lda $name+1
asl a
ror $name+1
ror $name
dey
bne -""")
bne -
+""")
}
}
"&" -> TODO("bitand (u)wordvar bytevar")
"^" -> TODO("bitxor (u)wordvar bytevar")
"|" -> TODO("bitor (u)wordvar bytevar")
"&" -> {
asmgen.out(" lda $otherName | and $name | sta $name")
if(dt in WordDatatypes)
asmgen.out(" lda #0 | sta $name+1")
}
"^" -> asmgen.out(" lda $otherName | eor $name | sta $name")
"|" -> asmgen.out(" lda $otherName | ora $name | sta $name")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@@ -1068,9 +1100,76 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
println("warning: slow stack evaluation used (4): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (4): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
val valueiDt = value.inferType(program)
if(!valueiDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueiDt.typeOrElse(DataType.STRUCT)
fun multiplyWord() {
asmgen.out("""
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1
""")
}
fun divideWord() {
if (dt == DataType.WORD) {
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.divmod_w_asm
sta $name
sty $name+1
""")
} else {
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.divmod_uw_asm
sta $name
sty $name+1
""")
}
}
fun remainderWord() {
if(dt==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
sta $name
lda P8ZP_SCRATCH_W2+1
sta $name+1
""")
}
when(valueDt) {
in ByteDatatypes -> {
@@ -1125,27 +1224,35 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc P8ZP_SCRATCH_B1
sta $name+1""")
}
"*" -> TODO("mul (u)word (u)byte")
"/" -> TODO("div (u)word (u)byte")
"%" -> TODO("(u)word remainder (u)byte")
"*" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
asmgen.signExtendStackLsb(valueDt)
multiplyWord()
}
"/" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
asmgen.signExtendStackLsb(valueDt)
divideWord()
}
"%" -> {
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
asmgen.signExtendStackLsb(valueDt)
remainderWord()
}
"<<" -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
ldy P8ESTACK_LO,x
beq +
- asl $name
rol $name+1
dey
bne -
ldy P8ESTACK_LO+1,x
beq +
- asl $name
rol $name+1
dey
bne -
+""")
}
">>" -> {
asmgen.translateExpression(value)
if(dt==DataType.UWORD) {
asmgen.out("""
inx
ldy P8ESTACK_LO,x
ldy P8ESTACK_LO+1,x
beq +
- lsr $name+1
ror $name
@@ -1154,8 +1261,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""") }
else {
asmgen.out("""
inx
ldy P8ESTACK_LO,x
ldy P8ESTACK_LO+1,x
beq +
- lda $name+1
asl a
@@ -1166,9 +1272,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
}
}
"&" -> TODO("bitand (u)word (u)byte")
"^" -> TODO("bitxor (u)word (u)byte")
"|" -> TODO("bitor (u)word (u)byte")
"&" -> {
asmgen.out(" lda P8ESTACK_LO+1,x | and $name | sta $name")
if(dt in WordDatatypes)
asmgen.out(" lda #0 | sta $name+1")
}
"^" -> asmgen.out(" lda P8ESTACK_LO+1,x | eor $name | sta $name")
"|" -> asmgen.out(" lda P8ESTACK_LO+1,x | ora $name | sta $name")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@@ -1178,65 +1288,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// note: ** (power) operator requires floats.
"+" -> asmgen.out(" lda $name | clc | adc P8ESTACK_LO+1,x | sta $name | lda $name+1 | adc P8ESTACK_HI+1,x | sta $name+1")
"-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name | lda $name+1 | sbc P8ESTACK_HI+1,x | sta $name+1")
"*" -> {
asmgen.out("""
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1
""")
}
"/" -> {
if (dt == DataType.WORD) {
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.divmod_w_asm
sta $name
sty $name+1
""")
} else {
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.divmod_uw_asm
sta $name
sty $name+1
""")
}
}
"%" -> {
if(dt==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
sta $name
lda P8ZP_SCRATCH_W2+1
sta $name+1
""")
}
"*" -> multiplyWord()
"/" -> divideWord()
"%" -> remainderWord()
"<<", ">>" -> throw AssemblyError("shift by a word value not supported, max is a byte")
"&" -> asmgen.out(" lda $name | and P8ESTACK_LO+1,x | sta $name | lda $name+1 | and P8ESTACK_HI+1,x | sta $name+1")
"^" -> asmgen.out(" lda $name | eor P8ESTACK_LO+1,x | sta $name | lda $name+1 | eor P8ESTACK_HI+1,x | sta $name+1")
@@ -1252,13 +1306,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" inx")
}
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression) {
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
asmgen.out(" jsr floats.pop_float_fac1")
asmgen.saveRegister(CpuRegister.X)
asmgen.saveRegister(CpuRegister.X, false, scope)
when (operator) {
"**" -> {
asmgen.out("""
@@ -1303,16 +1358,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X)
asmgen.restoreRegister(CpuRegister.X, false)
}
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference) {
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) {
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype
if(valueDt != DataType.FLOAT)
throw AssemblyError("float variable expected")
val otherName = asmgen.asmVariableName(ident)
asmgen.saveRegister(CpuRegister.X)
asmgen.saveRegister(CpuRegister.X, false, scope)
when (operator) {
"**" -> {
asmgen.out("""
@@ -1372,12 +1427,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X)
asmgen.restoreRegister(CpuRegister.X, false)
}
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double) {
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) {
val constValueName = asmgen.getFloatAsmConst(value)
asmgen.saveRegister(CpuRegister.X)
asmgen.saveRegister(CpuRegister.X, false, scope)
when (operator) {
"**" -> {
asmgen.out("""
@@ -1444,7 +1499,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X)
asmgen.restoreRegister(CpuRegister.X, false)
}
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {
@@ -1537,7 +1592,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
println("warning: slow stack evaluation used (6): ${mem.addressExpression::class.simpleName} at ${mem.addressExpression.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (6): ${mem.addressExpression::class.simpleName} at ${mem.addressExpression.position}") // TODO
asmgen.translateExpression(mem.addressExpression)
asmgen.out("""
jsr prog8_lib.read_byte_from_address_on_stack
@@ -1606,7 +1662,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
println("warning: slow stack evaluation used (7): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (7): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO
asmgen.translateExpression(memory.addressExpression)
asmgen.out("""
jsr prog8_lib.read_byte_from_address_on_stack
@@ -75,7 +75,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x79 // temp storage for a single byte
override val SCRATCH_REG = 0x7a // temp storage for a register
override val SCRATCH_REG = 0x7a // 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
+142 -72
View File
@@ -15,95 +15,165 @@ class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
class FSignature(val pure: Boolean, // does it have side effects?
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
override fun toString(): String {
val paramConvs = params.mapIndexed { index, it ->
when {
it.reg!=null -> "$index:${it.reg}"
it.variable -> "$index:variable"
else -> "$index:???"
}
}
val returnConv =
when {
returns.reg!=null -> returns.reg.toString()
returns.floatFac1 -> "floatFAC1"
else -> "<no returnvalue>"
}
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
}
}
class FSignature(val name: String,
val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
val known_returntype: DataType?, // specify return type if fixed, otherwise null if it depends on the arguments
val constExpressionFunc: ConstExpressionCaller? = null) {
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
val returns = when(known_returntype) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(known_returntype, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(known_returntype, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(known_returntype, null, true)
in PassByReferenceDatatypes -> ReturnConvention(known_returntype!!, RegisterOrPair.AY, false)
else -> {
val paramType = actualParamTypes.first()
if(pure)
// return type depends on arg type
when(paramType) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(paramType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
else -> ReturnConvention(paramType, null, false)
}
else {
ReturnConvention(paramType, null, false)
}
}
}
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// one parameter? via register/registerpair
val paramConv = when(val paramType = actualParamTypes[0]) {
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
}
CallConvention(listOf(paramConv), returns)
}
else -> {
// multiple parameters? via variables
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
CallConvention(paramConvs, returns)
}
}
}
}
val BuiltinFunctions = mapOf(
private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place:
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s):
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
// normal functions follow:
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FSignature(true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
"rsave" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"set_carry" to FSignature(false, emptyList(), null),
"clear_carry" to FSignature(false, emptyList(), null),
"set_irqd" to FSignature(false, emptyList(), null),
"clear_irqd" to FSignature(false, emptyList(), null),
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
"memcopy" to FSignature(false, listOf(
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("rnd" , true, emptyList(), DataType.UBYTE),
FSignature("rndw" , true, emptyList(), DataType.UWORD),
FSignature("rndf" , true, emptyList(), DataType.FLOAT),
FSignature("exit" , false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
FSignature("rsave" , false, emptyList(), null),
FSignature("rrestore" , false, emptyList(), null),
FSignature("set_carry" , false, emptyList(), null),
FSignature("clear_carry" , false, emptyList(), null),
FSignature("set_irqd" , false, emptyList(), null),
FSignature("clear_irqd" , false, emptyList(), null),
FSignature("read_flags" , false, emptyList(), DataType.UBYTE),
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
FSignature("memcopy" , false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FSignature(false, listOf(
FParam("numbytes", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("memset" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FSignature(false, listOf(
FSignature("memsetw" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
"substr" to FSignature(false, listOf(
FSignature("strlen" , true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
FSignature("substr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
"leftstr" to FSignature(false, listOf(
FSignature("leftstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
"rightstr" to FSignature(false, listOf(
FSignature("rightstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null)
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("strcmp" , false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
)
val BuiltinFunctions = functionSignatures.associateBy { it.name }
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
@@ -143,17 +213,17 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
}
val func = BuiltinFunctions.getValue(function)
if(func.returntype!=null)
return InferredTypes.knownFor(func.returntype)
// function has return values, but the return type depends on the arguments
if(func.known_returntype!=null)
return InferredTypes.knownFor(func.known_returntype)
// function has return values, but the return type depends on the arguments
return when (function) {
"abs" -> {
val dt = args.single().inferType(program)
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return dt
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
dt
else
throw FatalAstException("weird datatype passed to abs $dt")
InferredTypes.InferredType.unknown()
}
"max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) {
@@ -285,9 +355,9 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
if(vardecl!=null) {
if(vardecl.datatype!=DataType.STR)
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
throw SyntaxError("strlen must have string argument", position)
if(vardecl.autogeneratedDontRemove) {
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
}
}
@@ -0,0 +1,87 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
internal class BinExprSplitter(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
// if(decl.type==VarDeclType.VAR ) {
// val binExpr = decl.value as? BinaryExpression
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
// val assign = Assignment(target, augExpr, binExpr.position)
// println("SPLIT VARDECL $decl")
// return listOf(
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
// IAstModification.InsertAfter(decl, assign, parent)
// )
// }
// }
// return noModifications
// }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN expression ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
X RightExpr.
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also service
// the expressiontrees that are not used in an assignment statement...
}
return noModifications
}
private fun isSimpleExpression(expr: Expression) =
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) =
if (target.identifier!=null || target.memoryAddress!=null || target.arrayindexed!=null)
target.isInRegularRAM(namespace)
else
false
}
+67 -12
View File
@@ -1,11 +1,10 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
@@ -25,7 +24,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
@@ -79,8 +78,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
}
}
super.visit(directive)
@@ -167,12 +168,12 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope()
val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
@@ -180,13 +181,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node2)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
@@ -199,7 +202,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
@@ -208,4 +212,55 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
}
}
fun checkRecursiveCalls(errors: ErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
val printed = mutableSetOf<Subroutine>()
for(chain in cycles) {
if(chain[0] !in printed) {
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
printed.add(chain[0])
}
}
}
}
private fun recursionCycles(): List<List<Subroutine>> {
val chains = mutableListOf<MutableList<Subroutine>>()
for(caller in calls.keys) {
val visited = calls.keys.associateWith { false }.toMutableMap()
val recStack = calls.keys.associateWith { false }.toMutableMap()
val chain = mutableListOf<Subroutine>()
if(hasCycle(caller, visited, recStack, chain))
chains.add(chain)
}
return chains
}
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
// mark current node as visited and add to recursion stack
if(recStack[sub]==true)
return true
if(visited[sub]==true)
return false
// mark visited and add to recursion stack
visited[sub] = true
recStack[sub] = true
// recurse for all neighbours
for(called in calls.getValue(sub)) {
if(hasCycle(called, visited, recStack, chain)) {
chain.add(called)
return true
}
}
// pop from recursion stack
recStack[sub] = false
return false
}
}
@@ -13,7 +13,7 @@ import prog8.ast.statements.VarDecl
import prog8.compiler.target.CompilationTarget
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
internal class VarConstantValueTypeAdjuster(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@@ -58,15 +58,16 @@ 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?.index?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
if(decl.isArray){
if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arraysize = decl.arraysize
if(arraysize==null) {
// for arrays that have no size specifier attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) {
return listOf(IAstModification.SetExpression(
@@ -75,14 +76,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
decl
))
}
}
else if(decl.arraysize?.constIndex()==null) {
val size = decl.arraysize!!.index.constValue(program)
if(size!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
size, decl
))
} else if(arraysize.constIndex()==null) {
// see if we can calculate the size from other fields
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
if(cval!=null) {
arraysize.indexVar = null
arraysize.origExpression = null
arraysize.indexNum = cval
}
}
}
@@ -1,15 +1,11 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
@@ -279,80 +275,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return noModifications
}
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
// if(decl.type==VarDeclType.VAR ) {
// val binExpr = decl.value as? BinaryExpression
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
// val assign = Assignment(target, augExpr, binExpr.position)
// println("SPLIT VARDECL $decl")
// return listOf(
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
// IAstModification.InsertAfter(decl, assign, parent)
// )
// }
// }
// return noModifications
// }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps:
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN LEFTEXPR ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
/ \ / \ X RightExpr.
.. .. .. ..
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
if (!assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.InsertBefore(assignment, firstAssign, parent),
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also service
// the expressiontrees that are not used in an assignment statement...
}
return noModifications
}
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope): Boolean {
return when {
target.identifier!=null -> target.isInRegularRAM(namespace)
target.memoryAddress!=null -> target.isInRegularRAM(namespace)
target.arrayindexed!=null -> {
val index = target.arrayindexed!!.arrayspec.index
if(index is NumericLiteralValue)
target.isInRegularRAM(namespace)
else
false
}
else -> false
}
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource == listOf("lsb")) {
val arg = functionCall.args[0]
@@ -429,6 +351,13 @@ X = BinExpr X = LeftExpr
}
// no need to check for left val constant (because of associativity)
val rnum = rightVal?.number?.toDouble()
if(rnum!=null && rnum<0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
return expr
}
return null
}
@@ -443,12 +372,16 @@ X = BinExpr X = LeftExpr
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
0.0 -> {
// left
return expr.left
}
val rnum = rightVal.number.toDouble()
if (rnum == 0.0) {
// left
return expr.left
}
if(rnum<0.0) {
expr.operator = "+"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
return expr
}
}
if (leftVal != null) {
@@ -461,6 +394,7 @@ X = BinExpr X = LeftExpr
}
}
return null
}
@@ -543,7 +477,10 @@ X = BinExpr X = LeftExpr
when (expr.operator) {
"%" -> {
if (cv == 1.0) {
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
val idt = expr.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
@@ -672,8 +609,10 @@ X = BinExpr X = LeftExpr
if (amount == 0) {
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when (targetDt) {
val targetIDt = expr.left.inferType(program)
if(!targetIDt.isKnown)
throw FatalAstException("unknown dt")
when (val targetDt = targetIDt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
@@ -683,7 +622,7 @@ X = BinExpr X = LeftExpr
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
} else if (amount >= 8) {
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
}
@@ -705,7 +644,10 @@ X = BinExpr X = LeftExpr
if (amount == 0) {
return expr.left
}
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
val idt = expr.left.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
when (idt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position)
@@ -722,8 +664,9 @@ X = BinExpr X = LeftExpr
return NumericLiteralValue.optimalInteger(0, expr.position)
} else if (amount >= 8) {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8)
return msb
if (amount == 8) {
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
@@ -731,14 +674,6 @@ X = BinExpr X = LeftExpr
if (amount > 16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return null
} else if (amount >= 8) {
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if (amount == 8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
else -> {
+7 -1
View File
@@ -5,7 +5,7 @@ import prog8.ast.base.ErrorReporter
internal fun Program.constantFold(errors: ErrorReporter) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
val valuetypefixer = VarConstantValueTypeAdjuster(this)
valuetypefixer.visit(this)
if(errors.isEmpty()) {
valuetypefixer.applyModifications()
@@ -53,3 +53,9 @@ internal fun Program.simplifyExpressions() : Int {
opti.visit(this)
return opti.applyModifications()
}
internal fun Program.splitBinaryExpressions() : Int {
val opti = BinExprSplitter(this)
opti.visit(this)
return opti.applyModifications()
}
@@ -25,12 +25,12 @@ internal class StatementOptimizer(private val program: Program,
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
return listOf(IAstModification.Remove(block, parent as INameScope))
}
if (block !in callgraph.usedSymbols) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
return listOf(IAstModification.Remove(block, parent as INameScope))
}
}
return noModifications
@@ -42,16 +42,16 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine.containsNoCodeNorVars()) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.parent)
IAstModification.Remove(it, it.definingScope())
}.toMutableList()
removals += IAstModification.Remove(subroutine, parent)
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
return removals
}
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent))
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
return noModifications
@@ -63,7 +63,7 @@ internal class StatementOptimizer(private val program: Program,
if(decl.type == VarDeclType.VAR)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent))
return listOf(IAstModification.Remove(decl, decl.definingScope()))
}
return noModifications
@@ -74,7 +74,7 @@ internal class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent))
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
}
}
@@ -127,7 +127,7 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent))
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
}
return noModifications
@@ -150,7 +150,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
return listOf(IAstModification.Remove(ifStatement, parent))
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
@@ -183,12 +183,12 @@ internal class StatementOptimizer(private val program: Program,
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, parent))
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, parent))
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
}
}
@@ -265,7 +265,7 @@ internal class StatementOptimizer(private val program: Program,
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, parent))
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
}
}
return noModifications
@@ -276,12 +276,12 @@ internal class StatementOptimizer(private val program: Program,
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
}
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
@@ -308,7 +308,7 @@ internal class StatementOptimizer(private val program: Program,
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, parent))
return listOf(IAstModification.Remove(jump, jump.definingScope()))
return noModifications
}
@@ -341,7 +341,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, parent))
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
} else if (op2 == "-") {
// A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
@@ -352,7 +352,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, parent))
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
}
}
}
@@ -374,10 +374,10 @@ internal class StatementOptimizer(private val program: Program,
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
val targetIDt = assignment.target.inferType(program, assignment)
val targetIDt = assignment.target.inferType(program)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
@@ -394,7 +394,7 @@ internal class StatementOptimizer(private val program: Program,
when (bexpr.operator) {
"+" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
@@ -408,7 +408,7 @@ internal class StatementOptimizer(private val program: Program,
}
"-" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
@@ -420,18 +420,18 @@ internal class StatementOptimizer(private val program: Program,
}
}
}
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"<<" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
">>" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
}
@@ -20,20 +20,20 @@ internal class UnusedCodeRemover(private val program: Program, private val error
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
removals.add(IAstModification.Remove(sub, sub.definingScope()))
}
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
removals.add(IAstModification.Remove(block, block.definingScope()))
}
// remove modules that are not imported, or are empty (unless it's a library modules)
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removals.add(IAstModification.Remove(it, it.parent))
removals.add(IAstModification.Remove(it, it.definingScope()))
}
return removals
@@ -91,8 +91,10 @@ internal class UnusedCodeRemover(private val program: Program, private val error
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.isInRegularRAM(program.namespace))
linesToRemove.add(assign1)
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
linesToRemove.add(assign1)
}
}
}
+1 -1
View File
@@ -92,7 +92,7 @@ internal class ModuleImporter {
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.mapIndexed { i, it -> i to it }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
+27 -11
View File
@@ -5,8 +5,6 @@ import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
@@ -18,8 +16,8 @@ import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.io.CharConversionException
import java.nio.file.Path
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -186,7 +184,7 @@ class TestC64Zeropage {
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp1.available())
assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
@@ -220,7 +218,7 @@ class TestC64Zeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available())
assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
@@ -273,19 +271,37 @@ class TestC64Zeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available())
assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available())
}
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
@Test
fun testReservedLocations() {
val zp = CX16MachineDefinition.CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+1 -1
View File
@@ -41,7 +41,7 @@ of that build task, you can start the compiler with:
(You should probably make an alias...)
.. note::
.. hint::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
+7
View File
@@ -96,6 +96,12 @@ when compiled an ran on a C-64 you get this:
:align: center
:alt: result when run on C-64
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
.. image:: _static/primes_cx16.png
:align: center
:alt: result when run on CX16 emulator
Design principles and features
@@ -165,6 +171,7 @@ If you're targeting the CommanderX16, there's the `x16emu <https://github.com/co
building.rst
programming.rst
syntaxreference.rst
libraries.rst
todo.rst
+94
View File
@@ -0,0 +1,94 @@
************************
Compiler library modules
************************
The compiler provides several "built-in" library modules with useful subroutine and variables.
Some of these may be specific for a certain compilation target, or work slightly different,
but some effort is put into making them available across compilation targets.
This means that as long as your program is only using the subroutines from these
libraries and not using hardware- and/or system dependent code, and isn't hardcoding certain
assumptions like the screen size, the exact same source program can
be compiled for multiple different target platforms. Many of the example programs that come
with Prog8 are written like this.
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
of these library modules automatically as required.
.. caution::
The resulting compiled binary program *only works on the target machine it was compiled for*.
You must recompile the program for every target you want to run it on.
syslib
------
The "system library" for your target machine. It contains many system-specific definitions such
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
to write programs that work on both targets without modifications.
conv
----
Routines to convert strings to numbers or vice versa.
- numbers to strings, in various formats (binary, hex, decimal)
- strings in decimal, hex and binary format into numbers
textio (txt.*)
--------------
This will probably be the most used library module. It contains a whole lot of routines
dealing with text-based input and output (to the screen). Such as
- printing strings and numbers
- reading text input from the user via the keyboard
- filling or clearing the screen and colors
- scrolling the text on the screen
- placing individual characters on the screen
diskio
------
Provides several routines that deal with disk drive I/O, such as:
- show directory
- display disk drive status
- load and save data from and to the disk
- delete and rename files on the disk
floats
------
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
point variables. This includes ``print_f``, the routine used to print floating point numbers.
graphics
--------
High-res monochrome bitmap graphics routines:
- clearing the screen
- drawing lines
- drawing circles and discs (filled circles)
- plotting individual pixels
math
----
Low level math routines. You should not normally have to bother with this directly.
The compiler needs it to implement most of the math operations in your programs.
cx16logo
--------
A 'fun' module that contains the Commander X16 logo and that allows you
to print it anywhere on the screen.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.
The compiler needs it for verious built-in system routines.
+150 -122
View File
@@ -260,6 +260,12 @@ Note that the various keywords for the data type and variable type (``byte``, ``
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance.
It's possible to assign a new array to another array, this will overwrite all elements in the original
array with those in the value array. The number and types of elements have to match.
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
**Arrays at a specific memory location:**
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
@@ -287,7 +293,7 @@ This @-prefix can also be used for character byte values.
You can concatenate two string literals using '+' (not very useful though) or repeat
a string literal a given number of times using '*'. You can also assign a new string
value to another string. No bounds check is done so be sure the destination string is
large enough to contain the new value::
large enough to contain the new value (it is overwritten in memory)::
str string1 = "first part" + "second part"
str string2 = "hello!" * 10
@@ -296,7 +302,13 @@ large enough to contain the new value::
string1 = "new value"
.. info::
There are several 'escape sequences' to help you put special characters into strings, such
as newlines, quote characters themselves, and so on. The ones used most often are
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
read the syntax reference on strings.
.. hint::
Strings and uwords (=memory address) can often be interchanged.
An array of strings is actually an array of uwords where every element is the memory
address of the string. You can pass a memory address to assembly functions
@@ -361,13 +373,6 @@ address you specified, and setting the varible will directly modify that memory
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
.. note::
Directly accessing random memory locations is not yet supported without the
intermediate step of declaring a memory-mapped variable for the memory location.
The advantages of this however, is that it's clearer what the memory location
stands for, and the compiler also knows the data type.
Converting types into other types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -645,23 +650,20 @@ There's a set of predefined functions in the language. These are fixed and can't
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
sin(x)
Sine. (floating point version)
Math
^^^^
abs(x)
Absolute value.
atan(x)
Arctangent.
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
cos(x)
Cosine. (floating point version)
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
sin8(x)
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
sin16u(x)
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
sin16(x)
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
Cosine. (floating point version)
cos8u(x)
Fast 8-bit ubyte cosine of angle 0..255, result is in range 0..255
@@ -675,14 +677,11 @@ cos16u(x)
cos16(x)
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
abs(x)
Absolute value.
deg(x)
Radians to degrees.
tan(x)
Tangent.
atan(x)
Arctangent.
floor (x)
Rounds the floating point down to an integer towards minus infinity.
ln(x)
Natural logarithm (base e).
@@ -690,45 +689,48 @@ ln(x)
log2(x)
Base 2 logarithm.
rad(x)
Degrees to radians.
round(x)
Rounds the floating point to the closest integer.
sin(x)
Sine. (floating point version)
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
sin8(x)
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
sin16u(x)
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
sin16(x)
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte.
sqrt(x)
Floating point Square root.
round(x)
Rounds the floating point to the closest integer.
tan(x)
Tangent.
floor (x)
Rounds the floating point down to an integer towards minus infinity.
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
Array operations
^^^^^^^^^^^^^^^^
rad(x)
Degrees to radians.
any(x)
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
deg(x)
Radians to degrees.
max(x)
Maximum of the values in the array value x
min(x)
Minimum of the values in the array value x
sum(x)
Sum of the values in the array value x
sort(array)
Sort the array in ascending order (in-place)
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
floating point values.
reverse(array)
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
all(x)
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
len(x)
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
@@ -737,15 +739,83 @@ len(x)
length of the string during execution, the value of len(string) may no longer be correct!
(use strlen function if you want to dynamically determine the length)
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
max(x)
Maximum of the values in the array value x
min(x)
Minimum of the values in the array value x
reverse(array)
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
sum(x)
Sum of the values in the array value x
sort(array)
Sort the array in ascending order (in-place)
Supported are arrays of bytes or word values.
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
Finally, note that sorting an array with strings in it will not do what you might think;
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
Sorting strings alphabetically has to be programmed yourself if you need it.
Strings and memory blocks
^^^^^^^^^^^^^^^^^^^^^^^^^
memcopy(from, to, numbytes)
Efficiently copy a number of bytes from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will often be to write a specialized copy routine in assembly yourself!
memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the screen, very fast specialized subroutines are
available in the ``textio`` and ``graphics`` library modules.
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
Modifies in-place, doesn't return a value (so can't be used in an expression).
strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Don't confuse this with ``len`` and ``sizeof``
strcmp(string1, string2)
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with eachother
using ``==``, ``<`` etcetera (it will use strcmp for you under water automatically).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Also, you have to make sure yourself that start and length are within bounds of the strings.
Modifies in-place, doesn't return a value (so can't be used in an expression).
Miscellaneous
^^^^^^^^^^^^^
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
lsb(x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
@@ -753,19 +823,10 @@ lsb(x)
msb(x)
Get the most significant byte of the word x.
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
mkword(msb, lsb)
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
So mkword($80, $22) results in $8022.
any(x)
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
all(x)
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
rnd()
returns a pseudo-random byte from 0..255
@@ -799,51 +860,6 @@ ror2(x)
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
memcopy(from, to, numbytes)
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will always be to write a specialized copy routine in assembly yourself!
memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the character screen, very fast specialized subroutines are
available in the ``txt`` block (part of the ``textio`` module)
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
set_carry() / clear_carry()
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
rsave()
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you
@@ -858,10 +874,22 @@ rrestore()
read_flags()
Returns the current value of the CPU status register.
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
set_carry() / clear_carry()
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
Library routines
+33 -5
View File
@@ -78,7 +78,7 @@ Directives
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
It's not possible to return cleanly to BASIC when the program exits. The only choice is
@@ -122,7 +122,8 @@ Directives
- For a module option, there is ``enable_floats``, which will tell the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
Otherwise, floating point support is not enabled.
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
importing the ``floats`` library is required anyway and that will enable it for you automatically.
- There's also ``no_sysinit`` which cause the resulting program to *not* include
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
take care of that yourself. The program will just start running from whatever state the machine is in when the
@@ -402,6 +403,24 @@ Struct variables can be assigned a struct literal value (also in their declarati
Color rgb = [255, 100, 0] ; note that the value is an array
String
^^^^^^
``"hello"`` is a string translated into the default character encoding (PETSCII)
``@"hello"`` is a string translated into the alternate character encoding (Screencodes/pokes)
There are several escape sequences available to put special characters into your string value:
- ``\\`` - the backslash itself, has to be escaped because it is the escape symbol by itself
- ``\n`` - newline character (move cursor down and to beginning of next line)
- ``\r`` - carriage return character (more or less the same as newline if printing to the screen)
- ``\"`` - quote character (otherwise it would terminate the string)
- ``\'`` - apostrophe character (has to be escaped in character literals, is okay inside a string)
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
Operators
---------
@@ -486,6 +505,8 @@ takes no parameters. If the subroutine returns a value, usually you assign it t
If you're not interested in the return value, prefix the function call with the ``void`` keyword.
Otherwise the compiler will warn you about discarding the result of the call.
Multiple return values
^^^^^^^^^^^^^^^^^^^^^^
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
(referencing a routine in kernel ROM) can return more than one return value.
@@ -493,9 +514,16 @@ For example a status in the carry bit and a number in A, or a 16-bit value in A/
It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible.
You can still call the subroutine and not store the results.
But if you want to do something with the values it returns, you'll have to write
a small block of custom inline assembly that does the call and stores the values
appropriately. Don't forget to save/restore the registers if required.
**There is an exception:** if there's just one return value in a register, and one or more others that are returned
as bits in the status register (such as the Carry bit), the compiler allows you to call the subroutine.
It will then store the result value in a variable if required, and *keep the status register untouched
after the call* so you can use a conditional branch statement for that.
Note that this makes no sense inside an expression, so the compiler will still give an error for that.
If there really are multiple return values (other than a combined 16 bit return value in 2 registers),
you'll have to write a small block of custom inline assembly that does the call and stores the values
appropriately. Don't forget to save/restore any registers that are modified.
Subroutine definitions
+3 -2
View File
@@ -16,8 +16,9 @@ Currently there are two machines that are supported as compiler target (selectab
This chapter explains the relevant system details of these machines.
.. note::
If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
.. hint::
If you only use standard kernel and prog8 library routines,
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
Memory Model
+7 -8
View File
@@ -2,15 +2,15 @@
TODO
====
- get rid of all other TODO's in the code ;-)
- make it possible for array literals to not only contain compile time constants?
- implement @stack for asmsub parameters
- 64tass doesn't output all labels anymore in the vice-mon-list, so %breakpoint labels are no longer present....
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
- use VIC banking to move up the graphics bitmap memory location. Don't move it under the ROM though as that would require IRQ disabling and memory bank swapping for every bitmap manipulation
- add some primitives/subroutines/examples for using custom char sets, copying the default charset.
- recursive subroutines? via %option recursive, allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
- use VIC banking to move up the graphics bitmap memory location. Move it to $e000 under the kernal rom?
- some support for recursive subroutines?
- via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
- Or via a special recursive call operation that copies the current values of all local vars (including arguments) to the stack, replaces the arguments, jsr subroutine, and after returning copy the stack back to the local variables
- get rid of all other TODO's in the code ;-)
More optimizations
^^^^^^^^^^^^^^^^^^
@@ -19,8 +19,7 @@ Add more compiler optimizations to the existing ones.
- further optimize assignment codegeneration, such as the following:
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- can such parameter passing to subroutines be optimized to avoid copying?
- detect var->var argument passing to subroutines and avoid the second variable and copying of the value
- more optimizations on the language AST level
- more optimizations on the final assembly source level
- note: subroutine inlining is abandoned because of problems referencing non-local stuff. Can't move everything around.
+866
View File
@@ -0,0 +1,866 @@
%import textio
%import floats
%import syslib
%zeropage basicsafe
main {
sub start() {
rotations()
strings()
integers()
floatingpoint()
testX()
}
sub rotations() {
ubyte[] ubarr = [%11000111]
uword[] uwarr = [%1100111110101010]
repeat(10) {
txt.chrout('\n')
}
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
rol(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
rol(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
rol(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
rol(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
uwarr[0] = %1100111110101010
ror(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
ror(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
ror(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
ror(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
set_carry()
ror(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
clear_carry()
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
rol2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
rol2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
rol2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
rol2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
ror2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
ror2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
ror2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
ror2(uwarr[0])
txt.print_uwbin(uwarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
rol(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
rol(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
rol(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
rol(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
set_carry()
ror(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
ror(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
ror(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
ror(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
set_carry()
ror(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
rol2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
rol2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
rol2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
rol2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
ror2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
ror2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
ror2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
ror2(ubarr[0])
txt.print_ubbin(ubarr[0], true)
txt.chrout('\n')
txt.chrout('\n')
&ubyte membyte = $c000
uword addr = $c000
@(addr) = %10110101
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
rol(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
rol(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
rol(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
rol(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
txt.chrout('\n')
@(addr) = %10110101
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
ror(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
ror(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
ror(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
set_carry()
ror(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
txt.chrout('\n')
@(addr) = %10110101
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
rol2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
rol2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
rol2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
rol2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
txt.chrout('\n')
@(addr) = %10110101
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
ror2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
ror2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
ror2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
ror2(@(addr))
txt.print_ubbin(@(addr), true)
txt.chrout('\n')
txt.chrout('\n')
testX()
}
sub strings() {
const uword ADDR = $8400
const uword ADDR2 = $8000
memset(ADDR2, 40*25, '*')
memset(ADDR2, 40, '1')
memset(ADDR2+24*40, 39, '2')
memsetw(ADDR2, 40*25/2, $3132)
memsetw(ADDR2, 20, $4142)
memsetw(ADDR2+24*40, 19, $4241)
memcopy(ADDR2, ADDR, 200)
str result = "?" *10
str s1 = "irmen"
str s2 = "hello"
str dots = "....."
ubyte ub
byte bb
ubyte zero=0
bb = strcmp(s1, s2)
txt.print_b(bb)
txt.chrout('\n')
bb = strcmp(s2, s1)
txt.print_b(bb)
txt.chrout('\n')
txt.print_ub(s1==s2)
txt.chrout('\n')
txt.print_ub(s1<s2)
txt.chrout('\n')
txt.print_ub(s1>s2)
txt.chrout('\n')
bb = zero+strcmp(s1,s2)*1+zero
txt.print_b(bb)
txt.chrout('\n')
bb = zero+strcmp(s2,s1)*1+zero
txt.print_b(bb)
txt.chrout('\n')
ub = strlen(s1)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+strlen(s1)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
leftstr(s1, result, 3)
txt.print(result)
txt.chrout('\n')
leftstr(s1, result, len(s1))
txt.print(result)
txt.chrout('\n')
txt.chrout('\n')
result = "x"*8
rightstr(s2, result, 3)
txt.print(result)
txt.chrout('\n')
rightstr(s2, result, len(s1))
txt.print(result)
txt.chrout('\n')
result = "y"*10
substr(s2, result, 1, 3)
txt.print(result)
txt.chrout('\n')
testX()
}
sub integers() {
ubyte[] ubarr = [1,2,3,4,5,0,4,3,2,1, 255, 255, 255]
byte[] barr = [1,2,3,4,5,-4,0,-3,2,1, -128, -128, -127]
uword[] uwarr = [100,200,300,400,0,500,400,300,200,100]
word[] warr = [100,200,300,400,500,0,-400,-300,200,100,-99, -4096]
ubyte zero=0
ubyte ub
ubyte ub2
byte bb
uword uw
word ww
repeat(20) {
txt.chrout('\n')
}
ub = read_flags()
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+read_flags()*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = rnd()
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+rnd()*1+zero
txt.print_ub(ub)
txt.chrout('\n')
uw = rndw()
txt.print_uw(uw)
txt.chrout('\n')
uw = zero+rndw()*1+zero
txt.print_uw(uw)
txt.chrout('\n')
uw = 50000
ub = sqrt16(uw)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+sqrt16(uw)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
bb = -100
bb = sgn(bb)
txt.print_b(bb)
txt.chrout('\n')
bb = -100
bb = zero+sgn(bb)*1+zero
txt.print_b(bb)
txt.chrout('\n')
ub = 100
bb = sgn(ub)
txt.print_b(bb)
txt.chrout('\n')
ub = 100
bb = zero+sgn(ub)*1+zero
txt.print_b(bb)
txt.chrout('\n')
ww = -1000
bb = sgn(ww)
txt.print_b(bb)
txt.chrout('\n')
bb = zero+sgn(ww)*1+zero
txt.print_b(bb)
txt.chrout('\n')
uw = 1000
bb = sgn(uw)
txt.print_b(bb)
txt.chrout('\n')
bb = zero+sgn(uw)*1+zero
txt.print_b(bb)
txt.chrout('\n')
ub = 0
uw = sin16u(ub)
txt.print_uw(uw)
txt.chrout('\n')
uw = zero+sin16u(ub)*1+zero
txt.print_uw(uw)
txt.chrout('\n')
ub = 0
uw = cos16u(ub)
txt.print_uw(uw)
txt.chrout('\n')
uw = zero+cos16u(ub)*1+zero
txt.print_uw(uw)
txt.chrout('\n')
ub = 0
ww = sin16(ub)
txt.print_w(ww)
txt.chrout('\n')
ww = zero+sin16(ub)*1+zero
txt.print_w(ww)
txt.chrout('\n')
ub = 0
ww = cos16(ub)
txt.print_w(ww)
txt.chrout('\n')
uw = 0
ww = zero+cos16(ub)*1+zero
txt.print_w(ww)
txt.chrout('\n')
ub2 = 0
ub = sin8u(ub2)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+sin8u(ub2)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub2 = 0
ub = cos8u(ub2)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+cos8u(ub2)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub2 = 0
bb = sin8(ub2)
txt.print_b(bb)
txt.chrout('\n')
bb = zero+sin8(ub2)*1+zero
txt.print_b(bb)
txt.chrout('\n')
ub2 = 0
bb = cos8(ub2)
txt.print_b(bb)
txt.chrout('\n')
bb = zero+cos8(ub2)*1+zero
txt.print_b(bb)
txt.chrout('\n')
bb = -100
bb = abs(bb)
txt.print_b(bb)
txt.chrout('\n')
bb = -100
bb = zero+abs(bb)*1+zero
txt.print_b(bb)
txt.chrout('\n')
ww = -1000
ww = abs(ww)
txt.print_w(ww)
txt.chrout('\n')
ww = -1000
ww = zero+abs(ww)*1+zero
txt.print_w(ww)
txt.chrout('\n')
ub = min(ubarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+min(ubarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
bb = min(barr)
txt.print_b(bb)
txt.chrout('\n')
bb = zero+min(barr)*1+zero
txt.print_b(bb)
txt.chrout('\n')
uw = min(uwarr)
txt.print_uw(uw)
txt.chrout('\n')
uw = zero+min(uwarr)*1+zero
txt.print_uw(uw)
txt.chrout('\n')
ww = min(warr)
txt.print_w(ww)
txt.chrout('\n')
ww = zero+min(warr)*1+zero
txt.print_w(ww)
txt.chrout('\n')
ub = max(ubarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+max(ubarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
bb = max(barr)
txt.print_b(bb)
txt.chrout('\n')
bb = zero+max(barr)*1+zero
txt.print_b(bb)
txt.chrout('\n')
uw = max(uwarr)
txt.print_uw(uw)
txt.chrout('\n')
uw = zero+max(uwarr)*1+zero
txt.print_uw(uw)
txt.chrout('\n')
ww = max(warr)
txt.print_w(ww)
txt.chrout('\n')
ww = zero+max(warr)*1+zero
txt.print_w(ww)
txt.chrout('\n')
ub = any(ubarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+any(ubarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = any(barr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+any(barr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = any(uwarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+any(uwarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = any(warr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+any(warr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = all(ubarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+all(ubarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = all(barr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+all(barr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = all(uwarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+all(uwarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = all(warr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+all(warr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
uw = sum(ubarr)
txt.print_uw(uw)
txt.chrout('\n')
uw = zero+sum(ubarr)*1+zero
txt.print_uw(uw)
txt.chrout('\n')
ww = sum(barr)
txt.print_w(ww)
txt.chrout('\n')
ww = zero+sum(barr)*1+zero
txt.print_w(ww)
txt.chrout('\n')
uw = sum(uwarr)
txt.print_uw(uw)
txt.chrout('\n')
uw = zero+sum(uwarr)*1+zero
txt.print_uw(uw)
txt.chrout('\n')
ww = sum(warr)
txt.print_w(ww)
txt.chrout('\n')
ww = zero+sum(warr)*1+zero
txt.print_w(ww)
txt.chrout('\n')
sort(ubarr)
sort(barr)
sort(uwarr)
sort(warr)
reverse(ubarr)
reverse(barr)
reverse(uwarr)
reverse(warr)
testX()
}
sub floatingpoint() {
ubyte[] barr = [1,2,3,4,5,0,4,3,2,1]
float[] flarr = [1.1, 2.2, 3.3, 0.0, -9.9, 5.5, 4.4]
ubyte zero=0
ubyte ub
byte bb
uword uw
float fl
float fzero=0.0
fl = -9.9
fl = abs(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = fzero+abs(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = 9.9
fl = atan(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = 9.9
fl = fzero+atan(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = ceil(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = fzero+ceil(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = cos(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = fzero+cos(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = sin(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = fzero+sin(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = 9.9
fl = tan(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = 9.9
fl = fzero+tan(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = 3.1415927
fl = deg(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = 3.1415927
fl = fzero+deg(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = 90
fl = rad(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = 90
fl = fzero+rad(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = floor(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = fzero+floor(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = 3.1415927
fl = ln(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = 3.1415927
fl = fzero+ln(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = 3.1415927
fl = log2(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = 3.1415927
fl = fzero+log2(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = round(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
fl = fzero+round(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = -9.9
bb = sgn(fl)
txt.print_b(bb)
txt.chrout('\n')
fl = -9.9
bb = zero+sgn(fl)*1+zero
txt.print_b(bb)
txt.chrout('\n')
fl = 3.1415927
fl = sqrt(fl)
floats.print_f(fl)
txt.chrout('\n')
fl = 3.1415927
fl = fzero+sqrt(fl)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = rndf()
floats.print_f(fl)
txt.chrout('\n')
fl = fzero+rndf()*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
swap(fl, fzero)
swap(fzero, fl)
ub = any(flarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+any(flarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
ub = all(flarr)
txt.print_ub(ub)
txt.chrout('\n')
ub = zero+all(flarr)*1+zero
txt.print_ub(ub)
txt.chrout('\n')
reverse(flarr)
for ub in 0 to len(flarr)-1 {
floats.print_f(flarr[ub])
txt.chrout(',')
}
txt.chrout('\n')
fl = max(flarr)
floats.print_f(fl)
txt.chrout('\n')
fl = fzero+max(flarr)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = min(flarr)
floats.print_f(fl)
txt.chrout('\n')
fl = fzero+min(flarr)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
fl = sum(flarr)
floats.print_f(fl)
txt.chrout('\n')
fl = fzero+sum(flarr)*1.0+fzero
floats.print_f(fl)
txt.chrout('\n')
testX()
}
asmsub testX() {
%asm {{
stx _saveX
lda #13
jsr txt.chrout
lda #'x'
jsr txt.chrout
lda #'='
jsr txt.chrout
lda _saveX
jsr txt.print_ub
lda #' '
jsr txt.chrout
lda #'s'
jsr txt.chrout
lda #'p'
jsr txt.chrout
lda #'='
jsr txt.chrout
tsx
txa
jsr txt.print_ub
lda #13
jsr txt.chrout
ldx _saveX
rts
_saveX .byte 0
}}
}
}
+85
View File
@@ -0,0 +1,85 @@
%target c64
%import syslib
%import textio
%zeropage basicsafe
%option no_sysinit
; Create a custom character set on the C64.
main {
sub start() {
txt.color(1)
txt.print("creating charset...\n")
charset.make_custom_charset()
; activate the new charset in RAM
ubyte block = c64.CIA2PRA
const ubyte PAGE1 = ((c64.Screen >> 6) & $F0) | ((charset.CHARSET >> 10) & $0E)
c64.CIA2PRA = (block & $FC) | (lsb(c64.Screen >> 14) ^ $03)
c64.VMCSB = PAGE1
txt.print("\n @ @ @ @\n")
}
}
charset {
const uword CHARSET = $2000
sub copy_rom_charset() {
; copies the charset from ROM to RAM so we can modify it
set_irqd()
ubyte bank = @($0001)
@($0001) = bank & %11111011 ; enable CHAREN, so the character rom accessible at $d000
memcopy($d000, CHARSET, 256*8*2) ; copy the charset to RAM
@($0001) = bank ; reset previous memory banking
clear_irqd()
}
sub make_custom_charset() {
copy_rom_charset()
; make all characters italic
ubyte c
for c in 0 to 255 {
uword ptr = CHARSET + c*$0008
@(ptr) >>= 2
@(ptr+1) >>= 2
@(ptr+2) >>= 1
@(ptr+3) >>= 1
;@(ptr+4) >>= 0
;@(ptr+5) >>= 0
@(ptr+6) <<= 1
@(ptr+7) <<= 1
ptr = CHARSET + 256*8 + c*$0008
@(ptr) >>= 2
@(ptr+1) >>= 2
@(ptr+2) >>= 1
@(ptr+3) >>= 1
;@(ptr+4) >>= 0
;@(ptr+5) >>= 0
@(ptr+6) <<= 1
@(ptr+7) <<= 1
}
; add a smiley over the '@'
ubyte[] smiley = [
%00111100,
%01000010,
%10100101,
%10000001,
%10100101,
%10011001,
%01000010,
%00111100
]
memcopy(smiley, CHARSET, len(smiley))
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -166,7 +166,7 @@ main {
else
c64.SPRPTR[i] = $2000/64 ; small ball
c64.SPCOL[i] = spritecolors[(zc>>13) as byte + 4] ; further away=darker color
c64.SPCOL[i] = spritecolors[(zc>>13) as ubyte + 4] ; further away=darker color
}
}
}
+3 -2
View File
@@ -36,7 +36,7 @@ main {
cx16.GRAPH_set_colors(1, 0, 0)
draw_lines_hiddenremoval()
;draw_lines()
; draw_lines()
anglex += 217
angley -= 505
@@ -191,6 +191,7 @@ main {
sub facing_away(ubyte edgePointsIdx) -> ubyte {
; simplistic visibility determination by checking the Z component of the surface normal
; TODO: actually take the line of sight vector into account
ubyte p1 = shipdata.facesPoints[edgePointsIdx]
edgePointsIdx++
ubyte p2 = shipdata.facesPoints[edgePointsIdx]
@@ -223,7 +224,7 @@ main {
shipdata {
; Ship model data converted from BBC Elite's Cobra MK 3
; Ship model data converted from BBC Elite's Cobra MK 3
; downloaded from http://www.elitehomepage.org/archive/index.htm
const ubyte totalNumberOfEdges = 51
+2
View File
@@ -3,12 +3,14 @@
%target cx16
%zeropage basicsafe
main {
sub start() {
void cx16.screen_set_mode($80)
cx16.r0=0
void cx16.screen_set_mode(0)
cx16.FB_init()
cx16.GRAPH_init()
+17
View File
@@ -0,0 +1,17 @@
%import textio
%import cx16logo
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
repeat {
ubyte col = rnd() % (txt.DEFAULT_WIDTH-12) + 3
ubyte row = rnd() % (txt.DEFAULT_HEIGHT-7)
cx16logo.logo_at(col, row)
txt.plot(col-3, row+7 )
txt.print("commander x16")
}
}
}
-47
View File
@@ -1,47 +0,0 @@
%target c64
%import textio
%import syslib
%zeropage basicsafe
%option no_sysinit
%launcher none
%address 50000
; This example shows the directory contents of disk drive 8.
; You load it with LOAD "diskdir-sys50000",8,1
; and then call it with SYS 50000.
main {
sub start() {
txt.print("directory of disk drive #8:\n\n")
diskdir(8)
}
sub diskdir(ubyte drivenumber) {
c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0)
c64.OPEN() ; open 1,8,0,"$"
c64.CHKIN(1) ; use #1 as input channel
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
; while not key pressed / EOF encountered, read data.
while not (@($c6) | c64.STATUS) {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ')
ubyte @zp char
do {
char = c64.CHRIN()
txt.chrout(char)
} until char==0
txt.chrout('\n')
repeat 2 {
void c64.CHRIN() ; skip 2 bytes
}
}
c64.CLOSE(1)
c64.CLRCHN() ; restore default i/o devices
}
}
-43
View File
@@ -1,43 +0,0 @@
%target c64
%import textio
%import syslib
%option no_sysinit
%zeropage basicsafe
; This example shows the directory contents of disk drive 8.
main {
sub start() {
txt.print("directory of disk drive #8:\n\n")
diskdir(8)
}
sub diskdir(ubyte drivenumber) {
c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0)
c64.OPEN() ; open 1,8,0,"$"
c64.CHKIN(1) ; use #1 as input channel
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
; while not key pressed / EOF encountered, read data.
while not (@($c6) | c64.STATUS) {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ')
ubyte @zp char
do {
char = c64.CHRIN()
txt.chrout(char)
} until char==0
txt.chrout('\n')
repeat 2 {
void c64.CHRIN() ; skip 2 bytes
}
}
c64.CLOSE(1)
c64.CLRCHN() ; restore default i/o devices
}
}
+2 -2
View File
@@ -9,14 +9,14 @@
; Note: this program is compatible with C64 and CX16.
main {
const ubyte width = 255
const uword width = 320
const ubyte height = 200
const ubyte max_iter = 16
sub start() {
graphics.enable_bitmap_mode()
ubyte pixelx
uword pixelx
ubyte pixely
for pixely in 0 to height-1 {
+1 -1
View File
@@ -30,7 +30,7 @@ main {
txt.print("es")
txt.print(" left.\nWhat is your next guess? ")
void txt.input_chars(input)
ubyte guess = lsb(conv.str2uword(input))
ubyte guess = conv.str2ubyte(input)
if guess==secretnumber {
ending(true)
+4 -2
View File
@@ -218,7 +218,7 @@ waitkey:
if linepos and blocklogic.isLineFull(linepos)
blocklogic.collapse(linepos)
lines += num_lines
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines at once
score += scores[num_lines-1]
speedlevel = 1+lsb(lines/10)
drawScore()
@@ -249,8 +249,10 @@ waitkey:
txt.print("────────────────────────")
c64.CHROUT('K')
while c64.GETIN()!=133 {
ubyte key = 0
while key!=133 {
; endless loop until user presses F1 to restart the game
key = c64.GETIN()
}
}
+23 -29
View File
@@ -1,41 +1,21 @@
%import textio
%import syslib
%import floats
%import syslib
%zeropage basicsafe
main {
struct Color {
ubyte red
ubyte green
ubyte blue
}
Color c1 = [11,22,33]
Color c2 = [11,22,33]
Color c3 = [11,22,33]
uword[] colors = [ c1, c2, c3]
sub start() {
txt.print_ub(c1.red)
txt.chrout('\n')
txt.print_ub(c1.green)
txt.chrout('\n')
txt.print_ub(c1.blue)
txt.chrout('\n')
str s1 = "irmen"
str s2 = "hello"
; byte c = strcmp(s1, s2)
; txt.print_b(c)
; txt.chrout('\n')
txt.print_ub(s1<=s2)
txt.chrout('\n')
c1 = [99,88,77]
txt.print_ub(c1.red)
txt.chrout('\n')
txt.print_ub(c1.green)
txt.chrout('\n')
txt.print_ub(c1.blue)
txt.chrout('\n')
testX()
}
@@ -44,8 +24,23 @@ main {
stx _saveX
lda #13
jsr txt.chrout
lda #'x'
jsr txt.chrout
lda #'='
jsr txt.chrout
lda _saveX
jsr txt.print_ub
lda #' '
jsr txt.chrout
lda #'s'
jsr txt.chrout
lda #'p'
jsr txt.chrout
lda #'='
jsr txt.chrout
tsx
txa
jsr txt.print_ub
lda #13
jsr txt.chrout
ldx _saveX
@@ -53,5 +48,4 @@ main {
_saveX .byte 0
}}
}
}
+20 -22
View File
@@ -1,7 +1,6 @@
%import textio
%import floats
%zeropage basicsafe
%option enable_floats
; Note: this program is compatible with C64 and CX16.
@@ -18,7 +17,6 @@ main {
&str ms1 = $c000
byte[4] barray
ubyte[4] ubarray
word[4] warray
@@ -84,20 +82,20 @@ main {
uw=muwarray[bb]
fl=mflarray[bb]
A=s1[bb*3]
ub=s1[bb*3]
bb=barray[bb*3]
ub=ubarray[bb*3]
ww=warray[bb*3]
uw=uwarray[bb*3]
fl=flarray[bb*3]
A=ms1[bb*3]
ub=ms1[bb*3]
bb=mbarray[bb*3]
ub=mubarray[bb*3]
ww=mwarray[bb*3]
uw=muwarray[bb*3]
fl=mflarray[bb*3]
; A=s1[bb*3]
; ub=s1[bb*3]
; bb=barray[bb*3]
; ub=ubarray[bb*3]
; ww=warray[bb*3]
; uw=uwarray[bb*3]
; fl=flarray[bb*3]
; A=ms1[bb*3]
; ub=ms1[bb*3]
; bb=mbarray[bb*3]
; ub=mubarray[bb*3]
; ww=mwarray[bb*3]
; uw=muwarray[bb*3]
; fl=mflarray[bb*3]
; write array
barray[2]++
@@ -133,11 +131,11 @@ main {
uwarray[bb] = uw
flarray[bb] = fl
s1[bb*3] = ub
barray[bb*3] = bb
ubarray[bb*3] = ub
warray[bb*3] = ww
uwarray[bb*3] = uw
flarray[bb*3] = fl
; s1[bb*3] = ub
; barray[bb*3] = bb
; ubarray[bb*3] = ub
; warray[bb*3] = ww
; uwarray[bb*3] = uw
; flarray[bb*3] = fl
}
}
+999
View File
@@ -0,0 +1,999 @@
%import textio
%import conv
%import diskio
%option no_sysinit
%zeropage basicsafe
; Prog8 adaptation of the Text-Elite galaxy system trading simulation engine.
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
; Note: this program is compatible with C64 and CX16.
main {
const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy.travel_to(1, numforLave)
market.init(0) ; Lave's market is seeded with 0
ship.init()
planet.display(false)
repeat {
str input = "????????"
txt.print("\nCash: ")
util.print_10s(ship.cash)
txt.print("\nCommand (?=help): ")
ubyte num_chars = txt.input_chars(input)
txt.chrout('\n')
if num_chars {
when input[0] {
'?' -> {
txt.print("\nCommands are:\n"+
"buy jump info cash >=save\n"+
"sell teleport market hold <=load\n"+
"fuel galhyp local quit\n")
}
'q' -> break
'b' -> trader.do_buy()
's' -> trader.do_sell()
'f' -> trader.do_fuel()
'j' -> trader.do_jump()
't' -> trader.do_teleport()
'g' -> trader.do_next_galaxy()
'i' -> trader.do_info()
'm' -> trader.do_show_market()
'l' -> trader.do_local()
'c' -> trader.do_cash()
'h' -> trader.do_hold()
'<' -> trader.do_load()
'>' -> trader.do_save()
}
}
}
}
}
trader {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy
ubyte planet
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
diskio.status(8)
txt.chrout('\n')
return
}
ship.cash = savedata.cash
ship.Max_cargo = savedata.max_cargo
ship.fuel = savedata.fuel
memcopy(&savedata.cargo0, ship.cargohold, len(ship.cargohold))
galaxy.travel_to(savedata.galaxy, savedata.planet)
planet.display(false)
}
sub do_save() {
savedata.galaxy = galaxy.number
savedata.planet = planet.number
savedata.cash = ship.cash
savedata.max_cargo = ship.Max_cargo
savedata.fuel = ship.fuel
memcopy(ship.cargohold, &savedata.cargo0, len(ship.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
diskio.status(8)
txt.chrout('\n')
}
}
sub do_jump() {
txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = ship.fuel
ship.fuel = 255
jump_to_system()
ship.fuel = fuel
}
sub jump_to_system() {
void txt.input_chars(input)
ubyte current_planet = planet.number
ubyte x = planet.x
ubyte y = planet.y
if galaxy.search_closest_planet(input) {
ubyte distance = planet.distance(x, y)
if distance <= ship.fuel {
galaxy.init_market_for_planet()
ship.fuel -= distance
txt.print("\n\nHyperspace jump! Arrived at:\n")
planet.display(true)
return
}
txt.print("Insufficient fuel\n")
} else {
txt.print(" Not found!\n")
}
galaxy.travel_to(galaxy.number, current_planet)
}
sub do_buy() {
txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if market.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market.current_price[ci] * amount
txt.print(" Total price: ")
util.print_10s(price)
if price > ship.cash {
txt.print(" Not enough cash!\n")
} else {
ship.cash -= price
ship.cargohold[ci] += amount
market.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void txt.input_chars(commodity)
ubyte ci = market.match(commodity)
if ci & 128 {
txt.print("Unknown\n")
} else {
txt.print("\nHow much? ")
void txt.input_chars(input)
ubyte amount = conv.str2ubyte(input)
if ship.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = market.current_price[ci] * amount
txt.print(" Total price: ")
util.print_10s(price)
ship.cash += price
ship.cargohold[ci] -= amount
market.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
txt.print("\nBuy fuel. Amount? ")
void txt.input_chars(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = ship.Max_fuel - ship.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * ship.Fuel_cost
if price > ship.cash {
txt.print("Not enough cash!\n")
} else {
ship.cash -= price
ship.fuel += buy_fuel
}
}
sub do_cash() {
txt.print("\nCheat! Set cash amount: ")
void txt.input_chars(input)
ship.cash = conv.str2uword(input)
}
sub do_hold() {
txt.print("\nCheat! Set cargohold size: ")
void txt.input_chars(input)
ship.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy() {
galaxy.travel_to(galaxy.number+1, planet.number)
planet.display(false)
}
sub do_info() {
txt.print("\nSystem name (empty=current): ")
num_chars = txt.input_chars(input)
if num_chars {
ubyte current_planet = planet.number
if galaxy.search_closest_planet(input) {
planet.display(false)
} else {
txt.print(" Not found!")
}
galaxy.travel_to(galaxy.number, current_planet)
} else {
planet.display(false)
}
}
sub do_local() {
galaxy.local_area()
}
sub do_show_market() {
market.display()
txt.print("\nFuel: ")
util.print_10s(ship.fuel)
txt.print(" Cargohold space: ")
txt.print_ub(ship.cargo_free())
txt.print("t\n")
}
}
ship {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel = Max_fuel
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
ubyte[17] cargohold = 0
sub init() {
memset(cargohold, len(cargohold), 0)
}
sub cargo_free() -> ubyte {
ubyte ci
ubyte total = 0
for ci in 0 to len(cargohold)-1 {
if market.units[ci]==0 ; tonnes only
total += cargohold[ci]
}
return Max_cargo - total
}
}
market {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity = 0
uword[17] current_price = 0
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all operations are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = planet.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
ubyte ci
txt.chrout('\n')
planet.print_name_uppercase()
txt.print(" trade market:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
for ci in 0 to len(names)-1 {
util.print_right(13, names[ci])
txt.print(" ")
util.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship.cargohold[ci])
txt.chrout('\n')
}
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if util.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
galaxy {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxynum) {
number = 1
planet.number = 255
seed = [base0, base1, base2]
repeat galaxynum-1 {
nextgalaxy()
}
}
sub nextgalaxy() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxynum, ubyte system) {
init(galaxynum)
generate_next_planet() ; always at least planet 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet()
}
planet.name = make_current_planet_name()
init_market_for_planet()
}
sub init_market_for_planet() {
market.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet(uword nameptr) -> ubyte {
ubyte x = planet.x
ubyte y = planet.y
ubyte current_planet_num = planet.number
init(number)
ubyte found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet()
planet.name = make_current_planet_name()
if util.prefix_matches(nameptr, planet.name) {
ubyte distance = planet.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet_num)
return found
}
sub local_area() {
ubyte current_planet = planet.number
ubyte px = planet.x
ubyte py = planet.y
ubyte pn = 0
init(number)
txt.print("\nGalaxy #")
txt.print_ub(number)
txt.print(" - systems in vicinity:\n")
do {
generate_next_planet()
ubyte distance = planet.distance(px, py)
if distance <= ship.Max_fuel {
if distance <= ship.fuel
txt.chrout('*')
else
txt.chrout('-')
txt.chrout(' ')
planet.name = make_current_planet_name()
planet.display(true)
txt.print(" (")
util.print_10s(distance)
txt.print(" LY)\n")
}
pn++
} until pn==0
travel_to(number, current_planet)
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
ubyte longname
sub generate_next_planet() {
determine_planet_properties()
longname = lsb(seed[0]) & 64
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet_properties() {
; create the planet's characteristics
planet.number++
planet.x = msb(seed[1])
planet.y = msb(seed[0])
planet.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
planet.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if planet.govtype <= 1
planet.economy = (planet.economy | 2)
planet.techlevel = (msb(seed[1]) & 3) + (planet.economy ^ 7)
planet.techlevel += planet.govtype >> 1
if planet.govtype & 1
planet.techlevel++
planet.population = 4 * planet.techlevel + planet.economy
planet.population += planet.govtype + 1
planet.productivity = ((planet.economy ^ 7) + 3) * (planet.govtype + 4)
planet.productivity *= planet.population * 8
ubyte seed2_msb = msb(seed[2])
planet.radius = mkword((seed2_msb & 15) + 11, planet.x)
planet.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
if planet.species_is_alien {
planet.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
planet.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
planet.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet.species_kind = (planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
rol(xh)
rol(xl)
return mkword(xh, xl)
}
sub debug_seed() {
txt.print("\ngalaxy #")
txt.print_ub(number)
txt.print("\ngalaxy seed0=")
txt.print_uwhex(galaxy.seed[0], true)
txt.print("\ngalaxy seed1=")
txt.print_uwhex(galaxy.seed[1], true)
txt.print("\ngalaxy seed2=")
txt.print_uwhex(galaxy.seed[2], true)
txt.chrout('\n')
}
}
planet {
%option force_output
str[] species_sizes = ["Large", "Fierce", "Small"]
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship", "Communist", "Confederacy", "Democracy", "Corporate State"]
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet", "world", "place", "little planet", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy, then increases by 1 for each generated planet
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
ubyte species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str name = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte x = goatsoup_rnd_number() & $3e
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
name[nx] = 0
name[0] |= 32 ; uppercase first letter
return name
}
sub goatsoup_rnd_number() -> ubyte {
ubyte x = goatsoup_rnd[0] * 2
uword a = x as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = x
x = goatsoup_rnd[1]
ubyte ac = x + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = x
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt16(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet_result
reset_rnd()
recursive_soup()
return planet_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = name[0] | 32
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = name[0] | 32
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc=='e' or cc=='o' or cc==0
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(ubyte compressed) {
if compressed {
print_name_uppercase()
txt.print(" TL:")
txt.print_ub(techlevel+1)
txt.chrout(' ')
txt.print(econnames[economy])
txt.chrout(' ')
txt.print(govnames[govtype])
} else {
txt.print("\n\nSystem: ")
print_name_uppercase()
txt.print("\nPosition: ")
txt.print_ub(x)
txt.chrout('\'')
txt.print_ub(y)
txt.chrout(' ')
txt.chrout('#')
txt.print_ub(number)
txt.print("\nEconomy: ")
txt.print(econnames[economy])
txt.print("\nGovernment: ")
txt.print(govnames[govtype])
txt.print("\nTech Level: ")
txt.print_ub(techlevel+1)
txt.print("\nTurnover: ")
txt.print_uw(productivity)
txt.print("\nRadius: ")
txt.print_uw(radius)
txt.print("\nPopulation: ")
txt.print_ub(population >> 3)
txt.print(" Billion\nSpecies: ")
if species_is_alien {
if species_size < len(species_sizes) {
txt.print(species_sizes[species_size])
txt.chrout(' ')
}
if species_color < len(species_colors) {
txt.print(species_colors[species_color])
txt.chrout(' ')
}
if species_look < len(species_looks) {
txt.print(species_looks[species_look])
txt.chrout(' ')
}
if species_kind < len(species_kinds) {
txt.print(species_kinds[species_kind])
}
} else {
txt.print("Human Colonials")
}
txt.chrout('\n')
txt.print(soup())
txt.chrout('\n')
}
}
sub print_name_uppercase() {
ubyte c
for c in name
txt.chrout(c | 32)
}
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc #$81
asl a
tay
lda wordlists,y
sta P8ZP_SCRATCH_W1
lda wordlists+1,y
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_REG
asl a
tay
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
}}
}
}
util {
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
ubyte ix=0
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
pc &= 127
sc &= 127
if pc != sc
return false
prefixptr++
stringptr++
}
return false
}
sub print_right(ubyte width, uword string) {
repeat width - strlen(string) {
txt.chrout(' ')
}
txt.print(string)
}
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
%asm {{
jsr conv.uword2decimal
lda conv.uword2decimal.decTenThousands
ldy #0 ; have we started printing?
cmp #'0'
beq +
jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decThousands
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decHundreds
cmp #'0'
bne +
cpy #0
beq ++
+ jsr c64.CHROUT
iny
+ lda conv.uword2decimal.decTens
jsr c64.CHROUT
lda #'.'
jsr c64.CHROUT
lda conv.uword2decimal.decOnes
jsr c64.CHROUT
rts
}}
}
asmsub testX() {
%asm {{
stx _saveX
lda #13
jsr txt.chrout
lda _saveX
jsr txt.print_ub
lda #13
jsr txt.chrout
ldx _saveX
rts
_saveX .byte 0
}}
}
}
+3 -3
View File
@@ -172,10 +172,10 @@ expression :
| left = expression EOL? bop = ('+' | '-' ) EOL? right = expression
| left = expression EOL? bop = ('<<' | '>>' ) EOL? right = expression
| left = expression EOL? bop = ('<' | '>' | '<=' | '>=') EOL? right = expression
| left = expression EOL? bop = ('==' | '!=') EOL? right = expression
| left = expression EOL? bop = '&' EOL? right = expression
| left = expression EOL? bop = '^' EOL? right = expression
| left = expression EOL? bop = '|' EOL? right = expression
| left = expression EOL? bop = ('==' | '!=') EOL? right = expression
| rangefrom = expression rto = ('to'|'downto') rangeto = expression ('step' rangestep = expression)? // can't create separate rule due to mutual left-recursion
| left = expression EOL? bop = 'and' EOL? right = expression
| left = expression EOL? bop = 'or' EOL? right = expression
@@ -271,7 +271,7 @@ asmsub_decl : identifier '(' asmsub_params? ')' asmsub_clobbers? asmsub_returns?
asmsub_params : asmsub_param (',' EOL? asmsub_param)* ;
asmsub_param : vardecl '@' identifier ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed. TODO implement stack='stack'
asmsub_param : vardecl '@' identifier ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed.
asmsub_clobbers : 'clobbers' '(' clobber? ')' ;
@@ -279,7 +279,7 @@ clobber : identifier (',' identifier)* ; // A,X,Y allowed
asmsub_returns : '->' asmsub_return (',' EOL? asmsub_return)* ;
asmsub_return : datatype '@' (identifier | stack='stack') ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed
asmsub_return : datatype '@' identifier ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed
if_stmt : 'if' expression EOL? (statement | statement_block) EOL? else_part? ; // statement is constrained later