ときどきの雑記帖 RE* (新南口)
考える○○○
訳語
KotlinとC#からみたJava言語 - Qiita を見ていたら、nullなんちゃら演算子というのが いくつも出てきて ??? となった。
C#にはそれぞれ導入時期は異なるが、NULLに配慮した次のような演算子がある。
- null合体演算子(??)
- 左側のオペランドがnullではない場合、そのオペランドの値を、それ以外の場合は、右側のオペランドの評価結果を返す演算子
- null合体割り当て演算子(=??)
- 左側のオペランドがnullに評価された場合にのみ、右側のオペランドの値を左側のオペランドに割り当てる演算子
- null免除演算子(!)
- オペランドをnull非許容型として解釈するよう指示を与える演算子。コンパイラの静的フロー分析にのみ影響を与え、実行時には影響を与えない
- null条件演算子(?.、?[])
- 左側のオペランドがnull参照ののときにはnullを、それ以外の場合は左側のオペランドのメンバ(フィールドやプロパティやメソッドなどのこと)である右側のオペランドを評価し、その結果を返す。インデクサーを呼び出すときのみ?[]を使用する
一体どんな言葉からこの訳語が出てきたのか疑問に思ったのでちょっと調べてみた。
「合体演算子」、「免除演算子」、「条件演算子」の元の言葉はそれぞれ次の通り。 boxing → ボックス化のときも思ったけど、 なんか機械翻訳の結果そのままとか 英和辞典の見出しの最初の方から考えなしに とってきたんじゃなかろうかという感じがひしひしと。
null coalescing operator
?? and ??= operators - C# reference | Microsoft Docs
The null-coalescing operator ?? returns the value of its left-hand operand if it isn’t null; otherwise, it evaluates the right-hand operand and returns its result. The ?? operator doesn’t evaluate its right-hand operand if the left-hand operand evaluates to non-null.
確かに coalescing (coalesce) には「合体」という訳もあるのだけど
null forgiving operator
null免除演算子は英語ではnull forgiving operatorというらしい ! (null 免除) 演算子 - C# リファレンス | Microsoft Docs
C# 8.0 以降では、単項の接尾辞 ! 演算子は null 免除演算子です。 有効な null 許容注釈コンテキストでは、null 免除演算子を使用して、 参照型の式 x が null: x! ではないことを宣言します。 単項の接頭辞 ! 演算子は、論理否定演算子です。
Available in C# 8.0 and later, the unary postfix ! operator is the null-forgiving operator. In an enabled nullable annotation context, you use the null-forgiving operator to declare that expression x of a reference type isn’t null: x!. The unary prefix ! operator is the logical negation operator.
「免除」よりは「寛容」とかじゃないすかねえ(ぐぐったらそれが一番に出てきた)。
「免除」の意味合いでいうなら、「NULLチェック」をしてない?
null-conditional operator
null条件演算子 は null-conditional operator。
Member access operators and expressions - C# reference | Microsoft Docs
- If a evaluates to null, the result of a?.x or a? is null.
- If a evaluates to non-null, the result of a?.x or a? is the same as the result of a.x or a , respectively.
これは特に…と言いたいところだけど
条件演算子
null-conditional operator のところを読んでいて気がついたのだけど、 いわゆる「条件演算子」(Cの ? : ね)も訳語がおかしくないか? という感じがしてきた。
conditional operator をすなおに読めば「条件付きの演算子」といった感じになると思うのだけど、 これを「条件演算子」と読み替えるのはありだったんだろうか? 「条件付きの~」という言い回しが「野暮ったい」のは否定しないけど。 それにイマサラ変えようってわけにもいかんよねえ。
bug
Palo Alto Tiny BASIC 3.0の記事を読むとこういう部分がある
d) In Version 1.0 there, are two known bugs: FOR 1=1 TO 32767 will never end, and ABS(-32767-1) gives a negative result. In Version 2.0, there is one known bug: ABS(0) gives an error. These bugs are fixed in Version 3.0.
ということで、どんな処理をしていてそういうバグになったのか。 また、どのように修正したのかを眺めてみることにしよう。
まずはABSから。
abs
diff –side-by-side の結果をちょっと幅を狭めて (Hugo(のテーマ)で幅を調整するのってどこをいじるのがいいんだろう? たとえばPC の時なんかは幅を広げたい)ぺたり。 左側が1.0のコードで、右側が2.0のコードになっています。 まずはABSから。
ABS: CALL PARN ABS: CALL PARN
> DCX D
CALL CHKSGN CALL CHKSGN
MOV A,H | INX D
ORA H <
JM QHOW <
RET RET
1.0では、CHKSGN で絶対値をとった結果がマイナスだったらエラーにしているが、 これは16ビット幅の2の補数 -32768 を食わせるとその結果も-32768となって符号が負になるから。
2.0での修正方針(DEレジスタペアの操作)がよくわからんけど、確かに-32768食わせても大丈夫っぽい。 ただし今度は0を食わせたときにおかしくなるというのは
CHKSGN: MOV A,H CHKSGN: MOV A,H
ORA A ORA A ;
RP RP ;hlの最上位が立ってなければリターン
CHGSGN: MOV A,H CHGSGN: MOV A,H
> ORA L
> RZ ;hlが0ならリターン
> MOV A,H
> PUSH PSW
CMA CMA
MOV H,A MOV H,A
MOV A,L MOV A,L
CMA CMA
MOV L,A MOV L,A
INX H INX H
> POP PSW
> XRA H
> JP QHOW ;反転した結果と元の値が同符号ならエラー
MOV A,B MOV A,B
XRI 80H XRI 80H
MOV B,A MOV B,A
RET RET
(1.0の方も含めて)最後の方でBレジスタの値をごにょごにょしてるけど なんだっけこれ。
20200913追記
DEレジスタペアの操作については 復活!TINY BASIC に解説あり(表示されるエラーメッセージの調整のためらしい)。
for next
for の部分は変更なし。 差分が出ているのは1.0で即値が10進表記だったのが2.0で16進表記になっているため。
FOR: CALL PUSHA FOR: CALL PUSHA
CALL SETVAL CALL SETVAL
DCX H DCX H
SHLD LOPVAR SHLD LOPVAR
LXI H,TAB5-1 LXI H,TAB5-1
JMP EXEC JMP EXEC
FR1: RST 3 FR1: RST 3
SHLD LOPLMT SHLD LOPLMT
LXI H,TAB6-1 LXI H,TAB6-1
JMP EXEC JMP EXEC
FR2: RST 3 FR2: RST 3
JMP FR4 JMP FR4
FR3: LXI H,1 | FR3: LXI H,1H
FR4: SHLD LOPINC FR4: SHLD LOPINC
FR5: LHLD CURRNT FR5: LHLD CURRNT
SHLD LOPLN SHLD LOPLN
XCHG XCHG
SHLD LOPPT SHLD LOPPT
LXI B,10 | LXI B,0AH
LHLD LOPVAR LHLD LOPVAR
XCHG XCHG
MOV H,B MOV H,B
MOV L,B MOV L,B
DAD SP DAD SP
DB 3EH DB 3EH
FR7: DAD B FR7: DAD B
MOV A,M MOV A,M
INX H INX H
ORA M ORA M
JZ FR8 JZ FR8
MOV A,M MOV A,M
DCX H DCX H
CMP D CMP D
JNZ FR7 JNZ FR7
MOV A,M MOV A,M
CMP E CMP E
JNZ FR7 JNZ FR7
XCHG XCHG
LXI H,0 | LXI H,0H
DAD SP DAD SP
MOV B,H MOV B,H
MOV C,L MOV C,L
LXI H,10 | LXI H,0AH
DAD D DAD D
CALL MVDOWN CALL MVDOWN
SPHL SPHL
FR8: LHLD LOPPT FR8: LHLD LOPPT
XCHG XCHG
RST 6 RST 6
ということでNEXT部の方を
NEXT: RST 7 NEXT: RST 7
JC QWHAT JC QWHAT
SHLD VARNXT SHLD VARNXT
NX0: PUSH D NX0: PUSH D ;
XCHG XCHG
LHLD LOPVAR LHLD LOPVAR
MOV A,H MOV A,H
ORA L ORA L
JZ AWHAT JZ AWHAT
RST 4 RST 4
JZ NX3 JZ NX3
POP D POP D
CALL POPA CALL POPA
LHLD VARNXT LHLD VARNXT
JMP NX0 JMP NX0
NX3: MOV E,M NX3: MOV E,M
INX H INX H
MOV D,M MOV D,M
LHLD LOPINC LHLD LOPINC
PUSH H PUSH H
> MOV A,H
> XRA D ;H ^ D
> MOV A,D
DAD D DAD D ;(LOPVAR)+LOPINC
XCHG | JM NX4
> XRA H ;(old H ^ D) ^ new H
> JM NX5
> NX4: XCHG
LHLD LOPVAR LHLD LOPVAR
MOV M,E MOV M,E
INX H INX H
MOV M,D MOV M,D
LHLD LOPLMT LHLD LOPLMT
POP PSW POP PSW
ORA A ORA A
JP NX1 JP NX1
XCHG XCHG
NX1: CALL CKHLDE NX1: CALL CKHLDE
POP D POP D
JC NX2 JC NX2
LHLD LOPLN LHLD LOPLN
SHLD CURRNT SHLD CURRNT
LHLD LOPPT LHLD LOPPT
XCHG XCHG
RST 6 RST 6
> NX5: POP H
> POP D
NX2: CALL POPA NX2: CALL POPA
RST 6 RST 6
うーむ。8080のコードをステップ実行できる環境が欲しい…
FOR~NEXTの制御変数(LOPVAR)に増分(LOPINC)を足しこむところでごにょごにょやってますな。 バグの説明を見ると、1.0ではただ単純にlimitを越えたかどうかで判定していた (ので、32767を超える整数値が表現できないため FOR 1=1 TO 32767 will never end になると) のを色々条件を見るようにしたと。