开源纯C#工控网关+组态软件(八)表达式编译器
一、引子
监控画面的主要功能之一就是跟踪下位机变量变化,并将这些变化展现为动画。大部分时候,界面上一个图元组件的某个状态,与单一变量Tag绑定,比如电机的运行态,绑定一个MotorRunning信号;但有些时候不会这么简单,比如温度计在温度高于50℃显示红色;
某设备报警,可能是多个条件其中之一触发的结果;变量变化触发一系列连锁反应…如此种种。考虑到工控行业大部分技术人员并非计算机专业出身,如何能够用最少的编码解决各种复杂的变量-动画绑定问题,无疑要费一番心思。
文章图片
二、方案选型
针对变量动画绑定问题,可以选择的方案包括如下几种:
- 脚本编译器
但基于下面几种考虑,我没有实现这类的脚本编译器:
- 不同于大部分组态软件包含一个独立的界面设计器,我用Visual Studio来肩挑语法编辑、调试、编译和界面设计的重任,没必要多此一举的搞一个独立的脚本编译器。
- C#结合Visual Studio来调用通讯、数据库链接的各类函数,C#包含强大的语法功能,配合.NET 类库几乎无所不能,同时C#也支持脚本化,没有必要在使用其他脚本语言。
- 运算符重载。
运算符重载是C#一个强大的语法功能,可以重载的操作符如下:
运算符 |
可重载性 |
+、-、!、~、++、--、true、false |
可以重载这些一元运算符。 true和false运算符必须成对重载。 |
+、-、*、/、%、&、|、^、<<、>> |
可以重载这些二元运算符。 |
==、!=、<、>、<=、>= |
可以重载比较运算符。必须成对重载。 |
&&、|| |
不能重载条件逻辑运算符。 但可以使用能够重载的&和|进行计算。 |
[] |
不能重载数组索引运算符,但可以定义索引器。 |
() |
不能重载转换运算符,但可以定义新的转换运算符。 |
+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>= |
不能显式重载赋值运算符。 在重写单个运算符如+、-、%时,它们会被隐式重写。 |
=、.、?:、->、new、is、sizeof、typeof |
比如有一种复数类型Complex,有两个坐标x和y;定义ComplexA大于ComplexB为: A的x,y中至少有一个大于B的x,y。我只需要重载>操作符(相应的最好重载>=,<,<=),以后只需要A>B就能代替重复啰嗦的A.x>B.x||A.y>B.y。更可喜的是,重载后的>,<这些运算符,在.Net表达式树(ExpressionTree)中已经替换了它原来的语义。因此运算符重载在我这个编译器也有它用武之地。
但出于下面两个原因,它只适合作为编译引擎的辅助,而不适合单独使用:
- 首先运算符重载只针对特定的类型;对于不熟悉C#语法特性的编程者,理解并正确的使用运算符重载不是件容易的事。
- 运算符重载可以减少重复的代码,让语法更简洁;但依然要写C#代码,不适合大部分工控人员。
- 订阅事件
var tag1 = App.Server["A"];
var tag2 = App.Server["B"];
var tag3 = App.Server["C"];
if (tag1 != null && tag2 != null && tag3 != null
{
tag1.ValueChanged += (s, e) =>
{
if (tag1.Value.Boolean && tag2.Value.Boolean && tag3.Value.Boolean)
{
//执行
}
};
tag2.ValueChanged += (s, e) =>
{
if (tag1.Value.Boolean && tag2.Value.Boolean && tag3.Value.Boolean)
{
//执行
}
};
tag3.ValueChanged += (s, e) =>
{
if (tag1.Value.Boolean && tag2.Value.Boolean && tag3.Value.Boolean)
{
//执行
}
};
}
看上去不算复杂吧?如果界面上有50个动画,这样的代码就要写50次。不但浪费时间,改起来麻烦,查起来也麻烦。更糟糕的是,不懂编程的人还用不了。
- 表达式编译器
比如温度计在温度高于50℃显示红色,就一句话【temperature>50】; 某设备显示报警,可能是多个报警变量其中之一触发的结果,只需写【Alarm1||Alarm2||Alarm3】…借助微软强大的表达式引擎,如果能解析这类变量表达式,设计者只需要知道图元与变量的逻辑关系;而极少数表达式也难以企及的功能,略微懂一点C#就可以实现。这样就可以做到使用简单,上手容易,同时又可以满足复杂的需求。
同时还有下面几个额外的好处:
最少的编码量:在一个界面的cs文件里,几乎没有代码。绑定逻辑在XAML内用直观的方式嵌入:
文章图片
- 可以用复制、粘贴和文本替换等功能减少重复编码;
- 可以充分利用WPF的设计器扩展,实现一个简单的语法编辑器,实现语法高亮、自动完成并执行语法检查;
- 查找变量逻辑和修改很方便。
三、自己实现一个编译器
- 编译原理
逆波兰表达式的优势在于只用两种简单操作,入栈和出栈就可以搞定任何普通表达式的运算。其运算方式如下:
如果当前字符为变量或者为数字,则压栈,如果是运算符,则将栈顶两个元素弹出作相应运算,结果再入栈,最后当表达式扫描完后,栈里的就是结果。
如何实现自己的编译器,微软已经给大家现成的轮子了。微软的Expression类提供了一套拼接、编译Lambda表达式的完整方法,可以用它轻松定义你自己的语法。相关知识可以参考博客园 装配脑袋 的自己动手开发编译器系列文章:http://www.cnblogs.com/Ninputer/archive/2011/06/18/2084383.html。下面就以这个SCADA项目为例:
- 定义语法
GetOperatorLevel函数按照C#的运算符优先级定义运算优先级。
定义了@开头的自定义函数如@Date取当前日期、@App取当前路径等。
IsConstant方法定义系统常数,其中True/False表示逻辑常量,字符串常量用’’。
- 编译过程
- RpnExpression方法:将中缀表达式转换为逆波兰表达式。用关键字将表达式字符串分割为一个数组;按照优先级出栈入栈;返回一个逆波兰表达式顺序的字符串列表。
- ComplieRpnExp方法:根据逆波兰表达式顺序,依次弹出运算符转换为Expression的各子类如二元表达式BinaryExpression、条件表达式ConditionalExpression、常数表达式ConstantExpression等;参数首先判断是否常数,如果不是,则调用GetTagExpression方法,将字符串转换为方法调用MethodCallExpression,最终会将该参数编译为一个Tag。经过处理最终返回一个LambdaExpression。
- Eval方法将LambdaExpression编译为一个委托;相关的Tag加入列表TagList。
- 表达式与动画绑定
List
private void HMI_Loaded(object sender, RoutedEventArgs e)
{
lock (this)
{
_valueChangedList = cvs1.BindingToServer(App.Server);
}
}
private void HMI_Unloaded(object sender, RoutedEventArgs e)
{
lock (this)
{
App.Server.RemoveHandles(_valueChangedList);
}
}
其中, BindingToServer就是对当前界面所有图元进行地毯式扫描,搜索出各控件相关的TagReadText表达式并用Eval类编译之;编译的结果转换为带返回值的函数和一个相关Tag的列表;遍历这个Tag列表,将其值变化事件ValueChanged与这个函数链接起来。这样,在加载界面的时候已经完成了编译过程,相关变量的值一旦改变,就会根据表达式返回一个值,如果这个值是布尔量,同时与电机的运行动画绑定,就完成了从表达式到动画的触发过程。
- 复杂报警条件
- 未来改进
支持复杂语法:目前的语法仅仅是简单的四则运算和逻辑表达式。未来考虑支持多段表达式、函数(如正余弦)、属性引用等复杂语法。
github地址:https://github.com/GavinYellow/SharpSCADA。QQ群:102486275
相关文章:
- .NET十年回顾
- 开源纯C#工控网关+组态软件
- 开源纯C#工控网关+组态软件(三)加入一个新驱动:西门子S7
- 开源纯C#工控网关+组态软件(四)上下位机通讯原理
- 开源纯C#工控网关+组态软件(五)从网关到人机界面
- 开源纯C#工控网关+组态软件(六)图元组件
- 开源纯C#工控网关+组态软件(七)数据采集与归档
原文地址:http://www.cnblogs.com/evilcat/p/8379640.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
【开源纯C#工控网关+组态软件(八)表达式编译器】
文章图片
推荐阅读
- 探索免费开源服务器tomcat的魅力
- 让我们回到纯真年代|让我们回到纯真年代,活着
- #20170613#-日课
- 要玩转这个星际争霸II开源AI,你只需要i5+GTX1050
- 杂阿含经638
- redis|redis 常见问题一
- 《超时空同居》观影体验(笑或哭都很纯粹)
- java|微软认真聆听了开源 .NET 开发社区的炮轰( 通过CLI 支持 Hot Reload 功能)
- SEL儿童情感训练是单纯的学科教育和知识灌输无法教给孩子的,也是我国教育体制中最为缺失的部分。
- 纯非纯之复杂!