基于语法分析器GOLD Parser开发的数学表达式计算器

  最近发现一款文法分析神器,看完官网(http://goldparser.org/)的介绍后感觉很犀利的样子,于是就拿来测试了一番,写了一个数学表达式分析的小程序,支持的数学运算符如下所示:
常规运算:+ - * / ^ sqrt sqrt2(a,b) pow2(a) pow(a,b)
三角函数:sin cos tan cot asin acos atan acot
指数对数:log2(a) log10(a) ln(a) logn(a,b) e^
最大最小:max(a,b,...) min(a,b,...)

一、         GOLD Parser简介 

  GOLD Parser是一款强大的文法分析工具,支持c++, c, c#, Java, Python, Pascal等多种语言,详细信息请参见官网 http://goldparser.org/

使用该工具主要包括三个步骤:

  1. 编写我们要解析的语言的语法描述(采用GOLD Meta-Language编写)
  2. 使用GOLD Builder工具编译我们的语法文件,生成egt格式的表文件,该文件中存储了编译完成的文法表,供后面解析引擎使用
  3. 选择一种我们熟悉的编程语言,下载对应的与文法表解析引擎,然后在我们的程序中调用该引擎对我们需要解析的语言进行解析即可

二、        数学表达式语法定义

从官网上下载GOLD Parser Builder Tool,按照提示进行安装,安装完成后就可以编写语法定义了。主界面如下,工具中附带的测试工具十分强大,写完语法定义后,可以直接对语法进行测试,生成语法树状图。

在编写语法描述之前,首先我们先熟悉一下GOLD Meta-Language的基本特性,该语言主要由以下几部分组成:

1. 语法文件属性描述

这部分是用来描述我们即将编写的语法文件的相关信息的,如语法名称、作者、版本号等等。格式如下:

"Name"    = ‘My Programming Language‘
"Version" = ‘1.0 beta‘
"Author"  = ‘John Q. Public‘

"Start Symbol" = <Statement>  //必不可少,表示定义的开始,上面的可以不写

2. 字符集定义

这部分是用来描述我们语言中所要用到的字符集,GOLD Meta-Language中预先定义了很多字符集,如常用的数字集合{Number}、字母集合{Letter}、可打印字符集合{Printable}等等,也可以使用Unicode码指定字符集范围{&4F00..&99E0},表示从4F00到99E0之间的所有字符。格式如下:

{String Char} = {Printable} – [”]          //表示从可打印的字符中减去”字符

我们可以定义多个字符集供我们定义的语言使用

3. 终结符(Terminal)定义

终结符是指我们定义的语言中能被语法分析器识别的最小单元,举例说明一下,比如下面一个数学表达式:3.3+sin(a+b1),终结符为“3.3”“+”“sin”“(”“a”“b1”“)”,终结符通常是采用正则表达式定义的,如果我们对正则表达式不了解,那么强烈建议我们去补补正则表达式的相关知识了。在语法文件中,变量及数字的终结符采用如下方式定义

Variable = {Letter}{Number}*  //表示一个字母后面跟0个或多个数字,如a,b,x1,y34

NumberValue = {Number}+ | ({Number}+‘.‘{Number}*)  //表示整数或小数

4. Productions定义(这个不好翻译o(╯□╰)o,就用英文表示吧)

我们所描述的语言的语法是由一系列Production定义的,而一个Production是由若干个终结符(Terminal)和非终结符(Nonterminal)组成,非终结符通常是由尖括号<>界定,并由若干个终结符及非终节符定义。下图表示的是一个Production,表示语言中的if-then-end语句,其中<Stm>, <Exp>, <Stmts>是非终结符,if, then, end是终结符。

一系列相同类型的Production组成一个规则集(Role),我们所描述的语言的语法就是由规则集定义,下面两幅图两种表示是等价的,是同一个规则集。

在熟悉了GOLD Meta-Language的语法之后,就可以着手编写数学表达式的语法定义了。本人定义的语法文件如下:

! Welcome to GOLD Parser Builder 5.2

"Name" = ‘Calculator‘

"Version" = ‘v1.0‘

"Author" = ‘xxchen‘

"Start Symbol" = <Exp>

Variable = {Letter}{Number}*

NumberValue = {Number}+ |   ({Number}+‘.‘{Number}*)

<Exp> ::= <Exp> ‘+‘ <Exp   Mult>

         | <Exp> ‘-‘ <Exp Mult>

         | <Exp Mult>

<Exp Mult> ::= <Exp Mult> ‘*‘   <Value>

            | <Exp Mult> Variable

            | <Exp Mult> ‘/‘   <Value>

            | <Value>

<Exp Func> ::= <Exp Func1>

            | <Exp Func2>

            | <Exp Funcn>

<Exp Func1> ::= ‘sin‘ <Value>

            | ‘cos‘ <Value>

            | ‘tan‘ <Value>

            | ‘cot‘ <Value>

            | ‘asin‘ <Value>

            | ‘acos‘ <Value>

            | ‘atan‘ <Value>

            | ‘acot‘ <Value>

            | ‘sqrt‘ <Value>

            | ‘log2(‘ <Value> ‘)‘

            | ‘log10(‘ <Value> ‘)‘

            | ‘pow2(‘ <Value> ‘)‘

            | ‘e^‘ <Value>

            | ‘ln‘ <Value>

<Exp Func2> ::= <ExpValue>   ‘^‘ <Value>

             | ‘pow(‘ <Exp> ‘,‘   <Exp> ‘)‘

             | ‘sqrt2(‘ <Exp> ‘,‘   <Exp> ‘)‘

             | ‘logn(‘ <Exp> ‘,‘   <Exp> ‘)‘

<Params> ::= <Params> ‘,‘   <Exp>

            | <Exp>

<Exp Funcn> ::= ‘max(‘   <Params> ‘)‘

             | ‘min(‘ <Params> ‘)‘

<Param> ::= NumberValue

           | Variable

<ExpValue> ::= <Param>

           | ‘-‘ <Param>

           | ‘(‘ <Exp> ‘)‘ 

           | ‘|‘ <Exp> ‘|‘

<Value> ::= <ExpValue>

           | <Exp Func>

写完后,直接点软件右下角的Next按钮,在没有提示错误后会生成一个.egt文法表文件,该文件在后面的程序编写过程中需要用到。

三、        利用解析引擎编写代码

由于个人比较熟悉c#语言,故采用了c#语言版本的解析引擎,其它语言版本的引擎在官网上也有提供。在正式编写代码之前,还可以利用Builder Tool来生成对应引擎的解析框架,在Project-Create a Skeleton Program菜单下可以打开向导进行设置,选择对应的语言及解析引擎,就可以生成相应的解析框架了。

自动生成出来的解析框架非常简单,如下所示,主要有两个函数需要注意,第一个是Parse函数,该函数接受一个TextReader类型的参数,用来读取需要解析的内容,里面的解析逻辑都已自动生成;第二个是CreateNewObject函数,我们需要修改的就是这个函数,在引擎解析过程中,我们需要根据每个步骤的解析结果生成我们需要的对象,以实现我们需要的逻辑。在不影响整体框架的前提下,其它部分可以任意修改,在这里我添加了一个带参数的构造函数,参数是文法表文件的路径,然后在构造函数中初始化解析引擎。

为了实现计算逻辑,这里定义了一个简单的表达式类,该类的构造函数可以接受一个常数,或者一个变量,或者接受若干个表达式。

/// <summary>

/// 表达式类

/// </summary>

public class Expression

{

    /// <summary>

    /// Initializes a new instance of the <see cref="Expression"/> class.

    /// </summary>

    /// <param   name="value">接受一个常数</param>

    public Expression(double value)

    {

          _value = t => value;

    }

    /// <summary>

    /// Initializes a new instance of the <see cref="Expression"/> class.

    /// </summary>

    /// <param   name="variable">接受一个变量</param>

    public Expression(string variable)

    {

          _value = t => t[variable];

          _varList = new List<string> { variable };

    }

    /// <summary>

    /// Initializes a new instance of the <see cref="Expression"/> class.

    /// </summary>

    /// <param   name="func">表达式计算函数</param>

    /// <param   name="exps">接受若干个表达式</param>

    public Expression(Func<double[], double> func, params Expression[] exps)

    {

          _value = t => func(exps.Select(e => e._value(t)).ToArray());

          foreach (var exp in exps)

          {

              if(exp._varList == null)

                continue;

              if(_varList == null)

                  _varList = new List<string>();

              _varList.AddRange(exp._varList);

          }

          if (_varList != null)

              _varList = _varList.Distinct().ToList();

    }

    /// <summary>

    /// 存储变量名称的链表

    /// </summary>

    private readonly List<string> _varList;

    /// <summary>

    /// 获取表达式中的变量

    /// </summary>

    /// <returns></returns>

    public IEnumerable<string> GetVariables()

    {

          if(_varList == null)

              yield break;

          foreach (var var in _varList)

              yield return var;

    }

    /// <summary>

    /// The _value

    /// </summary>

    private readonly Func<Dictionary<string, double>, double> _value;

    /// <summary>

    /// 获取表达式的值,用于计算没有变量的表达式

    /// </summary>

    /// <returns>System.Double.</returns>

    public double GetValue()

    {

          return GetValue(null);

    }

    /// <summary>

    /// 获取表达式的值,用于计算有变量的表达式

    /// </summary>

    /// <param   name="varTable">参数表</param>

    /// <returns>System.Double.</returns>

    public double GetValue(Dictionary<string, double> varTable)

    {

          try

          {

              return _value(varTable);

          }

          catch (Exception)

          {

              return double.NaN;

          }

    }

}

   再来看一下解析引擎中生成的CreateNewObject函数,下面只截取了部分代码,里面的逻辑也很简单,比如引擎在解析完数字后,可以根据注释,这里是// <Param> ::= NumberValue ,表示r中数据的个数为1,其中r[0].Data对应的就是NumberValue的值,这时我们只需要返回一个常数表达式即可。在解析完变量后,注释的代码是// <Param> ::= Variable,返回一个变量表达式即可。在解析完+号时,对应的注释代码是// <Exp> ::= <Exp> ‘+‘ <Exp Mult> 表明r中数据的个数是3,r[0].Data及r[2].Data是我们之前的数据解析完时返回的表达式,对应于解析树中的<Exp>及<Exp Mult>,r[1].Data是”+”号,故在这个节点我们需要生成一个新的加法表达式,然后返回该表达式即可。

Expression exp1, exp2;

switch ((ProductionIndex)r.Parent.TableIndex())

{

    case ProductionIndex.Exp_Plus:

        // <Exp> ::= <Exp> ‘+‘ <Exp   Mult>

        exp1 = r[0].Data as Expression;

        exp2 = r[2].Data as Expression;

        result = new Expression(t => t[0] + t[1], exp1, exp2);

        break;

    case ProductionIndex.Exp_Minus:

        // <Exp> ::= <Exp> ‘-‘ <Exp   Mult>

        exp1 = r[0].Data as Expression;

        exp2 = r[2].Data as Expression;

        result = new Expression(t => t[0] - t[1], exp1, exp2);

        break;

    case ProductionIndex.Param_Numbervalue:

        // <Param> ::= NumberValue

        result = new Expression(double.Parse(r[0].Data.ToString()));

        break;

    case ProductionIndex.Param_Variable:

        // <Param> ::= Variable

        result = new Expression(r[0].Data.ToString());

        break;

……省略类似部分

至此,数学表达式的解析引擎已经构造完成,使用方法如下:

//根据文发表文件构造解析引擎

var filePath = Path.Combine(Directory.GetCurrentDirectory(), "calculator.egt");

var parser = new CalculatorParser(filePath);

//解析读入的字符串

parser.Parse(new StringReader(line));

//读取解析结果,即一个表达式

var exp = parser.Exp;

//计算表达式的值

result =   exp.GetValue();

四、        实验效果

程序可以计算用户任意输入的表达式,如果发现表达式有误,则会提示用户在哪个位置出现了错误。程序还可以识别变量,并且对数字后面紧接变量的表达方式理解为乘法运算,如3d表示3*d。图中的cos-3-4.d会理解为cos(-3)-4.0xd,其中d为变量

五、        总结

总的来说GOLD Parser是一个非常强大的文法分析工具,可以解析任意有规律的文本文件,如xml, json, html, c, c++, java, c#等等,这些语言的语法描述文件在官网上也都能找得到(不用自己重头再写了)。如果要想解析一门新的语言或者数据描述文件,那么就得自己写语法描述文件,对于语法不是很复杂的语言,在官网上找点资料,然后照着例子写两遍就能搞定了(从刚接触GOLD Parser到完成这个小程序一共花了不到1天时间)。语法写完后,借助现有的解析引擎,程序的编写就非常简单了。

源代码下载地址:http://vdisk.weibo.com/s/yVSnUWjONKKp0

【原创】转载请说明出处!

基于语法分析器GOLD Parser开发的数学表达式计算器

时间: 2024-10-27 12:01:16

基于语法分析器GOLD Parser开发的数学表达式计算器的相关文章

编译器--简单数学表达式计算器

做了一个能够计算简单数学表达式值的小计算器,算不上是编译器,但用到了编译器的知识.最近在看一些编译器的东西,所以动手写这个最简单的计算器,既是对那些抽象的编译器知识有个形象的认识,也为后面添加复杂的东西--语句打下基础.此计算器是以<编译原理与实践>中实现的tiny编译器为参考写的,tiny是一个值得去研究的编译器,可以说是麻雀虽小,五脏俱全.从词法分析到代码生成都有,并且代码非常清晰易懂.我觉得想要了解编译器,可以从tiny入手,去将它跑起来并分析.废话不多说,开始记录这个小计算器. 先说下

构造可配置词法语法分析器生成器(中)

本文为笔者原创,转载请注明出处 http://blog.csdn.net/xinghongduo   语法分析器 语法分析器(grammar parser)是编译器的核心部分之一,它的作用是检测词法分析器返回的token序列是否符合文法定义的规则.一个完整的语法分析器除了检测语法正确性外还要包含对出错的处理以及错误恢复等功能. 文法和文法类型 文法是定义一个语言的所有规则的集合,形式上定义为四元组G={VT,VN,S,P},其中: VT是非空有限符号集合,它的每个符号称为终结符,文法产生的语言由

构造可配置词法语法分析器生成器(下)

本文为笔者原创,转载请注明出处 http://blog.csdn.net/xinghongduo mylex & xparser mylex & xparser是笔者实现的类似于Lex和Yacc的词法语法分析器生成器,它接受正则表达式定义的词法规则和BNF定义的语法规则,自动构造对应的以C为宿主语言的词法分析器源程序mylex.h, mylex.c和语法分析器源程序xparser.h, xparser.c. mylex & xparser特点如下: 轻量无依赖:构造器的主要代码仅2

语法分析器自动生成工具一览

Lex/Yacc 它生于Unix,是最经典的词法\语法分析器,是经典教材中的示例御用工具.现在它也支持在Windows上生成(安装环境),然而其所生成语法分析器的语言仅有C语言. Flex/Bison 与前者类似,Bison与Yacc有很高的兼容性.生成语言为C.C++和Java. CoCo/R 较早的一个语法分析器生成工具.其生成语法分析器的语言极其之多,包括C#. Java. C++.F#.VB.Net.Oberon等等. ANTLR 作为翻译程序的一部分,你可以使用简单的操作符和动作来参数

构造可配置词法语法分析器生成器(上)

本文为笔者原创,转载请注明出处 http://blog.csdn.net/xinghongduo 前言 源程序在被编译为目标程序需要经过如下6个过程:词法分析,语法分析,语义分析,中间代码生成,代码优化,目标代码生成.词法分析和语法分析是编译过程的初始阶段,是编译器的重要组成部分,早期相关理论和工具缺乏的环境下,编写词法语法分析器是很繁琐的事情.上世纪70年代,贝尔实验室的M. E. Lesk,E. Schmidt和Stephen C. Johnson分别为Unix系统编写了词法分析器生成器Le

两周自制脚本语言-第5天 设计语法分析器

第5天 设计语法分析器 5.1 Stone语言的语法 代码清单 5.1 Stone 语言的语法定义 primary : "(" expr ")" | NUMBER | IDENTIFIER | STRING factor : "-" primary | primary expr : factor { OP factor } block : "{" [ statement ] { (";" | EOL) [

转:基于科大讯飞语音API语音识别开发详解

最近项目需要用到android语音识别,立马就想到科大讯飞,结合官方实例及阅读API文档,初步的完成了Android语音识别,下面是实现过程实录. 一.准备工作 1.你需要android手机应用开发基础 2.科大讯飞语音识别SDK android版 3.科大讯飞语音识别开发API文档 4.android手机 关于科大讯飞SDK及API文档,请到科大语音官网下载:http://open.voicecloud.cn/ 当然SDK和API有多个版本可选,按照你的需要下载,其次,下载需要填写资料申请注册

基于gulp的前端框架开发规范

前端开发及相关规范 - 基于gulp的前端框架开发规范 1.前端开发工具的安装和使用说明 前端开发工具的目录结构 htmlcodeBuilder - v0.9 ├── statics ├── html //静态文件开发 ├── js // 非require引入的js文件 ├── Lib // 第三方JS包 ├── ve_2_1 // ├── css // 样式目录 ├── fonts // bootstrap的图标字体 ├── img // 图片目录 ├── less // less源码 ├──

Qt计算器开发(二):信号槽实现数学表达式合法性检查

表达式的合法性 因为我们的计算器不是单步计算的,所以我们可以一次性输入一个长表达式.然而如果用户输入的长表达式不合法的话,那么就会引发灾难.所以有必要对于用户的输入做一个限制. 一些限制举例: 比如,在输入了左括号以后那么接下来,不能输入运算符.此时运算符要是不可用的状态.在输入了右括号以后不能直接输入数字,此时数字键应该是不可用状态.等等此类的. 在这里我使用了信号与槽的方式来实现.点击不同的按钮,会发展出不同的信号.在这之后的事,点击按钮本身无需考虑,这也是信号槽的良好的设计理念造成的效果,