-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathforth.asm
2876 lines (2499 loc) · 76.1 KB
/
forth.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; Basic FORTH interpreter.
.cpu "65816"
.include "header.asm"
;;; Memory Map
init_rsp = $7EFF ; initial return stack pointer
init_psp = $7D00 ; initial parameter stack pointer
tib = $7F00 ; location of terminal input buffer
bufadr = $2000 ; block buffer
cp_val = $0300 + ram_routines_sz ; start of RAM dictionary
;;; Constants
precedence = $80 ; precedence bit bitmask
smudge = $40 ; smudge bit bitmask
noctrl = $1F ; no control bits bitmask
kbbuf = $0200 ; keyboard scancode buffer
kblshf = $01 ; left shift modifier
;;; Direct page variables
.virtual $C0
cp_v .word ? ; Pointer to the next cell in dict
toin_v .word ? ; Current offset into TIB.
vdp_cp .word ? ; Current VDP pos.
kbstate .byte ? ; Current state of keyboard handler
kbwoff .byte ? ; Write offset of keyboard circular
; buffer
kbroff .byte ? ; Read offset of keyboard circular
; buffer
kbpar .byte ? ; Keyboard parity store
dict_head
.word ? ; Latest entry in the dictionary
keymod_val
.word ? ; Holds modifier bits for keyboard
n_tib .word ?
s_addr .word ?
state_val
.word ?
bufblk .byte ?
tmp ; Temporary storage
* = *+8
.cerror * >= $0100, "Too many direct page variables"
.endvirtual
;;; Main program
clc
xce
rep #FLAGM|FLAGX
.al
.xl
;; Copy EEPROM routines to RAM.
lda #ram_routines_sz - 1
ldx #ram_routines_begin
ldy #$0300
mvn 0,0
;; Reset dictionary.
lda #cp_val
sta cp_v
lda #load_last_entry
sta dict_head
;; Clear return and data stacks.
ldx #init_psp
lda #init_rsp
tcs
;; Set STATE to interpretation.
jsr left_bracket.body
;; Clear keyboard state.
sep #FLAGM
.as
stz kbroff
stz kbwoff
stz kbpar
stz kbstate
;; Mark buffer as free
stz bufblk
;; Register keyboard interrupt handler by writing the
;; instruction jmp kbint to $0104, the NMI interrupt handler
;; set by the monitor.
lda #$4c ; opcode for jmp
sta $0104
lda #kbint & $FF ; low byte of kbint
sta $0105
lda #kbint >> 8 ; high byte of kbint
sta $0106
;; Enable keyboard interrupt by setting BCR6 to 1 and BCR5 to
;; 0.
lda BCR
ora #BCR6
and #~BCR5
sta BCR
rep #FLAGM
.al
;; Run boot program.
lda #boot_t
sta s_addr
lda #len(boot)
sta n_tib
stz toin_v
jsr interpret.body
brk
;; Routines to be copied into RAM (at the location $0300).
ram_routines_begin = *
.logical $0300
.dsection ram_routines
.endlogical
ram_routines_end = *
ram_routines_sz = ram_routines_end - ram_routines_begin
;;; Keyboard interrupt handler
;; Both X and Y *must* be saved because we are going to enter
;; 8-bit index mode, so if we don't save these two registers
;; and this interrupt was called while X was unset (i.e. most
;; of the time) then the upper bytes of X and Y would be lost.
;; I didn't save Y originally when I wrote this and it caused
;; lots of strange bugs!
kbint rep #FLAGM|FLAGX ; always push 16-bit values so we know
; what the return stack looks
; like---useful for debugging
.al
.xl
phy
phx
pha
sep #FLAGM|FLAGX ; 8-bit memory and index mode
.as
.xs
ldx kbstate ; load current state into X
lda PD4 ; load port 4 data into A
and #$04 ; data bit is bit 2
;; Jump to handler depending on current state, with status
;; register based on data bit.
jmp (_jtable,x)
;; Start bit: checks that data bit is 0. If so, advances the
;; state; otherwise signals an error.
_start bne _error ; if bit read is not 0, error
bra _next
;; Data bit: shifts bit read into current byte in buffer.
_data beq _noflip ; if data bit is 1, flip parity
inc kbpar
_noflip txy
ldx kbwoff ; load write offset into X
adc #-$04 ; C set iff data bit is 1
ror kbbuf,x ; rotate data bit into current byte
tyx
bra _next
;; Parity bit: errors if parity bit does not match expected
;; parity.
_parity adc #-$04 ; C set iff parity bit is 1
lda kbpar
adc #0 ; A = parity bit + kbpar
;; The parity bit should be set iff an even number of data
;; bits were set. Thus, counting the parity bit, an odd
;; number of bits should have been set, meaning that we expect
;; the LSB of A to be 1.
bit #1 ; test LSB of A
beq _error ; if 0, error
;; Go to the next bit and return.
_next inx
inx
stx kbstate ; increment state by 2 since addresses
; are 2 bytes long
_ret rep #FLAGM|FLAGX
.al
.xl
pla
plx
ply
rti
.as
.xs
;; Stop bit: errors if bit is not 1, otherwise finishes
;; writing the current byte.
_stop beq _error ; if bit read is 0, discard packet
;; Check if key just presssed is ESC. If so, print the
;; program counter at the time of the interrupt before
;; returning for debugging purposes.
ldx kbwoff
lda kbbuf,x ; get the last scancode
cmp $76 ; does it equal escape?
bne _flush ; if not, return from interrupt
rep #FLAGM ; 16-bit accumulator
lda 8,s ; offset 8 on stack is PC
;; print PC to monitor
sep #FLAGM
cli
xba
jsl SEND_HEX_OUT
xba
jsl SEND_HEX_OUT
jsl SEND_CR
sei
bra _error
_flush inc kbwoff
;; Fall through to error since we need to clear the next byte
;; and state anyways.
;; On an error, discard current byte and reset state.
_error ldx kbwoff
stz kbbuf,x ; discard current byte
stz kbstate ; reset state
stz kbpar ; reset parity
bra _ret
;; The jump table used to select the proper handler.
_jtable .word _start
.word _data
.word _data
.word _data
.word _data
.word _data
.word _data
.word _data
.word _data
.word _parity
.word _stop
.al
.xl
;;; Load boot string into memory, convert \n to ' '.
boot = binary("boot.fs")
boot_t .for i:=0, i<len(boot), i+=1
.if boot[i]==10
.byte ' '
.else
.byte boot[i]
.endif
.endfor
;;; Each entry in the dictionary is formatted as a variable-length
;;; name field (as a counted string) followed by a 2-byte field for
;;; the link, followed by the executable code and/or data field. The
;;; entries are laid out sequentially, and the address of the latest
;;; entry is stored in LATEST. Each subroutine is called with JSR and
;;; returns with RTS, and should be called with and return with FLAGM
;;; and FLAGX reset. The parameter stack pointer is stored in X.
last_entry := 0
entry .segment name, word, immediate=false
\name .text len (\word) | (\immediate*precedence), \word
.word last_entry
last_entry := \name
\name.body
.endsegment
;;; --------------------------------
;;; DATA STACK MANIPULATION
;;; --------------------------------
;;; ?DUP ( x -- 0 | x x ) Conditionally duplicate the top item on the
;;; stack if its value is non-zero.
.entry question_dup, "?DUP"
lda 0,x
beq _return ; if x == 0, return
dex
dex
sta 0,x
_return rts
;;; DEPTH ( -- +n ) Return the number of single-cell values that were
;;; on the stack before this word executed.
.entry depth, "DEPTH"
stx tmp
lda #init_psp
sec
sbc tmp
lsr a ; a := (init_psp-x)/2
dex
dex
sta 0,x
rts
;;; DROP ( x -- ) Drop one cell from the stack.
.entry drop, "DROP"
inx
inx
rts
;;; 2DROP ( x1 x2 -- )
.entry two_drop, "2DROP"
inx
inx
inx
inx
rts
;;; DUP ( x -- x x ) Duplicate the top cell on the stack.
.entry dup, "DUP"
lda 0,x
dex
dex
sta 0,x
rts
;;; 2DUP ( x1 x2 -- x1 x2 x1 x2 )
.entry two_dup, "2DUP"
lda 2,x ; a = x1
ldy 0,x ; y = x2
dex
dex
dex
dex
sta 2,x
sty 0,x
rts
;;; NIP ( x1 x2 -- x2 )
.entry nip, "NIP"
lda 0,x
sta 2,x
inx
inx
rts
;;; OVER ( x1 x2 -- x1 x2 x1 )
.entry over, "OVER"
lda 2,x
dex
dex
sta 0,x
rts
;;; 2OVER ( x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2 ) Copy cell pair x1,x2 to
;;; the top of the stack.
.entry two_over, "2OVER"
lda 6,x ; a = x1
ldy 4,x ; y = x2
dex
dex
dex
dex
sta 2,x
sty 0,x
rts
;;; PICK ( +n -- x ) Place a copy of the nth stack entry on top of the
;;; stack, where n=0 refers to the top of the stack.
.entry pick, "PICK"
lda 0,x ; a = n
inc a ; offset n by 1 to account for n's
; place on the stack
asl a ; multiply a by 2 to account for cell
; size
sta tmp
txy
lda (tmp),y ; a gets cell at sp+n*2
sta 0,x
rts
;;; ROT ( x1 x2 x3 -- x2 x3 x1 )
.entry rot, "ROT"
lda 4,x ; a = x1
ldy 0,x ; y = x3
sta 0,x
lda 2,x ; a = x2
sty 2,x
sta 4,x
rts
;;; SWAP ( x1 x2 -- x2 x1 )
.entry swap, "SWAP"
lda 0,x
ldy 2,x
sta 2,x
sty 0,x
rts
;;; 2SWAP ( x1 x2 x3 x4 -- x3 x4 x1 x2 ) Exchange the top two cell
;;; pairs.
.entry two_swap, "2SWAP"
lda 6,x ; a = x1
ldy 2,x ; y = x3
sta 2,x
sty 6,x
lda 4,x ; a = x2
ldy 0,x ; y = x4
sta 0,x
sty 4,x
rts
;;; TUCK ( x1 x2 -- x2 x1 x2 ) Place a copy of the top stack item
;;; below the second stack item.
.entry tuck, "TUCK"
lda 2,x ; a = x1
ldy 0,x ; y = x2
dex
dex
sty 4,x
sta 2,x
sty 0,x
rts
;;; --------------------------------
;;; RETURN STACK MANIPULATION
;;; --------------------------------
;;; 2>R ( S: x1 x2 -- ) ( R: -- x1 x2 )
.entry two_to_r, "2>R"
ply ; pull return address
lda 2,x ; a = x1
pha
lda 0,x ; a = x2
pha
phy ; push return address back
inx
inx
inx
inx
rts
;;; 2R> ( S: -- x1 x2 ) ( R: x1 x2 -- )
.entry two_r_from, "2R>"
dex
dex
dex
dex
ply ; pull return address
pla
sta 0,x
pla
sta 2,x
phy ; push return address back
rts
;;; 2R@ ( S: -- x1 x2 ) ( R: x1 x2 -- x1 x2 )
.entry two_r_fetch, "2R@"
dex
dex
dex
dex
lda 5,s ; a = x2
sta 2,x
lda 3,s ; a = x1
sta 0,x
rts
;;; >R ( S: x -- ) ( R: -- x ) Pop the top item from the data stack
;;; and place it on the return stack.
.entry to_r, ">R"
ply ; pull return address
lda 0,x
inx
inx ; pop x from data stack
pha ; place x on return stack
phy ; push return address back
rts ; return
;;; R> ( S: -- x ) ( R: x -- ) Remove the top item from the return
;;; stack and place it on the data stack.
.entry r_from, "R>"
pla ; pull return address
ply ; pull x
dex
dex
sty 0,x ; push x to data stack
pha ; push return address back
rts ; return
;;; R@ ( S: -- x ) ( R: x -- x ) Place a copy of the item on top of
;;; the return stack onto the data stack.
.entry r_fetch, "R@"
lda 3,s ; load x
dex
dex
sta 0,x ; push x to data stack
rts
;;; UNLOOP ( -- ) ( R: x1 x2 -- ) Drops the top two items from the
;;; return stack.
.entry unloop, "UNLOOP"
pla
ply
ply
pha
rts
;;; --------------------------------
;;; PROGRAMMER CONVENIENCES
;;; --------------------------------
;;; .S ( -- ) Display the stack, for debugging purposes.
.entry dot_s, ".S"
;; Print number of entries on stack.
jsr lit.body
.word '<'
jsr emit.body
jsr bl.body
jsr emit.body
jsr depth.body
jsr dot.body
jsr lit.body
.word '>'
jsr emit.body
jsr bl.body
jsr emit.body
;; Print individual entries on stack.
ldy #init_psp-2
_loop stx tmp
cpy tmp
blt _end ; if y<sp, exit loop
lda 0,y ; get item y is pointing to
dex
dex
sta 0,x ; push it onto stack
phy
jsr dot.body ; print cell
ply
dey
dey ; go to next item
bra _loop
_end rts
;;; ? ( addr -- ) Fetch the contents of the given address and display
;;; the result according to the current base.
.entry question, "?"
jsr fetch.body
jsr dot.body
rts
;;; --------------------------------
;;; ARITHMETIC OPERATIONS
;;; --------------------------------
;;; + ( n1 n2 -- n3 ) Adds n1 to n2 to get n3.
.entry plus, "+"
lda 0,x ; a := n2
clc
adc 2,x ; a += n1
sta 2,x ; n3 := a
inx
inx ; pop n2 off stack
rts
;;; - ( n1 n2 -- n3 ) Subtracts n2 from n1 to get n3.
.entry minus, "-"
lda 2,x ; a := n1
sec
sbc 0,x ; a -= n2
sta 2,x ; n3 := a
inx
inx ; pop n2 off stack
rts
;;; / ( n1 n2 -- n3 ) Divide n1 by n2, leaving the quotient n3.
.entry slash, "/"
jsr slash_mod.body
jsr nip.body
rts
;;; /MOD ( n1 n2 -- n3 n4 ) Divide n1 by n2, leaving the remainder n3
;;; and quotient n4.
.entry slash_mod, "/MOD"
_dividend = tmp
_divisor = tmp+2
_quotient = tmp+4
_neg = tmp+6 ; negative flag (msb)
ldy #0 ; holds negative flag
lda 2,x ; get dividend
sta _dividend ; save dividend
bpl _dividend_pos ; if positive, don't negate
eor #$FFFF
inc a
sta _dividend ; save -dividend
tya
eor #$8000 ; toggle negative flag
tay
_dividend_pos
lda 0,x ; get divisor
sta _divisor ; save divisor
bpl _divisor_pos ; if positive, don't negate
eor #$FFFF
inc a
sta _divisor ; save -divisor
tya
eor #$8000 ; toggle negative flag
tay
_divisor_pos
sty _neg ; save negative flag
;; Shift the divisor leftwards, storing the amount shifted in
;; y. Stop when divisor >= dividend (unsigned); since
;; dividend is at maximum $80, a nonzero divisor will always
;; be greater than or equal to it when shifted enough without
;; losing information.
ldy #0 ; number of shifts
lda _divisor
_shift_loop
cmp _dividend
bge _end_shift ; if divisor >= dividend, no need to
; shift more
asl a ; shift divisor left once
iny ; count the shifts
bra _shift_loop ; try to shift again
_end_shift
sta _divisor ; save shifted divisor
stz _quotient ; initialize quotient to 0
;; Perform long division with y+1 places.
_div_loop
lda _dividend ; load current dividend
sec
sbc _divisor ; subtract the shifted divisor, set
; carry bit if dividend >= divisor
php
rol _quotient ; shift carry bit into quotient (1 if
; dividend >= divisor, 0 otherwise)
plp ; get carry bit from subtraction
blt _no_sub ; if dividend<divisor, don't save the
; subtracted dividend
sta _dividend ; save subtracted dividend
_no_sub lsr _divisor ; shift divisor right once
dey ; count shift
bpl _div_loop ; if y is not negative, do another
; iteration
;; Return quotient and remainder, negated if _neg is set.
lda _dividend ; load remainder
bit _neg
bpl _no_neg_remainder ; if _neg not set, don't negate
; remainder
eor #$FFFF
inc a ; negate remainder
_no_neg_remainder
sta 2,x ; store in n3
lda _quotient ; load quotient
bit _neg
bpl _no_neg_quotient ; if _neg not set, don't negate
; quotient
eor #$FFFF
inc a ; negate quotient
_no_neg_quotient
sta 0,x ; store in n4
rts
;;; 1+ ( n1 -- n2 ) Add one to n1, leaving n2.
.entry one_plus, "1+"
inc 0,x
rts
;;; 1- ( n1 -- n2 ) Subtract one from n1, leaving n2.
.entry one_minus, "1-"
dec 0,x
rts
;;; 2+ ( n1 -- n2 ) Add two to n1, leaving n2.
.entry two_plus, "2+"
lda 0,x
inc a
inc a
sta 0,x
rts
;;; 2- ( n1 -- n2 ) Subtract two from n1, leaving n2.
.entry two_minus, "2-"
lda 0,x
dec a
dec a
sta 0,x
rts
;;; 2* ( x1 -- x2 ) Return x2, the result of shifting x1 one bit
;;; toward the most-significant bit, filling the least significant bit
;;; with zero.
.entry two_star, "2*"
asl 0,x
rts
;;; 2/ ( x1 -- x2 ) Return x2, the result of shifting x1 one bit
;;; toward the least-significant bit, leaving the most-significant bit
;;; unchanged.
.entry two_slash, "2/"
;; Set C flag depending on msb of x1.
lda 0,x
bmi _neg
clc
bra _shift
_neg sec
;; Shift x1.
_shift ror a
sta 0,x
rts
;;; LSHIFT ( x1 u -- x2 ) Perform a logical left shift of u places on
;;; x1, giving x2. Fill the vacated least-significant bits with
;;; zeroes.
.entry lshift, "LSHIFT"
lda 2,x ; a = x1
ldy 0,x ; y = u
beq _end
_loop asl a
dey
bne _loop
sta 2,x
_end inx
inx
rts
;;; MOD ( n1 n2 -- n3 ) Divide n1 by n2, giving the remainder n3.
.entry mod, "MOD"
jsr slash_mod.body
jsr drop.body
rts
;;; RSHIFT ( x1 u -- x2 ) Perform a logical right shift of u places on
;;; x1, giving x2. Fill the vacated most-significant bits with
;;; zeroes.
.entry rshift, "RSHIFT"
lda 2,x ; a = x1
ldy 0,x ; y = u
beq _end
_loop lsr a
dey
bne _loop
sta 2,x
_end inx
inx
rts
;;; --------------------------------
;;; LOGICAL OPERATIONS
;;; --------------------------------
;;; ABS ( n -- +n ) Replace the top stack item with its absolute
;;; value.
.entry abs, "ABS"
lda 0,x
bpl _end
eor #$FFFF
inc a
sta 0,x
_end rts
;;; AND ( x1 x2 -- x3 ) x3 is the bitwise and of x1 and x2.
.entry and_, "AND"
lda 0,x
and 2,x
sta 2,x
inx
inx
rts
;;; INVERT ( x1 -- x2 ) Invert all bits of x1, giving its logical
;;; inverse x2.
.entry invert, "INVERT"
lda 0,x
eor #$FFFF
sta 0,x
rts
;;; MAX ( n1 n2 -- n3 ) Return n3, the greater of n1 and n2.
.entry max, "MAX"
lda 2,x ; a = n1
cmp 0,x ; compare with n2
bpl _greater
lda 0,x
sta 2,x
_greater
inx
inx
rts
;;; MIN ( n1 n2 -- n3 ) Return n3, the lesser of n1 and n2.
.entry min, "MIN"
lda 2,x ; a = n1
cmp 0,x ; compare with n2
bmi _less
lda 0,x
sta 2,x
_less inx
inx
rts
;;; NEGATE ( n -- -n ) Change the sign of the top stack value.
.entry negate, "NEGATE"
lda 0,x
eor #$FFFF
inc a
sta 0,x
rts
;;; OR ( x1 x2 -- x3 ) x3 is the bitwise inclusive or of x1 and x2.
.entry or, "OR"
lda 0,x
ora 2,x
sta 2,x
inx
inx
rts
;;; WITHIN ( x1 x2 x3 -- flag ) Return true if x1 is greater than or
;;; equal to x2 and less than x3. The values may all be either
;;; unsigned integers or signed integers, but must all be the same
;;; type.
.entry within, "WITHIN"
;; To accomodate both signednesses, use the formula x1-x2 <
;; x3-x2 with an unsigned comparison.
lda 0,x ; a = x3
sec
sbc 2,x ; a = x3-x2
sta tmp ; save a in tmp
lda 4,x ; a = x1
sec
sbc 2,x ; a = x1-x2
inx
inx
inx
inx
cmp tmp ; compare x1-x2 to x3-x2
blt _less
stz 0,x
rts
_less lda #-1
sta 0,x
rts
;;; XOR ( x1 x2 -- x3 ) x3 is the bitwise exclusive or of x1 and x2.
.entry xor, "XOR"
lda 0,x
eor 2,x
sta 2,x
inx
inx
rts
;;; --------------------------------
;;; MEMORY OPERATIONS
;;; --------------------------------
;;; ! ( x addr -- ) Stores x into the cell at addr.
.entry store, "!"
lda 2,x
sta (0,x)
txa
clc
adc #4
tax
rts
;;; +! ( x addr -- ) Adds x to the current value of the cell at addr.
.entry plus_store, "+!"
lda 2,x
clc
adc (0,x)
sta (0,x)
txa
clc
adc #4
tax
rts
;;; 2! ( x1 x2 addr -- ) Store the cell pair x1 x2 in the two cells
;;; beginning at addr, removing three cells from the stack. The order
;;; of the two cells in memory is the same as on the stack.
.entry two_store, "2!"
lda 0,x ; load addr into a
sta tmp ; save addr into tmp
lda 2,x ; load x2 into a
sta (tmp) ; save x2 into addr
ldy #2
lda 4,x ; load x1 into a
sta (tmp),y ; store x1 into addr+2
txa
clc
adc #6
tax ; pop 3 cells off stack
rts
;;; 2@ ( addr -- x1 x2 ) Push the cell pair x1 x2 at addr onto the top
;;; of the stack.
.entry two_fetch, "2@"
lda 0,x ; load addr into a
sta tmp ; save addr into tmp
dex
dex ; leave space for x2
lda (tmp) ; load x2
sta 0,x ; store on stack
ldy #2
lda (tmp),y ; load x1
sta 2,x ; store on stack
rts
;;; @ ( addr -- x ) Replace addr with the contents of the cell at
;;; addr.
.entry fetch, "@"
lda (0,x)
sta 0,x
rts
;; ;;; BLANK ( addr u -- ) Set a region of memory, at address addr of
;; ;;; length u, to ASCII blanks.
;; .entry blank, "BLANK"
;; lda 2,x
;; sta tmp ; move addr to tmp
;; ldy #0 ; y stores index
;; _loop cpy 0,x ; compare y to u
;; beq _end
;; stz (tmp),y
;; iny
;; bra _loop
;; _end inx
;; inx
;; inx
;; inx
;; rts
;;; --------------------------------
;;; STRINGS
;;; --------------------------------
;;; For the interpreter, it's easier to just write a definition to
;;; check if two strings are equal.
;; ;;; COMPARE ( addr1 u1 addr2 u2 -- n ) Compare the string specified by
;; ;;; addr1 u1 to the string specified by addr2 u2 and return a result
;; ;;; code n, which is 0 if the strings are equal, -1 if the first