Chinaunix

标题: 写了一个计算四则混合运算的小程序,大家来评审一下 ^_^ [打印本页]

作者: BetonArmEE    时间: 2013-03-16 22:24
提示: 作者被禁止或删除 内容自动屏蔽
作者: starwing83    时间: 2013-03-17 08:38
我觉得你应该先把四则运算的BNF搞清楚,然后写递归向下就很简单了,比如这样:

simpleexpr ::= number | '(' expr ')'
expr ::= ['-'] simpleexpr { binop expr }

这样总共就四个函数了:read_number, simpleexpr, binop和expr,结构会很清晰。

另外最好定义一下比较清晰的函数签名。

以上是参考Lua脚本语言的parser部分提出的。
作者: starwing83    时间: 2013-03-17 10:08
本帖最后由 starwing83 于 2013-03-17 10:48 编辑

这个是从Lua源代码里面单独抽出来的(思路是Lua的,代码是自己写的),只有你的一半不到,你可以看看(我没有处理空格):
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <setjmp.h>
  4. #include <math.h>

  5. typedef enum BinOpr {
  6.     OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW, OP_NON
  7. } BinOpr;

  8. static struct { int left, right; } binop_prio[] = {
  9.     {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, {10,9},
  10. };

  11. #define UNARY_PRIO 8

  12. static BinOpr get_binop(const char **s) {
  13.     switch (**s) {
  14.     case '+': ++*s; return OP_ADD;
  15.     case '-': ++*s; return OP_SUB;
  16.     case '*': ++*s; return OP_MUL;
  17.     case '/': ++*s; return OP_DIV;
  18.     case '%': ++*s; return OP_MOD;
  19.     case '^': ++*s; return OP_POW;
  20.     default: return OP_NON;
  21.     }
  22. }

  23. static double doexpr(int op, double a, double b) {
  24.     switch (op) {
  25.     case OP_ADD: return a+b;
  26.     case OP_SUB: return a-b;
  27.     case OP_MUL: return a*b;
  28.     case OP_DIV: return a/b;
  29.     case OP_MOD: return a-floor(a/b)*b;
  30.     case OP_POW: return pow(a, b);
  31.     default: return 0;
  32.     }
  33. }

  34. typedef struct ExprContext {
  35.     jmp_buf jbuf;
  36.     const char *errmsg, *s;
  37.     BinOpr op;
  38. } Expr;

  39. static double error(Expr *e, const char *msg) {
  40.     e->errmsg = msg;
  41.     longjmp(e->jbuf, 1);
  42. }

  43. static double expr(Expr *e, int limit) {
  44.     double n;
  45.     BinOpr op;
  46.     if (*e->s == '-') {
  47.         ++e->s;
  48.         n = -expr(e, UNARY_PRIO);
  49.     }
  50.     else if (*e->s == '(') {
  51.         ++e->s;
  52.         n = expr(e, 0);
  53.         if (*e->s++ != ')') error(e, "')' expected");
  54.     }
  55.     else {
  56.         const char *s = e->s;
  57.         n = strtod(s, (char**)&e->s);
  58.         if (e->s == s) error(e, "'number' expected");
  59.     }
  60.     op = get_binop(&e->s);
  61.     while (op != OP_NON && binop_prio[op].left > limit) {
  62.         n = doexpr(op, n, expr(e, binop_prio[op].right));
  63.         op = e->op;
  64.     }
  65.     e->op = op;
  66.     return n;
  67. }

  68. double calc(const char *s, const char **perr) {
  69.     Expr e;
  70.     e.s = s;
  71.     e.errmsg = NULL;
  72.     if (setjmp(e.jbuf) == 0) {
  73.         double n = expr(&e, 0);
  74.         if (*e.s != '\n' && *e.s != '\0' && *e.s != '=')
  75.             error(&e, "traling chars detected");
  76.         return n;
  77.     }
  78.     if (perr) *perr = e.errmsg;
  79.     return 0;
  80. }

  81. int main(void) {
  82.     char buff[BUFSIZ];
  83.     while (printf("> "), fgets(buff, BUFSIZ, stdin) != NULL) {
  84.         const char *errmsg = NULL;
  85.         double n = calc(buff, &errmsg);
  86.         if (errmsg) printf("ERROR: %s\n", errmsg);
  87.         else printf("%g\n", n);
  88.     }
  89.     return 0;
  90. }
复制代码

作者: linux_c_py_php    时间: 2013-03-17 11:40
后续遍历,  这是<竞赛经典入门>里建表达式树的东西.
作者: pmerofc    时间: 2013-03-17 12:42
提示: 作者被禁止或删除 内容自动屏蔽
作者: pmerofc    时间: 2013-03-17 20:23
提示: 作者被禁止或删除 内容自动屏蔽
作者: sonicling    时间: 2013-03-18 11:04
回复 6# pmerofc


    这是简单语言。实用一些的想避免大函数都不行,几十个运算符,光switch case都要写几十上百行。
作者: starwing83    时间: 2013-03-18 11:09
回复 7# sonicling


    你见过几十个运算符的语言么?C?那也才三十多个,没有上百行吧?

而且,其实很多switch-case是可以被归结成一个数据表的啊。

说到最后,其实还是设计上的问题。我上面的帖子最开始其实是两个函数simpleexpr和expr,是觉得simpleexpr只用了一次而已,就人肉内联到expr了而已。

Lua语言,这是真实语言了吧,它的lparser里面就根本没有大函数的。
作者: pmerofc    时间: 2013-03-18 11:25
提示: 作者被禁止或删除 内容自动屏蔽
作者: pmerofc    时间: 2013-03-18 11:36
提示: 作者被禁止或删除 内容自动屏蔽
作者: sonicling    时间: 2013-03-18 14:06
starwing83 发表于 2013-03-18 11:09
回复 7# sonicling

    你见过几十个运算符的语言么?C?那也才三十多个,没有上百行吧?

而且,其实很多switch-case是可以被归结成一个数据表的啊。

说到最后,其实还是设计上的问题。我上面的帖子最开始其实是两个函数simpleexpr和expr,是觉得simpleexpr只用了一次而已,就人肉内联到expr了而已。

Lua语言,这是真实语言了吧,它的lparser里面就根本没有大函数的。


llex里的llex函数100多行,可能不能算大函数,比他大的lex函数多的去,gcc和llvm的lex7、8百行,真不知道怎么写出来的,他们的作者该向lua学习学习。

switch-case如果归结成数据表,就不是递归下降了。lua分析算术运算的部分根本就不能算是递归下降,那是算符优先分析,后面跟这个优先级表呢。如果真用递归下降,subexpr函数篇幅可以砍一半,代价是要分散出n个结构相同的函数。

编译前端的复杂度由语言本身决定,函数有多大是可以想象的。parser简单,复杂度肯定在lexer;lexer也很简单,复杂度肯定在AST。他们是一条线的流程,总得有个大函数集中处理各种情况。

lua确实是真实语言。我忽视了。语法和词法分析的函数大小和BNF大小至少成正比。C语言的BNF一条写出来都有十几行,对应的函数小不了。lua这种BNF每条不超过3行的语言的确可以全部小函数。
作者: sonicling    时间: 2013-03-18 14:09
pmerofc 发表于 2013-03-18 11:36
回复 7# sonicling

如果结构简单,行数多点我觉得倒也无妨


就是这个意思。复杂度是问题固有的,coder能做的只是转嫁,而不能消除。总得有人站出来承担责任。
作者: fenghw8088    时间: 2013-03-19 11:13
回复 3# starwing83
你的这个思路超好!
解析字符串的场景很多,我也早就想学一下BNF,有没有合适的材料、书籍推荐一下?

   




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2