产生原因:
(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 functionget_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 inpatterns) { 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 returnwords; 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 functionparse(words) { 2 //<expr> => <term> (('+' <term>) | ('-' <term>))* 3 //<term> => <factor> (('*' <factor>) | ('/' <factor>))* 4 //<factor> => ('(' <expr> ')') | 'number' 5 p = 0; 6 7 functiontype() { 8 if (p >= words.length) return null; 9 return words[p][0]; 10 } 11 functionmatch(sym) { 12 if (words[p][0] ==sym) { 13 return words[p++][1]; 14 } 15 console.log(' error '); 16 } 17 functionexpr() { 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 returnvalue; 29 } 30 functionterm() { 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 returnvalue; 42 } 43 functionfactor() { 44 if (type() == '(') { 45 match('('); 46 value =expr(); 47 match(')'); 48 } else if (type() == 'number') { 49 value = match('number'); 50 } 51 returnvalue; 52 } 53 54 returnexpr(); 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(' ')); 10 ans =parse(words); 11 console.log(ans); 12 rl.prompt(); 13 }); 14 15 rl.on('close', function() { 16 console.log(' bye bye!'); 17 process.exit(0); 18 }); 19 20 functionget_words(buf){ 21 patterns =[ 22 ['(', /(/], 23 [')', /)/], 24 ['+', /+/], 25 ['-', /-/], 26 ['*', /*/], 27 ['/', ///], 28 ['number', /d+(.d+)?/] 29 ]; 30 31 words =[]; 32 flag = true; 33 while (buf &&flag) { 34 flag = false; 35 for (p inpatterns) { 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 returnwords; 48 } 49 50 functionparse(words) { 51 //<expr> => <term> (('+' <term>) | ('-' <term>))* 52 //<term> => <factor> (('*' <factor>) | ('/' <factor>))* 53 //<factor> => ('(' <ecpr> ')') | 'number' 54 p = 0; 55 56 functiontype() { 57 if (p >= words.length) return null; 58 return words[p][0]; 59 } 60 functionmatch(sym) { 61 if (words[p][0] ==sym) { 62 return words[p++][1]; 63 } 64 console.log(' error '); 65 } 66 functionexpr() { 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 returnvalue; 78 } 79 functionterm() { 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 returnvalue; 91 } 92 functionfactor() { 93 if (type() == '(') { 94 match('('); 95 value =expr(); 96 match(')'); 97 } else if (type() == 'number') { 98 value = match('number'); 99 } 100 returnvalue; 101 } 102 103 returnexpr(); 104 }