背景
无论开始学习什么编程语言,入门教程一般都是创建一个简易计算器。
因为这是真正理解编译器和解释器背后隐藏着什么样的魔法的第一步。
数学表达式求值器是一个“解释器”,能够动态计算如下公式: 2 + 5 * 3 。
例如,如果您必须参数化应用程序的某些部分并需要执行一些计算,这非常有用。
解释器是一种能够动态执行用定义的语言编写的命令的程序。
编译器与解释器的不同之处在于,它将程序转换为特定于处理器的语言(即汇编语言),从而提供更好的性能。
巴科斯范式BNF
巴科斯范式(Backus Normal Form,BNF),是一种用于表示上下文无关文法的语言,上下文无关文法描述了一类形式语言。
它是由约翰·巴科斯(John Backus)和彼得·诺尔(Peter Naur)首先引入的用来描述计算机语言语法的符号集。
BNF 规定是推导规则(产生式)的集合,写为:
<符号> ::= <使用符号的表达式>
这里的 <符号> 是非终结符,而表达式由一个符号序列,或用指示选择的竖杠 '|' 分隔的多个符号序列构成,每个符号序列整体都是左端的符号的一种可能的替代。
从未在左端出现的符号叫做终结符。
简单来说,BNF 规范是一组类似下面的推导规则。
<symbol> ::= __expression__
示例:
<street-address> ::= <house-num> <street-name> <opt-apt-num> <EOL>
街道地址由门牌号、街道名称、可选的公寓说明符和行尾组成。
实现步骤
1、定义语言
构建解释器的第一步是定义要解释的语言。
这种语言将以 BNF(巴科斯-诺尔形式)表示法来定义,它本身就是一种描述其他语言的语言。
2、词法分析器Lexer 和解析器Parser
我们的程序必须能够阅读并理解提供给它的所有短语。
为了读取这些短语,我们将创建一个“词法分析器”。为了理解它们,我们需要所谓的“解析器”。
许多程序员仍然使用丑陋的手工制作的“switch case”模式来检测表达式中的元素。
由于不允许预编译,这使得语法不可维护且功能不强大。
在本文中,词法分析器和解析器将由外部专用工具生成。
最知名的是 Lex 和 Yacc,但我们将重点关注 ANTLR,因为它易于使用且能够生成 C# 代码。
3、执行引擎
创建解析器和词法分析器后,解释器就可以开始执行短语背后的隐藏含义。
NCalc项目介绍
NCalc 是 .NET 中的数学表达式计算器。
NCalc 可以解析任何表达式并计算结果,包括静态或动态参数以及自定义函数。
此框架的更多技术信息,请阅读:https://www.codeproject.com/Articles/18880/State-of-the-Art-Expression-Evaluation。
主要功能
简单的表达
Expression e = new Expression("2 + 3 * 5");
Debug.Assert(17 == e.Evaluate());
计算.NET 数据类型
Debug.Assert(123456 == new Expression("123456").Evaluate()); // integers
Debug.Assert(new DateTime(2001, 01, 01) == new Expression("#01/01/2001#").Evaluate()); // date and times
Debug.Assert(123.456 == new Expression("123.456").Evaluate()); // floating point numbers
Debug.Assert(true == new Expression("true").Evaluate()); // booleans
Debug.Assert("azerty" == new Expression("'azerty'").Evaluate()); // strings
处理 System.Math 中的数学函数
Debug.Assert(0 == new Expression("Sin(0)").Evaluate());
Debug.Assert(2 == new Expression("Sqrt(4)").Evaluate());
Debug.Assert(0 == new Expression("Tan(0)").Evaluate());
计算自定义函数
Expression e = new Expression("SecretOperation(3, 6)");
e.EvaluateFunction += delegate(string name, FunctionArgs args)
{
if (name == "SecretOperation")
args.Result = (int)args.Parameters[0].Evaluate() + (int)args.Parameters[1].Evaluate();
};
Debug.Assert(9 == e.Evaluate());
处理 unicode 字符
Debug.Assert("経済協力開発機構" == new Expression("'経済協力開発機構'").Evaluate());
Debug.Assert("Hello" == new Expression(@"'\u0048\u0065\u006C\u006C\u006F'").Evaluate());
Debug.Assert("だ" == new Expression(@"'\u3060'").Evaluate());
Debug.Assert("\u0100" == new Expression(@"'\u0100'").Evaluate());
定义参数,甚至是动态参数或表达式
Expression e = new Expression("Round(Pow([Pi], 2) + Pow([Pi2], 2) + [X], 2)");
e.Parameters["Pi2"] = new Expression("Pi * [Pi]");
e.Parameters["X"] = 10;
e.EvaluateParameter += delegate(string name, ParameterArgs args)
{
if (name == "Pi")
args.Result = 3.14;
};
Debug.Assert(117.07 == e.Evaluate());
在分布式缓存中进行缓存
序列化
var compiled = Expression.Compile(expression, true);
var serialized = JsonConvert.SerializeObject(compiled, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All // We need this to allow serializing abstract classes
});
反序列化
var deserialized = JsonConvert.DeserializeObject<LogicalExpression>(serialized, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
Expression.CacheEnabled = false; // We cannot use NCalc's built in cache at the same time.
var exp = new Expression(deserialized);
exp.Parameters = new Dictionary<string, object> {
{"waterlevel", inputValue}
};
var evaluated = exp.Evaluate();