实现一个计算器

时间:2021-09-07 10:18:07

产生原因:

(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