アセンブラーは、他の「コンパイラー」と同様に、言語文法プロセッサーにフィードする語彙アナライザーとして作成するのが最適です。
アセンブリ言語は通常、通常のコンパイル済み言語よりも簡単です。構造が行の境界をまたぐことを心配する必要がなく、通常は形式が固定されているからです。
2年ほど前に教育目的で(架空の)CPUのアセンブラを書きましたが、基本的に各行を次のように扱いました。
- オプションのラベル (例:
:loop
)。
- 操作 (例:
mov
)。
- オペランド (例:
ax,$1
)。
これを行う最も簡単な方法は、トークンを簡単に区別できるようにすることです。
そのため、最初からラベルを付けなければならないというルールを作成しました:
。これにより、線の分析が非常に簡単になりました。行を処理するプロセスは次のとおりです。
- コメントを取り除きます (最初
;
に文字列の外側から行末まで)。
- 存在する場合はラベルを抽出します。
- 最初の単語は操作です。
- 残りはオペランドです。
あなたの人生を楽にするために、異なるオペランドにも特別なマーカーがあると簡単に主張することができます。これはすべて、入力形式を制御できることを前提としています。Intel または AT&T 形式を使用する必要がある場合は、少し難しくなります。
私がそれにアプローチした方法は、呼び出される単純な操作ごとの関数 (たとえば、、、doJmp
)がありdoCall
、doRet
その関数がオペランドで許可されるものを決定するというものでした。
たとえばdoCall
、数値またはラベルのみを許可し、doRet
何も許可しません。
たとえば、encInstr
関数のコード セグメントは次のとおりです。
private static MultiRet encInstr(
boolean ignoreVars,
String opcode,
String operands)
{
if (opcode.length() == 0) return hlprNone(ignoreVars);
if (opcode.equals("defb")) return hlprByte(ignoreVars,operands);
if (opcode.equals("defbr")) return hlprByteR(ignoreVars,operands);
if (opcode.equals("defs")) return hlprString(ignoreVars,operands);
if (opcode.equals("defw")) return hlprWord(ignoreVars,operands);
if (opcode.equals("defwr")) return hlprWordR(ignoreVars,operands);
if (opcode.equals("equ")) return hlprNone(ignoreVars);
if (opcode.equals("org")) return hlprNone(ignoreVars);
if (opcode.equals("adc")) return hlprTwoReg(ignoreVars,0x0a,operands);
if (opcode.equals("add")) return hlprTwoReg(ignoreVars,0x09,operands);
if (opcode.equals("and")) return hlprTwoReg(ignoreVars,0x0d,operands);
関数はhlpr...
単純にオペランドを取り、命令を含むバイト配列を返しました。,
これらは、adc addなど、多くの操作で同様のオペランド要件がある場合に役立ちます。and
上記の場合、すべて 2 つのレジスタ オペランドが必要です (2 番目のパラメーターは、命令に対して返されるオペコードを制御します)。
オペランドのタイプを簡単に区別できるようにすることで、どのオペランドが提供されているか、それらが正当かどうか、どのバイト シーケンスを生成するかを確認できます。操作を独自の機能に分離することで、適切な論理構造が提供されます。
さらに、ほとんどの CPU はオペコードから操作への合理的な論理変換に従うため (チップ設計者の負担を軽減するため)、すべてのオペコードで非常によく似た計算が行われ、たとえばインデックス付きアドレス指定が可能になります。
可変長命令を許可する CPU でコードを適切に作成するには、2 つのパスで行うのが最善です。
最初のパスでは、コードを生成せず、命令の長さだけを生成します。これにより、遭遇したすべてのラベルに値を割り当てることができます。2 番目のパスはコードを生成し、それらの値が既知であるため、それらのラベルへの参照を埋めることができます。上記のignoreVars
コード セグメントは、この目的で使用されました (コードのバイト シーケンスが返されたため、長さを知ることができましたが、シンボルへの参照はすべて 0 を使用しました)。