DEC社 TOPS-20のSNOBOL言語で8080アセンブラを作成(コード編)

SNOBOL

SNOBOL

SNOBOL言語で8080アセンブラ

2018年4月、歴史的に貴重なDEC社 TOPS-20のSNOBOL言語で8080アセンブラ(ASM80.SNO)を作成しました。このブログでは、ASM80.SNOのコード編を書きます。

ASM80 By SNOBOL

ASM80 By SNOBOL

ASM80.SNOの設計編は、下記ブログを参照してください。

DEC社 TOPS-20のSNOBOL言語で8080アセンブラを作成(設計編)

ASM80.SNOのSNOBOLコード説明

ASM80.SNOのSNOBOLコードの全体は、このブログの最後に示します。

SNOBOL言語で記述して約400ステップ規模で、8080アセンブラを作成することができました。SNOBOL言語の強力なパターンマッチ能力の力ですね。

入出力デバイス定義

ソースファイル名を入力して、入出力用デバイスの定義を行います。

 
*======================================================
*      8080 Assembler
*
     OUTPUT("CON_DEV" , "TTY:" ,"T")
     INPUT( "KEY_DEV" , "TTY:")
     CON_DEV = "Input i8080 ASM Source ? "
     ISRC = KEY_DEV
     OLST = ISRC  
     OLST  (".ASM" | ".asm") = ".LST" 
     INPUT( "INPUT_PS1"  , "DSK:" ISRC)
     INPUT( "INPUT_PS2"  , "DSK:" ISRC)
     OUTPUT("OUTPUT_LST" , "DSK:" OLST)
  • 変数 ISRC : .ASM ソースファイル名(KEY_DEVから入力)
  • 変数 OLST : .LST リスト出力ファイル名 (拡張子.ASMを.LSTに置換)

デバイスの定義を行います。

デバイス名 デバイス
CON_DEV TTY:出力 ターミナルモード
KEY_DEV TTY:入力
INPUT_PS1 DSK:ISRC ソース入力 パス1用
INPUT_PS2 DSK:ISRC ソース入力 パス2用
OUTPUT_LST DSK:OLST リスト出力

関数定義

プログラムで使用するユーザー関数と演算子を定義します。

 
*------------------------------------------
*    Function Define
*
    DEFINE('ADDLTBL(LABEL,VAL) V')
    DEFINE('REFLTBL(LABEL) ')
    DEFINE('GETIMM(STR) IMM')
    DEFINE('TOUPPER(STR) UC,LC')
    DEFINE('CVSTR2BIN(STR,BASE) C,E,DIGIT,DGSET,N')
    DEFINE('CVBIN2HEX(BIN,N)    Q,R')
    DEFINE('CVOCT2HEX(STRO) ')
    OPSYN( "%"   , "REMDR" ,2)
*==================================================
    :(START)
関数名 機能 概要
ADDLTBL
(LABEL,VAL)
ラベル登録 ラベルテーブルにLABELでVALを登録
REFLTBL
(LABEL)
ラベル参照 ラベルテーブルからLABELを参照
GETIMM
(STR)
直値参照 STRから直値を参照
TOUPPER
(STR)
英大文字変換 STRを英大文字に変換
CVSTR2BIN
(STR,BASE)
文字列基数変換 文字列STRを基数(BASE)で整数に変換
CVBIN2HEX
(BIN,N)
整数ヘキサ変換 整数BINをN桁16進数に変換
CVOCT2HEX
(STR)
8進to16進変換 8進数文字列STRを16進数2桁に変換

演算子「%」を演算子シノニム定義で、剰余を算出する組み込み関数 REMDR()の呼び出しに定義します。すなわち、「N % D」は、REMDR(N,D)を呼び出します。

STARTラベルに分岐して、関数定義本体の実行をスキップします。

関数本体の定義

ラベル登録

ラベルテーブルにLABELをキーとしてVALを登録します。

 
*==================================================
*  Add Label Table        ADDLTBL(LABEL,VAL)
*
ADDLTBL ADDLTBL = ""
     V = EQ(PASS ,1) TBL_LAB1<label>
     V = EQ(PASS ,2) TBL_LAB2<label>
     IDENT(V,"")                     :F(FRETURN)
     TBL_LAB1<label> = EQ(PASS ,1) VAL
     TBL_LAB2<label> = EQ(PASS ,2) VAL
     :(RETURN)

PASSによりふたつのラベルテーブルTBL_LAB1<○>とTBL_LAB2<○>を使い分けます。

  1. LABLEが登録済みの場合は、二重登録エラーなので FRETURNします。
  2. ラベルテーブル<LABEL>にVALを登録します。

ラベル参照

ラベルテーブルからLABELキーとして参照します。

 
*==================================================
*  Ref Label Table        REFLTBL(LABEL)
*
REFLTBL REFLTBL = "0"
     EQ(PASS ,1)                   :F(REF.NX)
     IDENT(TBL_LAB1<LABEL>,"")     :S(RETURN)
REF.NX
     REFLTBL = TBL_LAB1<LABEL>
     :(RETURN)

PASS=1のとき、LABELが未登録なら “0”を返します。
パス1用のラベルテーブルTBL_LAB1<LABEL>を参照します。

直値参照

STRから直値を参照します。

 
*==================================================
*   Get Immediate Value          GETIMM(STR)
*
GETIMM   GETIMM = 0
    PT_IMMH = SPAN("0123456789ABCDEF") . IMM "H" . HB REM
    PT_IMMB = SPAN("01")               . IMM "B" . HB REM
    PT_IMM  = SPAN("0123456789")       . IMM          REM
    PT_LAB  = SPAN(LABEL_X)            . IMM          REM
GETIMM.N 
    HB = "D"
    STR (PT_IMMH | PT_IMMB | PT_IMM | PT_LAB)    :F(FRETURN)
    BASE = 10
    BASE = IDENT(HB ,"H") 16
    BASE = IDENT(HB ,"B")  2
    GETIMM = CVSTR2BIN(IMM,BASE)    :S(RETURN)
    STR = REFLTBL(IMM)
   :(GETIMM.N)
  1. 直値のパターンに一致しない場合は、パターン不一致エラーで FRETURNします。
  2. 直値のパターンに一致した場合は、一致部分のIMMとHB識別を設定します。HB識別(”H” or “B” or “”)によりBASE(16 or 2 or 10)に変換します。
  3. 一致部分 IMMをBASE進数の数値に変換します。
  4. 数値変換が正常なら、数値を戻り値にします。
  5. 数値変換が異常なら、ラベルとみなしてIMMでラベル参照してSTRに設定して直値のパターンチェックを繰り返します。

英大文字変換

STRを英大文字に変換します。

 
*==================================================
*   Convert String Case Lower To Upper  TOUPPER(STR)
*
TOUPPER   TOUPPER = STR
     UC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
     LC = 'abcdefghijklmnopqrstuvwxyz'
     TOUPPER = REPLACE(STR,LC,UC)        
     :(RETURN)

文字列基数変換

文字列STRを基数(BASE)で整数に変換します。

 
*==================================================
*  Convert String To Binary  CVSTR2BIN(STR,BASE)
*
CVSTR2BIN  CVSTR2BIN = 0
     C  = 1
     E = SIZE(STR)
     DGSET = SUBSTR("0123456789ABCDEF" ,1,BASE)
CVS2B.LOOP
     DIGIT = SUBSTR(STR ,C ,1)
     DGSET  BREAK(DIGIT) . N               :F(FRETURN)
     CVSTR2BIN = CVSTR2BIN * BASE + SIZE(N)
     C = LT(C,E) C + 1                 :S(CVS2B.LOOP)
     :(RETURN)

整数ヘキサ変換

整数BINをN桁16進数に変換します。

 
*==================================================
*  Convert Binary To Hex  CVBIN2HEX(BIN,N)
*
CVBIN2HEX  CVBIN2HEX = ""
CVB2H.LOOP
      Q = BIN / 16
      R = REMDR(BIN ,16)
      BIN = Q
      "0123456789ABCDEF" LEN(R) LEN(1) . S
      CVBIN2HEX = S CVBIN2HEX 
      N = GT(N,1) N - 1                      :S(CVB2H.LOOP)
      :(RETURN)

8進to16進変換

8進数文字列STRを16進数2桁に変換します。

 
*==================================================
*  Convert STR(Oct) To STR(Hex)  CVOCT2HEX(STRO)
*
CVOCT2HEX CVOCT2HEX = ""
     CVOCT2HEX = CVBIN2HEX(CVSTR2BIN(STRO,8),2)
     :(RETURN)

各種の定義

 
*======================================================
START
     OUTPUT = "i8080 Assembler START " ISRC " ---> " OLST
*----------------------------
*  EQU / LABEL Define
*
     TBL_LAB1 = TABLE()
     TBL_LAB2 = TABLE()
     LABEL_X = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
     PT_LABEL = (POS(0) SPAN(LABEL_X) . LABEL ":") 

ASM80の開始メッセージを表示します。

ラベルテーブルを定義します。

テーブル名 用途
TBL_LAB1 パス1用ラベルテーブル
TBL_LAB2 パス2用ラベルテーブル

ラベルのパターン(PT_LABEL)を定義します。左端から英字、数字、アンダーラインの組合せの後にコロン(:)がついた文字列をラベルとします。

8080命令コードの定義

 
*
*  8080 Operation Code Define    OP Code Octal "377" "+Len"
*
     OPCODE = TABLE()
     OPCODE<"MOV" > = "1DS+1";     OPCODE<"MVI" > = "0D6+2"
     OPCODE<"INR" > = "0R4+1";     OPCODE<"DCR" > = "0R5+1"
     OPCODE<"ADD" > = "20R+1";     OPCODE<"ADC" > = "21R+1"
     OPCODE<"SUB" > = "22R+1";     OPCODE<"SBB" > = "23R+1"
     OPCODE<"ANA" > = "24R+1";     OPCODE<"XRA" > = "25R+1"
     OPCODE<"ORA" > = "26R+1";     OPCODE<"CMP" > = "27R+1"
     OPCODE<"ADI" > = "306+2";     OPCODE<"ACI" > = "316+2"
     OPCODE<"SUI" > = "326+2";     OPCODE<"SBI" > = "336+2"
     OPCODE<"ANI" > = "346+2";     OPCODE<"XRI" > = "356+2"
     OPCODE<"ORI" > = "366+2";     OPCODE<"CPI" > = "376+2"
     OPCODE<"RLC" > = "007+1";     OPCODE<"RRC" > = "017+1"
     OPCODE<"RAL" > = "027+1";     OPCODE<"RAR" > = "037+1"
     OPCODE<"OUT" > = "323+2";     OPCODE<"IN" > = "333+2"
     OPCODE<"HLT" > = "166+1";     OPCODE<"RST" > = "3V7+1"
     OPCODE<"PUSH"> = "3R5+1";     OPCODE<"POP" > = "3R1+1"
     OPCODE<"SHLD"> = "042+3";     OPCODE<"LHLD"> = "052+3"
     OPCODE<"STA" > = "062+3";     OPCODE<"LDA" > = "072+3"
     OPCODE<"LXI" > = "0P1+3";     OPCODE<"DAD" > = "0Q1+1"
     OPCODE<"STAX"> = "0P2+1";     OPCODE<"LDAX"> = "0Q2+1"
     OPCODE<"INX" > = "0P3+1";     OPCODE<"DCX" > = "0Q3+1"
     OPCODE<"JMP" > = "303+3"
     OPCODE<"JC" > = "332+3";     OPCODE<"JNC" > = "322+3"
     OPCODE<"JZ" > = "312+3";     OPCODE<"JNZ" > = "302+3"
     OPCODE<"JP" > = "362+3";     OPCODE<"JM" > = "372+3"
     OPCODE<"JPE" > = "352+3";     OPCODE<"JPO" > = "342+3"
     OPCODE<"CALL"> = "315+3"
     OPCODE<"CC" > = "334+3";     OPCODE<"CNC" > = "324+3"
     OPCODE<"CZ" > = "314+3";     OPCODE<"CNZ" > = "304+3"
     OPCODE<"CP" > = "364+3";     OPCODE<"CM" > = "374+3"
     OPCODE<"CPE" > = "354+3";     OPCODE<"CPO" > = "344+3"
     OPCODE<"RET" > = "311+1"
     OPCODE<"RNZ" > = "300+1";     OPCODE<"RZ" > = "310+1"
     OPCODE<"RNC" > = "320+1";     OPCODE<"RC" > = "330+1"
     OPCODE<"RPO" > = "340+1";     OPCODE<"RPE" > = "350+1"
     OPCODE<"RP" > = "360+1";     OPCODE<"RM" > = "370+1"
     OPCODE<"XCHG"> = "353+1";     OPCODE<"XTHL"> = "343+1"
     OPCODE<"SPHL"> = "371+1";     OPCODE<"PCHL"> = "351+1"
     OPCODE<"CMA" > = "057+1";     OPCODE<"STC" > = "067+1"
     OPCODE<"CMC" > = "077+1";     OPCODE<"DAA" > = "047+1"
     OPCODE<"EI" > = "373+1";     OPCODE<"DI" > = "363+1"
     OPCODE<"NOP" > = "000+1"

OPCODEテーブルに命令をキーとして命令コードと命令長を登録します。命令コードは、8進数3桁で8bitを表します。プラス(+)の後ろに命令長を1~3で定義します。


「OPCODE<“JMP” > = “303+3″」は、JMP命令の定義です。
3バイト命令長で、命令コードは8進数で(303)=2進数で(11 000 011)=16進数で(C3)です。


「OPCODE<“MOV” > = “1DS+1″」は、MOV命令の定義です。
1バイト命令長で、命令コードは8進数で(1DS)=2進数で(01 ddd sss)で、dddは転送先レジスタ、sssは転送元レジスタです。


疑似命令の定義

 
*-----------------------------------
*    PSEUDO INSTRUCTION
*
     P_PSEUDO = "ORG" | "DB" | "DW" | "DS" | "EQU"

以下の疑似命令をサポートします。

機能 疑似命令
定数定義 ラベル名 EQU 数値
アドレス設定 ORG アドレス
1バイトの定数定義 DB バイト定数
2バイトの定数定義 DW ワード定数
領域定義 DS 領域数

8080命令のオペランド定義

 
*-----------------------------------
*    8080 MPU INSTRUCTION
*
     A_BD     = "LDAX" | "STAX"
     A_BDHS   = "DAD"  | "INX"  | "DCX" 
     A_RM_RM  = "MOV"
     A_RM_IMM = "MVI"
     A_BDHS_I = "LXI"
     A_BDHA   = "PUSH" | "POP"
     A_VEC    = "RST"
     A_RM     = "INR"  | "DCR"  | "ADD"  | "ADC"  | "SUB"  | "SBB"  | "ANA"  | "XRA"  | "ORA"  | "CMP"
     A_IMM    = "ADI"  | "ACI"  | "SUI"  | "SBI"  | "ANI"  | "XRI"  | "ORI"  | "CPI"
     A_PORT   = "IN"   | "OUT"
     A_NON    =          "HLT"  | "RLC"  | "RRC"  | "RAL"  | "RAR" 
     A_NON    =  A_NON | "RET"  | "RC"   | "RNC"  | "RZ"   | "RNZ"  | "RPE"  | "RPO" | "RP"   | "RM"
     A_NON    =  A_NON | "XCHG" | "XTHL" | "SPHL" | "PCHL" 
     A_NON    =  A_NON | "CMA"  | "STC"  | "CMC"  | "DAA"  | "EI"   | "DI"   | "NOP"
     A_ADR    =          "JMP"  | "JC"   | "JNC"  | "JZ"   | "JNZ"  | "JPE"  | "JPO" | "JP"   | "JM" 
     A_ADR    =  A_ADR | "CALL" | "CC"   | "CNC"  | "CZ"   | "CNZ"  | "CPE"  | "CPO" | "CP"   | "CM" 
     A_ADR    =  A_ADR | "LDA"  | "STA"  | "LHLD" | "SHLD"
*
     P_INST1 = A_BDHS  | A_BD
     P_INST2 = A_RM_RM | A_RM_IMM | A_RM | A_IMM | A_VEC | A_NON | A_BDHA | A_ADR | A_BDHS_I | A_PORT

8080命令をオペランドの形式で分類して定義します。

形式 命令 オペランド部
RM_RM MOV Rd,Rs
RM_IMM MVI Rd,Imd8
RM INR/DCR
ADD/ADC/SUB/SBB
ANA/XRA/ORA/CMP
R
IMM ADI/ACI/SUI/SBI
ANI/XRI/ORI/CPI
Imd8
PORT IN /OUT Port
NON HLT/EI /DI /NOP
RLC/RRC/RAL/RAR
RET
RC /RNC/RZ /RNZ
RP /RM /RPE/RPO
XCHG/XTHL/SPHL/PCHL
CMA/STC/CMC/DAA
ADR JMP
JC /JNC/JZ /JNZ
JP /JM /JPE/JPO
CALL
CC /CNC/CZ /CNZ
CP /CM /CPE/CPO
LDA /STA/LHLD/SHLD
Adr
VEC RST Vect
BDHA PUSH/POP Rp
BD LDAX/STAX Rp
BDHS DAD/INX/DCX Rp
BDHS_I LXI Rp,Imd16

命令のパターン P_INST を定義します。

LINEの開始処理

 
     OBJHEX = ARRAY(3)
     PASS = 1
FILE_START
     INPUT_DEV = EQ(PASS ,1) "INPUT_PS1"
     INPUT_DEV = EQ(PASS ,2) "INPUT_PS2"
     PC     = 0
     LN     = 1
LINE_START 
     MSG_ERR = ""; ERSW = 0  
     OPLEN = 0
     LINESRC = $INPUT_DEV                      :F(FILE_END)
     LINE = TOUPPER(LINESRC)
     LINE  (";" REM) =
     LINE PT_LABEL   =                         :F(LINE_NX)
     ADDLTBL(LABEL,PC)                         :F(ERR_LABEL)
LINE_NX
     LINE ( " END" ) =                         :S(OUT_OBJ)
     LINE = TRIM(LINE) 
     EQ(SIZE(LINE),0)                          :S(OUT_OBJ)
  1. 生成オブジェクトを格納するOBJHEX配列を3個定義します。
  2. PASS←1に設定
  3. PASSにより INPUT_DEVデバイスを設定します。INPUT_PSx (x=1or2)
  4. プログラムカウンタ PC←0に設定します。
  5. 行カウンタ LN←1に設定します。
  6. エラーをリセット(ERSW←0)します。
  7. 命令長 OPLEN←0に設定します。
  8. 変数LINESRCにソースコードを入力します。EOFなら、:F(FILE_END)に分岐します。
  9. 変数LINEに英大文字変換して格納します。
  10. セミコロン(;)から後ろを消して、コメントを除去します。
  11. ラベルパターンに一致した場合は、ラベル部をLABELに設定してラベルテーブルにキーLABELでPCを登録します。
  12. END文なら、OUT_OBJに分岐します。
  13. 行の右端の不要な空白を除去し、空行ならOUT_OBJに分岐します。

疑似命令と命令のパターン一致

 
*
     LINE (P_PSEUDO . PSEUDO (NULL | " ")) =   :S($PSEUDO)
     LINE (P_INST1  . INST   (NULL | " ")) =   :S(INST_OP)
     LINE (P_INST2  . INST   (NULL | " ")) =   :S(INST_OP)
     :(ERR_INST)
INST_OP
     OPC = OPCODE
     OPC ("+" ANY("0123"))  . OPLEN  =  
     INST  (A_BD     )                      :S(X_BD)
     INST  (A_BDHS   )                      :S(X_BDHS)
     INST  (A_RM_RM  )                      :S(X_RM_RM)
     INST  (A_RM_IMM )                      :S(X_RM_IMM)
     INST  (A_RM     )                      :S(X_RM)
     INST  (A_IMM    )                      :S(X_IMM)
     INST  (A_PORT   )                      :S(X_PORT)
     INST  (A_NON    )                      :S(X_NON)
     INST  (A_ADR    )                      :S(X_ADR)
     INST  (A_VEC    )                      :S(X_VEC)
     INST  (A_BDHA   )                      :S(X_BDHA)
     INST  (A_BDHS_I )                      :S(X_BDHS_I)
       :(ERR_INST)

疑似命令(P_PSEUDO)にパターン一致した場合は、一致部分を変数PSEUDOに格納して S($PSEUDO)に分岐します。変数PSEUDOの内容に($PSEUDO)で分岐できるのは、SNOBOL言語の凄さです。
ラベル ORG or DB or DW or DS or EQU に分岐します。

P_PSEUDO = “ORG” | “DB” | “DW” | “DS” | “EQU”

命令パターン(P_INST1/P_INST2)に一致した場合は、一致部分を変数INSTに格納します。命令パターンに一致しない場合は、ERR_INSTに分岐して命令エラーとします。

P_INST1 と P_INST2 に分離したのは、意味があります。SNOBOLパターンマッチの記述方法に不備があるのか、ミスマッチが発生しました。

  • LDAX命令が”LDA”に一致
  • STAX命令が”STA”に一致
  • INX命令が”IN”に一致

そこで、最初にP_INST1でパターンマッチし、不一致の場合だけP_INS2のパターンマッチを行います。命令コードを変数 OPCに設定して、 プラス(+)の後ろの命令長を変数 OPLENに設定します。命令 INST の種別により、各オペランド解析形式へ分岐します。

エラー処理

 
*
ERR_REG   MSG_ERR = " ; Error! Registor "           :(ERROR_LST)
ERR_INST  MSG_ERR = " ; Error! Instruction "        :(ERROR_LST)
ERR_ADR   MSG_ERR = " ; Error! Address "            :(ERROR_LST)
ERR_IMM   MSG_ERR = " ; Error! Immediate "          :(ERROR_LST)
ERR_PORT  MSG_ERR = " ; Error! IO Port "            :(ERROR_LST)
ERR_VEC   MSG_ERR = " ; Error! Vec (0..7) "         :(ERROR_LST)
ERR_LABEL MSG_ERR = " ; Error! Duplication LABEL "  :(ERROR_LST)
*
ERROR_LST OPLEN = 0; ERSW = 1
*

各エラーによりMSG_ERRにエラーメッセージを格納して、命令長 OPLEN←0として、ERSW←1とします。

オブジェクト出力

 
OUT_OBJ
    EQ(PASS,2)                            :F(OUT_NX)
    ADR = CVBIN2HEX(PC,4)
    OBJ = EQ(OPLEN ,0) "    " " " 
    OBJ = GE(OPLEN ,1) ADR    " " 
    OBJ = GE(OPLEN ,1) OBJ OBJHEX<1>
    OBJ = GE(OPLEN ,2) OBJ OBJHEX<2>
    OBJ = GE(OPLEN ,3) OBJ OBJHEX<3>
    OBJ = OBJ DUPL(" ",14 - SIZE(OBJ)) 
    OUTPUT_LST = OBJ LINESRC MSG_ERR
    EQ(ERSW,0)                          :S(OUT_NX)
    ERR = LPAD(LN ,5 ," ") "  " LINESRC MSG_ERR
    OUTPUT = ERR
OUT_NX
    PC = PC + OPLEN
    LN = LN + 1
    :(LINE_START)
  1. PASS=2のとき、LSTファイルを作成してデバイスOUTPUT_LSTに出力します。
  2. エラーがある場合(ERSW=1)は、行番号、ソース(LINESRC)、エラーメッセージ(MSG_ERR)を出力します。
  3. プログラムカウンタ PCを命令長 OPLEN進めます。
  4. 行カウンタ LNをインクリメントします。
  5. LINE_STARTに分岐して、次の行へ進みます。

EOFとパスの終了

 
FILE_END 
    PASS = LT(PASS,2) PASS + 1           :S(FILE_START)
PASS_END
     OUTPUT = "i8080 Assembler END.. " ISRC " ---> " OLST
    :(END)
  1. ソースファイル入力がEOFになれば、PASSを進めます。
  2. パス2が終了すれば、ASM80の終了メッセージを表示しENDに分岐して終了します。

各疑似命令の処理

疑似命令 ORG、DB、DW、DS、EQUの処理を行います。

ORG 疑似命令

 
*-------------------------------------------------(ORG xxxx)
ORG
    PC = GETIMM(LINE)                         :F(ERR_ADR)
    :(OUT_OBJ)
  1. LINEからGETIMM()でアドレスを取得して、PCに設定します。

DB 疑似命令

 
*-------------------------------------------------(DB IMM8)
DB
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    GT(IMM,255)                                :S(ERR_IMM)
    OPLEN = 1
    OBJHEX<1> = CVBIN2HEX(IMM,2)
    :(OUT_OBJ)
  1. LINEからGETIMM()で直値(IMM)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーに分岐します。
  2. 命令長 OPLEN←1として、オブジェクトの1バイト目に定数IMMを設定します。

DW 疑似命令

 
*-------------------------------------------------(DW IMM16)
DW
    AD = GETIMM(LINE)                         :F(ERR_IMM)
    OPLEN = 2
    OBJHEX<1> = CVBIN2HEX(AD % 256 ,2)
    OBJHEX<2> = CVBIN2HEX(AD / 256 ,2)
    :(OUT_OBJ)
  1. LINEからGETIMM()で直値(AD)を取得します。
  2. 命令長 OPLEN←2として、オブジェクトの1バイト目に定数ADの下位バイト、2バイト目にADの上位バイトを設定します。

DS 疑似命令

 
*-------------------------------------------------(ORG xxxx)
DS
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    OPLEN = IMM
    OBJHEX<1> = "??"
    OBJHEX<2> = "??"
    OBJHEX<3> = "??"
    :(OUT_OBJ)
  1. LINEからGETIMM()で直値(IMM)を取得します。
  2. 命令長 OPLEN←IMMとして領域を確保します。
  3. オブジェクトの1~3バイト目には、”??”を設定します。

EQU 疑似命令

 
*-------------------------------------------------(EQU xxxx)
EQU
    LINE (SPAN(LABEL_X) . DEFLAB " ") =        :F(ERR_LABEL)
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    ADDLTBL(DEFLAB,IMM)                        :F(ERR_LABEL)
    :(OUT_OBJ)
  1. ラベルの形式でなければ、LABELエラーに分岐します。ラベルの場合は、DEFLABにラベルを設定します。
  2. GETIMM()で、直値(IMM)を取得します。
  3. ラベルDEFLABをキーとしてIMMをラベル登録します。

8080命令の処理

オペランド形式ごとに、オペランドの解析をしてオブジェクトコードをOBJHEX<>配列に設定します。

命令 オペランド RM_RM形式

MOV命令は、オペランドに転送先レジスタと転送元レジスタを指定します。

転送先 転送元
A,B,C,D,E,H,L A,B,C,D,E,H,L
A,B,C,D,E,H,L M
M A,B,C,D,E,H,L
 
*
*-------------------------------------------------(MOV RM,RM)
*        MOV
X_RM_RM
    REGNM = "ABCDEHL"
    MOVRR = ANY(REGNM) . REGD "," ANY(REGNM) . REGS
    MOVRM = ANY(REGNM) . REGD "," "M"        . REGS
    MOVMR = "M"        . REGD "," ANY(REGNM) . REGS
    LINE (MOVRR | MOVRM | MOVMR) =                   :F(ERR_REG)
    OPC "D" = REPLACE(REGD ,"BCDEHLMA" ,"01234567")
    OPC "S" = REPLACE(REGS ,"BCDEHLMA" ,"01234567")
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,C,D,E,H,L,M,A)をコード(0,1,2,3,4,5,6,7)に変換します。
  3. 命令コード OPCのレジスタ部分(D S)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド RM_IMM形式

MVI命令は、オペランドにレジスタ(B,C,D,E,H,L,M,A)とバイト定数を指定します。

 
*-------------------------------------------------(MVI RM,IMD8)
*        MVI
X_RM_IMM
    LINE (ANY("ABCDEHLM") . REGD "," ) =             :F(ERR_REG)
    OPC "D" = REPLACE(REGD ,"BCDEHLMA" ,"01234567") 
    IMM = GETIMM(LINE)                               :F(ERR_IMM)
    GT(IMM,255)                                      :S(ERR_IMM)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(IMM,2)
    :(OUT_OBJ)
  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. GETIMM()で、カンマ”,”の右側のバイト定数(IMM)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーとします。
  3. レジスタ名(B,C,D,E,H,L,M,A)をコード(0,1,2,3,4,5,6,7)に変換します。
  4. 命令コード OPCのレジスタ部分(D)をコードで置換します。
  5. オブジェクトの1バイト目に命令コード OPCを設定します。
  6. オブジェクトの2バイト目にIMMを設定します。

命令 オペランド RM形式

下記命令は、オペランドにレジスタ(B,C,D,E,H,L,M,A)とバイト定数を指定します。

INR/DCR
ADD/ADC/SUB/SBB/ANA/XRA/ORA/CMP

 
*-------------------------------------------------(xxx RM)
*        INR/DCR
*        ADD/ADC/SUB/SBB/ANA/XRA/ORA/CMP
X_RM
    LINE (ANY("ABCDEHLM") . REG) =            :F(ERR_REG)
    OPC "R" = REPLACE(REGD ,"BCDEHLMA" ,"01234567")
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,C,D,E,H,L,M,A)をコード(0,1,2,3,4,5,6,7)に変換します。
  3. 命令コード OPCのレジスタ部分(D)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド IMM形式

下記命令は、オペランドにバイト定数を指定します。

ADI/ACI/SUI/SBI/ANI/XRI/ORI/CPI

 
*-------------------------------------------------(xxx IMM8)
*        ADI/ACI/SUI/SBI/ANI/XRI/ORI/CPI
X_IMM
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    GT(IMM,255)                                :S(ERR_IMM)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(IMM,2)
    :(OUT_OBJ)
  1. GETIMM()で、バイト定数(IMM)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーとします。
  2. オブジェクトの1バイト目に命令コード OPCを設定します。
  3. オブジェクトの2バイト目にバイト定数 IMMを設定します。

命令 オペランド PORT形式

下記命令は、オペランドにバイト定数を指定します。

IN/OUT

 
*-------------------------------------------------(xxx PORT)
*        IN/OUT
X_PORT
    PORT = GETIMM(LINE)                         :F(ERR_IMM)
    GT(PORT,255)                                :S(ERR_PORT)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(PORT,2)
    :(OUT_OBJ)
  1. GETIMM()で、バイト定数(PORT)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーとします。
  2. オブジェクトの1バイト目に命令コード OPCを設定します。
  3. オブジェクトの2バイト目にバイト定数 PORTを設定します。

命令 オペランド NON形式

下記命令は、オペランドがありません。

HLT/RLC/RRC/RAL/RAR
RET/RC /RNC/RZ /RNZ/RP /RM /RPE/RPO
XCHG/XTHL/SPHL/PCHL
CMA/STC/CMC/DAA/EI /DI /NOP

 
*-------------------------------------------------(xxx)
*        HLT/RLC/RRC/RAL/RAR
*        RET/RC /RNC/RZ /RNZ/RP /RM /RPE/RPO 
*        XCHG/XTHL/SPHL/PCHL 
*        CMA/STC/CMC/DAA/EI /DI /NOP
X_NON
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
  1. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド ADR形式

下記命令は、オペランドにアドレスを指定します。

JMP /JC /JNC/JZ /JNZ/JP /JM /JPE/JPO
CALL/CC /CNC/CZ /CNZ/CP /CM /CPE/CPO
LDA /STA/LHLD/SHLD

 
*-------------------------------------------------(xxx ADR)
*        JMP /JC /JNC/JZ /JNZ/JP /JM /JPE/JPO
*        CALL/CC /CNC/CZ /CNZ/CP /CM /CPE/CPO
*        LDA /STA/LHLD/SHLD
X_ADR
    ADR = GETIMM(LINE)                         :F(ERR_ADR)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(ADR % 256 ,2)
    OBJHEX<3> = CVBIN2HEX(ADR / 256 ,2)
    :(OUT_OBJ)
  1. GETIMM()で、ワード定数(ADR)を取得します。
  2. オブジェクトの1バイト目に命令コード OPCを設定します。
  3. オブジェクトの2バイト目にADRの下位バイト、3バイト目にADRの上位バイトを設定します。

命令 オペランド VEC形式

RST命令は、オペランドにベクトルを指定します。

 
*-------------------------------------------------(RST Vect)
*        RST
X_VEC
    VEC = GETIMM(LINE)                         :F(ERR_IMM)
    GT(VEC,7)                                  :S(ERR_VEC)
    OPC "V" = VEC 
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
  1. GETIMM()で、定数(VEC)を取得します。0~7の定数定義なので、7を超える場合はVECエラーとします。
  2. ベクトル(0,1,2,3,4,5,6,7)をコード(0,1,2,3,4,5,6,7)に変換します。
  3. 命令コード OPCのベクトル部分(V)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド BDHA形式

PUSH/POP命令は、オペランドにレジスタ(B,D,H,PSW)を指定します。

 
*-------------------------------------------------(xxx BDHA)
*        PUSH/POP
X_BDHA
    LINE (ANY("BDH") | "PSW") . REG =      :F(ERR_REG)
    REG "PSW" = "A"
    OPC "R" = REPLACE(REG ,"BDHA" ,"0246")  
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,D,H,PSW)をコード(0,2,4,6)に変換します。
  3. 命令コード OPCのレジスタ部分(R)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド BD形式

LDAX/STAX命令は、オペランドにレジスタ(B,D)を指定します。

 
*-------------------------------------------------(xxx BD)
*        LDAX/STAX
X_BD
    LINE (ANY("BD") . REG) =                  :F(ERR_REG)
    REG  = REPLACE(REG ,"BD" ,"02")
    OPC "P" = REG
    OPC "Q" = REG + 1 
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,D)をコード(0,2)に変換します。
  3. 命令コード OPCのレジスタ部分(P Q)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド BDHS形式

DAD/INX/DCX命令は、オペランドにレジスタ(B,D,H,SP)を指定します。

 
*-------------------------------------------------(xxx BDHS) 
*         DAD/INX/DCX
X_BDHS
    LINE (ANY("BDH") | "SP") . REG  =       :F(ERR_REG)
    REG "SP" = "S"
    REG  = REPLACE(REG ,"BDHS" ,"0246")
    OPC "P" = REG
    OPC "Q" = REG + 1 
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,D,H,SP)をコード(0,2,4,6)に変換します。
  3. 命令コード OPCのレジスタ部分(P Q)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド BDHS_I形式

LXI命令は、オペランドにレジスタ(B,D,H,PSW)とワード定数 を指定します。

 
*-------------------------------------------------(xxx BDHS,IMM16)
*          LXI
X_BDHS_I
    LINE (ANY("BDH") | "SP") . REG ","  =      :F(ERR_REG)
    REG "SP" = "S"
    ADR = GETIMM(LINE)                         :F(ERR_ADR)
    OPC "P" = REPLACE(REG ,"BDHS" ,"0246")
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(ADR % 256 ,2)
    OBJHEX<3> = CVBIN2HEX(ADR / 256 ,2)
    :(OUT_OBJ)
END
  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. GETIMM()で、カンマ”,”の右側のワード定数(ADR)を取得します。
  3. レジスタ名(B,D,H,PSW)をコード(0,2,4,6)に変換します。
  4. 命令コード OPCのレジスタ部分(P)をコードで置換します。
  5. オブジェクトの1バイト目に命令コード OPCを設定します。
  6. オブジェクトの2バイト目にADRの下位バイト、3バイト目にADRの上位バイトを設定します。

 

SNOBOLによる8080アセンブラコード完成

SNOBOL言語による8080アセンブラのコードが完成しました。強力なパターンマッチ能力で簡潔にASM80.SNOを記述できました。

ASM80.SNOのテストについては、次回に書きます。

ASM80.SNO ソースコード

ASM80.SNOのSNOBOLコードの全体を示します。
ソースコード

*======================================================
*      8080 Assembler
*
     OUTPUT("CON_DEV" , "TTY:" ,"T")
     INPUT( "KEY_DEV" , "TTY:")
     CON_DEV = "Input i8080 ASM Source ? "
     ISRC = KEY_DEV
     OLST = ISRC  
     OLST  (".ASM" | ".asm") = ".LST" 
     INPUT( "INPUT_PS1"  , "DSK:" ISRC)
     INPUT( "INPUT_PS2"  , "DSK:" ISRC)
     OUTPUT("OUTPUT_LST" , "DSK:" OLST)
*------------------------------------------
*    Function Define
*
    DEFINE('ADDLTBL(LABEL,VAL) V')
    DEFINE('REFLTBL(LABEL) ')
    DEFINE('GETIMM(STR) IMM')
    DEFINE('TOUPPER(STR) UC,LC')
    DEFINE('CVSTR2BIN(STR,BASE) C,E,DIGIT,DGSET,N')
    DEFINE('CVBIN2HEX(BIN,N)    Q,R')
    DEFINE('CVOCT2HEX(STRO) ')
    OPSYN( "%"   , "REMDR" ,2)
*==================================================
    :(START)
*==================================================
*  Add Label Table        ADDLTBL(LABEL,VAL)
*
ADDLTBL ADDLTBL = ""
     V = EQ(PASS ,1) TBL_LAB1<LABEL>
     V = EQ(PASS ,2) TBL_LAB2<LABEL>
     IDENT(V,"")                     :F(FRETURN)
     TBL_LAB1<LABEL> = EQ(PASS ,1) VAL
     TBL_LAB2<LABEL> = EQ(PASS ,2) VAL
     :(RETURN)
*==================================================
*  Ref Label Table        REFLTBL(LABEL)
*
REFLTBL REFLTBL = "0"
     EQ(PASS ,1)                   :F(REF.NX)
     IDENT(TBL_LAB1<LABEL>,"")     :S(RETURN)
REF.NX
     REFLTBL = TBL_LAB1<LABEL>
     :(RETURN)
*==================================================
*   Get Immediate Value          GETIMM(STR)
*
GETIMM   GETIMM = 0
    PT_IMMH = SPAN("0123456789ABCDEF") . IMM "H" . HB REM
    PT_IMMB = SPAN("01")               . IMM "B" . HB REM
    PT_IMM  = SPAN("0123456789")       . IMM          REM
    PT_LAB  = SPAN(LABEL_X)            . IMM          REM
GETIMM.N 
    HB = "D"
    STR (PT_IMMH | PT_IMMB | PT_IMM | PT_LAB)    :F(FRETURN)
    BASE = 10
    BASE = IDENT(HB ,"H") 16
    BASE = IDENT(HB ,"B")  2
    GETIMM = CVSTR2BIN(IMM,BASE)    :S(RETURN)
    STR = REFLTBL(IMM)
   :(GETIMM.N)
*==================================================
*   Convert String Case Lower To Upper  TOUPPER(STR)
*
TOUPPER   TOUPPER = STR
     UC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
     LC = 'abcdefghijklmnopqrstuvwxyz'
     TOUPPER = REPLACE(STR,LC,UC)        
     :(RETURN)
*==================================================
*  Convert String To Binary  CVSTR2BIN(STR,BASE)
*
CVSTR2BIN  CVSTR2BIN = 0
     C  = 1
     E = SIZE(STR)
     DGSET = SUBSTR("0123456789ABCDEF" ,1,BASE)
CVS2B.LOOP
     DIGIT = SUBSTR(STR ,C ,1)
     DGSET  BREAK(DIGIT) . N               :F(FRETURN)
     CVSTR2BIN = CVSTR2BIN * BASE + SIZE(N)
     C = LT(C,E) C + 1                 :S(CVS2B.LOOP)
     :(RETURN)
*==================================================
*  Convert Binary To Hex  CVBIN2HEX(BIN,N)
*
CVBIN2HEX  CVBIN2HEX = ""
CVB2H.LOOP
      Q = BIN / 16
      R = REMDR(BIN ,16)
      BIN = Q
      "0123456789ABCDEF" LEN(R) LEN(1) . S
      CVBIN2HEX = S CVBIN2HEX 
      N = GT(N,1) N - 1                      :S(CVB2H.LOOP)
      :(RETURN)
*==================================================
*  Convert STR(Oct) To STR(Hex)  CVOCT2HEX(STRO)
*
CVOCT2HEX CVOCT2HEX = ""
     CVOCT2HEX = CVBIN2HEX(CVSTR2BIN(STRO,8),2)
     :(RETURN)
*======================================================
START
     OUTPUT = "i8080 Assembler START " ISRC " ---> " OLST
*----------------------------
*  EQU / LABEL Define
*
     TBL_LAB1 = TABLE()
     TBL_LAB2 = TABLE()
     LABEL_X = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
     PT_LABEL = (POS(0) SPAN(LABEL_X) . LABEL ":") 
*
*  8080 Operation Code Define    OP Code Octal "377" "+Len"
*
     OPCODE = TABLE()
     OPCODE<"MOV" > = "1DS+1";     OPCODE<"MVI" > = "0D6+2"
     OPCODE<"INR" > = "0R4+1";     OPCODE<"DCR" > = "0R5+1"
     OPCODE<"ADD" > = "20R+1";     OPCODE<"ADC" > = "21R+1"
     OPCODE<"SUB" > = "22R+1";     OPCODE<"SBB" > = "23R+1"
     OPCODE<"ANA" > = "24R+1";     OPCODE<"XRA" > = "25R+1"
     OPCODE<"ORA" > = "26R+1";     OPCODE<"CMP" > = "27R+1"
     OPCODE<"ADI" > = "306+2";     OPCODE<"ACI" > = "316+2"
     OPCODE<"SUI" > = "326+2";     OPCODE<"SBI" > = "336+2"
     OPCODE<"ANI" > = "346+2";     OPCODE<"XRI" > = "356+2"
     OPCODE<"ORI" > = "366+2";     OPCODE<"CPI" > = "376+2"
     OPCODE<"RLC" > = "007+1";     OPCODE<"RRC" > = "017+1"
     OPCODE<"RAL" > = "027+1";     OPCODE<"RAR" > = "037+1"
     OPCODE<"OUT" > = "323+2";     OPCODE<"IN"  > = "333+2"
     OPCODE<"HLT" > = "166+1";     OPCODE<"RST" > = "3V7+1"
     OPCODE<"PUSH"> = "3R5+1";     OPCODE<"POP" > = "3R1+1"
     OPCODE<"SHLD"> = "042+3";     OPCODE<"LHLD"> = "052+3"
     OPCODE<"STA" > = "062+3";     OPCODE<"LDA" > = "072+3"
     OPCODE<"LXI" > = "0P1+3";     OPCODE<"DAD" > = "0Q1+1"
     OPCODE<"STAX"> = "0P2+1";     OPCODE<"LDAX"> = "0Q2+1"
     OPCODE<"INX" > = "0P3+1";     OPCODE<"DCX" > = "0Q3+1"
     OPCODE<"JMP" > = "303+3"
     OPCODE<"JC"  > = "332+3";     OPCODE<"JNC" > = "322+3"
     OPCODE<"JZ"  > = "312+3";     OPCODE<"JNZ" > = "302+3"
     OPCODE<"JP"  > = "362+3";     OPCODE<"JM"  > = "372+3"
     OPCODE<"JPE" > = "352+3";     OPCODE<"JPO" > = "342+3"
     OPCODE<"CALL"> = "315+3"
     OPCODE<"CC"  > = "334+3";     OPCODE<"CNC" > = "324+3"
     OPCODE<"CZ"  > = "314+3";     OPCODE<"CNZ" > = "304+3"
     OPCODE<"CP"  > = "364+3";     OPCODE<"CM"  > = "374+3"
     OPCODE<"CPE" > = "354+3";     OPCODE<"CPO" > = "344+3"
     OPCODE<"RET" > = "311+1"
     OPCODE<"RNZ" > = "300+1";     OPCODE<"RZ"  > = "310+1"
     OPCODE<"RNC" > = "320+1";     OPCODE<"RC"  > = "330+1"
     OPCODE<"RPO" > = "340+1";     OPCODE<"RPE" > = "350+1"
     OPCODE<"RP"  > = "360+1";     OPCODE<"RM"  > = "370+1"
     OPCODE<"XCHG"> = "353+1";     OPCODE<"XTHL"> = "343+1"
     OPCODE<"SPHL"> = "371+1";     OPCODE<"PCHL"> = "351+1"
     OPCODE<"CMA" > = "057+1";     OPCODE<"STC" > = "067+1"
     OPCODE<"CMC" > = "077+1";     OPCODE<"DAA" > = "047+1"
     OPCODE<"EI"  > = "373+1";     OPCODE<"DI"  > = "363+1"
     OPCODE<"NOP" > = "000+1"
*-----------------------------------
*    PSEUDO INSTRUCTION
*
     P_PSEUDO = "ORG" | "DB" | "DW" | "DS" | "EQU"
*-----------------------------------
*    8080 MPU INSTRUCTION
*
     A_BD     = "LDAX" | "STAX"
     A_BDHS   = "DAD"  | "INX"  | "DCX" 
     A_RM_RM  = "MOV"
     A_RM_IMM = "MVI"
     A_BDHS_I = "LXI"
     A_BDHA   = "PUSH" | "POP"
     A_VEC    = "RST"
     A_RM     = "INR"  | "DCR"  | "ADD"  | "ADC"  | "SUB"  | "SBB"  | "ANA"  | "XRA"  | "ORA"  | "CMP"
     A_IMM    = "ADI"  | "ACI"  | "SUI"  | "SBI"  | "ANI"  | "XRI"  | "ORI"  | "CPI"
     A_PORT   = "IN"   | "OUT"
     A_NON    =          "HLT"  | "RLC"  | "RRC"  | "RAL"  | "RAR" 
     A_NON    =  A_NON | "RET"  | "RC"   | "RNC"  | "RZ"   | "RNZ"  | "RPE"  | "RPO" | "RP"   | "RM"
     A_NON    =  A_NON | "XCHG" | "XTHL" | "SPHL" | "PCHL" 
     A_NON    =  A_NON | "CMA"  | "STC"  | "CMC"  | "DAA"  | "EI"   | "DI"   | "NOP"
     A_ADR    =          "JMP"  | "JC"   | "JNC"  | "JZ"   | "JNZ"  | "JPE"  | "JPO" | "JP"   | "JM" 
     A_ADR    =  A_ADR | "CALL" | "CC"   | "CNC"  | "CZ"   | "CNZ"  | "CPE"  | "CPO" | "CP"   | "CM" 
     A_ADR    =  A_ADR | "LDA"  | "STA"  | "LHLD" | "SHLD"
*
     P_INST1 = A_BDHS  | A_BD
     P_INST2 = A_RM_RM | A_RM_IMM | A_RM | A_IMM | A_VEC | A_NON | A_BDHA | A_ADR | A_BDHS_I | A_PORT
*--------------------------------------------
     OBJHEX = ARRAY(3)
     PASS = 1
FILE_START
     INPUT_DEV = EQ(PASS ,1) "INPUT_PS1"
     INPUT_DEV = EQ(PASS ,2) "INPUT_PS2"
     PC     = 0
     LN     = 1
LINE_START 
     MSG_ERR = ""; ERSW = 0  
     OPLEN = 0
     LINESRC = $INPUT_DEV                      :F(FILE_END)
     LINE = TOUPPER(LINESRC)
     LINE  (";" REM) =
     LINE PT_LABEL   =                         :F(LINE_NX)
     ADDLTBL(LABEL,PC)                         :F(ERR_LABEL)
LINE_NX
     LINE ( " END" ) =                         :S(OUT_OBJ)
     LINE = TRIM(LINE) 
     EQ(SIZE(LINE),0)                          :S(OUT_OBJ)
*
     LINE (P_PSEUDO . PSEUDO (NULL | " ")) =   :S($PSEUDO)
     LINE (P_INST1  . INST   (NULL | " ")) =   :S(INST_OP)
     LINE (P_INST2  . INST   (NULL | " ")) =   :S(INST_OP)
     :(ERR_INST)
INST_OP
     OPC = OPCODE<INST>
     OPC ("+" ANY("0123"))  . OPLEN  =  
     INST  (A_BD     )                      :S(X_BD)
     INST  (A_BDHS   )                      :S(X_BDHS)
     INST  (A_RM_RM  )                      :S(X_RM_RM)
     INST  (A_RM_IMM )                      :S(X_RM_IMM)
     INST  (A_RM     )                      :S(X_RM)
     INST  (A_IMM    )                      :S(X_IMM)
     INST  (A_PORT   )                      :S(X_PORT)
     INST  (A_NON    )                      :S(X_NON)
     INST  (A_ADR    )                      :S(X_ADR)
     INST  (A_VEC    )                      :S(X_VEC)
     INST  (A_BDHA   )                      :S(X_BDHA)
     INST  (A_BDHS_I )                      :S(X_BDHS_I)
       :(ERR_INST)
*
ERR_REG   MSG_ERR = " ; Error! Registor "           :(ERROR_LST)
ERR_INST  MSG_ERR = " ; Error! Instruction "        :(ERROR_LST)
ERR_ADR   MSG_ERR = " ; Error! Address "            :(ERROR_LST)
ERR_IMM   MSG_ERR = " ; Error! Immediate "          :(ERROR_LST)
ERR_PORT  MSG_ERR = " ; Error! IO Port "            :(ERROR_LST)
ERR_VEC   MSG_ERR = " ; Error! Vec (0..7) "         :(ERROR_LST)
ERR_LABEL MSG_ERR = " ; Error! Duplication LABEL "  :(ERROR_LST)
*
ERROR_LST OPLEN = 0; ERSW = 1
*
OUT_OBJ
    EQ(PASS,2)                            :F(OUT_NX)
    ADR = CVBIN2HEX(PC,4)
    OBJ = EQ(OPLEN ,0) "    " " " 
    OBJ = GE(OPLEN ,1) ADR    " " 
    OBJ = GE(OPLEN ,1) OBJ OBJHEX<1>
    OBJ = GE(OPLEN ,2) OBJ OBJHEX<2>
    OBJ = GE(OPLEN ,3) OBJ OBJHEX<3>
    OBJ = OBJ DUPL(" ",14 - SIZE(OBJ)) 
    OUTPUT_LST = OBJ LINESRC MSG_ERR
    EQ(ERSW,0)                          :S(OUT_NX)
    ERR = LPAD(LN ,5 ," ") "  " LINESRC MSG_ERR
    OUTPUT = ERR
OUT_NX
    PC = PC + OPLEN
    LN = LN + 1
    :(LINE_START)
FILE_END 
    PASS = LT(PASS,2) PASS + 1           :S(FILE_START)
PASS_END
     OUTPUT = "i8080 Assembler END.. " ISRC " ---> " OLST
    :(END)
*-------------------------------------------------(ORG xxxx)
ORG
    PC = GETIMM(LINE)                         :F(ERR_ADR)
    :(OUT_OBJ)
*-------------------------------------------------(DB IMM8)
DB
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    GT(IMM,255)                                :S(ERR_IMM)
    OPLEN = 1
    OBJHEX<1> = CVBIN2HEX(IMM,2)
    :(OUT_OBJ)
*-------------------------------------------------(DW IMM16)
DW
    AD = GETIMM(LINE)                         :F(ERR_IMM)
    OPLEN = 2
    OBJHEX<1> = CVBIN2HEX(AD % 256 ,2)
    OBJHEX<2> = CVBIN2HEX(AD / 256 ,2)
    :(OUT_OBJ)
*-------------------------------------------------(ORG xxxx)
DS
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    OPLEN = IMM
    OBJHEX<1> = "??"
    OBJHEX<2> = "??"
    OBJHEX<3> = "??"
    :(OUT_OBJ)
*-------------------------------------------------(EQU xxxx)
EQU
    LINE (SPAN(LABEL_X) . DEFLAB " ") =        :F(ERR_LABEL)
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    ADDLTBL(DEFLAB,IMM)                        :F(ERR_LABEL)
    :(OUT_OBJ)
*
*-------------------------------------------------(MOV RM,RM)
*        MOV
X_RM_RM
    REGNM = "ABCDEHL"
    MOVRR = ANY(REGNM) . REGD "," ANY(REGNM) . REGS
    MOVRM = ANY(REGNM) . REGD "," "M"        . REGS
    MOVMR = "M"        . REGD "," ANY(REGNM) . REGS
    LINE (MOVRR | MOVRM | MOVMR) =                   :F(ERR_REG)
    OPC "D" = REPLACE(REGD ,"BCDEHLMA" ,"01234567")
    OPC "S" = REPLACE(REGS ,"BCDEHLMA" ,"01234567")
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
*-------------------------------------------------(MVI RM,IMD8)
*        MVI
X_RM_IMM
    LINE (ANY("ABCDEHLM") . REGD "," ) =             :F(ERR_REG)
    OPC "D" = REPLACE(REGD ,"BCDEHLMA" ,"01234567") 
    IMM = GETIMM(LINE)                               :F(ERR_IMM)
    GT(IMM,255)                                      :S(ERR_IMM)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(IMM,2)
    :(OUT_OBJ)
*-------------------------------------------------(xxx RM)
*        INR/DCR
*        ADD/ADC/SUB/SBB/ANA/XRA/ORA/CMP
X_RM
    LINE (ANY("ABCDEHLM") . REG) =            :F(ERR_REG)
    OPC "R" = REPLACE(REGD ,"BCDEHLMA" ,"01234567")
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
*-------------------------------------------------(xxx IMM8)
*        ADI/ACI/SUI/SBI/ANI/XRI/ORI/CPI
X_IMM
    IMM = GETIMM(LINE)                         :F(ERR_IMM)
    GT(IMM,255)                                :S(ERR_IMM)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(IMM,2)
    :(OUT_OBJ)
*-------------------------------------------------(xxx PORT)
*        IN/OUT
X_PORT
    PORT = GETIMM(LINE)                         :F(ERR_IMM)
    GT(PORT,255)                                :S(ERR_PORT)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(PORT,2)
    :(OUT_OBJ)
*-------------------------------------------------(xxx)
*        HLT/RLC/RRC/RAL/RAR
*        RET/RC /RNC/RZ /RNZ/RP /RM /RPE/RPO 
*        XCHG/XTHL/SPHL/PCHL 
*        CMA/STC/CMC/DAA/EI /DI /NOP
X_NON
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
*-------------------------------------------------(xxx ADR)
*        JMP /JC /JNC/JZ /JNZ/JP /JM /JPE/JPO
*        CALL/CC /CNC/CZ /CNZ/CP /CM /CPE/CPO
*        LDA /STA/LHLD/SHLD
X_ADR
    ADR = GETIMM(LINE)                         :F(ERR_ADR)
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(ADR % 256 ,2)
    OBJHEX<3> = CVBIN2HEX(ADR / 256 ,2)
    :(OUT_OBJ)
*-------------------------------------------------(RST Vect)
*        RST
X_VEC
    VEC = GETIMM(LINE)                         :F(ERR_IMM)
    GT(VEC,7)                                  :S(ERR_VEC)
    OPC "V" = VEC 
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
*-------------------------------------------------(xxx BDHA)
*        PUSH/POP
X_BDHA
    LINE (ANY("BDH") | "PSW") . REG =      :F(ERR_REG)
    REG "PSW" = "A"
    OPC "R" = REPLACE(REG ,"BDHA" ,"0246")  
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
*-------------------------------------------------(xxx BD)
*        LDAX/STAX
X_BD
    LINE (ANY("BD") . REG) =                  :F(ERR_REG)
    REG  = REPLACE(REG ,"BD" ,"02")
    OPC "P" = REG
    OPC "Q" = REG + 1 
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
*-------------------------------------------------(xxx BDHS) 
*         DAD/INX/DCX
X_BDHS
    LINE (ANY("BDH") | "SP") . REG  =       :F(ERR_REG)
    REG "SP" = "S"
    REG  = REPLACE(REG ,"BDHS" ,"0246")
    OPC "P" = REG
    OPC "Q" = REG + 1 
    OBJHEX<1> = CVOCT2HEX(OPC)
    :(OUT_OBJ)
*-------------------------------------------------(xxx BDHS,IMM16)
*          LXI
X_BDHS_I
    LINE (ANY("BDH") | "SP") . REG ","  =      :F(ERR_REG)
    REG "SP" = "S"
    ADR = GETIMM(LINE)                         :F(ERR_ADR)
    OPC "P" = REPLACE(REG ,"BDHS" ,"0246")
    OBJHEX<1> = CVOCT2HEX(OPC)
    OBJHEX<2> = CVBIN2HEX(ADR % 256 ,2)
    OBJHEX<3> = CVBIN2HEX(ADR / 256 ,2)
    :(OUT_OBJ)
END