
旧方案正则表达式词法分析在上一代平台采用正则表达式来进行词法分析词法分析器代码如下/// summary /// 文本格式表达式语法分析器 /// /summary /// remarks /// ${XXX} - 参变量 /// XXX - 参数 /// /remarks public sealed class LexicalAnalyzer { private static readonly Regex REGEX new Regex( \(|\)|/|[\\-\]{1,2}|\*|[!]?|(([^]|)*)|(\$\{([^\}])*\})|(\$\{([^\}])*\})|[^,\u0000-\u0020!\-\*\/\(\)], RegexOptions.Compiled | RegexOptions.IgnoreCase ); private Match match; private Match previousMatch; private string expression; /// summary /// 初始化 see crefLexicalAnalyzer/ 对象的新实例。 /// /summary /// param nameexpression表达式/param public LexicalAnalyzer( string expression ) { this.expression expression; } /// summary /// 当前符号 /// /summary public string Current { get { return match.Value; } } /// summary /// 上一个符号 /// /summary public string Previous { get { if ( previousMatch ! null ) { return previousMatch.Value; } return String.Empty; } } /// summary /// 上一个表达式段 /// /summary public Match PreviousMatch { get { return previousMatch; } } /// summary /// 移动到下一标识 /// /summary /// returns/returns public string MoveNext() { previousMatch match; if ( match null ) { match REGEX.Match( expression ); } else { match match.NextMatch(); } if ( match.Success ) { return Current; } return String.Empty; } }然后将词法分析器切分出的表达式片段进行语法分析处理得到逆波兰表达式。再根据逆波兰表达式构建出表达式对象。旧方案的不足正则表达式本身的解析效率低。无法处理一些高级语法也许只是我写正则表达式的能力问题如字符串的转义字符负号的处理等等。新方案采用ANTLR语法分析器生成工具ANTLR是一款强大的语法分析器生成工具可用于读取、处理、执行和翻译结构化的文本或二进制文件。它被广泛应用于学术领域和工业生产实践是众多语言、工具和框架的基石。ANTLR使得语法和语言类应用程序的开发更加容易只要写好语法规则ANTLR就可以根据语法规则生成C#语言编写的词法分析器和语法分析器。ANTLR的最新版是4.13.2易元平台用的是3.1版本据官方的说法4比3更易于使用语法规则编写更简单。LL(*)文法LL(*)可以算是形式语言中介绍的上下文无关文法的子集具体理论笔者也没有深入研究过这里直接给出已经写好的语法文件有兴趣读者可以自行研究。因为是3.1版本支持语法的友好度不如4版有些表达写起来比较绕来来回回摸索了好久。grammar AntlrExpression; options{ outputAST; languageCSharp2; ASTLabelTypeCommonTree; } tokens{ MINUS; } expression : or_expr ; or_expr : and_expr ( OROR^ and_expr )* ; and_expr : not_expr ( ANDAND^ not_expr )* ; not_expr : NOT^ not_expr | NOT^? logical_expr ; logical_expr : NOT^? compare_expr ; compare_expr : like_expr ( ( LT | GT | EQEQ | LE | GE | NE )^ like_expr )? ; like_expr : in_expr ( LIKE^ in_expr )? ; in_expr : add_expr ( IN^ ( LPAREN! add_expr ( COMMA! add_expr )* RPAREN! | LPAREN! RPAREN!) )? ; add_expr : mul_expr ( ( ADD | SUB )^ mul_expr )* ; mul_expr : minus_expr ( ( MUL | DIV | MOD )^ minus_expr )* ; minus_expr : atom | - atom - ^(MINUS atom) ; func_expr : ID^ LPAREN! or_expr (COMMA! or_expr)* RPAREN! | ID^ LPAREN! RPAREN! ; atom : BOOL_TRUE | BOOL_FALSE | NULLNULL | EMPTY | func_expr | INT | UINT | LONG | ULONG | FLOAT | DOUBLE | DECIMAL | P | STRING | VAR | LPAREN! expression RPAREN! ; LPAREN : ( ; RPAREN : ) ; COMMA : , ; ADD : ; SUB : - ; MUL : * ; DIV : / ; MOD : % ; EQEQ : ; NE : ! ; LT : ; LE : ; GT : ; GE : ; NOT : ! ; ANDAND : ; OROR : || ; P : (ID:)? NID; VAR : ${ ([ JOIN_SEQUENCE ] | ID[ JOIN_SEQUENCE ] | ID:)? ID }; fragment JOIN_SEQUENCE : ( ((f|i|l|r)!)? ID (# ID?)? | - ((f|i|l|r)!)? ID (# ID?)? ( ID )); BOOL_TRUE : true ; BOOL_FALSE : false ; NULLNULL : null ; EMPTY : empty ; IN : (i|I)(n|N) ; LIKE : (l|L)(i|I)(k|K)(e|E) ; ID : (a..z|A..Z|_) (a..z|A..Z|0..9|_)* ; INT : (0..9) ; UINT : 0..9(U|u) ; LONG : 0..9(L|l) ; ULONG : 0..9(U|u)(L|l) ; DECIMAL : ((0..9) . (0..9)* EXPONENT? | . (0..9) EXPONENT? | (0..9) EXPONENT) (M|m)? | (0 | 1..9 0..9*)(M|m) ; FLOAT : ((0..9) . (0..9)* EXPONENT? | . (0..9) EXPONENT? | (0..9) EXPONENT | (0 | 1..9 0..9*)) (F|f) ; DOUBLE : ((0..9) . (0..9)* EXPONENT? | . (0..9) EXPONENT? | (0..9) EXPONENT | (0 | 1..9 0..9*)) (D|d) ; NID : (a..z|A..Z|0..9|_) ; STRING : ( ESC_SEQ | ~(\\|) )* ; fragment EXPONENT : (e|E) (|-)? (0..9) ; fragment HEX_DIGIT : (0..9|a..f|A..F) ; fragment ESC_SEQ : \\ (b|t|n|f|r|\|\\) | UNICODE_ESC | OCTAL_ESC ; fragment OCTAL_ESC : \\ (0..3) (0..7) (0..7) | \\ (0..7) (0..7) | \\ (0..7) ; fragment UNICODE_ESC : \\ u HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT ; WS : ( |\t|\r|\n) { Skip(); } ; ERROR_CHAR : . ;生成C#的解析器ANTLR是用Java编写的所以要运行ANTLR需要Java运行时。上面的语法文件中已经指明languageCSharp2语言为C#所以生成时会自动生成AntlrExpressionLexer.cs词法分析器和AntlrExpressionParser.cs语法分析器两个类。使用解析器利用解析器可以把表达式文本分析称表达式Token树再遍历Token树生成易元平台中的表达式代码如下图所示// 分析表达式文本 private static Expression InnerParse(Kernel kernel, string expressionString) { try { ICharStream charStream new ANTLRStringStream(expressionString); AntlrExpressionLexer lexer new AntlrExpressionLexer(charStream); CommonTokenStream tokens new CommonTokenStream(lexer); AntlrExpressionParser parser new AntlrExpressionParser(tokens); parser.TreeAdaptor new CommonTreeAdaptor(); AstParserRuleReturnScopeCommonTree, IToken expressionReturn parser.expression(); CommonTree tree expressionReturn.Tree; return Create(kernel, null, tree); } catch(Expression.ExpressionParsePositionException ex) { // 分析详细错误 throw new ExpressiveParseException(ex.Message, ex) { Markdown BuildExceptionMessage(expressionString, ex) }; } catch(Exception ex) { throw new ExpressiveException(Formatter.String(Resources.Ex_Ea_Expressive_ExpressionParser_ParseError, expressionString), ex); } } // 创建易元平台的表达式 private static Expression Create(Kernel kernel, Expression parent, CommonTree tree) { try { Expression expression TokenMapping.Create(kernel, tree.Token, tree); expression.ParseChildren(kernel, parent, tree, Create); return expression; } catch (ExpressiveParseException ex) { Expression.ExpressionParsePositionException eppe; if (TrySyntaxError(tree, Resources.Ex_Ea_Expressive_ExpressionParser_SyntaxError, ex, out eppe)) { // 可以分析细节信息然后封装后抛出 throw eppe; } throw new ExpressiveParseException(Resources.Ex_Ea_Expressive_ExpressionParser_SyntaxError); } }上面仅列出部分关键代码代码已在Gitee上开源查看完整的代码点击这里ExpressionParser.cs常规函数和中缀函数常规函数的形式是FunctionName(arg1, arg2, ..., argN)常规函数可以任意增加只要函数名符合语法要求即可。中缀函数是把首个操作数放前面函数名在中间形式如下arg1 FunctionName(arg2, ..., argN) // 比如 arg1 In (1, 2, 3)中缀函数目前只有In和Like是在语法文件中限定的不改语法规则文件的话不能增加中缀函数。后期考虑调整语法规则增加扩展中缀函数支持。效果展示任意表达式计算条件表达式友好配置分类: 零代码低代码, 开源, 易元平台免责声明本内容来自平台创作者博客园系信息发布平台仅提供信息存储空间服务。好文要顶 关注我 收藏该文 微信分享良村粉丝 - 17 关注 - 48加关注10升级成为会员« 上一篇 我的开源项目分享-基于SharpBrowser二次开发的定制浏览器过程中填坑无数» 下一篇 元数据驱动开发 - 面向对象编程思想的补充posted 2026-05-14 11:15 良村 阅读(136) 评论(1) 收藏 举报