Writing a mini-language

后端 未结 9 1833
南方客
南方客 2021-01-31 23:40

I have an application that needs to allow users to write expressions similar to excel:

(H1 + (D1 / C3)) * I8

and more complex things like

If(H1 = \'True

9条回答
  •  傲寒
    傲寒 (楼主)
    2021-02-01 00:00

    A little recursive-descent parser is perfect for this. You probably don't even have to build a parse tree - you can do the evaluation as you parse.

     /* here's a teeny one in C++ */
    void ScanWhite(const char* &p){
      while (*p==' ') p++;
    }
    
    bool ParseNum(const char* &p, double &v){
      ScanWhite(p);
      if (!DIGIT(*p)) return false;
      const char* p0 = p;
      while(DIGIT(*p)) p++;
      if (*p == '.'){
        p++;
        while(DIGIT(*p)) p++;
      }
      v = /* value of characters p0 up to p */;
      return true;
    }
    
    bool ParseId(const char* &p, double &v){
      ScanWhite(p);
      if (ALPHA(p[0]) && DIGIT(p[1])){
        v = /* value of cell whose name is p[0], p[1] */;
        p += 2;
        return true;
      }
      return false;
    }
    
    bool ParseChar(const char* &p, char c){
      ScanWhite(p);
      if (*p != c) return false;
      p++;
      return true;
    }
    
    void ParseExpr(const char* &p, double &v); /* forward declaration */
    
    void ParsePrimitive(const char* &p, double &v){
      if (ParseNum(p, v));
      else if (ParseId(p, v));
      else if (ParseChar(p, '(')){
        ParseExpr(p, v);
        if (!ParseChar(p, ')'){/* throw syntax error */}
      }
      else {/* throw syntax error */}
    }
    #define PARSE_HIGHER ParsePrimitive
    
    void ParseUnary(const char* &p, double &v){
      if (ParseChar(p, '-')){
        ParseUnary(p, v);
        v = -v;
      }
      else {
        PARSE_HIGHER(p, v);
      }
    }
    #undef  PARSE_HIGHER
    #define PARSE_HIGHER ParseUnary
    
    void ParseProduct(const char* &p, double &v){
      double v2;
      PARSE_HIGHER(p, v);
      while(true){
        if (ParseChar(p, '*')){
          PARSE_HIGHER(p, v2);
          v *= v2;
        }
        else if (ParseChar(p, '/')){
          PARSE_HIGHER(p, v2);
          v /= v2;
        }
        else break;
      }
    }
    #undef  PARSE_HIGHER
    #define PARSE_HIGHER ParseProduct
    
    void ParseSum(const char* &p, double &v){
      double v2;
      PARSE_HIGHER(p, v);
      while(true){
        if (ParseChar(p, '+')){
          PARSE_HIGHER(p, v2);
          v += v2;
        }
        else if (ParseChar(p, '-')){
          PARSE_HIGHER(p, v2);
          v -= v2;
        }
        else break;
      }
    }
    #undef  PARSE_HIGHER
    #define PARSE_HIGHER ParseSum
    
    void ParseExpr(const char* &p, double &v){
      PARSE_HIGHER(p, v);
    }
    
    double ParseTopLevel(const char* buf){
      const char* p = buf;
      double v;
      ParseExpr(p, v);
      return v;
    }
    

    Now if you just call ParseTop, it will calculate the value of an expression for you.

    The reason for the PARSE_HIGHER macro is to make it easier to add operators at intermediate levels of precedence.

    To do the "if" statement is a little more involved. Each parse routine needs an additional "enable" argument, so it does no calculation unless it's enabled. Then you parse the word "if", parse the test expression, and then parse the two result expressions, with the inactive one disabled.

提交回复
热议问题