-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaxx.py
More file actions
5265 lines (4757 loc) · 258 KB
/
axx.py
File metadata and controls
5265 lines (4757 loc) · 258 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
#!/usr/bin/env python3
# cython: language_level=3
"""
axx general assembler designed and programmed by Taisuke Maekawa
Refactored with OOP design for improved maintainability
"""
from decimal import Decimal, getcontext
try:
import readline # GNU readline (Unix/macOS only; not available on Windows)
except ImportError:
pass # Gracefully degrade on Windows or environments without readline
import string
import subprocess
import itertools
import struct
import sys
import os
import math
import re
import tempfile
# Pass1 リラクゼーションの初期比較用番兵オブジェクト。
# モジュールレベルで1度だけ生成することで、run() を複数回呼び出しても
# 同一オブジェクトとの `is not` 比較が常に正しく機能する。
_RELAXATION_SENTINEL = object()
# Expression mode constants
EXP_PAT = 0
EXP_ASM = 1
# exp_typ は後方互換のため残存するが、実際には AssemblerState.exp_typ を使用する。
# このグローバルは参照されなくなった(修正1対応)。
exp_typ = 'i' # deprecated – do not use directly
# Special bracket characters
OB = chr(0x90) # open double bracket
CB = chr(0x91) # close double bracket
# Constants
UNDEF = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
VAR_UNDEF = 0
# Character sets
CAPITAL = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LOWER = "abcdefghijklmnopqrstuvwxyz"
DIGIT = '0123456789'
XDIGIT = "0123456789ABCDEF"
ALPHABET = LOWER + CAPITAL
# Error messages
ERRORS = [
"",
"Invalid syntax.",
"Address out of range.",
"Value out of range.",
"",
"Register out of range.",
"Port number out of range."
]
class AssemblerState:
"""Global state container for the assembler"""
def __init__(self):
# File paths
self.outfile = ""
self.expfile = ""
self.expfile_elf = ""
self.impfile = ""
# Program counter and padding
self.pc = 0
self.padding = 0
# pc_instr_start: binary_list中の$$が命令先頭アドレスを指すように、
# makeobj呼び出し前にself.state.pcの値を保存する。
self.pc_instr_start = 0
# _in_binary_list: makeobj実行中はTrue。$$がpc_instr_startを
# 返すのはbinary_list評価中のみ。.equなど他のコンテキストでは
# $$=現在のPC (self.state.pc) を返す。
self._in_binary_list = False
# Character sets for parsing
self.lwordchars = DIGIT + ALPHABET + "_."
self.swordchars = DIGIT + ALPHABET + "_%$-~&|"
# Current context
self.current_section = ".text"
self.current_file = ""
# Data structures
self.labels = {}
self.sections = {}
self.symbols = {}
self.patsymbols = {}
self.export_labels = {}
self.pat = []
# VLIW configuration
self.vliwinstbits = 41
self.vliwnop = []
self.vliwbits = 128
self.vliwset = []
self.vliwflag = False
self.vliwtemplatebits = 0x00
self.vliwstop = 0
self.vcnt = 1
# Expression mode and errors
self.expmode = EXP_PAT
self.error_undefined_label = False
self.error_label_conflict = False
# Assembly configuration
self.align = 16
self.bts = 8
self.endian = 'little'
self.byte = 'yes'
self.pas = 0
self.debug = False
# Current line info
self.cl = ""
self.ln = 0
self.fnstack = []
self.lnstack = []
# Variables (a-z)
self.vars = [VAR_UNDEF for i in range(26)]
# Debug strings
self.deb1 = ""
self.deb2 = ""
# Expression type mode: 'i' = integer, 'f' = float.
# スレッドセーフかつ再入安全にするため、モジュールレベルのグローバルではなく
# インスタンス変数として保持する。変更箇所では必ず try/finally で元に戻す。
self.exp_typ: str = 'i'
# Pass 1 size-estimation mode:
# True の間は未定義ラベルを UNDEF ではなく 0 として返す。
# forward参照があっても makeobj がサイズを正しく計算できるようにするため。
self._pass1_size_mode = False
# リラクゼーション用: 直前のpass1でのラベルアドレス記録
# {label_name: pc_value} の辞書。収束判定に使う。
# モジュールレベルの番兵で初期化することで run() 複数回呼び出しも安全。
self._pass1_prev_label_pcs = _RELAXATION_SENTINEL
# 標準出力へのリスト出力: True のときのみ lineassemble0 がリストを stdout に出力する。
# False(デフォルト)のときはエラー/警告のみ表示し、バイナリ出力は通常通り行う。
self.verbose: bool = False
# stdin 入力を保持する一時ファイルパス。
# 固定名 "axx.tmp" の代わりに tempfile で生成したパスを使うことで
# 複数インスタンスの同時実行時の競合を防ぐ。
# None のとき未生成。run() 終了後に cleanup される。
self.stdin_tmp_path: str | None = None
# ELF OSABI (FreeBSD==9,Linux==0)
self.osabi: int = 9 # default OSABI==9(FreeBSD)
# ELF relocatable object file output (-r / -m options)
self.elf_objfile: str = ""
self.elf_machine: int = 62 # default EM_X86_64
# ELF relocation tracking
# relocations: list of (section_name, sec_rel_byte_offset, sym_name, reloc_type, addend)
self.relocations = []
# _elf_tracking: True while assembling an instruction during pass2 ELF output
self._elf_tracking = False
# _elf_label_refs_seen: リロケーション候補。各エントリ: (label_name, abs_value, word_idx)
# word_idx は makeobj() 内で参照が発生した objl インデックス(0 以上)。
self._elf_label_refs_seen = [] # [(label_name, abs_word_value, word_idx)]
# makeobj() が現在生成中のワードインデックス(objl への追加前の len(objl))。
# -1 は makeobj() の外(センチネル値)。
self._elf_current_word_idx: int = -1
# _elf_var_to_label: match() で !x がラベルを直接キャプチャしたとき
# 変数名 → (label_name, label_value) を記録する辞書。
# makeobj() 内で変数を読む際にリロケーション情報の生成に使う。
# ラベル値そのものではなく式(label+offset等)でキャプチャした場合は
# この辞書には登録しない(_elf_capturing_var が None に戻る前に複数の
# get_value() 呼び出しが起きたケースは登録を取り消す)。
self._elf_var_to_label: dict = {} # {var_letter: (label_name, label_value)}
# _elf_capturing_var: match() が !x 式を評価している最中にセットされる変数名。
# get_value() はこれを見て _elf_var_to_label を更新する。
# None のとき「キャプチャ中でない」。
self._elf_capturing_var: str | None = None
class StringUtils:
"""Utility functions for string manipulation"""
@staticmethod
def upper(s):
"""Convert string to uppercase"""
return ''.join(c.upper() if c in LOWER else c for c in s)
@staticmethod
def q(s, t, idx):
"""Quick comparison of substring"""
return StringUtils.upper(s[idx:idx+len(t)]) == StringUtils.upper(t)
@staticmethod
def skipspc(s, idx):
"""Skip spaces and tabs in string"""
while idx < len(s) and s[idx] in ' \t':
idx += 1
return idx
@staticmethod
def skip_squote_literal(s, i):
"""シングルクォートリテラルを先読みで消費する共通ヘルパー。
Fix 5 (DRY): remove_comment_asm と vcnt カウントループに同一ロジックが
重複していた。どちらもこのメソッドを呼び出すことで一元管理する。
消費できた場合は消費後の idx を返す。
消費できなかった(孤立クォート)場合は i+1 を返す(クォート自体を読み飛ばす)。
消費したかどうかは (戻り値 > i + 1) で判定できる。
対応パターン:
'\\xNN' ← 16進エスケープ (0〜2桁)
'\\x' ← 4文字エスケープ (backslash + char + closeq)
'x' ← 通常3文字リテラル
"""
j = i + 1
if j < len(s) and s[j] == '\\' and j + 1 < len(s):
esc_char = s[j + 1]
if esc_char in 'xX':
k = j + 2
hex_digits = 0
while k < len(s) and s[k] in '0123456789abcdefABCDEF' and hex_digits < 2:
k += 1
hex_digits += 1
if k < len(s) and s[k] == '\'':
return k + 1 # '\xNN' 消費成功
# 閉じクォートが見つからない → 孤立クォート
elif j + 2 < len(s) and s[j + 2] == '\'':
return j + 3 # '\x' 4文字消費成功
# 対応クローズクォートなし → 孤立クォート
elif j < len(s) and j + 1 < len(s) and s[j + 1] == '\'':
return j + 2 # 'x' 3文字消費成功
# 孤立クォート: クォート文字そのものだけ読み飛ばす
return i + 1
@staticmethod
def reduce_spaces(text):
"""Reduce multiple spaces to single space"""
return re.sub(r'\s{2,}', ' ', text)
@staticmethod
def remove_comment(l):
"""Remove /* style comments"""
idx = 0
while idx < len(l):
if l[idx:idx+2] == '/*':
return "" if idx == 0 else l[0:idx]
idx += 1
return l
@staticmethod
def remove_comment_asm(l):
"""Remove ; style comments, but preserve semicolons inside string literals.
エスケープされた引用符 \\" は文字列の開始・終了とみなさない。
これにより "hello \\"world\\"; not a comment" のような入力で
誤ってコメント開始位置がずれる問題を修正。
Fix ⑦: 旧実装のシングルクォートトグル方式では、'a'b'c' のような
連続する文字リテラルが並ぶ入力で in_squote フラグが誤って残留し、
後続のセミコロンがコメント開始とみなされない問題があった。
修正後: トグル方式を廃止し、先読みペア確認方式を採用する。
シングルクォートを見つけたら:
- '\\x' 形式(エスケープ付き4文字): 丸ごと消費してスキップ
- 'x' 形式(通常3文字) : 丸ごと消費してスキップ
- 対応するクローズクォートが見つからない孤立クォート: そのまま通過
これにより 'a'b'c';comment のような入力でも正しくセミコロンを検出できる。
"""
in_dquote = False
i = 0
while i < len(l):
ch = l[i]
# ダブルクォート文字列内のエスケープ処理
if ch == '\\' and in_dquote:
if i + 1 < len(l):
i += 2
else:
i += 1 # 末尾の孤立したバックスラッシュ
continue
if ch == '"':
# Fix ①: 開き・閉じ両方でトグルする。
# 旧実装は `not in_dquote` 条件のため開きクォートしか処理せず、
# 閉じダブルクォートが来ても in_dquote が True のまま固着していた。
# バックスラッシュエスケープ (\") は上の if ブロックで消費済みなので
# ここでは素直にトグルするだけで正しい。
in_dquote = not in_dquote
elif ch == '\'' and not in_dquote:
# Fix 5 (DRY): 共通ヘルパーでシングルクォートリテラルを消費する。
i = StringUtils.skip_squote_literal(l, i)
continue
elif ch == ';' and not in_dquote:
return l[:i].rstrip()
i += 1
# Fix 4: ループを抜けた時点で in_dquote=True なら未終端ダブルクォート文字列。
# 行内のセミコロンはコメントとして扱われていないため出力に残る。
# Fix 7 (new): 警告を stdout ではなく stderr に出力する。
# バイナリ出力をパイプで受け取っている場合に stdout が汚染されないようにする。
if in_dquote:
print(f" warning - unterminated string literal in line: {l!r}", file=sys.stderr)
return l.rstrip()
@staticmethod
def get_param_to_spc(s, idx):
"""Get parameter up to space"""
t = ""
idx = StringUtils.skipspc(s, idx)
while idx < len(s) and s[idx] != ' ':
t += s[idx]
idx += 1
return t, idx
@staticmethod
def get_param_to_eon(s, idx):
"""Get parameter to end of line or !!"""
t = ""
idx = StringUtils.skipspc(s, idx)
while idx < len(s) and s[idx:idx+2] != '!!':
t += s[idx]
idx += 1
return t, idx
@staticmethod
def get_string(l2):
"""Get quoted string with proper escape sequence handling"""
idx = 0
idx = StringUtils.skipspc(l2, idx)
if l2 == '' or idx >= len(l2) or l2[idx] != '"':
return ""
idx += 1
s = ""
while idx < len(l2):
if l2[idx] == '\\' and idx + 1 < len(l2):
# エスケープシーケンスを処理
next_char = l2[idx + 1]
if next_char == '"':
s += '"'
idx += 2
elif next_char == '\\':
s += '\\'
idx += 2
elif next_char == 'n':
s += '\n'
idx += 2
elif next_char == 't':
s += '\t'
idx += 2
elif next_char == 'r':
s += '\r'
idx += 2
elif next_char in 'xX':
# Fix: \xNN 形式の16進エスケープ(asciistr と同様の処理)
idx += 2
hex_str = ''
while idx < len(l2) and l2[idx] in '0123456789abcdefABCDEF' and len(hex_str) < 2:
hex_str += l2[idx]
idx += 1
# Fix 9: 3桁以上の16進数字が続く場合、余剰桁はそのまま文字列として
# 出力されるためユーザーが気づきにくい。警告を出す。
if idx < len(l2) and l2[idx] in '0123456789abcdefABCDEF':
print(f" warning - '\\x' escape takes at most 2 hex digits; "
f"extra digit(s) treated as literal characters in: {l2!r}")
if hex_str:
s += chr(int(hex_str, 16))
else:
# \x の後に16進数字がない場合はそのまま保持
s += 'x'
else:
# その他のエスケープはそのまま保持
s += next_char
idx += 2
elif l2[idx] == '"':
return s
else:
s += l2[idx]
idx += 1
# 末尾まで達したが閉じ引用符がなかった
# Fix 7 (new): 警告を stderr に出力する(stdout 汚染防止)。
print(f" warning - unterminated string literal: {l2!r}", file=sys.stderr)
return s
class Parser:
"""Parser for extracting tokens and strings from assembly code"""
def __init__(self, state):
self.state = state
def get_intstr(self, s, idx):
"""Get integer string from position"""
fs = ''
while idx < len(s) and s[idx] in DIGIT:
fs += s[idx]
idx += 1
return fs, idx
def get_floatstr(self, s, idx):
"""Get float string from position.
Fix 11: 単項マイナスは factor() が先に処理するため、
factor1() から呼ばれる get_floatstr() で先頭の '-' を
再度消費すると二重解釈になる。
'-inf' だけは特別扱いとして残す(単項マイナス + inf では
factor() の負号処理と組み合わせると正しく動くが、
'-inf' という単一トークンとして見た方が自然で、
既存の使用箇所すべてでその前提で動いている)。
それ以外の数値先頭 '-' は消費しない。
"""
if s[idx:idx+4] == '-inf':
return '-inf', idx + 4
elif s[idx:idx+3] == 'inf':
return 'inf', idx + 3
elif s[idx:idx+3] == 'nan':
return 'nan', idx + 3
else:
fs = ''
# Fix 11: 先頭の '-' は factor() の単項マイナス処理が担うため
# ここでは消費しない(二重解釈を防ぐ)。
while idx < len(s) and s[idx] in "0123456789.":
fs += s[idx]
idx += 1
# Accept exponent part: e/E followed by optional sign and digits.
# 修正: 指数部の数字が続かない場合('1e', '1e+' など)は
# 指数部をなかったことにして idx を巻き戻す。
# そのまま float('1e') を呼ぶと ValueError になるため。
if idx < len(s) and s[idx] in "eE":
saved_idx = idx
saved_fs = fs
fs += s[idx]
idx += 1
if idx < len(s) and s[idx] in "+-":
fs += s[idx]
idx += 1
digits_start = idx
while idx < len(s) and s[idx] in "0123456789":
fs += s[idx]
idx += 1
if idx == digits_start:
# 数字がひとつもなかった → 指数部を破棄して巻き戻す
fs = saved_fs
idx = saved_idx
return fs, idx
def isfloatstr(self,s,idx):
sidx=idx
v,idx = self.get_floatstr(s,idx)
if idx==sidx:
return False
else:
return True
def get_curlb(self, s, idx):
"""Get curly bracket content.
修正③: 閉じブレース '}' が見つからないまま文字列末尾に達した場合は
f=False を返してエラーを呼び出し元に知らせる。旧実装は f=True のまま
不完全な内容 t を返し、壊れた式がサイレントに評価されていた。
"""
idx = StringUtils.skipspc(s, idx)
f = False
t = ''
if idx < len(s) and s[idx] == '{':
idx += 1
idx = StringUtils.skipspc(s, idx)
start_idx = idx
while idx < len(s) and s[idx] != '}':
t += s[idx]
idx += 1
if idx >= len(s):
# 閉じブレースが見つからなかった
print(f" error - missing closing '}}' in expression: '{{{t}'")
# Fix ④修正: 旧実装は start_idx('{' 内部)を返していたため、
# 呼び出し元パーサーが '{' の中身を後続の式として誤解析していた。
# len(s) を返してパースを強制終了させる。
return False, '', len(s)
# '}' を消費
idx += 1
f = True
return f, t, idx
def get_symbol_word(self, s, idx):
"""Get symbol word from position"""
t = ""
if idx < len(s) and s[idx] not in DIGIT and s[idx] in self.state.swordchars:
t = s[idx]
idx += 1
while idx < len(s) and s[idx] in self.state.swordchars:
t += s[idx]
idx += 1
return StringUtils.upper(t), idx
def get_label_word(self, s, idx):
"""Get label word from position.
A trailing ':' is consumed as part of the label definition only when
it is NOT immediately followed by '=' (which would form ':=' – an
assignment operator rather than a label terminator).
ラベル名は大文字・小文字を区別する(case-sensitive)。
「foo:」と定義した場合、「FOO」では参照できない。
"""
t = ""
if idx < len(s) and (s[idx] == '.' or (s[idx] not in DIGIT and s[idx] in self.state.lwordchars)):
t = s[idx]
idx += 1
while idx < len(s) and s[idx] in self.state.lwordchars:
t += s[idx]
idx += 1
# Consume ':' only when it is a label terminator, not part of ':='
if idx < len(s) and s[idx] == ':' and (idx + 1 >= len(s) or s[idx + 1] != '='):
idx += 1
return t, idx
def get_params1(self, l, idx):
"""Get parameters separated by ::"""
idx = StringUtils.skipspc(l, idx)
if idx >= len(l):
return "", idx
s = ""
while idx < len(l):
if l[idx:idx+2] == '::':
idx += 2
break
else:
s += l[idx]
idx += 1
return s.rstrip(' \t'), idx
def enfloat(a):
# Fix: a が 0〜2^32-1 の範囲外のとき struct.pack('I', a) は struct.error になる。
# 呼び出し元でマスクしているが念のため二重ガードする。
try:
float_value = struct.unpack('f', struct.pack('I', int(a) & 0xFFFFFFFF))[0]
except (struct.error, OverflowError, ValueError):
float_value = 0.0
return float_value
def endouble(a):
# Fix: a が 0〜2^64-1 の範囲外のとき struct.pack('Q', a) は struct.error になる。
try:
double_value = struct.unpack('d', struct.pack('Q', int(a) & 0xFFFFFFFFFFFFFFFF))[0]
except (struct.error, OverflowError, ValueError):
double_value = 0.0
return double_value
# enflt / endbl は enfloat / endouble の別名。
# factor1() および xeval() の safe_env から参照されるが、
# これまで定義が存在せず NameError でクラッシュしていた。
enflt = enfloat
endbl = endouble
class IEEE754Converter:
"""IEEE 754 floating point conversion utilities"""
@staticmethod
def decimal_to_ieee754_32bit_hex(a):
"""Convert decimal to IEEE 754 32-bit hex.
Uses Python's struct module for the actual bit conversion so that
the result is identical to what the hardware would produce. The
Decimal-based path was previously incorrect because Decimal.adjusted()
returns a *decimal* (base-10) exponent, not a binary (base-2) one,
which produced wrong bit patterns for most non-power-of-10 values.
"""
if a == 'inf':
return "0x7F800000"
elif a == '-inf':
return "0xFF800000"
elif a == 'nan':
return "0x7FC00000"
# Fix 1: 不正な文字列("0x10", "1+2" 等)は Decimal() が
# decimal.InvalidOperation を送出する。ValueError に統一して呼び出し元で捕捉できるようにする。
try:
fval = float(Decimal(a))
except Exception as _e:
raise ValueError(f"decimal_to_ieee754_32bit_hex: invalid input {a!r}") from _e
try:
bits = struct.unpack('I', struct.pack('f', fval))[0]
except (struct.error, OverflowError) as _e:
raise ValueError(f"decimal_to_ieee754_32bit_hex: cannot pack {fval!r}") from _e
return f"0x{bits:08X}"
@staticmethod
def decimal_to_ieee754_64bit_hex(a):
"""Convert decimal to IEEE 754 64-bit hex.
Uses struct for correctness (same reason as the 32-bit variant).
"""
if a == 'inf':
return "0x7FF0000000000000"
elif a == '-inf':
return "0xFFF0000000000000"
elif a == 'nan':
return "0x7FF8000000000000"
# Fix 1: 不正な文字列は Decimal() が decimal.InvalidOperation を送出する。
# ValueError に統一して呼び出し元で捕捉できるようにする。
try:
fval = float(Decimal(a))
except Exception as _e:
raise ValueError(f"decimal_to_ieee754_64bit_hex: invalid input {a!r}") from _e
try:
bits = struct.unpack('Q', struct.pack('d', fval))[0]
except (struct.error, OverflowError) as _e:
raise ValueError(f"decimal_to_ieee754_64bit_hex: cannot pack {fval!r}") from _e
return f"0x{bits:016X}"
@staticmethod
def decimal_to_ieee754_128bit_hex(a):
"""Convert decimal to IEEE 754 128-bit (binary128 / quad) hex.
Python's struct does not support binary128, so we implement the
conversion with the Decimal module at high precision.
The binary exponent is found by integer bit-length of the scaled
integer approximation, which avoids any float-precision loss.
Precision is set to 60 digits to cover the 112-bit significand
(~34 significant decimal digits) with rounding headroom.
"""
BIAS = 16383
SIGNIFICAND_BITS = 112
EXPONENT_BITS = 15
getcontext().prec = 60 # 112ビット仮数部(約34桁)を十分カバー
if a == 'inf':
a = 'Infinity'
elif a == '-inf':
a = '-Infinity'
elif a == 'nan':
a = 'NaN'
d = Decimal(a)
if d.is_nan():
sign = 0
exponent = (1 << EXPONENT_BITS) - 1
fraction = 1 << (SIGNIFICAND_BITS - 1)
elif d == Decimal('Infinity'):
sign = 0
exponent = (1 << EXPONENT_BITS) - 1
fraction = 0
elif d == Decimal('-Infinity'):
sign = 1
exponent = (1 << EXPONENT_BITS) - 1
fraction = 0
elif d == 0:
sign = 0
exponent = 0
fraction = 0
else:
sign = 0 if d >= 0 else 1
d = abs(d)
two = Decimal(2)
# 2進指数を純粋なDecimal演算で求める:
# d に 2^SIGNIFICAND_BITS を掛けて整数化し、
# そのbit長から指数を逆算する。float変換は一切使わない。
scaled = int(d * (two ** SIGNIFICAND_BITS))
if scaled == 0:
exp_unbiased = -(BIAS - 1)
else:
# bit_length() - 1 = floor(log2(scaled))
exp_unbiased = scaled.bit_length() - 1 - SIGNIFICAND_BITS
scale = two ** exp_unbiased
normalized = d / scale
# 念のため境界を確認・微調整
while normalized >= 2:
exp_unbiased += 1
normalized /= 2
while normalized < 1:
exp_unbiased -= 1
normalized *= 2
biased_exp = exp_unbiased + BIAS
# Fix: 非常に大きな数値(例: Decimal('1e100000'))は biased_exp が
# 最大指数 (1<<EXPONENT_BITS)-1 を超えて Infinity になる。
# 最大有限指数を超えた場合は +Infinity として扱う。
_MAX_EXP = (1 << EXPONENT_BITS) - 1
if biased_exp >= _MAX_EXP:
# Overflow → Infinity
sign_bit = sign
exponent = _MAX_EXP
fraction = 0
bits = (sign_bit << 127) | (exponent << SIGNIFICAND_BITS) | fraction
return f"0x{bits:032X}"
if biased_exp <= 0:
# サブノーマル数
exponent = 0
shift = two ** (1 - BIAS - SIGNIFICAND_BITS)
fraction = int(d / shift + Decimal('0.5'))
else:
exponent = biased_exp
# 仮数部ビット(最近接丸め)
fraction = int((normalized - 1) * (two ** SIGNIFICAND_BITS) + Decimal('0.5'))
fraction &= (1 << SIGNIFICAND_BITS) - 1
bits = (sign << 127) | (exponent << SIGNIFICAND_BITS) | fraction
return f"0x{bits:032X}"
@staticmethod
def decimal_eval_expr(text):
"""四則演算式を Decimal で評価して binary128 の整数ビットパターンを返す。
対応文法:
expr = term (('+' | '-') term)*
term = factor (('*' | '/') factor)*
factor = '(' expr ')' | ['-'] number
number = decimal リテラル (Decimal で解析)
シンボル・ラベルを含む式(Decimal で解析できない場合)は
ValueError を送出する。呼び出し側は except して
float モードのフォールバックに切り替える。
"""
getcontext().prec = 60
text = text.strip()
def skip(s, i):
while i < len(s) and s[i] in ' \t':
i += 1
return i
def parse_number(s, i):
i = skip(s, i)
neg = False
if i < len(s) and s[i] == '-':
neg = True
i += 1
i = skip(s, i)
# Fix Q: 'inf' / 'nan' を Decimal で正しく処理する。
# 旧実装は '0123456789.' 以外を即 ValueError にしていたため
# qad{inf} / qad{-inf} が xeval フォールバックに落ち、
# そこで float(inf) → int() が OverflowError になっていた。
for kw, dval in (('inf', Decimal('Infinity')), ('nan', Decimal('NaN'))):
if s[i:i+len(kw)] == kw:
v = -dval if neg else dval
return v, i + len(kw)
# 符号のみは数値ではない
if i >= len(s) or s[i] not in '0123456789.':
raise ValueError(f"expected number at {i!r}")
start = i
while i < len(s) and s[i] in '0123456789.':
i += 1
if i < len(s) and s[i] in 'eE':
i += 1
if i < len(s) and s[i] in '+-':
i += 1
while i < len(s) and s[i] in '0123456789':
i += 1
try:
v = Decimal(s[start:i])
except Exception as _e:
# Fix ⑨修正: Decimal() は decimal.InvalidOperation を送出することがあり、
# 呼び出し側の except (ValueError, ZeroDivisionError) では補足されず
# 伝播してしまう。すべての変換失敗を ValueError に統一する。
raise ValueError(f"invalid decimal literal: {s[start:i]!r}") from _e
return (-v if neg else v), i
def parse_factor(s, i):
i = skip(s, i)
if i < len(s) and s[i] == '(':
# Fix 2 (修正): '(' ブランチも深いネストで RecursionError が発生する。
# '-' / '+' ブランチは Fix 1 で修正済みだったが、括弧ブランチは未対処だった。
try:
v, i = parse_expr(s, i + 1)
except RecursionError:
raise ValueError("decimal_eval_expr: expression nesting too deep")
i = skip(s, i)
if i < len(s) and s[i] == ')':
i += 1
return v, i
if i < len(s) and s[i] == '-':
# Fix 1: 深いネストで RecursionError が発生する可能性がある。
# ValueError に変換して呼び出し元の except で捕捉できるようにする。
try:
v, i = parse_factor(s, i + 1)
except RecursionError:
raise ValueError("decimal_eval_expr: expression nesting too deep")
return -v, i
if i < len(s) and s[i] == '+':
try:
return parse_factor(s, i + 1)
except RecursionError:
raise ValueError("decimal_eval_expr: expression nesting too deep")
return parse_number(s, i)
def parse_term(s, i):
v, i = parse_factor(s, i)
while True:
i = skip(s, i)
if i < len(s) and s[i] == '*':
t, i = parse_factor(s, i + 1)
v *= t
elif i + 1 < len(s) and s[i] == '/' and s[i+1] == '/':
# '//' → floor division('/' の前に判定する)
t, i = parse_factor(s, i + 2)
if t == 0:
raise ZeroDivisionError("floor division by zero in qad{}")
v = Decimal(int(v // t))
elif i < len(s) and s[i] == '/' and (i + 1 >= len(s) or s[i+1] != '/'):
t, i = parse_factor(s, i + 1)
if t == 0:
raise ZeroDivisionError("division by zero in qad{}")
v /= t
elif i < len(s) and s[i] == '%':
# Fix: '%' (modulo) が未実装だったため qad{N%M} が ValueError を
# 送出し float フォールバックで 128bit 精度が失われていた。
t, i = parse_factor(s, i + 1)
if t == 0:
raise ZeroDivisionError("modulo by zero in qad{}")
v = Decimal(int(v) % int(t))
else:
break
return v, i
def parse_expr(s, i):
v, i = parse_term(s, i)
while True:
i = skip(s, i)
if i < len(s) and s[i] == '+':
t, i = parse_term(s, i + 1)
v += t
elif i < len(s) and s[i] == '-':
t, i = parse_term(s, i + 1)
v -= t
else:
break
return v, i
val, _ = parse_expr(text, 0)
# Decimal を文字列に変換して binary128 に変換
return IEEE754Converter.decimal_to_ieee754_128bit_hex(str(val))
class VariableManager:
"""Manages assembler variables (a-z)"""
def __init__(self, state):
self.state = state
def get(self, s):
"""Get variable value by letter"""
c = ord(StringUtils.upper(s))
return self.state.vars[c - ord('A')]
def put(self, s, v):
"""Set variable value by letter"""
if StringUtils.upper(s) in CAPITAL:
c = ord(StringUtils.upper(s))
# 【バグ修正⑦】旧実装は常に int(v) で切り捨てていた。
# !F / !D 経由の値は match() 側で既に IEEE754 整数ビットパターンに
# 変換されてから渡されるので int() で問題ない。
# しかし exp_typ='f' モードで a:=3.14 のように生の float が渡された
# 場合は int(3.14)==3 となり精度が失われる。
# 修正: Decimal 型も適切に処理する。小数部を持つ値はそのまま保持し、
# 整数値のみ int に変換する。
if isinstance(v, Decimal):
# Fix ⑭ (新規): Decimal('Infinity') / Decimal('NaN') は
# to_integral_value() が OverflowError / InvalidOperation を
# 投げ、旧実装の except Exception でそのまま int(v) が再試行
# されるが int(Decimal('Infinity')) も OverflowError になる。
# 結果として vars に壊れた値が書き込まれるか、別の例外が伝播した。
# 修正: 有限値かどうかを先に確認し、非有限は float として保持する。
if not v.is_finite():
self.state.vars[c - ord('A')] = float(v)
elif v == v.to_integral_value():
self.state.vars[c - ord('A')] = int(v)
else:
self.state.vars[c - ord('A')] = float(v)
elif isinstance(v, float) and not v.is_integer():
self.state.vars[c - ord('A')] = v
else:
# Fix: float('inf') / float('nan') / Decimal('Infinity') などは
# int() が OverflowError / ValueError になる。非有限値はそのまま保持。
try:
self.state.vars[c - ord('A')] = int(v)
except (OverflowError, ValueError):
self.state.vars[c - ord('A')] = v
class LabelManager:
"""Manages assembly labels"""
def __init__(self, state):
self.state = state
def get_section(self, k):
"""Get label section"""
self.state.error_undefined_label = False
try:
v = self.state.labels[k][1]
except (KeyError, IndexError):
v = UNDEF
self.state.error_undefined_label = True
return v
def get_value(self, k):
"""Get label value"""
self.state.error_undefined_label = False
try:
v = self.state.labels[k][0]
except (KeyError, IndexError):
if self.state._pass1_size_mode:
# Pass1 サイズ推定モード中は 0 を返す。
# error_undefined_label は False のまま(Trueにするとmakeobjがスキップする)。
return 0
v = UNDEF
self.state.error_undefined_label = True
# pass2 または単パスモード(pas==0)のときだけエラーを表示する。
# pass1 では forward reference が多発するため表示しない。
if self.state.pas == 2 or self.state.pas == 0:
_fn = self.state.current_file or ""
_ln = self.state.ln
print(f" error - Label undefined: '{k}'"
f" [{_fn}:{_ln}]", file=sys.stderr)
return v
# ELF リロケーション追跡
# is_equ=True の定数ラベルはリロケーション不要なので追跡しない
_is_equ = len(self.state.labels[k]) > 2 and self.state.labels[k][2]
if self.state._elf_tracking and not self.state.error_undefined_label and not _is_equ:
if self.state._elf_capturing_var is not None:
# match() が !var 式を評価中: 変数→ラベル名の対応を記録。
# 1 回の変数キャプチャで get_value() が 2 回以上呼ばれた場合
# (複合式: label1 + label2 など)は信頼できないので登録を取り消す。
cv = self.state._elf_capturing_var
if cv not in self.state._elf_var_to_label:
self.state._elf_var_to_label[cv] = (k, v)
else:
# 2 回目以降 → 複合式なのでリロケーション不可としてマーク
self.state._elf_var_to_label[cv] = None
elif self.state._elf_current_word_idx >= 0:
# makeobj() 内でオブジェクト式がラベルを直接参照
self.state._elf_label_refs_seen.append(
(k, v, self.state._elf_current_word_idx))
return v
def put_value(self, k, v, s, is_equ=False):
"""Set label value.
is_equ=True : .equ で定義された定数ラベル(リロケーション不要)
is_equ=False : アドレスラベル(リロケーション対象になり得る)
"""
if self.state.pas == 1 or self.state.pas == 0:
# パス1/インタラクティブ: 同名ラベルの二重定義はエラー
if k in self.state.labels:
# Fix 3 (new): インポートラベル(is_imported=True)と同名のソースラベルを
# 定義しようとした場合、インポートラベルはリラクゼーション先頭で
# _imported_labels から復元されるため「既存ラベル」として検出される。
# しかしソースラベルによるインポートラベルの上書きは正当な操作なので
# エラーにせず警告のみ出して続行する。
existing = self.state.labels[k]
old_is_imported = len(existing) > 3 and existing[3]
if old_is_imported:
# インポートラベルの上書き:警告のみ(エラーにしない)
pass
else:
self.state.error_label_conflict = True
print(f" error - label already defined.")
return False
elif self.state.pas == 2:
# パス2: パス1で登録されていないラベルが新出現した場合はエラー
if k not in self.state.labels:
self.state.error_label_conflict = True
print(f" error - label '{k}' not defined in pass 1.")
return False
# Fix 8 (original): パス1とパス2で is_equ フラグが食い違う場合を検出する。
# Fix 4 (new): ただし imp_label() が is_equ=True で登録したインポートラベルを
# ソース内のアドレスラベル(is_equ=False)が上書きする場合は意図的なものなので
# 警告を抑制する。is_imported フラグを確認して判断する。
old_is_equ = len(self.state.labels[k]) > 2 and self.state.labels[k][2]
old_is_imported = len(self.state.labels[k]) > 3 and self.state.labels[k][3]