解析算式也好,脚本也罢,在分析语法之前,一般都会引入词法分析的过程。简单说比如我们有如下算式

acosd(2)+cosd(30)*12+1.23E-21

为了处理简单,在真正计算之前都会先将输入分解成一个个的单词(Token)。比如上面的内容经过处理,如果能变成下面的形式,就容易处理了。

acosd,(,2,),+,cosd,(,30,),*,12+1.23E-21

这里,类似于acosd,cosd和(,),*之类的要素可以通过简单的字符串比较来解决。但是数字还有最后e指数就没有那么简单了。当然如果你认为这正好是展现编程基本功的机会的话,没有人拦着你,但是在本软件中我们采用了正则表达式来解决这个问题。

例如e指数可以如下定义:

"((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))[eE][+-]?[0-9]+"

对于一般形式的小数:

"(\\.[0-9]+)|([0-9]+\\.[0-9]*)"

整数就更简单了。

"[0-9]+"

这样一来我们就可以象处理+/-号一样处理其他要素了。正则表达式的语法大家可以参照【精通正则表达式(第3版) 】一书。至于实际的内容建议大家看一下另一篇文章:

自己开发计算器(0)-扩展巴科斯范式(EBNF)

下面看看源代码。先从使用的地方看起。

 


  
  1. QList<Token*> CalculateEngine::analyzeToken(QString strQuestion)  
  2. {  
  3.     typedef TokenAnalyzer<Token, Token::EType, QList<Token*>::iterator> MyAnalyzer;  
  4.  
  5.     class MyFactory : public MyAnalyzer::TokenPatternFactory  
  6.     {  
  7.         virtual int createPatterns(QList<MyAnalyzer::TokenPattern*>& list) const 
  8.         {  
  9.             FunctionManager* manager = FunctionManager::getInstance();  
  10.             QList<QString> functions = manager->functions();  
  11.  
  12.             QString funPattern;  
  13.             foreach(QString funName, functions)  
  14.             {  
  15.                 if(funPattern.length() > 0)  
  16.                 {  
  17.                     funPattern += "|";  
  18.                 }  
  19.                 funPattern += funName;  
  20.             }  
  21.             list.append(new MyAnalyzer::TokenPattern(Token::FunctionName, funPattern));  
  22.  
  23.             list.append(new MyAnalyzer::TokenPattern(Token::Number, "((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))[eE][+-]?[0-9]+"));  
  24.             list.append(new MyAnalyzer::TokenPattern(Token::Number, "(\\.[0-9]+)|([0-9]+\\.[0-9]*)"));  
  25.             list.append(new MyAnalyzer::TokenPattern(Token::Number, "[0-9]+"));  
  26.             list.append(new MyAnalyzer::TokenPattern(Token::Operator, "[-+*/%]"));  
  27.             list.append(new MyAnalyzer::TokenPattern(Token::Parenthese, "[()]"));  
  28.             list.append(new MyAnalyzer::TokenPattern(Token::Comma, ","));  
  29.  
  30.             return list.count();  
  31.         }  
  32.     };  
  33.  
  34.     MyFactory factory;  
  35.  
  36.     MyAnalyzer analyzer;  
  37.     QList<Token*> tokenList = analyzer.analyzeToken(strQuestion, &factory);  
  38.     return tokenList;  

在花费大量篇幅准备正则表达式以后调用TokenAnalyzer::analyzeToken就OK了。下面是TokenAnalyzer的源代码。考虑到Token的类型可能会因为需求而不同,这里采用了模板类。


  
  1. #ifndef TOKENANALYZER_H
  2. #define TOKENANALYZER_H
  3. #include<QString>
  4. #include<QList>
  5. #include<QRegExp>
  6. template<typename Token, typename TokenType, typename TokenIterator>
  7. class TokenAnalyzer
  8. {
  9. public:
  10. struct TokenPattern
  11. {
  12. TokenPattern(TokenType _type, QString _regex):regex(_regex),type(_type){}
  13. QRegExp regex;
  14. TokenType type;
  15. };
  16. class TokenPatternFactory
  17. {
  18. public:
  19. virtual int createPatterns(QList<TokenPattern*>& list) const= 0;
  20. };
  21. TokenAnalyzer(){}
  22. QList<Token*> analyzeToken(QString strInput, const TokenPatternFactory* factory);
  23. private:
  24. struct Context
  25. {
  26. Context(QList<Token*>& list, TokenIterator& _it, TokenPattern& _pattern, QString& _content)
  27. :tokenList(list), it(_it), pattern(_pattern), content(_content){}
  28. QList<Token*>& tokenList;
  29. TokenIterator& it;
  30. TokenPattern& pattern;
  31. QString& content;
  32. };
  33. void analyzeContent(Context& context);
  34. };
  35. template<typename Token, typename TokenType, typename TokenIterator>
  36. QList<Token*> TokenAnalyzer<Token, TokenType, TokenIterator>::analyzeToken(QString strInput, const TokenPatternFactory* factory)
  37. {
  38. QList<Token*> tokenList;
  39. tokenList.append(new Token(strInput));
  40. QList<TokenPattern*> list;
  41. factory->createPatterns(list);
  42. foreach(TokenPattern* pattern, list)
  43. {
  44. TokenIterator it = tokenList.begin();
  45. while(it != tokenList.end())
  46. {
  47. Token* token = *it;
  48. if(token->isNoType())
  49. {
  50. QString content = token->getContent();
  51. Context context(tokenList, it, *pattern, content);
  52. analyzeContent(context);
  53. }
  54. it++;
  55. }
  56. }
  57. return tokenList;
  58. }
  59. template<typename Token, typename TokenType, typename TokenIterator>
  60. void TokenAnalyzer<Token, TokenType, TokenIterator>::analyzeContent(Context& context)
  61. {
  62. Token* token = *context.it;
  63. int tokenBegin = context.content.indexOf(context.pattern.regex);
  64. if(tokenBegin != -1)
  65. {
  66. int matchedLength = context.pattern.regex.matchedLength();
  67. int tokenEnd = tokenBegin + matchedLength;
  68. if(tokenBegin > 0)
  69. {
  70. context.it = context.tokenList.insert(context.it, new Token(context.content.left(tokenBegin)));
  71. context.it++;
  72. }
  73. if(tokenEnd < context.content.length())
  74. {
  75. context.it = context.tokenList.insert(context.it,
  76. new Token(context.pattern.type, context.content.mid(tokenBegin, matchedLength)));
  77. context.it++;
  78. context.content.remove(0, tokenEnd);
  79. analyzeContent(context);
  80. }
  81. else
  82. {
  83. token->setContent(context.content.mid(tokenBegin, tokenEnd));
  84. token->setType(context.pattern.type);
  85. context.content.remove(0, tokenEnd);
  86. }
  87. }
  88. else
  89. {
  90. token->setContent(context.content);
  91. }
  92. }
  93. #endif // TOKENANALYZER_H

算上定义正则表达式的部分,正好120行。

当然也少不了,真正的主角Token类.


  
  1. class Token
  2. {
  3. public:
  4. enum EType
  5. {
  6. NoType,
  7. Operator,
  8. Number,
  9. FunctionName
  10. };
  11. Token(EType type, QString content);
  12. Token(QString content);
  13. EType getType();
  14. QString getContent();
  15. void setType(EType type);
  16. bool isNoType();
  17. void setContent(QString content);
  18. private:
  19. EType mType;
  20. QString mContent;
  21. };

其他关联文章请参考。