-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathassembler.py
More file actions
1707 lines (1576 loc) · 85.1 KB
/
assembler.py
File metadata and controls
1707 lines (1576 loc) · 85.1 KB
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
from common import *
###- CONSTANTS -###
# Comment symbols
COMMENT_SYMBOLS = ['#', ';', '//']
# Operand separators
OPERAND_SEPARATORS = ', \t'
# Space characters for inbetween operands and instruction
INSTRUCTION_SPACING = ' \t'
# Opcode position in first word; how much to shift opcode relative to the first word (in bits, left)
OPCODE_POSITION = 12
# Word length in bits
WORD_LENGTH = 16
# Max length an instruction can have (in words)
INSTRUCTION_MAX_LENGTH = 1
###- ALL POSSIBLE OPERANDS -###
# Operands
# Format : '<1 char label>':[<position in word>, <word position>, <bit length>, <1: signed, 0: unsigned>, <type flags>, "<full name>"]
# Type flags :
## Bit 0 is allow immediates
## Bit 1 is allow registers
## Bit 2 is allow mem locations
## Bit 3 is reserved for the merge flag
## Bit 4 is reserved for indicating that a mem location parameter is at the beginning of the list
## Bit 5 is reserved for indicating that a mem location parameter is at the end of the list
## Bit 6 is reserved for the "ANY" flag, which overrides type-checking if the type is "immediate" (or no type information)
## For the first word in every line, bits 0-2 all have to be 0, and bits 3-5 represent the type of the line (Label, Definition, ORG, DB, Instruction)
## The rest are up to you
OPERANDS= {
'F' : [0, 0, 4, 0, 0b1000010, "Register C"], # Register C
'A' : [8, 0, 4, 0, 0b1000010, "Register A"], # Register A
'B' : [4, 0, 4, 0, 0b1000010, "Register B"], # Register B
'I' : [0, 0, 8, 1, 0b1000001, "Immediate"], # Immediate
'C' : [10, 0, 2, 0, 0b1000001, "Condition"], # Condition
'O' : [0, 0, 4, 1, 0b1000001, "Offset"], # Offset
'M' : [0, 0, 10, 0, 0b1110101, "Memory Address"], # Memory address
}
###- NATIVE INSTRUCTIONS -###
# Format: '<mnemonic>':[<opcode>, '<operand flags in order of use in opcode>', <opcode specific mask>, <size in user-defined words>]
# [A=0] means operand A is optional and defaults to 0
# - parameter type requirements are derived from operand flag type flags
OPCODES = {
'nop':[[0x0, '', 0x0000, 1],], # nop : Does nothing
'hlt':[[0x1, '', 0x0000, 1],], # hlt : Halts machine
'add':[[0x2, 'ABF', 0x0000, 1],], # add A B C : pseudo-code: C <- A + B
'sub':[[0x3, 'ABF', 0x0000, 1],], # sub A B C : pseudo-code: C <- A - B
'nor':[[0x4, 'ABF', 0x0000, 1],], # nor A B C : pseudo-code: C <- !(A | B)
'and':[[0x5, 'ABF', 0x0000, 1],], # and A B C : pseudo-code: C <- A & B
'xor':[[0x6, 'ABF', 0x0000, 1],], # xor A B C : pseudo-code: C <- A ^ B
'rsh':[[0x7, 'AF', 0x0000, 1],], # rsh A C : pseudo-code: C <- A >> 1 (logical shift)
'ldi':[[0x8, 'AI', 0x0000, 1],], # ldi A immediate : pseudo-code: A <- immediate
'adi':[[0x9, 'AI', 0x0000, 1],], # adi A immediate : pseudo-code: A <- A + immediate
'jmp':[[0xA, 'M', 0x0000, 1],], # jmp address : pseudo-code: PC <- address
'brh':[[0xB, 'CM', 0x0000, 1],], # brh condition address : pseudo-code: PC <- condition ? address : PC + 1
'cal':[[0xC, 'M', 0x0000, 1],], # cal address : pseudo-code: PC <- address (and push PC + 1 to stack)
'ret':[[0xD, '', 0x0000, 1],], # ret : pseudo-code: PC <- top of stack (and pop stack)
'lod':[[0xE, 'AB[O=0]', 0x0000, 1],], # lod A B [offset=0] : pseudo-code: B <- mem[A + offset]
'str':[[0xF, 'AB[O=0]', 0x0000, 1],], # str A B [offset=0] : pseudo-code: mem[A + offset] <- B
} # Opcodes
###- PSEUDO-INSTRUCTIONS -###
# Pseudo-instructions
# Format : 'label':['<resolution as formatted string>', '<operand flags in order>']
# - instructions must be separated by newlines ('\n')
# - parameter type requirements are derived from operand flag type flags
PSEUDO_INSTRUCTIONS = {
# pseudo-instructions from Matt's assembler
'cmp':[['sub {0} {1} r0', 'AB'],], # cmp A B : sub A B r0 # do a comparison between registers A, B and set the flags accordingly
'mov':[['add {0} r0 {1}', 'AF'],], # mov A C : add A r0 C # move contents of register A to register C
'lsh':[['add {0} {0} {1}', 'AF'],], # lsh A C : add A A C # do a "left-shift by 1" operation on register A and store the result in C
'inc':[['adi {0} 1', 'A'],], # inc A : adi A 1 # increment register A by 1
'dec':[['adi {0} -1', 'A'],], # dec A : adi A -1 # decrement register A by 1
'not':[['nor {0} r0 {1}', 'AF'],], # not A C : nor A r0 C # do the bitwise "NOT" operation on register A and store the result in C
#--------------------------------------------------------------------------------#
'nnd':[['and {0} {1} {2}\n'+ # nnd A B C : and A B C
'not {2} {2}' , 'ABF'],], # not C C # do the bitwise "not AND" operation on registers A, B and store the result in C
'xnr':[['xor {0} {1} {2}\n'+ # xnr A B C : xor A B C
'not {2} {2}' , 'ABF'],], # not C C # do the bitwise "not XOR" operation on registers A, B and store the result in C
'orr':[['nor {0} {1} {2}\n'+ # orr A B C : nor A B C
'not {2} {2}' , 'ABF'],], # not C C # do the bitwise "OR" operation on registers A, B and store the result in C
'nim':[['nor {0} r0 {2}\n'+ # nim A B C : nor B
'and {2} {1} {2}' , 'ABF'],], # and C A C # do the bitwise "not IMPLIES" operation on registers A, B and store the result in C
# !(A -> B) = A & (!B)
'imp':[['nim {0} {1} {2}\n'+ # imp A B C : nim A B C
'not {2} {2}' , 'ABF'],], # not C C # do the bitwise "IMPLIES" operation on registers A, B and store the result in C
# A -> B = !(!(A -> B))
#--------------------------------------------------------------------------------#
'use_devices': [['ldi {0} 248', 'A'],], # use_display rbp : ldi rbp 240 # store pixel display's base pointer in rbp
'set_x': [['str {0} {1} -8', 'AB'],], # set_x rbp rX : str rX rbp 0 # store value at rX into pixel display's X port
'set_xi': [['ldi {1} {2}\n'+ # set_xi rbp rBuf imm : ldi rBuf imm
'set_x {0} {1}' , 'ABI'],], # set_x rbp rBuf # store immediate value into pixel display's X port
'set_y': [['str {0} {1} -7', 'AB'],], # set_y rbp rY : str rY rbp 1 # store value at rY into pixel display's Y port
'set_yi': [['ldi {1} {2}\n'+ # set_yi rbp rBuf imm : ldi rBuf imm
'set_y {0} {1}' , 'ABI'],], # set_y rbp rBuf # store immediate value into pixel display's Y port
'set_pixel': [['str {0} r0 -6', 'A'],], # set_pixel rbp : str r0 rbp 2 # trigger pixel display's Draw Pixel port to draw current pixel
'clr_pixel': [['str {0} r0 -5', 'A'],], # clr_pixel rbp : str r0 rbp 3 # trigger pixel display's Clear Pixel port to clear current pixel
'get_pixel': [['lod {0} {1} -4', 'AB'],], # get_pixel rbp rDest : lod rDest rbp 4 # load pixel at current pixel position
'cpy_disp_buffer': [['str {0} r0 -3', 'A'],], # cpy_disp_buffer rbp : str r0 rbp 5 # copy pixel display buffer to screen
'clr_disp_buffer': [['str {0} r0 -2', 'A'],], # clr_disp_buffer rbp : str r0 rbp 6 # clear pixel display buffer
'clr_display':[['clr_disp_buffer {0}\n'+ # clr_display rbp : clr_disp_buffer rbp
'cpy_disp_buffer {0}' , 'A'],], # cpy_disp_buffer rbp # clear both display and display buffer
#--------------------------------------------------------------------------------#
'add_char': [['str {0} {1} -1', 'AB'],], # add_char rbp rChar : str rChar rbp 0 # append character at rChar to character display buffer
'add_chari': [['ldi {1} {2}\n'+ # add_chari rbp rBuf imm : ldi rBuf imm
'add_char {0} {1}', 'ABI'],], # add_char rbp rBuf
# append immediate character imm to character display buffer
'cpy_char_buffer': [['str {0} r0 0', 'A'],], # cpy_char_buffer rbp : str r0 rbp 1 # copy character display buffer to char display
'clr_char_buffer': [['str {0} r0 1', 'A'],], # clr_char_buffer rbp : str r0 rbp 2 # clear character display buffer
'clr_char_display': [['clr_char_buffer {0}\n'+ # clr_char_display rbp : clr_char_buffer rbp
'cpy_char_buffer {0}' , 'A'],], # cpy_char_buffer rbp
# clear both char display and buffer
#--------------------------------------------------------------------------------#
'set_num': [['str {0} {1} 2', 'AB'],], # set_num rbp rNum : str rNum rbp # set number display's buffer to number in rNum
'set_numi': [['ldi {1} {2}\n'+ # set_numi rbp rBuf imm : ldi rBuf imm
'set_num {0} {1}', 'ABI'],], # set_num rbp rBuf
# set number display's buffer to immediate imm
'clr_num_display': [['str {0} r0 3', 'A'],], # clr_num_display rbp : str r0 rbp 1 # clear number display
'num_mode_signed': [['str {0} r0 4', 'A'],], # num_mode_signed rbp : str r0 rbp 2 # set number display to signed mode
'num_mode_unsigned':[['str {0} r0 5', 'A'],], # num_mode_unsigned rbp : str r0 rbp 3 # set number display to unsigned mode
#--------------------------------------------------------------------------------#
'get_rng': [['lod {0} {1} 6', 'AB'],], # get_rng rbp rDest : lod rDest rbp # put a random number in rDest
'get_cont_state': [['lod {0} {1} 7', 'AB'],], # get_cont_state rbp rDest : lod rDest rbp
# put the controller's current state in rDest
}
###- STARTING SYMBOLS -###
# Dictionary that the assembler starts with
# [<resolution value>, <custom type flags>]
# - in most situations custom type flags aren't needed, unless you need custom behavior with the type system.
STARTING_SYMBOLS = {
'r0': [0, 0,], 'r1': [1, 0,], 'r2': [2, 0,], 'r3': [3, 0,], # Registers
'r4': [4, 0,], 'r5': [5, 0,], 'r6': [6, 0,], 'r7': [7, 0,],
'r8': [8, 0,], 'r9': [9, 0,], 'r10': [10, 0,], 'r11': [11, 0,],
'r12': [12, 0,], 'r13': [13, 0,], 'r14': [14, 0,], 'r15': [15, 0,],
'eq': [0, 0,], 'ne': [1, 0,], 'ge': [2, 0,], 'lt': [3, 0,], # Conditions
'=': [0, 0,], '!=': [1, 0,], '>=': [2, 0,], '<': [3, 0,],
'z': [0, 0,], 'nz': [1, 0,], 'c': [2, 0,], 'nc': [3, 0,],
'zero': [0, 0,], 'notzero': [1, 0,], 'carry': [2, 0,], 'notcarry': [3, 0,],
'pixel_x': [240, 0,], # Screen
'pixel_y': [241, 0,],
'draw_pixel': [242, 0,],
'clear_pixel': [243, 0,],
'load_pixel': [244, 0,],
'buffer_screen': [245, 0,],
'clear_screen_buffer': [246, 0,],
'write_char': [247, 0,], # Char display
'buffer_chars': [248, 0,],
'clear_chars_buffer': [249, 0,],
'show_number': [250, 0,],
'clear_number': [251, 0,],
'signed_mode': [252, 0,],
'unsigned_mode': [253, 0,],
'rng': [254, 0,], # Controller
'controller_input': [255, 0,],
}
###- UTILITY -###
# Define check
def is_define(word: str):
if(len(word) == 0):
return 0
return word == 'define'
# Label check
def is_label(word: str):
if(type(word) == int):
return 0
if(len(word) == 0):
return 0
return (word[0] == '.') | ((word[-1] == ':') << 1)
# Symbol check
def is_symbol(word: list, symbols: dict):
if(type(word[0]) == int):
return False
if(len(word[0]) == 0):
return False
return (word[0].lower() in symbols)
# Definition check
def is_definition(word: list, symbols: dict):
if(type(word[0]) == int):
return False
if(len(word[0]) == 0):
return False
return (word[0] in symbols)
# Merge offset parameters
def merge_offset_parameters(line: list, is_resolved: bool = False):
idx = 1
params = line[0]
while(idx != len(params)):
A = params[idx - 1]
B = params[idx]
if(B[1] & 0x8):
if(not ((A[1] & 0x1) and (B[1] & 0x1))):
fatal_error('assembler', f"merge_offset_parameters: on line {line[1]}, cannot merge \'{display_type(A, True)}\' and \'{display_type(B, True)}\'")
if((type(A[0]) == int) and (type(B[0]) == int)):
A[0] = A[0] + B[0]
elif(is_resolved):
fatal_error('assembler', f"merge_offset_parameters: line {line[1]} has unresolved parameters, couldn\'t merge.\n{rec_dump_array(line, 1)}\n{recompose_line(line)}")
else:
A[0] = f"{display_word([A[0], A[1] & (~0x30)])}, {display_word([B[0], B[1] & (~0x30)])}"
A[1] |= B[1] & 0x30
line[0].pop(idx)
continue
idx += 1
# Merge offset parameters, but output as a copy and only with the types
def merge_offset_types(line:list) -> list:
line_copy = deep_copy(line)
merge_offset_parameters(line_copy, False)
params = []
for param in line_copy[0]:
params.append(param[1])
return params
# Pseudo-instruction check
def is_pseudo(line: list):
if(len(line[0]) == 0):
fatal_error('assembler', f"is_pseudo: line {line[1]} is empty..? it should\'ve been filtered out by the assembler..\n{rec_dump_array(line, 1)}\n{resolve_line(line)}")
label = line[0][0][0]
if(label in PSEUDO_INSTRUCTIONS):
variations = PSEUDO_INSTRUCTIONS[label]
merged_param_types = merge_offset_types(line)[1:]
variant = -1
for variant_index in range(len(variations)):
if(variant > -1):
break
variation = variations[variant_index]
# Number of operands check
if(len(merged_param_types) != variation[0]):
continue
# Operand type check
offset = 0
for idx in range(len(merged_param_types)):
type_flags = variation[2][idx - offset]
# override check
if((merged_param_types[idx] & 1) and (type_flags & 0o100)):
continue
type_flags &= (~0o100)
if((merged_param_types[idx] & type_flags) != type_flags):
break
else:
variant = variant_index
# Might be a native-instruction with the same label
if((variant == -1) and (label in OPCODES)):
return (False, variant)
# Is a pseudo-instruction, but types might not match
return (True, variant)
# Is not a pseudo-instruction
return (False, -1)
# Instruction check
def is_instruction(line: list):
if(len(line[0]) == 0):
fatal_error('assembler', f"is_instruction: line {line[1]} is empty..? it should\'ve been filtered out by the assembler..\n{rec_dump_array(line, 1)}\n{resolve_line(line)}")
label = line[0][0][0]
if(label in OPCODES):
variations = OPCODES[label]
merged_param_types = merge_offset_types(line)[1:]
variant = -1
for idx in range(len(variations)):
if(variant > -1):
break
variation = variations[idx]
# Number of operands check
if((len(merged_param_types) < variation[1][1]) or (len(merged_param_types) > variation[1][2])):
continue
# Operand type check
for idx2 in range(len(merged_param_types)):
type_flags = variation[1][0][idx2][0][4]
# override check
if((merged_param_types[idx2] & 1) and (type_flags & 0o100)):
continue
type_flags &= (~0o100)
if((merged_param_types[idx2] & type_flags) != type_flags):
break
else:
variant = idx
return (True, variant)
return (False, -1)
# Turn label as it appears in code into how it'll be used in instructions ('Done:' -> '.Done')
def to_label(word: list, filename: str, line: int, caller: str):
if(len(word[0]) == 0):
return 0
result = is_label(word[0])
if(result != 0):
if(result == 1):
return word
elif(result == 2):
return ['.' + word[0][:-1], word[1]]
elif(result == 3):
return [word[0][:-1], word[1]]
else:
fatal_error('assembler', f"{caller}: {filename}:{line}: Could not interpret label \'{word}\' ({display_type(word, True)})")
# Convert label from many syntaxes into 1 syntax
def convert_label(word: list):
result = is_label(word[0])
if(result != 0):
if(result == 1):
return [word[0][1:] + ':', word[1]]
elif(result == 2):
return word
elif(result == 3):
return [word[0][1:], word[1]]
else:
fatal_error('assembler debug', f"convert_label: Could not interpret label \'{word[0]}\' ({display_type(word, True)})")
# Helper function for displaying the type of a word
def display_type(word: list, a_or_an: bool = False):
if(word[1] & 0b111):
sentence = "no type/"*((word[1] >> 6) & 1) + " ".join(["immediate"]*(word[1] & 1) + ["register"]*((word[1] >> 1) & 1))
elif(word[1] & (~0b111)):
t = (word[1] >> 3) & 0b111
sentence = ["no type", "label", "definition", "ORG directive", "DB directive", "instruction"][t]
else:
sentence = "no type"
if(a_or_an):
if(sentence[0].lower() in "aeiou"):
return "an " + sentence
return "a " + sentence
return sentence
# Helper function for displaying the types of an entire line
def display_types_line(line: list, cleaned_input: bool = False):
if(cleaned_input):
return f"{line[0].upper()} {', '.join(display_word([display_type([0, x[0] & (~0b100)]), x[0]], x[1] if(len(x) > 1) else None) for x in line[1:])}"
else:
return f"{display_word(line[0][0])} {', '.join(display_word([display_type([0, x[1] & (~0b100)]), x[1]]) for x in line[0][1:])}"
# Helper function for displaying the word based off of its flags
def display_word(word: list, optional_substitute: int = None):
disp_word = str(word[0])
if(optional_substitute != None):
disp_word = disp_word + '=' + str(optional_substitute)
if(word[1] & 0b111):
if(word[1] & 0b1000):
disp_word = '+' + disp_word
if(word[1] & 0b010):
disp_word = '%' + disp_word
if(word[1] & 0o100):
# * = any
disp_word = '*' + disp_word
if(word[1] & 0b100):
if(word[1] & 0b010000):
disp_word = '[' + disp_word
if(word[1] & (~0x7F)):
# + after = special behavior
disp_word = disp_word + '+'
if(word[1] & 0b100000):
disp_word = disp_word + ']'
elif((word[1] & 0x38) != 0):
return disp_word.upper()
else:
if(word[1] & (~0x7F)):
# + after = special behavior
disp_word = disp_word + '+'
return disp_word
# Resolve integers, ignores everything else
def resolve_integer(word: list, filename: str, line: int, caller: str):
# Return unmodified if is an int, or empty
if((type(word[0]) == int) or (len(word[0]) == 0)):
return word
# Auto-detect format
if(word[0][0] in '-0123456789'):
try:
offset = 0
if(word[0][0] == '-'):
offset = 1
if(word[0][offset:offset + 2] == '0x'):
return [int(word[0][:offset] + word[0][offset + 2:], 16), word[1]]
elif(word[0][offset:offset + 2] == '0o'):
return [int(word[0][:offset] + word[0][offset + 2:], 8), word[1]]
elif(word[0][offset:offset + 2] == '0b'):
return [int(word[0][:offset] + word[0][offset + 2:], 2), word[1]]
else:
return [int(word[0], 10), word[1]]
except ValueError as _ERR:
fatal_error('assembler', f"{caller}: {filename}:{line}: Could not resolve number \'{word[0]}\' which is {display_type(word, True)}\n{_ERR}")
# $ prefixed hexadecimal
elif(word[0][0] == '$'):
try:
return [int(word[0][1:], 16), word[1]]
except ValueError as _ERR:
fatal_error('assembler', f"{caller}: {filename}:{line}: Could not resolve hexadecimal \'{word[0]}\' which is {display_type(word, True)}\n{_ERR}")
# Return unmodified if not an integer
return word
# Handle character constant
def char_constant(string: str, idx: int, filename: str, line: int, caller: str, resolve_strings: bool = True, default_flags: int = 0b001):
idx_end = strfind_escape(string, '\'', idx + 1)
if(idx_end == -1):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Missing terminating \' character.\n{string}\n{' ' * idx}^{'~' * (len(string) - idx - 1)}")
idx_end += 1
if(not resolve_strings):
return (idx, idx_end, [[string[idx:idx_end], default_flags]])
try:
evaluated = eval(string[idx:idx_end])
except Exception as _ERR:
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Could not evaluate character constant.\n{string}\n{' ' * idx}^{'~' * (idx_end - idx - 1)}\n{_ERR}")
if(len(evaluated) > 1):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Too many characters in character constant.\n{string}\n{' ' * idx}^{'~' * (idx_end - idx - 1)}")
elif(len(evaluated) == 0):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Empty character constant.\n{string}\n{' ' * idx}^~")
return (idx, idx_end, [[ord(evaluated) & ((1 << WORD_LENGTH) - 1), default_flags]])
# Handle string constant
def string_constant(string: str, idx: int, filename: str, line: int, caller: str, resolve_strings: bool = True, default_flags: int = 0b001):
idx_end = strfind_escape(string, '\"', idx + 1)
if(idx_end == -1):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Missing terminating \" character.\n{string}\n{' ' * idx}^{'~' * (len(string) - idx - 1)}")
idx_end += 1
if(not resolve_strings):
return (idx, idx_end, [[string[idx:idx_end], default_flags]])
try:
evaluated = eval(string[idx:idx_end])
except Exception as _ERR:
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Could not evaluate string constant.\n{string}\n{' ' * idx}^{'~' * (idx_end - idx - 1)}\n{_ERR}")
if(len(evaluated) == 0):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Empty string constant.\n{string}\n{' ' * idx}^~")
return (idx, idx_end, [[(ord(char) & ((1 << WORD_LENGTH) - 1)), default_flags] for char in evaluated])
# Decompose instruction params (+ character literals with both ' and ")
def decompose_instruction_params(string: str, filename: str, line: int, caller: str, resolve_strings: bool = True, default_flags: int = 0b001):
idx = 0
output = []
while(idx < len(string)):
flags = default_flags
idx = inverted_strfind(string, OPERAND_SEPARATORS, idx)
if(idx == -1):
break
if(string[idx] == '['):
idx += 1
idx_end = strfind_escape(string, ']', idx)
if(idx_end == -1):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Missing closing bracket for memory location type indicator.\n{string}\n{' ' * (idx - 1)}^{'~' * (len(string) - idx)}")
params = decompose_instruction_params(string[idx:idx_end].strip(), filename, line, caller + " handling memloc:", resolve_strings, default_flags | 0b100)
if(len(params) == 0):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: Empty memory location type indicator.\n{string}\n{' ' * (idx - 1)}^{'~' * (idx_end - idx + 1)}")
params[0][1] |= 0b010000
params[-1][1] |= 0b100000
output = output + params
idx_end += 1
else:
if(string[idx] == '+'): # Parameter that should be merged
idx += 1
# If only a '+', add it to the output instead
try:
if(string[idx] in OPERAND_SEPARATORS):
output.append(['+', flags])
idx_end = idx
continue
except IndexError:
output.append(['+', flags])
break
# Check if not the first parameter
if(len(output) > 0):
# Add merge flag
flags = flags | 0b1000
if(string[idx] == '%'): # Register type
idx += 1
# If only a '%', add it to the output instead
try:
if(string[idx] in OPERAND_SEPARATORS):
output.append(['%', flags])
idx_end = idx
continue
except IndexError:
output.append(['%', flags])
break
flags = (flags & (~0b001)) | 0b010 # Swap immediate flag for register flag
if(string[idx] == '\''): # Character literal
idx, idx_end, constant = char_constant(string, idx, filename, line, caller, resolve_strings, flags)
output = output + constant
elif(string[idx] == '\"'): # Character literal (alias)
idx, idx_end, constants = string_constant(string, idx, filename, line, caller, resolve_strings, flags)
if(len(constants) != 1):
fatal_error('assembler', f"{caller}: {filename}:{line}:{idx + 1}: There must be exactly 1 character in string constant. (for instruction operands)\n{string}\n{' ' * idx}^{'~' * (idx_end - idx - 1)}")
output = output + constants
else: # Don't know (label or definition)
idx_end = strfind(string, OPERAND_SEPARATORS, idx)
if(idx_end == -1):
output.append([string[idx:], flags])
break
output.append([string[idx:idx_end], flags])
idx = idx_end
if(resolve_strings):
return [resolve_integer(x, filename, line, caller) for x in output]
return output
# Decompose DB params (+ character literals + string constants)
def decompose_DB_params(string: str, filename: str, line: int, caller: str, resolve_strings: bool = True, default_flags: int = 0b001):
idx = 0
output = []
while(idx < len(string)):
flags = default_flags
idx = inverted_strfind(string, OPERAND_SEPARATORS, idx)
if(idx == -1):
break
if(string[idx] == '\''): # Character literal
idx, idx_end, constant = char_constant(string, idx, filename, line, caller, resolve_strings, flags)
output = output + constant
elif(string[idx] == '\"'): # String constant (specific for DB)
idx, idx_end, constants = string_constant(string, idx, filename, line, caller, resolve_strings, flags)
output = output + constants
else: # Don't know (label or definition)
idx_end = strfind(string, OPERAND_SEPARATORS, idx)
if(idx_end == -1):
output.append([string[idx:], flags])
break
output.append([string[idx:idx_end], flags])
idx = idx_end
if(resolve_strings):
return [resolve_integer(x, filename, line, caller) for x in output]
return output
# Parse line
def parse_line(line: list, filename: str, caller: str, resolve_strings: bool = True):
decomposed_lines = []
decomposed_definitions = []
split_A = 0
split_B = 0
instruction = ''
# Labels and finding instruction
while(is_label(instruction) or (split_A == 0)):
split_B = strfind(line[0], INSTRUCTION_SPACING, split_A)
if(split_B == -1):
instruction = line[0][split_A:]
else:
instruction = line[0][split_A:split_B]
if(is_label(instruction)):
decomposed_lines.append([[[instruction, 0o10]], line[1]])
if(split_B == -1):
break
split_A = inverted_strfind(line[0], INSTRUCTION_SPACING, split_B)
if(split_B != -1):
parameters = line[0][split_A:]
else:
parameters = ""
if(is_label(instruction)):
return decomposed_lines, decomposed_definitions
instruction = instruction.lower()
# Definition
if(is_define(instruction)):
decomposed_definitions.append([[[instruction, 0o20]] + decompose_instruction_params(parameters, filename, line[1], caller, resolve_strings), line[1]])
# ORG
elif(instruction == 'org'):
decomposed_lines.append([[[instruction, 0o30]] + decompose_instruction_params(parameters, filename, line[1], caller, resolve_strings), line[1]])
# DB
elif(instruction == 'db'):
decomposed_lines.append([[[instruction, 0o40]] + decompose_DB_params(parameters, filename, line[1], caller, resolve_strings), line[1]])
# Assume it's an instruction
else:
decomposed_lines.append([[[instruction, 0o50]] + decompose_instruction_params(parameters, filename, line[1], caller, resolve_strings), line[1]])
return (decomposed_lines, decomposed_definitions)
# Recompose line
def recompose_line(line: list):
first_word = line[0][0]
# Label
if(first_word[1] == 0o10):
return convert_label(first_word)[0]
# Everything else
else:
return display_word(first_word) + (' '*(len(line[0]) > 1)) + ', '.join(display_word(x) for x in line[0][1:])
# Display multiple lines of Assembly
def print_assembly(lines: list, last_was: dict, line_width: int):
last_line = 0
special_case = False
for line in lines:
if(last_line == line[1]):
print("%s: "%(' ' * line_width), end='')
else:
print("%0*d: "%(line_width, line[1]), end='')
# Check if instruction
# if((line[0][0][0] not in ['org', 'db', 'define']) and (is_label(line[0][0][0]) == 0)):
if(line[0][0][1] == 0o50):
print(" ", end='')
print(recompose_line(line), end='')
# Display instruction variation
if((line[0][0][1] == 0o50) and (len(line[0][0]) > 2)):
print(" ; variation %d"%(line[0][0][2]), end='')
# I don't even know
if(((line[1] in last_was) and (last_line != line[1])) or special_case):
# Check if label
# if(is_label(line[0][0][0]) != 0):
if(line[0][0][1] == 0o10):
special_case = True
else:
print(" ; resolved from ; %s"%(recompose_line(last_was[line[1]])), end='')
special_case = False
last_line = line[1]
print()
# Display multiple lines of Assembly with machine code positions
def print_assembly_wordpos(lines: list, last_was: dict, line_width: int, hex_width: int):
last_line = 0
special_case = False
for line in lines:
if(last_line == line[1]):
print("%s:%0*X: "%(' ' * line_width, hex_width, line[2]), end='')
else:
print("%0*d:%0*X: "%(line_width, line[1], hex_width, line[2]), end='')
# Check if instruction
# if((line[0][0][0] not in ['org', 'db', 'define']) and (is_label(line[0][0][0]) == 0)):
if(line[0][0][1] == 0o50):
print(" ", end='')
print(recompose_line(line), end='')
if((line[0][0][1] == 0o50) and (len(line[0][0]) > 2)):
print(" ; variation %d"%(line[0][0][2]), end='')
# I don't even know
if(((line[1] in last_was) and (last_line != line[1])) or special_case):
# Check if label
# if(is_label(line[0][0][0]) != 0):
if(line[0][0][1] == 0o10):
special_case = True
else:
print(" ; resolved from ; %s"%(recompose_line(last_was[line[1]])), end='')
special_case = False
# Check if ORG directive
# elif(line[0][0][0] == 'org'):
elif(line[0][0][1] == 0o30):
print(" ; jump to 0x%0*X"%(hex_width, line[2]), end='')
last_line = line[1]
print()
# Strip line of comments
def remove_comment(comment_symbols: list, line: str):
idx = [line.find(symbol) for symbol in comment_symbols]
idx = [x for x in idx if(x != -1)]
if(len(idx) == 0):
return line
return line[:min(idx)]
# Bake a cake!
def bake_constants(matt_mode):
# Calculate number of operands and add to macro element
pop_keys = [pop for pop in PSEUDO_INSTRUCTIONS]
for pop in pop_keys:
popinfo=PSEUDO_INSTRUCTIONS[pop]
for idx in range(len(popinfo)):
popinfo[idx].insert(0, len(popinfo[idx][1]))
# Deal with multi-line pseudo-instructions in Matt mode later on
# Make requirement list
requirements = []
for c in popinfo[idx][2]:
requirements.append(OPERANDS[c][4])
popinfo[idx][2] = requirements
# Check operands
for operand in OPERANDS:
if(OPERANDS[operand][1] >= INSTRUCTION_MAX_LENGTH):
fatal_error('assembler', f"baking stage: wtf: Operand \'{OPERANDS[operand][5]}\' defined in a word outside set maximum length, are you sure it\'s correct?")
if(OPERANDS[operand][0] >= WORD_LENGTH):
fatal_error('assembler', f"baking stage: wtf: Operand \'{OPERANDS[operand][5]}\' shift amount is bigger than a word, are you sure it\'s correct?")
if((((OPERANDS[operand][1] + 1) * WORD_LENGTH) - OPERANDS[operand][0] - OPERANDS[operand][2]) < 0):
fatal_error('assembler', f"baking stage: wtf: Operand \'{OPERANDS[operand][5]}\' is defined outside the instruction, are you sure it\'s correct?")
# Make instructions more machine-friendly and check instruction lengths
for opcode in OPCODES:
variations = OPCODES[opcode]
for variation_index in range(len(variations)):
variation = variations[variation_index]
# Error and prompt user if instruction's length exceeds max length
if((variation[3] * WORD_LENGTH) > (INSTRUCTION_MAX_LENGTH * WORD_LENGTH)):
fatal_error('assembler', f"baking stage: wtf: Instruction \'{opcode.upper()}\' variation {variation_index} exceeds set maximum length, are you sure it\'s correct?")
processed_opcode=[]
operands=variation[1]
idx=0
minimum_operands=0
while(idx<len(operands)):
# Process optional operand
if(operands[idx]=='['):
idx_end=operands.find(']', idx)
if(idx_end==-1):
fatal_error('assembler', f"baking stage: syntax error: No closing brace for operand \'{operands[idx+1]}\' in instruction \'{opcode.upper()}\' variation {variation_index}.")
substr=operands[idx+1:idx_end]
if(substr[2:]==''):
fatal_error('assembler', f"baking stage: wtf: No default defined for operand \'{substr[0]}\' for instruction \'{opcode.upper()}\' variation {variation_index}.")
processed_opcode.append([OPERANDS[substr[0]], int(substr[2:])])
idx=idx_end+1
minimum_operands-=1
# Process sequence of mandatory operands
else:
idx_end=operands.find('[', idx)
if(idx_end==-1):
idx_end += len(operands) + 1
substr=operands[idx:idx_end]
processed_opcode = processed_opcode + [[OPERANDS[x]] for x in substr]
idx=idx_end
# Check operands used in instruction; make sure operands don't go outside the instruction
for index in range(len(processed_opcode)):
operand = processed_opcode[index][0]
if(operand[1] >= variation[3]):
fatal_error('assembler', f"baking stage: wtf: Operand \'{operand[5]}\' in instruction \'{opcode.upper()}\' variation {variation_index} is defined in a word outside the instruction\'s length, are you sure it\'s correct?")
maximum_operands=len(processed_opcode)
minimum_operands=minimum_operands+maximum_operands
processed_opcode=[processed_opcode, minimum_operands, maximum_operands]
variation[1]=processed_opcode
# Validity check
for opcode in OPCODES:
variations = OPCODES[opcode]
for variation_index in range(len(variations)):
variation = variations[variation_index]
operands=variation[1][0]
maxim=1
for idx, operand in enumerate(operands):
if(maxim <= len(operand)):
maxim=len(operand)
else:
fatal_error('assembler', f"baking stage: wtf: Optional operand \'{operands[idx-1][0][5]}\' declared inbetween mandatory ones in instruction \'{opcode.upper()}\' variation {variation_index}! (Will cause problems later)")
###- MAIN THING -###
# Assemble function
def assemble(assembly_filename: str, ROM_size: int, verbose_level: int, debug_flags: int, matt_mode: bool):
if(debug_flags & 0b1100):
if(debug_flags & 0x8):
print("ISA-defined symbols:")
for symbol in STARTING_SYMBOLS:
symbolinfo = STARTING_SYMBOLS[symbol]
print('- \'%s\' = %d (%s)'%(symbol, symbolinfo[0], display_word([display_type([symbolinfo[0], symbolinfo[1] & (~0b100)]), symbolinfo[1]])))
if(debug_flags & 0x4):
total = 0
print("Native-instructions: (%d)"%(len(OPCODES)))
for opcode in OPCODES:
variations=OPCODES[opcode]
print(f"{opcode}: ({len(variations)})")
for variation_index in range(len(variations)):
variation = variations[variation_index]
params = [display_word([display_type([0, x[0][4] & (~0b100)]), x[0][4]], x[1] if(len(x) != 1) else None) for x in variation[1][0]]
print(f'- {variation_index}: ' + opcode.upper() + ' ' + ', '.join(params))
total += 1
print("(Total: %d)"%(total))
total = 0
print("\nPseudo-instructions: (%d)"%(len(PSEUDO_INSTRUCTIONS)))
for label in PSEUDO_INSTRUCTIONS:
variations=PSEUDO_INSTRUCTIONS[label]
print(f"{label}: ({len(variations)})")
for variation_index in range(len(variations)):
variation = variations[variation_index]
params = [display_word([display_type([0, x & (~0b100)]), x], None) for x in variation[2]]
print(f'- {variation_index}: ' + label.upper() + ' ' + ', '.join(params) + ' -> ' + variation[1])
total += 1
print("(Total: %d)"%(total))
exit()
try:
assembly_file = open(assembly_filename, 'r')
except FileNotFoundError as _ERR:
fatal_error('assembler', f"{assembly_filename}: File not found.\n{_ERR}")
if(verbose_level >= 0):
print(f"assembler: Reading from \'{assembly_filename}\'")
if(matt_mode):
# Matt mode engaged.
print(f"assembler: Matt mode active. ORG, DB, & multi-line pseudo-instructions are disabled.")
lines = [line.strip() for line in assembly_file]
assembly_file.close()
# DEBUG: ROM address size constant
ROM_address_size = int(log2(ROM_size) + 3) >> 2
line_address_size = int(log(len(lines), 10) + 1)
word_display_size = int(WORD_LENGTH + 3) >> 2
if(verbose_level >= 2):
print("Address hex width: %d (%s)"%(ROM_address_size, ''.join(hex(x%16).upper()[2] for x in range(ROM_address_size))))
print("Line address width: %d (%s)"%(line_address_size, ''.join(chr(0x30 + (x%10)) for x in range(line_address_size))))
print("Word display width: %d (%s)"%(word_display_size, ''.join(hex(x%16).upper()[2] for x in range(word_display_size))))
# Remove comments and blanklines, and add line number
lines = [[remove_comment(COMMENT_SYMBOLS, line).strip(), idx+1] for idx, line in enumerate(lines)]
# Remove empty lines & add line numbers
lines = [line for line in lines if(len(line[0]) != 0)]
if(len(lines) == 0):
fatal_error('assembler', f"{assembly_filename}: File empty.")
# Populatesymbol table
symbols = STARTING_SYMBOLS
# Definitions table
definitions = {}
# Labels table
labels = {}
# Decompose instructions and separate labels
# DEBUG: show current job
if(verbose_level >= 1):
print("\nPARSING LINES..")
decomposed = []
decomposed_definitions = []
original_lines = {}
for line in lines:
new_lines, new_definitions = parse_line(line, assembly_filename, 'parser')
decomposed = decomposed + new_lines
decomposed_definitions = decomposed_definitions + new_definitions
new_lines, new_definitions = parse_line(line, assembly_filename, 'parser', False)
original_lines[line[1]] = new_lines + new_definitions
# DEBUG: display Assembly right now
if(verbose_level >= 3):
print("ASSEMBLY:")
print_assembly(decomposed, {}, line_address_size)
# DEBUG: display definitions
if(verbose_level >= 3):
print("\nDEFINITIONS:")
print_assembly(decomposed_definitions, {}, line_address_size)
# Resolve symbols
# DEBUG: show current job
if(verbose_level >= 1):
print("\nRESOLVING DEFINITIONS.. (ISA-DEFINED)")
for index in range(len(decomposed)):
line = decomposed[index]
line_number = line[1]
params = line[0]
line_before_change = deep_copy(line)
changed = False
for index2 in range(1, len(params)):
param = params[index2]
if(is_symbol(param, symbols)):
changed = True
symbolinfo = symbols[param[0].lower()]
params[index2] = [symbolinfo[0], param[1] | symbolinfo[1]]
# DEBUG: show resolved line
if(verbose_level >= 2):
if(changed):
print("%0*d: %s -> %s"%(line_address_size, line_number, recompose_line(line_before_change), recompose_line(line)))
# DEBUG: display Assembly right now
if(verbose_level >= 3):
print("ASSEMBLY:")
print_assembly(decomposed, {}, line_address_size)
# Memorize definitions (and resolve their parameters if needed)
# DEBUG: show current job
if(verbose_level >= 1):
print("\nMEMORIZING USER-DEFINED DEFINITIONS, PRE-LABELS..")
# Q: "why not tasks = decomposed_definitions?"
# A: "because I only want a list of pointers to the lines so I don't modify the original"
tasks = [x for x in decomposed_definitions]
if((verbose_level >= 1) and (len(tasks) == 0)):
print("Nothing to do.")
# Parameter check
for definition in tasks:
merged_types = merge_offset_types(definition)
# Once, there were 4 little definitions
if(len(merged_types) > 3): # One had too many parameters
fatal_error('assembler', f"definition parameter check: {assembly_filename}:{definition[1]}: Too many parameters for definition.")
elif(len(merged_types) == 2): # One had only a name
fatal_error('assembler', f"definition parameter check: {assembly_filename}:{definition[1]}: Only name given for definition.")
elif(len(merged_types) == 1): # One had no parameters
fatal_error('assembler', f"definition parameter check: {assembly_filename}:{definition[1]}: No parameters given for definition.")
# But one was juuust right
# Memorize or resolve
while(len(tasks) != 0):
progress = False
idx = 0
while(idx < len(tasks)):
definition = tasks[idx]
# Check if resolved
if((len(definition[0]) == 3) and (type(definition[0][2][0]) == int)):
progress = True
definitions[definition[0][1][0]] = definition[0][2][0]
if(verbose_level >= 2):
print(f"%0*d: \'%s\' = %d"%(line_address_size, definition[1], definition[0][1][0], definition[0][2][0]))
tasks.pop(idx)
continue
# Otherwise, try to resolve operands
else:
fully_resolved = True
line_before_change = deep_copy(definition)
changed = False
for idx2 in range(2, len(definition[0])):
if(is_definition(definition[0][idx2], definitions)):
progress = True
changed = True
definition[0][idx2][0] = definitions[definition[0][idx2][0]]
if(type(definition[0][idx2][0]) != int):
fully_resolved = False
if(changed and (verbose_level >= 2)):
print('%0*d: %s -> %s ; resolution'%(line_address_size, definition[1], recompose_line(line_before_change), recompose_line(definition)))
if(fully_resolved):
line_before_change = deep_copy(definition)
merge_offset_parameters(definition, True)
if((definition != line_before_change) and (verbose_level >= 2)):
print('%0*d: %s -> %s ; merge'%(line_address_size, definition[1], recompose_line(line_before_change), recompose_line(definition)))
idx += 1
if(not progress):
if(len(tasks) != 0):
if(verbose_level >= 2):
print("definition resolver (pre-labels): Couldn\'t resolve everything, likely only labels left, leaving the rest for after labels.")
break
if(verbose_level >= 2):
print(" > Loop <")
# Resolve definitions
# DEBUG: show current job
if(verbose_level >= 1):
print("\nRESOLVING USER-DEFINED DEFINITIONS, PRE-LABELS..")
for index in range(len(decomposed)):
line = decomposed[index]
line_number = line[1]
line_before_change = deep_copy(line)
changed = False
all_resolved = True
for index2 in range(1, len(line[0])):
if(is_definition(line[0][index2], definitions)):
changed = True
line[0][index2][0] = definitions[line[0][index2][0]]
if(type(line[0][index2][0]) != int):
all_resolved = False
# DEBUG: show resolved line
if(verbose_level >= 2):
if(changed):
print("%0*d: %s -> %s ; resolution"%(line_address_size, line_number, recompose_line(line_before_change), recompose_line(line)))
if(all_resolved):
line_before_change = deep_copy(line)
merge_offset_parameters(line, True)
if((verbose_level >= 2) and (line_before_change != line)):
print("%0*d: %s -> %s ; merge"%(line_address_size, line_number, recompose_line(line_before_change), recompose_line(line)))
# DEBUG: display Assembly after resolving definitions
if(verbose_level >= 3):
print("\nASSEMBLY NOW:")
print_assembly(decomposed, {}, line_address_size)
# Resolve pseudo-instructions
# DEBUG: show current job
if(verbose_level >= 1):
print("\nRESOLVING PSEUDO-INSTRUCTIONS..")
last_was = {}
cont=True
while(cont):
cont=False
index = 0
while(index < len(decomposed)):
line = decomposed[index]
line_number=line[1]
words=line[0]
res = is_pseudo(line)
# label exists
if(res[0]):
label = words[0][0]
# a variant exists
if(res[1] != -1):
merge_offset_parameters(line)
variant_index = res[1]
if(line_number not in last_was):
last_was[line_number] = original_lines[line[1]][-1]
cont=True
popinfo=PSEUDO_INSTRUCTIONS[label][variant_index]
gen_lines = popinfo[1].format(*[x[0] for x in words[1:]]).split('\n')
parsed = []
for gline in gen_lines:
new_lines, new_definitions = parse_line([gline, line_number], assembly_filename, 'pseudo-instruction resolver')
for line in new_lines:
line_number = line[1]
params = line[0]
for index2 in range(1, len(params)):
param = params[index2]
if(is_symbol(param, symbols)):
symbolinfo = symbols[param[0].lower()]
params[index2] = [symbolinfo[0], param[1] | symbolinfo[1]]