SIC 組譯器 (以 C# 語言實作)

程式專案下載:SicAssembler.zip

簡介

組譯器 (Assembler) 是用來將組合語言轉換成機器碼的程式,這是系統程式的一個重要主題,在本文中,我們將設計出 Beck 系統程式經典教科書中的 SIC 機器之組譯器,以下是一個簡單的 SIC 組合語言範例。

.--------------  SUM.sic -----------------------
SUMP    START    0
LOOP    LDA    I    . i++
        ADD    ONE
        STA    I
        ADD    SUM    . sum += i
        STA    SUM
        LDA    I
        COMP    N    . if (i < N) goto LOOP
        JLT    LOOP 
        LDA    SUM
        RSUB
I      WORD    0
SUM    WORD    0
N      WORD    10
ONE    WORD    1
END    SUMP

實作

我們使用 C# 實作出 SIC 組合語言的組譯器,其程式原始碼如下所示。

// 共有兩個檔案
// 程式檔 : SicAssembler.cs
// 測試檔 : SUM.sic
// ------------------------------ SicAssembler.cs ------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.VisualBasic;

namespace SICAssembler
{
    public class SIC
    {
        static void Main(string[] args)
        {
            SicAssembler asm = new SicAssembler();
            asm.parse(args[0]);
            asm.pass2();
            SicMemory memory = new SicMemory();
            SicLoader.load(asm.codes, memory);
            SicVirtualMachine vm = new SicVirtualMachine(memory, 0);
            vm.run();
        }
    }

    public class SicVirtualMachine
    {
        public static OpTable opTable = new OpTable();
        SicMemory mem;
        int A = 0, X = 0, L = 0, SW = 0, PC = 0;
        public SicVirtualMachine(SicMemory pMemory, int pCounter) {
            mem = pMemory;
            PC = pCounter;
        }

        public void dump()
        {
            Debug.log("A=" + A + " X=" + X + " L=" + L + " SW=" + SW + " PC=" + PC);
        }

        public void run() {
            while (runCode())
            {
            }
            dump();
        }

        public int compare(int a, int b)
        {
            if (a > b) return 1;
            else if (a < b) return -1;
            else return 0;
        }

        public bool runCode()
        {
            byte op = mem.memory[PC];
            byte a1 = mem.memory[PC + 1];
            byte a2 = mem.memory[PC + 2];
            int m = ((a1 & 0x7F) << 8) + a2;
            int word = mem.getWord(m);
            String opStr = String.Format("{0:X2}", op);
            Debug.log(opTable.codeToOp[opStr]+"\tmem("+String.Format("{0:X2}", m)+")\t= "+word);
            switch (op) {
                case 0x18: // ADD m
                    A += word;
                    break;
                case 0x40: // AND m
                    A &= word;
                    break;
                case 0x28: // COMP m
                    SW = compare(A, word);
                    break;
                case 0x24: // DIV m
                    A /= word;
                    break;
                case 0x3C: // J m
                    PC = m;
                    break;
                case 0x30: // JEQ m
                    if (SW == 0) PC = m;
                    break;
                case 0x34: // JGT m
                    if (SW > 0) PC = m;
                    break;
                case 0x38: // JLT m
                    if (SW < 0) PC = m;
                    break;
                case 0x48: // JSUB m
                    L = PC;
                    PC = m;
                    break;
                case 0x00: // LDA m
                    A = word;
                    break;
                case 0x50: // LDCH m
                    A = (A & 0xFF0) | mem.memory[m];
                    break;
                case 0x04: // LDX m
                    X = word;
                    break;
                case 0x20: // MUL m
                    A *= word;
                    break;
                case 0x44: // OR m
                    A |= word;
                    break;
//              case 0xD8: // RD m
                case 0x4C: // RSUB
                    if (L == 0) return false;
                    PC = L;
                    break;
                case 0x0C: // STA m
                    mem.setWord(A, m);
                    break;
                case 0x54: // STCH m
                    mem.memory[m] = (byte)A;
                    break;
                case 0x10: // STX m
                    mem.setWord(X, m);
                    break;
                case 0x1C: // SUB m
                    A -= word;
                    break;
//              case 0xB0: // SVC x
//              case 0xE0: // TD m
//                    break;
                case 0x2C: // TIX m
                    X++;
                    SW = compare(X, word);
                    break;
//                case 0xDC: // WD m
//                    break;
                default:
                    Debug.log(String.Format("Error : op={0:X2} not found !", op));
                    break;
            }
            PC += 3;
            return true;
        }
    }

    public class SicLoader
    {
        public static void load(CodeList codes, SicMemory m)
        {
            foreach (Code code in codes) {
                for (int i = 0; i < code.objCode.Length; i+=2)
                {
                    String hex = code.objCode;
                    int offset = code.offset + i/2;
                    byte b = Byte.Parse(hex.Substring(i, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
                    m.memory[offset] = b;
                    Debug.log(String.Format("{0:X2}:{1:X2}", offset, b));
                }
            }
        }
    }

    public class SicMemory
    {
        public byte[] memory = new byte[32768];

        public SicMemory() {
            for (int i = 0; i < memory.Length; i++)
                memory[i] = 0;
        }

        public void setWord(int word, int offset)
        {
            memory[offset] = (byte)(word >> 16);
            memory[offset + 1] = (byte)(word >> 8);
            memory[offset + 2] = (byte)(word);
        }

        public int getWord(int offset) 
        { 
            return memory[offset] << 16 | memory[offset + 1] << 8 | memory[offset + 2]; 
        }
   }

    public class SicAssembler
    {
        public static OpTable opTable = new OpTable();
        public CodeList codes = new CodeList();
        public SymbolTable symbolTable = new SymbolTable();

        public void parse(String pFileName)
        {
            Debug.log("======================= PARSE ================================");
            String text = IO.fileToText(pFileName);
            text = text.Replace('\r', ' ');
            String[] lines = text.Split('\n');
            int offset = 0;
            for (int i = 0; i < lines.Length; i++)
            {
                String line = lines[i];
                if (line.Trim().Length == 0) continue;
                if (line.StartsWith(".")) continue;
                Code code = new Code(line, opTable);
                code.lineNumber = i;
                if (code.offset >= 0)
                    offset = code.offset;
                else
                    code.offset = offset;
                Debug.log(code.ToString());
                if (code.label.Length > 0)
                    symbolTable.Add(code.label, code);
                codes.Add(code);
                offset = offset + code.codeSize;
            }
        }

        public void pass2()
        {
            Debug.log("======================= PASS2 ================================");
            for (int i = 0; i < codes.Count; i++)
            {
                Code code = codes[i];
                if (!code.isPseudo)
                {
                    if (code.arg.Length == 0)
                        code.objCode += "0000";
                    else
                    {
                        if (symbolTable.ContainsKey(code.arg))
                        {
                            int argCode = symbolTable[code.arg].offset;
                            if (code.x == 'X')
                                argCode += 0x8000;
                            code.objCode += String.Format("{0:X4}", argCode);
                        }
                        else Debug.error("ARG " + code.arg + " not found !");
                    }
                }
                Debug.log(code.ToString());
            }
        }
    }

    public class OpTable {
        public Dictionary<String, String> opToCode = new Dictionary<String, String>();
        public Dictionary<String, String> codeToOp = new Dictionary<String, String>();
        public static String opCodes = "ADD=18,ADDF=58,ADDR=90,AND=40,CLEAR=B4,COMP=28,COMPF=88,DIV=24,DIVF=64,DIVR=9C,FIX=C4," +
                                "FLOAT=C0,HIO=F4,J=3C,JEQ=30,JGT=34,JLT=38,JSUB=48,LDA=00,LDB=68,LDCH=50,LDF=70,LDL=08," +
                                "LDS=6C,LDT=74,LDX=04,LPS=D0,MUL=20,MULF=60,MULR=98,NORM=C8,OR=44,RD=D8,RMO=AC,RSUB=4C," +
                                "SHIFTL=A4,SHIFTR=A8,SIO=F0,SSK=EC,STA=0C,STB=78,STCH=54,STF=80,STI=D4,STL=14,STS=7C,STSW=E8," +
                                "STT=84,STX=10,SUB=1C,SUBF=5C,SUBR=94,SVC=B0,TD=E0,TIO=F8,TIX=2C,TIXR=B8,WD=DC," +
                                "RESB=,RESW=,BYTE=,WORD=,START=,END=";
        public OpTable() {
            Debug.log("======================= OP TABLE ================================");
            String[] records = opCodes.Split(',');
            for (int i = 0; i < records.Length; i++)
            {
                String[] tokens = records[i].Split('=');
                opToCode.Add(tokens[0], tokens[1]);
                if (tokens.Length > 1 && tokens[1].Length>0)
                    codeToOp.Add(tokens[1], tokens[0]);
                Debug.log(tokens[0] + "\t" + tokens[1]);
            }
        }
    }

    public class SymbolTable : Dictionary<String, Code> { }

    public class CodeList : List<Code> { }

    public class Code
    {
        public static String PSEUDO_OP = ",RESB,RESW,BYTE,WORD,START,END,";
        public String label = "", op = "", arg = "";
        public char x = ' ';

        public String objCode = null;
        public int offset = -1;
        public int codeSize = 0;
        public int lineNumber = 0;
        public bool isPseudo;

        public Code(String line, OpTable opTable)
        {
            String[] tokens = line.Split('\t');

            int argIdx;
            if (opTable.opToCode.ContainsKey(tokens[0]))
            {
                op = tokens[0].Trim();
                argIdx = 1;
            }
            else
            {
                label = tokens[0].Trim();
                op = tokens[1].Trim();
                argIdx = 2;
            }
            if (argIdx < tokens.Length)
            {
                String argStr = tokens[argIdx];
                String[] args = argStr.Split(',');
                arg = args[0].Trim();
                if (args.Length > 1)
                    if (args[1].Trim().Equals("X"))
                        x = 'X';
            }
            objCode = opTable.opToCode[op];
            if (objCode == null) Debug.error("op:" + op + " not found!");
            if (op.Equals("BYTE"))                  
            {
                if (arg.StartsWith("C'"))           // EOF      BYTE    C'EOF'
                {
                    String str = arg.Substring(2, arg.Length - 3);
                    codeSize = str.Length;
                    objCode = "";
                    for (int si = 0; si < str.Length; si++)
                    {
                        char ch = str[si];
                        int chInt = ch;
                        objCode += String.Format("{0:x2}", (uint) System.Convert.ToUInt32(chInt.ToString()));
                    }
                    objCode = objCode.ToUpper();
                }
                else if (arg.StartsWith("X'"))      // OUTPUT   BYTE    X'05'
                {
                    String str = arg.Substring(2, arg.Length - 3);
                    objCode = str;
                    codeSize = str.Length / 2;
                }
                else                                // THREE    BYTE    3
                    codeSize = 1;                    
            }
            else if (op.Equals("WORD"))             // FIVE     WORD    5
            {
                codeSize = 3;
                objCode = String.Format("{0:X6}", Int32.Parse(arg));
            }
            else if (op.Equals("RESB"))             // BUFFER   RESB    1024
                codeSize = Int32.Parse(arg) * 1;
            else if (op.Equals("RESW"))             // LENGTH   RESW    1
                codeSize = Int32.Parse(arg) * 3;
            else if (op.Equals("START"))            // COPY     START   1000
                offset = Int32.Parse(arg, System.Globalization.NumberStyles.AllowHexSpecifier);
            else if (op.Equals("END"))
                codeSize = 0;
            else
                codeSize = 3;  // SIC 中一個指令佔 3 byte
            isPseudo = (PSEUDO_OP.IndexOf(","+op+",") >= 0);
        }

        public override String ToString()
        {
            return lineNumber + "\t" + String.Format("{0:X}", offset) + "\t" + label + "\t" + op + "\t" + arg + "\t" + x+"\t"+objCode;
        }
    }

    public class IO
    {
        // 讀取文字檔,以字串形式傳回。
        public static String fileToText(String filePath)
        {
            StreamReader file = new StreamReader(filePath);
            String text = file.ReadToEnd();
            file.Close();
            return text;
        }
    }

    class Debug
    {
        public static void error(String msg)
        {
            Debug.log(msg);
            throw new Exception(msg);
        }

        public static void log(String msg)
        {
            Console.WriteLine(msg);
        }
    }
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License