旧方案:正则表达式词法分析
在上一代平台采用正则表达式来进行词法分析,词法分析器代码如下:
/// <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 cref="LexicalAnalyzer"/> 对象的新实例。 /// </summary> /// <param name="expression">表达式</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{ output=AST; language=CSharp2; ASTLabelType=CommonTree; } 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运行时。上面的语法文件中已经指明language=CSharp2语言为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(); AstParserRuleReturnScope<CommonTree, 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
+加关注
1
0
升级成为会员
« 上一篇: 我的开源项目分享-基于SharpBrowser二次开发的定制浏览器,过程中填坑无数
» 下一篇: 元数据驱动开发 - 面向对象编程思想的补充
posted @ 2026-05-14 11:15 良村 阅读(136) 评论(1) 收藏 举报