zoukankan      html  css  js  c++  java
  • 实现一个计算器

    产生原因:

    (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可以简单的跑起来:

    折起来吧,效果在前面。

      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 function get_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 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('
    error
    ');
     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
  • 相关阅读:
    像画笔一样慢慢画出Path的三种方法(补充第四种)
    占位符行为 PlaceHolderBehavior 的实现以及使用
    WPF实现物理效果 拉一个小球
    WPF实现Twitter按钮效果
    WPF自适应可关闭的TabControl 类似浏览器的标签页
    WPF仿百度Echarts人口迁移图
    WPF绘制简单常用的Path
    51Nod 1534 棋子游戏
    数论基础
    Buy a Ticket
  • 原文地址:https://www.cnblogs.com/backinfile/p/6185079.html
Copyright © 2011-2022 走看看