構文解析部分で少し悩みましたが、ひとまずプロトタイプはできました。パーサーを自分で作るのとは異なり、ジェネレータを使うとエラー処理をうまく書くのが難しいです。いまのところ最低限の処理しか実装していません。またたまに例外を発生しますのでお気を付けを……。
2017/3/25追記
アセンブラの正式版の説明はこちらに移動しました。お手数をおかけします。
構文解析について
行の構文解析のところで二つの方式で悩みました。
方式1: 命令の正当性を構文規則で表現する
こんな規則になります。
operation ::= ADDA BYTE_VALUE:value {: RESULT = new ADDA(Instruction.MODE_IMMEDIATE, value.intValue()); :} | ADDA LBRACKET BYTE_VALUE:value RBRACKET {: RESULT = new ADDA(Instruction.MODE_DIRECT, value.intValue()); :} | ADDA LBRACKET REG_X PLUS BYTE_VALUE:value RBRACKET {: RESULT = new ADDA(Instruction.MODE_INDEXED, value.intValue()); :} | ADDA LBRACKET SHORT_VALUE:value RBRACKET {: RESULT = new ADDA(Instruction.MODE_EXTENDED, value.intValue()); :} | 以下全命令、全アドレシングモードに対し繰り返し
この方式だと構文エラーが自動的に判定できるので(当たり前ですが)、アクションでは正しい構文であることを前提に書けばよいです。しかし命令とアドレシングモードの組み合わせだけ規則を書かないといけなくなるのが少しつらい。……と当初は思っていましたが、組み合わせは全部で203種類しかないので、いま考えるとこちらの方が良かったかという気がしています。作り直してみるか。
方式2:命令の正当性ををアクション側で判定する
今回採用した方式。
operation ::= opcode:opcode operand:operand {: Instruction i; Constructor<? extends Instruction> constructor; try { constructor = opcode.getConstructor(Operand.class); } catch (SecurityException | NoSuchMethodException e) { throw new RuntimeException("error occured in getConstructor"); } try { i = constructor.newInstance(operand); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { throw new RuntimeException("runtime exception has occured while processing operation deduction"); } catch (InvocationTargetException e) { if (e.getCause() instanceof UnsupportedOperandModeException) { System.err.println("unsupporeted operand mode: " + opcode.getSimpleName() + " " + operand); } else { throw new RuntimeException(e.getCause()); } i = null; } RESULT = i; :} ; opcode ::= ABA {: RESULT = ABA.class; :} | ADCA {: RESULT = ADCA.class; :} | ADCB {: RESULT = ADCB.class; :} | ADDA {: RESULT = ADDA.class; :} | ADDB {: RESULT = ADDB.class; :} | ANDA {: RESULT = ANDA.class; :} | ANDB {: RESULT = ANDB.class; :} | ASL {: RESULT = ASL.class; :} 以下命令分繰り返し
オペコード部分は対応する命令のクラスオブジェクトを返すようにして、operation規則のアクションではリフレクションを使ってコンストラクタを取得し、オブジェクトを生成します。命令とアドレシングモードの組み合わせの正当性は、各クラス自身でサポートするモードのリストを実装し、各命令の最上位の親クラス(Instructionクラス)のコンストラクタ内で正当性判定を行います。実際にはこんなコードになります。
public class ADDA extends NonBranchInstruction { /* NonBranchInstructionクラスはInstructionクラスの子クラス */ public int getSupportedModeFlag() { return ADDRESSING_IMMEDIATE | ADDRESSING_DIRECT | ADDRESSING_INDEXED | ADDRESSING_EXTENDED; } ... } public abstract class Instruction { protected Instruction(Operand op) throws UnsupportedOperandModeException { int addressing_mode = convertToAddressingMode(op); if (isValid(addressing_mode)) { this.operand = op; this.mode = addressing_mode; } else { throw new UnsupportedOperandModeException(); } } protected abstract int getSupportedModeFlag(); // 具体クラスで実装する。 private boolean isValid(int addressing_mode) { if (addressing_mode == ADDRESSING_UNDEFINED) { return getSupportedModeFlag() == ADDRESSING_UNDEFINED; } // 指定されたモードと命令自身が持つ実際のモードリスト(フラグ)を比較 return (addressing_mode & getSupportedModeFlag()) != 0; } ...
この方式だと抽象的にコードが書けるのですが、構文規則でできることをわざわざアクション中ですべて実装しているのが、ちょっとやり過ぎ感が否めません。
比較のために方式1でも試してみようと思います(いずれ……)。
コメント