[體驗編譯原理]編寫簡易計算機

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   os   ar   strong   資料   

Demo: CaculationTest

 

前言

有想過自己寫一個計算機嗎?輸入一些數學運算式就能自己計算解析產生結果。

如果沒有,可以現在開始想想,也許你會發現自己計算要不了幾秒鐘的運算式,讓程式計算卻沒這麼簡單。

 

假定

為了便於理解,我們現在簡化需求,資料類型只有整數,運算子只有加減乘除,沒有括弧。運行結果:

 

解析過程

逐個分析運算式字串的每一個char,將其解析為一系列的token。然後根據token代表的不同含義進行相應的操作,直到計算出最終結果。

(本例中並沒有全部解析完token,再遍曆token,而是邊解析邊進行操作。這樣做效率稍微高一點,但不能直接查看解析出來的全部token。)

這和我們閱讀也比較接近:我們從左往右依次讀取資訊,讀的過程中,我們會根據上下文即前後組成,形成一定的語義,如“其實不忍”,你可能會理解為”他實在不忍心“,或者理解為”他其實是不忍心的“,這得依照你對內容相關的理解去選擇了。

對照剛才的比喻,可以得出,token是語義的基本組成,或者說對字元組成的一種抽象。程式中將token抽象為以下資料結構:

 enum TokenType   {       Add,Sub,       Mul,Div,       Int,       Start,End   }   class Token   {       public TokenType Type;       public object Value;       public Token(TokenType type , object value = null)       {           Type = type;           Value = value;       }   }

 

 

Token和運算式

Token必須解析為運算式才會有意義。有了運算式,我們才能計算出最終結果。一個運算式,是能表示明確意義的一個或一組token。

在C#中,我們有一元運算式,二元運算式;運算式中有不同的運算,如加減法;在此例中,所有的運算式都可以計算出某個值,而且運算式之間可以相互計算形成新的運算式,如”運算式(1*2)+運算式(2*3)”。基於此,有Expression類,也並不複雜:

abstract class Expression    {        public abstract int GetValue();    }    class UnaryExpress : Expression    {        int _value;        public UnaryExpress(int value)        {            _value = value;        }        public override int GetValue()        {            return _value;        }    }    class BinaryExpression : Expression    {        TokenType _tokenType;        int _left;        int _right;        public BinaryExpression(TokenType tokenType, Expression left, Expression right)        {            _tokenType = tokenType;            _left = left.GetValue();            _right = right.GetValue();        }        public override int GetValue()        {            switch (_tokenType)            {                case TokenType.Add:                    return _left + _right;                case TokenType.Sub:                    return _left - _right;                case TokenType.Mul:                    return _left * _right;                case TokenType.Div:                    return _left / _right;                default:                    throw new Exception("unexceptional token!");            }        }    }

 

此例中,並沒有真正意義上的“一元運算式”,僅將數字看作它而已。二元運算式的值計算相對複雜,但類別也不多。

 

演算法優先順序

如果不算括弧,恐怕對我們來說,用自己的“原始方式”解析運算式,最大的麻煩就是解決演算法優先順序的問題。

為什麼“1+2*3” 不解析為1+2再乘以3呢,如何才能將其正確解析為2*3再和1相加呢?

首先我們需要順序分析每個token, 運算式的解析順序決定了最後的運算順序,看下原代碼中比較重要的這3個方法:

//解析加減        Expression ParseAddSub()        {            //左運算元為優先順序較高的運算子            var l = ParseMulDiv();            while (_token.Type == TokenType.Add || _token.Type == TokenType.Sub)            {                var t = _token.Type;                ParseToken();                var r = ParseMulDiv();//解析右運算元                l = new BinaryExpression(t, l, r);            }            return l;        }        //解析乘除        Expression ParseMulDiv()        {            var l = ParseUnary();            while (_token.Type == TokenType.Mul || _token.Type == TokenType.Div)            {                var t = _token.Type;                ParseToken();                var r=ParseUnary();                l = new BinaryExpression(t, l, r);            }            return l;        }        //解析一元運算式(目前只有單個整數)        Expression ParseUnary()        {            Expression ret = null;            if (_token.Type == TokenType.Int)            {                ret= new UnaryExpress((int)_token.Value);            }            //解析完int後,移到下一個token處,即+-*/            ParseToken();            return ret;        }

 

1*2+2*2,我們可以看作兩個乘法運算式相加,所以,解析加法運算的左右操作符前,程式嘗試讀取其是否是乘法運算式。

如果是1+2*3呢,左操作符當乘法運算解析時,並沒有匹配上,由最高優先順序的一元運算式決定其值,返回1,所以左操作符就是1了,當解析到+時,程式嘗 試解析右操作符,從優先順序比加法高一級的乘除開始,往上搜尋匹配。很明顯,2*3命中了乘法運算式,計算出右運算元的結果後,再和1相加,結果就正確了。

 

練習

如果研究透了demo,可以嘗試把括弧,模數運算,一無運算式(加號或減號)。

 

擴充

在IL代碼和LinqExpression API中,“運算式”,“二元運算式”,“賦值運算式”,“成員擷取運算式(.運算)”等,都很常見,解析的表達後對應的操作也很多,建立執行個體,引用實 例,訪問局部變數等,賦值等。有興趣可以查看一下Dynamic Linq的原代碼(後續章節中,我們會寫一個簡單易版的Dynamic Linq動態計算 IQueryable.Where(string))。

[體驗編譯原理]編寫簡易計算機

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.