實現一個計算器


產生原因:

(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 }
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM