产生原因:
(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
,表示w与W的交界处
词法分析
计算器用到的正则表达式:
+:+
-:-
*:*
/:/
(:(
):)
number:d+
注意:规则中有的字符要转义,如:+
不断用正则来匹配输入的字符串,就可以。
使用js来实现:
1 function get_words(buf){
2 patterns = [
3 ['(', /(/],
4 [')', /)/],
5 ['+', /+/],
6 ['-', /-/],
7 ['*', /*/],
8 ['/', ///],
9 ['number', /d+(.d+)?/]
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('
error
');
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可以简单的跑起来:
折起来吧,效果在前面。
