產生原因:
(1)一直以來,我都想寫一門語言,但無從下手。
(2)我找到了很多編譯原理的教程,但始終覺得內容晦澀,理解不了,所以先嘗試寫一個簡單的,比如:計算器。
(3)網上有很多關於計算器的實現,但大多需要有編譯原理的基礎,對於我這種小白實在難以理解。
(4)我決定采用暴力模擬的方式,需要用正則表達式,但我不想自己實現,所以用js。
最終要實現什么效果
計算器接受一串字符,處理后返回結果。
我們來看一下要做什么:
首先需要知道有哪些“元素”,比如“12+34×56"的元素有整數12,加號,整數34,乘號,整數56,這個過程稱為詞法分析。
然后根據符號的優先級進行組合,其過程相當於加括號,12+(34*56),這個過程稱為語法分析。
借用正則表達式,可以簡單暴力的實現詞法分析。
什么是正則表達式
正則表達式的概念,和編譯原理一樣,都要費好大功夫來理解,當初也是各種查資料。
盡量簡單的講一講吧。
定義:正則表達式是一種生成器,可以生成大量相同模式的字符串。
字符串的概念大家都懂,來看一下正則表達式是怎么生成字符串的。
例子:正則表達式 ab* 可以生成'a' , 'ab' , 'abb' , 'abbb' ...
正則表達式有三種規則(並,或,閉包),其他規則都是由這三個規則組合而成。
並,直接連在一起,表示相連,例如:abc 生成’abc'
或,以符號|分隔,表示或,例如:a|b生成'a','b'
閉包,加符號*,表示重復任意次,例如:a* 生成 '','a', 'aa', 'aaa'...
一些常用的規則:
[],例如:[abcd]等價於a|b|c|d
+,例如:a+等價於aa*
?,例如:a?等價於 a|空
\d,等價於[0123456789],還可以用[0-9]
\w,等價於[A-Za-z0-9]
\W,非\w
\b,表示\w與\W的交界處
詞法分析
計算器用到的正則表達式:
+:\+
-:-
*:\*
/:/
(:\(
):\)
number:\b\d+\b
注意:規則中有的字符要轉義,如:+
不斷用正則來匹配輸入的字符串,就可以。
使用js來實現:
1 function get_words(buf){ 2 patterns = [ 3 ['(', /\(/], 4 [')', /\)/], 5 ['+', /\+/], 6 ['-', /-/], 7 ['*', /\*/], 8 ['/', /\//], 9 ['number', /\b\d+(\.\d+)?\b/] 10 ]; 11 12 words = []; 13 flag = true; 14 while (buf && flag) { 15 flag = false; 16 for (p in patterns) { 17 buf = buf.trimLeft(); 18 ex = patterns[p][1].exec(buf); 19 if (ex && ex['index'] == 0) { 20 str = ex[0]; 21 flag = true; 22 buf = buf.slice(str.length,Infinity); 23 words.push([patterns[p][0], parseFloat(str)]); 24 break; 25 } 26 } 27 } 28 return words; 29 }
對於'12+34 * (78-56)',會得到:
number,12 +,NaN number,34 *,NaN (,NaN number,78 -,NaN number,56 ),NaN
至此,詞法分析完成。
語法分析
我們采用類似於正則的方式來描述語法分析的過程,可稱其為文法。
分析一波。
括號優先級最高,所以遇見括號就要計算,文法為:
<factor> => ( '(' <expr> ')' ) | 'number'
引號的稱終結符,尖括號的稱非終結符。
非終結符表示可以繼續推導,終結符表示推導終點。
其中<expr>表示整個算式
乘除優先級比加法高,所以遇見乘除就要計算,文法為:
<term> => <factor> ( ( '*' <factor> ) | ( '/' <factor> ) ) *
然后是加減:
<expr> => <term> ( ( '+' <term> ) | ( '-' <term> ) ) *
這些都可以用正則來理解。
其中每個非終結符都做成一個函數,翻譯過來就成。
1 function parse(words) { 2 // <expr> => <term> (('+' <term>) | ('-' <term>))* 3 // <term> => <factor> (('*' <factor>) | ('/' <factor>))* 4 // <factor> => ('(' <expr> ')') | 'number' 5 p = 0; 6 7 function type() { 8 if (p >= words.length) return null; 9 return words[p][0]; 10 } 11 function match(sym) { 12 if (words[p][0] == sym) { 13 return words[p++][1]; 14 } 15 console.log('\nerror\n'); 16 } 17 function expr() { 18 value = term(); 19 while (type() == '+' || type() == '-') { 20 if (type() == '+') { 21 match('+'); 22 value += term(); 23 } else { 24 match('-'); 25 value -= term(); 26 } 27 } 28 return value; 29 } 30 function term() { 31 value = factor(); 32 while (type() == '*' || type() == '/') { 33 if (type() == '*') { 34 match('*'); 35 value *= factor(); 36 } else { 37 match('/'); 38 value /= factor(); 39 } 40 } 41 return value; 42 } 43 function factor() { 44 if (type() == '(') { 45 match('('); 46 value = expr(); 47 match(')'); 48 } else if (type() == 'number') { 49 value = match('number'); 50 } 51 return value; 52 } 53 54 return expr(); 55 }
寫完了,哈哈。
總結
用node.js可以簡單的跑起來:
折起來吧,效果在前面。

1 var readline = require('readline'); 2 var rl = readline.createInterface(process.stdin, process.stdout); 3 4 rl.setPrompt('calc> '); 5 rl.prompt(); 6 7 rl.on('line', function(line) { 8 words = get_words(line); 9 //console.log(words.join('\n')); 10 ans = parse(words); 11 console.log(ans); 12 rl.prompt(); 13 }); 14 15 rl.on('close', function() { 16 console.log('\nbye bye!'); 17 process.exit(0); 18 }); 19 20 function get_words(buf){ 21 patterns = [ 22 ['(', /\(/], 23 [')', /\)/], 24 ['+', /\+/], 25 ['-', /-/], 26 ['*', /\*/], 27 ['/', /\//], 28 ['number', /\b\d+(\.\d+)?\b/] 29 ]; 30 31 words = []; 32 flag = true; 33 while (buf && flag) { 34 flag = false; 35 for (p in patterns) { 36 buf = buf.trimLeft(); 37 ex = patterns[p][1].exec(buf); 38 if (ex && ex['index'] == 0) { 39 str = ex[0]; 40 flag = true; 41 buf = buf.slice(str.length,Infinity); 42 words.push([patterns[p][0], parseFloat(str)]); 43 break; 44 } 45 } 46 } 47 return words; 48 } 49 50 function parse(words) { 51 // <expr> => <term> (('+' <term>) | ('-' <term>))* 52 // <term> => <factor> (('*' <factor>) | ('/' <factor>))* 53 // <factor> => ('(' <ecpr> ')') | 'number' 54 p = 0; 55 56 function type() { 57 if (p >= words.length) return null; 58 return words[p][0]; 59 } 60 function match(sym) { 61 if (words[p][0] == sym) { 62 return words[p++][1]; 63 } 64 console.log('\nerror\n'); 65 } 66 function expr() { 67 value = term(); 68 while (type() == '+' || type() == '-') { 69 if (type() == '+') { 70 match('+'); 71 value += term(); 72 } else { 73 match('-'); 74 value -= term(); 75 } 76 } 77 return value; 78 } 79 function term() { 80 value = factor(); 81 while (type() == '*' || type() == '/') { 82 if (type() == '*') { 83 match('*'); 84 value *= factor(); 85 } else { 86 match('/'); 87 value /= factor(); 88 } 89 } 90 return value; 91 } 92 function factor() { 93 if (type() == '(') { 94 match('('); 95 value = expr(); 96 match(')'); 97 } else if (type() == 'number') { 98 value = match('number'); 99 } 100 return value; 101 } 102 103 return expr(); 104 }