-
Notifications
You must be signed in to change notification settings - Fork 3
/
sasm.asm
3538 lines (3326 loc) · 75.7 KB
/
sasm.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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SASM - Simple/Stupid/Self-hosting Assembler ;;
;; for 16-bit x86 DOS-like Systems. ;;
;; ;;
;; Copyright 2019-2024 Michael Rasmussen ;;
;; See LICENSE.md for details ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; The primary purpose of this assembler is to be able
;; to assemble itself running only under software assembled
;; with itself (excluding the BIOS). DOS is of course the
;; obvious choice as a bootstrapping environment and .COM
;; files are easy to work with, so that's the basis.
;;
;; While the accepted syntax should be a strict subset of
;; what NASM allows and subsequently outputs, there are
;; some important differences (list not exhaustive):
;; * SASM is a one-pass assembler and only performs very
;; basic optimizations.
;; * Literal expression support is pretty basic,
;; especially in memory operands where only addition
;; and subtraction are supported.
;; * The only supported instructions are those that
;; were at some point needed. This can include
;; some operand types not being supported.
;; * NASM warns about resb/resw in code sections and
;; ouputs zeros while SASM doesn't output anything
;; for trailing resb/resw's.
;; * Error checking is limited, and SASM might emit
;; garbage opcode bytes when encountering an
;; illegal instruction (like ADD ES, 4). Check the
;; code with the C version or even better with NASM
;; from time to time.
;; * Non-short jumps aren't synthesized for <386 like
;; NASM does (e.g. jc FarAway -> jnc $+5/jmp FarAway)
;;
;; Development was generally done by first implementing
;; support for the new instruction/directive/etc. in the
;; accompanying C-version assembler (while being careful
;; to write in a fashion that would be implementable in
;; assembly), and then getting this version up to date.
;;
;; Starting out, it was hard to know whether everything
;; would fit in 64K, so I opted to play it safe and
;; use far pointers in most places I anticipated it could
;; become necessary. This helped stress test the assembler
;; as well as my sanity. That's why some (most) things are
;; dimensioned for sizes that haven't be necessary yet.
;;
;; Assemble using: nasm -f bin -o sasm.com sasm.asm
;; or sasm sasm.asm
;;
;; Calling convention (unless otherwise mentioned):
;;
;; Callee-saved: DS, BP, SI, DI
;; Scratch: AX, BX, CX, DX, ES
;; Return value: (DX:)AX or BX for pointer values,
;; boolean values via the carry flag
;;
cpu 8086
org 0x100
STACK_SIZE equ 1024 ; TODO: Figure out correct size..
TOKEN_MAX equ 16 ; Maximum length of token (adjust token buffer if increasing)
INST_MAX equ 6 ; Maximum length of directive/instruction (LOOPNE is longest)
BUFFER_SIZE equ 512 ; Size of input buffer
OUTPUT_MAX equ 0x2000 ; Maximum output size
LABEL_MAX equ 300 ; Maximum number of labels
FIXUP_MAX equ 600 ; Maximum number of fixups
EQU_MAX equ 100 ; Maximum number of equates
DISPATCH_SIZE equ INST_MAX+3 ; Size of DispatchListEntry
LABEL_ADDR equ 18 ; Offset of label address
LABEL_FIXUP equ 20 ; Offset of label fixup
LABEL_SIZE equ 22 ; Size of Label (TOKEN_MAX+2+2*sizeof(WORD))
FIXUP_ADDR equ 0 ; Offset of fixup address (into the output buffer)
FIXUP_LINK equ 2 ; Offset of fixup link pointer (INVALID_ADDR terminates list)
FIXUP_TYPE equ 4 ; Offset of fixup type (FIXUP_DISP16 or FIXUP_REL8)
FIXUP_SIZE equ 5 ; Size of Fixup
EQU_VAL equ 18 ; Offset of value in equate
EQU_SIZE equ 20 ; Size of equate (TOKEN_MAX+2+sizeof(WORD))
IF_MAX equ 8 ; Max %if nesting depth
QUOTE_CHAR equ 39 ; '\''
HEX_ADJUST equ 'A'-'0'-10
INVALID_ADDR equ 0xFFFF
; Value of Operand(L)Type:
; Less than 0xC0: Memory access with ModRM = OperandType
OP_REG equ 0xC0
OP_LIT equ 0xC1
; Register indices (OperandValue when OperandType = OP_REG)
; Lower 3 bits matches index, bits 4&5 give class (r8, r16, sreg)
R_AL equ 0x00
R_CL equ 0x01
R_DL equ 0x02
R_BL equ 0x03
R_AH equ 0x04
R_CH equ 0x05
R_DH equ 0x06
R_BH equ 0x07
R_AX equ 0x08
R_CX equ 0x09
R_DX equ 0x0a
R_BX equ 0x0b
R_SP equ 0x0c
R_BP equ 0x0d
R_SI equ 0x0e
R_DI equ 0x0f
R_ES equ 0x10
R_CS equ 0x11
R_SS equ 0x12
R_DS equ 0x13
; Condition Codes
CC_O equ 0x0
CC_NO equ 0x1
CC_C equ 0x2
CC_NC equ 0x3
CC_Z equ 0x4
CC_NZ equ 0x5
CC_NA equ 0x6
CC_A equ 0x7
CC_S equ 0x8
CC_NS equ 0x9
CC_PE equ 0xa
CC_PO equ 0xb
CC_L equ 0xc
CC_NL equ 0xd
CC_NG equ 0xe
CC_G equ 0xf
; Fixup types
FIXUP_DISP16 equ 0
FIXUP_REL8 equ 1
; Flags for %if handling
IF_ACTIVE equ 1 ; Current %if/%elif/%else is active
IF_WAS_ACTIVE equ 2 ; A block has previously been active (= skip %elif/%else)
IF_SEEN_ELSE equ 4 ; %else has been passed, only %endif is legal
ProgramEntry:
; Clear BSS
mov di, BSS
mov cx, ProgramEnd
sub cx, di
xor al, al
rep stosb
; First free paragraph is at the end of the program
; COM files get the largest available block
mov ax, ProgramEnd
add ax, 15
and ax, 0xfff0
add ax, STACK_SIZE
cli
mov sp, ax
sti
mov cl, 4
shr ax, cl
mov bx, cs
add ax, bx
mov [FirstFreeSeg], ax
call Init
call MainLoop
call Fini
xor al, al
jmp Exit
ParseCommandLine:
mov cl, [0x80]
inc cl ; Let count include CR
mov si, 0x81
mov di, InFileName
call CopyFileName
mov di, OutFileName
call CopyFileName
cmp byte [InFileName], 0
jne .HasIn
mov word [InFileName], 'A.'
mov word [InFileName+2], 'AS'
mov word [InFileName+4], 'M'
.HasIn:
cmp byte [OutFileName], 0
jne .Ret
mov di, OutFileName
mov si, InFileName
.Copy:
lodsb
cmp al, '.'
jbe .AppendExt
stosb
jmp .Copy
.AppendExt:
mov ax, '.C'
stosw
mov ax, 'OM'
stosw
xor al, al
stosb
.Ret:
ret
CopyFileName:
and cl, cl
jz .Done
cmp byte [si], 0x0D
je .Done
; Skip spaces
.SkipS:
cmp byte [si], ' '
jne .NotSpace
inc si
dec cl
jnz .SkipS
jmp short .Done
.NotSpace:
mov ch, 12
.Copy:
cmp byte [si], ' '
jbe .Done
movsb
dec cl
jz .Done
dec ch
jnz .Copy
.Done:
xor al, al
stosb
ret
Init:
call ParseCommandLine
mov bx, MsgHello
call PutString
mov bx, InFileName
call PutString
mov bx, MsgHello2
call PutString
mov bx, OutFileName
call PutString
call PutCrLf
mov ax, OUTPUT_MAX
mov bx, 1
call Malloc
mov [OutputSeg], ax
mov ax, LABEL_MAX
mov bx, LABEL_SIZE
call Malloc
mov [LabelSeg], ax
mov ax, FIXUP_MAX
mov bx, FIXUP_SIZE
call Malloc
mov [FixupSeg], ax
; Initial fixup list
mov es, ax
xor bx, bx
mov cx, FIXUP_MAX
.FixupInit:
mov ax, bx
add ax, FIXUP_SIZE
mov [es:bx+FIXUP_LINK], ax
mov bx, ax
dec cx
jnz .FixupInit
mov word [es:bx+FIXUP_LINK-FIXUP_SIZE], INVALID_ADDR ; Terminate free list
mov ax, EQU_MAX
mov bx, EQU_SIZE
call Malloc
mov [EquSeg], ax
mov byte [CpuLevel], 3
call ParserInit
ret
Fini:
call ParserFini
; Check for open %if blocks
cmp byte [NumIfs], 0
je .IfsOK
mov bx, MsgErrIfActive
jmp short Error
.IfsOK:
; Check if there are undefined labels
mov es, [LabelSeg]
xor bx, bx
mov cx, [NumLabels]
and cx, cx
jz .Done
.CheckLabels:
cmp word [es:bx+LABEL_ADDR], INVALID_ADDR
jne .Next
call PrintLabel
push cs
mov bx, MsgErrLabelUnd
jmp short Error
.Next:
add bx, LABEL_SIZE
dec cx
jnz .CheckLabels
.Done:
jmp WriteOutput
NotImplemented:
mov bx, MsgErrNotImpl
; Fall through
; Exit and print error message in BX
Error:
; Error in line ...
push bx
call PutCrLf
mov bx, MsgErrInLine
call PutString
mov ax, [CurrentLine]
call PutDec
mov al, ':'
call PutChar
mov al, ' '
call PutChar
pop bx
call PutString
call PutCrLf
; Print curren token
cmp byte [TokenLen], 0
jz .NoTok
mov bx, MsgCurToken
call PutString
mov bx, Token
call PutString
mov al, '"'
call PutChar
call PutCrLf
.NoTok:
mov al, 0xff
; Fall through
; Exit with error code in AL
Exit:
mov ah, 0x4c
int 0x21
MainLoop:
;
; Check if we're in a conditionally disabled block
;
xor bh, bh
mov bl, [NumIfs]
sub bl, 1
jc .NoIfs
test byte [Ifs+bx], IF_ACTIVE
jnz .NoIfs
.Skip:
mov al, [CurrentChar]
and al, al
jz .Done
cmp al, '%'
je .CheckDir
call MoveNext
jmp .Skip
.CheckDir:
call .GetToken
mov ax, [UToken+1]
cmp ax, 'IF'
je .Dispatch
cmp ax, 'EL'
je .Dispatch
cmp ax, 'EN'
je .Dispatch
jmp .Skip
.NoIfs:
; Grab next token
call .GetToken
.Dispatch:
cmp byte [TokenLen], 0
je .Done
; Dispatch
call Dispatch
jmp MainLoop
.Done:
mov al, [CurrentChar]
and al, al
jz .Ret
push ax
call PutHexByte
mov al, ' '
call PutChar
pop ax
call PutChar
call PutCrLf
mov bx, MsgErrNotDone
call Error
.Ret:
ret
.GetToken:
; Update line counter
xor ax, ax
xchg ax, [NumNewLines]
add [CurrentLine], ax
jmp GetToken
Dispatch:
push si
mov al, ':'
call TryConsume
jc .NotLabel
call DefineLabel
jmp short .Done
.NotLabel:
mov si, DispatchList
; Is the token too short/long to be a valid instruction/directive?
mov al, [TokenLen]
cmp al, 2
jb .CheckEQU
cmp al, INST_MAX
ja .CheckEQU
; Initialize fixup pointers
mov ax, INVALID_ADDR
mov [CurrentFixup], ax
mov [CurrentLFixup], ax
; And ExplicitSize
mov byte [ExplicitSize], 0
.Compare:
xor bx, bx
.CLoop:
mov al, [UToken+bx]
cmp al, [si+bx]
jne .Next
and al, al ; at NUL terminator?
jz .Match
inc bl
cmp bl, INST_MAX
jb .CLoop
.Match:
; Found match, dispatch
xor ah, ah
mov al, [si+INST_MAX]
mov bx, [si+INST_MAX+1]
call bx
cmp word [CurrentFixup], INVALID_ADDR
jne .FixupUnhandled
cmp word [CurrentLFixup], INVALID_ADDR
jne .FixupUnhandled
jmp short .Done
.FixupUnhandled:
mov bx, MsgErrFixupUnh
jmp Error
.Next:
add si, DISPATCH_SIZE
cmp si, DispatchListEnd
jb .Compare
.CheckEQU:
; Avoid clobbering Token
mov al, 'E'
call TryGetU
jc .Invalid
mov al, 'Q'
call TryGetU
jc .Invalid
mov al, 'U'
call TryGetU
jc .Invalid
call SkipWS
call MakeEqu
.Done:
pop si
ret
.Invalid:
mov bx, MsgErrUnknInst
jmp Error
; Allocate AX*BX bytes, returns segment in AX
Malloc:
; Calculate how many paragraphs are needed
mul bx
and dx, dx
jnz .Err ; Overflow
add ax, 15
mov cl, 4
shr ax, cl
mov cx, [FirstFreeSeg]
add ax, cx
cmp ax, [2] ; (PSP) Segment of the first byte beyond the memory allocated to the program
jae .Err ; Out of memory
mov [FirstFreeSeg], ax
mov ax, cx
ret
.Err:
mov bx, MsgErrMem
jmp Error
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Screen Output
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write character in AL
PutChar:
mov dl, al
mov ah, 2
int 0x21
ret
; Write CR and LF
PutCrLf:
mov al, 13
call PutChar
mov al, 10
jmp PutChar
; Write ASCIIZ string in BX
PutString:
mov al, [bx]
and al, al
jz .Done
push bx
call PutChar
pop bx
inc bx
jmp PutString
.Done:
ret
; Write decimal word in AX
PutDec:
push di
mov di, sp ; Assumes DS=SS
sub sp, 6
dec di
mov byte [di], 0
mov bx, 10
.DecLoop:
xor dx, dx
div bx
add dl, '0'
dec di
mov [di], dl
and ax, ax
jnz .DecLoop
mov bx, di
call PutString
add sp, 6
pop di
ret
; Write hexadecimal word in AX
PutHex:
push ax
mov al, ah
call PutHexByte
pop ax
; Fall through
; Write hexadecimal byte in AX
PutHexByte:
push ax
shr al, 1
shr al, 1
shr al, 1
shr al, 1
call PutHexDigit
pop ax
; Fall through
; Write hexadecimal digit in AL
PutHexDigit:
and al, 0x0f
add al, '0'
cmp al, '9'
jbe PutChar
add al, HEX_ADJUST
jmp PutChar
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; File Output
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write output buffer to file
WriteOutput:
; Create file
mov dx, OutFileName
mov cx, 0x0020 ; Attributes
mov ah, 0x3c
int 0x21
jc .Error
mov si, ax ; Save file handle in SI
; Write
mov ah, 0x40
mov bx, si
mov cx, [NumOutBytes]
; ds:dx -> buffer
mov dx, [OutputSeg]
push ds
mov ds, dx
xor dx, dx
int 0x21
pop ds
jc .Error
cmp cx, [NumOutBytes]
jne .Error
; Close file
mov ax, 0x3e00
mov bx, si
int 0x21
ret
.Error:
mov bx, MsgErrOutput
jmp Error
; Output byte in AL to output buffer
; Doesn't modify any registers
OutputByte:
push cx
push di
push es
mov es, [OutputSeg]
mov di, [NumOutBytes]
xor cx, cx
xchg cx, [PendingZeros]
and cx, cx
jnz .Zeros
cmp word [NumOutBytes], OUTPUT_MAX
jb .Output
.Overflow:
mov bx, MsgErrOutMax
jmp Error
.Output:
stosb
inc word [NumOutBytes]
inc word [CurrentAddress]
pop es
pop di
pop cx
ret
.Zeros:
add [NumOutBytes], cx
cmp word [NumOutBytes], OUTPUT_MAX
jae .Overflow
push ax
xor al, al
rep stosb
pop ax
jmp .Output
; Output word in AX
OutputWord:
call OutputByte
mov al, ah ; Only works because OutputByte is friendly
jmp OutputByte
; Output 8-bit immediate if AL is 0, output 16-bit immediate otherwise
; the immediate is assumed to be in OperandValue
OutputImm:
and al, al
jz OutputImm8
; Fall through
; Output 16-bit immediate from OperandValue
OutputImm16:
cmp word [CurrentFixup], INVALID_ADDR
je .Output
mov al, FIXUP_DISP16
call RegisterFixup
.Output:
mov ax, [OperandValue]
jmp OutputWord
; Output 8-bit immediate from OperandValue
OutputImm8:
mov al, [OperandValue]
jmp OutputByte
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Parser
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ParserInit:
mov word [CurrentLine], 1
; Open file for reading
mov dx, InFileName
mov ax, 0x3d00
int 0x21
jnc .OK
mov bx, MsgErrOpenIn
jmp Error
.OK:
mov [InputFile], ax
call FillInBuffer
call MoveNext
ret
ParserFini:
; Close input file
mov ax, 0x3e00
mov bx, [InputFile]
int 0x21
ret
FillInBuffer:
mov ah, 0x3f
mov bx, [InputFile]
mov cx, BUFFER_SIZE
mov dx, InputBuffer
int 0x21
jc .ReadError ; error code in AX
mov [InputBufferBytes], ax
mov word [InputBufferPos], 0
ret
.ReadError:
mov bx, MsgErrRead
jmp Error
ReadNext:
mov bx, [InputBufferPos]
cmp bx, [InputBufferBytes]
jb .HasData
call FillInBuffer
xor bx, bx
and ax, ax
jz .EOF
.HasData:
mov al, [InputBuffer+bx]
inc bx
mov [InputBufferPos], bx
.EOF:
mov [CurrentChar], al
cmp al, 10
jne .Ret
mov byte [GotNL], 1
inc word [NumNewLines]
.Ret:
ret
; Try to get character in AL and ReadNext. Returns carry clear on success.
; I.e. it doesn't skip spaces after consuming the character.
TryGet:
cmp [CurrentChar], al
jne .NoMatch
call ReadNext
clc
ret
.NoMatch:
stc
ret
; Like TryGet but case insensitive
TryGetU:
call TryGet
jc .NoMatch
ret
.NoMatch:
or al, ' ' ; ToLower
jmp TryGet
SkipWS:
mov al, [CurrentChar]
and al, al
jz .Done
cmp al, ' '
ja .CheckComment
call ReadNext
jmp SkipWS
.CheckComment:
cmp al, ';'
jne .Done
.SkipComment:
call ReadNext
mov al, [CurrentChar]
and al, al
jz .Done
cmp al, 10
je SkipWS
jmp .SkipComment
.Done:
ret
MoveNext:
call ReadNext
jmp SkipWS
; Consume CurrentChar if it matches AL, move next and
; return carry clear. Carry is set ortherwise.
; AL is left unchanged (for Expect)
TryConsume:
cmp [CurrentChar], al
jne .NoMatch
call MoveNext
clc
ret
.NoMatch:
stc
ret
; Abort if the next character isn't AL
Expect:
call TryConsume
jc .Error
ret
.Error:
mov bx, MsgErrExpected
mov [bx], al
jmp Error
; Get number from Token to AX
GetTokenNumber:
xor ax, ax
mov bx, UToken
cmp word [bx], '0X'
je .Hex
; Check for 'H' suffix
xor cx, cx
mov cl, [TokenLen]
push si
mov si, cx
cmp byte [bx+si-1], 'H'
pop si
je .HexSuffix
; Decimal number
xor ch, ch
.Dec:
mov cl, [bx]
and cl, cl
jnz .NotDone
ret
.NotDone:
inc bx
mov dx, 10
mul dx
and dx, dx
jnz .Error
sub cl, '0'
cmp cl, 9
ja .Error
add ax, cx
jmp .Dec
.HexSuffix:
dec cl
cmp byte [bx], '0'
jne .HexCheckLen
inc bx
dec cl
jnz .HexCheckLen
ret
.Hex:
add bx, 2
mov cl, [TokenLen]
sub cl, 2
.HexCheckLen:
cmp cl, 4
ja .Error
.HexCvt:
shl ax, 1
shl ax, 1
shl ax, 1
shl ax, 1
mov ch, [bx]
inc bx
sub ch, '0'
cmp ch, 9
jbe .Add
sub ch, HEX_ADJUST
.Add:
or al, ch
dec cl
jnz .HexCvt
ret
.Error:
mov bx, MsgErrInvalidNum
jmp Error
GetToken:
push di
xor di, di
.Get:
mov al, [CurrentChar]
mov ah, al
cmp al, '.'
je .Store
cmp al, '_'
je .Store
cmp al, '$'
je .Store
cmp al, '%'
je .Store
; IsDigit
mov bh, al
sub bh, '0'
cmp bh, 9
jbe .Store
mov bh, al
and bh, 0xDF ; to upper case
sub bh, 'A'
cmp bh, 26
ja .Done
and ah, 0xDF
.Store:
cmp di, TOKEN_MAX
jae .Next ; don't store if beyond limit
mov [Token+di], al
mov [UToken+di], ah
inc di
.Next:
call ReadNext
jmp .Get
.Done:
xor al, al
mov [Token+di], al ; zero-terminate
mov [UToken+di], al ; zero-terminate
mov [IsTokenNumber], al
mov ax, di
mov [TokenLen], al
pop di
call SkipWS
mov al, [Token]
sub al, '0'
cmp al, 9
ja .CheckSpecial
call GetTokenNumber
jmp short .HasNum
.CheckSpecial:
cmp word [Token], '$'
jne .CheckSpecial2
mov ax, [CurrentAddress]
jmp short .HasNum
.CheckSpecial2:
cmp byte [TokenLen], 2
jne .CheckEqu
cmp word [Token], '$$'
jne .CheckEqu
mov ax, [SectionStart]
jmp short .HasNum
.CheckEqu:
call FindEqu
cmp bx, INVALID_ADDR
je .NotEqu
; Found EQU
mov ax, [es:bx+EQU_VAL]
.HasNum:
mov word [OperandValue], ax
mov byte [IsTokenNumber], 1
.NotEqu:
ret
; Read one or two character literal to OperandValue
; Assumes initial quote character has been consumed
GetCharLit:
xor ah, ah
mov al, [CurrentChar]
mov [OperandValue], ax
call ReadNext
mov al, QUOTE_CHAR
call TryConsume
jnc .Done
mov al, [CurrentChar]
mov [OperandValue+1], al
call ReadNext
mov al, QUOTE_CHAR
call Expect
.Done:
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Literal Expression Parser
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
MAX_PREC equ 0xFF
OPER_LSH equ '<'|0x80
OPER_RSH equ '>'|0x80
OPER_LEQ equ '<'|0xC0