解釋器, C#, Scheme, 函數式編程
本文介紹了如何使用C#實現一個簡化但全功能的Scheme方言——iScheme及其解釋器,通過從零開始逐步構建,展示了編程語言/解釋器的工作原理。
Lucidaa.k.aLuc
如果你是通過移動設備閱讀本教程,或者認為本文的代碼字體太小的,請使用該鏈接以獲得更好的可讀性(博客園的markdown解析器實在詭異,這里就不多吐槽了)。
如果你對下面的內容感興趣:
那么請繼續閱讀。
如果你對以下內容感興趣:
本文則過于初級,你可以跳過本文,但歡迎指出本文的錯誤 :-)
代碼示例
public static int Add(int a, int b) { return a + b;}>> Add(3, 4)>> 7>> Add(5, 5)>> 10這段代碼定義了Add函數,接下來的>>符號表示對Add(3, 4)進行求值,再下一行的>> 7表示上一行的求值結果,不同的求值用換行分開。可以把這里的>>理解成控制臺提示符(即Terminal中的PS)。

解釋器(Inter iScheme是什么? OK,那么Scheme是什么? 以計算階乘為例: C#版階乘 iScheme版階乘 由于iScheme只是一個用于演示的語言,所以目前只提供對整數的支持。iScheme使用C#的 iScheme使用 與常見的編程語言(C#, java, C++, C)不同,Scheme使用波蘭表達式,即前綴表示法。例如: C#中的算術|邏輯|比較操作 對應的iScheme代碼 需要注意的幾點: iScheme使用 C#的順序語句 iScheme的順序語句 iScheme中的控制流操作只包含 if語句示例 iScheme使用 iScheme的列表示例 iScheme使用 iScheme的函數定義 對應的C#代碼 由于iScheme中沒有 以計算最大公約數為例: iScheme計算最大公約數 對應的C#代碼 和Scheme一樣,函數在iScheme中是頭等對象,這意味著: iScheme的高階函數示例 對iScheme的介紹就到這里——事實上這就是iScheme的所有元素,會不會太簡單了? -_- 接下來進入正題——從頭開始構造iScheme的解釋程序。 iScheme解釋器主要分為兩部分,解析(Parse)和求值(Evaluation): 詞法分析負責把源程序解析成一個個詞法單元(Lex),以便之后的處理。 iScheme的詞法分析極其簡單——由于iScheme的詞法元素只包含括號,空白,數字和變量名,因此C#自帶的 iScheme的詞法分析及測試 得到了詞素之后,接下來就是進行語法分析。不過由于Lisp類語言的程序即是語法樹,所以語法分析可以直接跳過。 以下面的程序為例: 程序即語法樹 更加直觀的圖片: 這使得抽象語法樹(Abstract Syntax Tree)的構建變得極其簡單(無需考慮操作符優先級等問題),我們使用 抽象語法樹的定義 然后用下面的步驟構建語法樹: 抽象語法樹的構建過程
iScheme編程語言

public static int Factorial(int n) { if (n == 1) { return 1; } else { return n * Factorial(n - 1); }}(def factorial (lambda (n) ( if (= n 1) 1 (* n (factorial (- n 1))))))數值類型
Int64類型作為其內部的數值表示方法。定義變量
def關鍵字定義變量>> (def a 3)>> 3>> a>> 3算術|邏輯|比較操作
// Arithmetic opsa + b * ca / (b + c + d)// Logical ops(cond1 && cond2) || cond3// Comparing opsa == b1 < a && a < 3; Arithmetic ops(+ a (* b c))(/ a (+ b c d)); Logical ops(or (and cond1 cond2) cond3); Comparing ops(= a b)(< 1 a 3)and,or和not代替了常見的&&,||和!——這在一定程度上增強了程序的可讀性。順序語句
begin關鍵字標識順序語句,并以最后一條語句的值作為返回結果。以求兩個數的平均值為例:int a = 3;int b = 5;int c = (a + b) / 2;(def c (begin (def a 3) (def b 5) (/ (+ a b) 2)))控制流操作
if。>> (define a (if (> 3 2) 1 2))>> 1>> a>> 1列表類型
list關鍵字定義列表,并提供first關鍵字獲取列表的第一個元素;提供rest關鍵字獲取列表除第一個元素外的元素。>> (define alist (list 1 2 3 4))>> (list 1 2 3 4)>> (first alist)>> 1>> (rest alist)>> (2 3 4)定義函數
func關鍵字定義函數:(def square (func (x) (* x x)))(def sum_square (func (a b) (+ (square a) (square b))))public static int Square (int x) { return x * x;}public static int SumSquare(int a, int b) { return Square(a) + Square(b);}遞歸
for或while這種命令式語言(Imperative Programming Language)的循環結構,遞歸成了重復操作的唯一選擇。(def gcd (func (a b) (if (= b 0) a (func (b (% a b))))))public static int GCD (int a, int b) { if (b == 0) { return a; } else { return GCD(b, a % b); }}高階函數
; Defines a multiply function.(def mul (func (a b) (* a b))); Defines a list map function.(def map (func (f alist) (if (empty? alist) (list ) (append (list (f (first alist))) (map f (rest alist))) ))); Doubles a list using map and mul.>> (map (mul 2) (list 1 2 3))>> (list 2 4 6)小結
解釋器構造
詞法分析
String#Split就足夠。public static String[] Tokenize(String text) { String[] tokens = text.Replace("(", " ( ").Replace(")", " ) ").Split(" /t/r/n".ToArray(), StringSplitOptions.RemoveEmptyEntries); return tokens;}// Extends String.Join for a smooth API.public static String Join(this String separator, IEnumerable<Object> values) { return String.Join(separator, values);}// Displays the lexes in a readable form.public static String PrettyPrint(String[] lexes) { return "[" + ", ".Join(lexes.Select(s => "'" + s + "'") + "]";}// Some tests>> PrettyPrint(Tokenize("a"))>> ['a']>> PrettyPrint(Tokenize("(def a 3)"))>> ['(', 'def', 'a', '3', ')']>> PrettyPrint(Tokenize("(begin (def a 3) (* a a))"))>> ['begin', '(', 'def', 'a', '3', ')', '(', '*', 'a', 'a', ')', ')']注意
String.Join這個靜態方法,所以這里使用C#的擴展方法(Extension Methods)對String類型做了一個擴展。語法樹生成
;(def x (if (> a 1) a 1)); 換一個角度看的話:( def x ( if ( > a 1 ) a 1 ))
SExpression類型定義iScheme的語法樹(事實上S Expression也是Lisp表達式的名字)。public class SExpression { public String Value { get; private set; } public List<SExpression> Children { get; private set; } public SExpression Parent { get; private set; } public SExpression(String value, SExpression parent) { this.Value = value; this.Children = new List<SExpression>(); this.Parent = parent; } public override String ToString() { if (this.Value == "(") { return "(" + " ".Join(Children) + ")"; } else { return this.Value; } }}current),然后重設當前節點。public static SExpression ParseAsIScheme(this String code) { SExpression program = new SExpression(value: "", parent: null); SExpression current = program; foreach (var lex in Tokenize(code)) { if (lex == "(") { SExpression newNode = new SExpr
新聞熱點
疑難解答