`

如何手写语法分析器

阅读更多

如何手写语法分析器

http://www.cppblog.com/vczh/archive/2008/06/15/53373.aspx

 

 

 

如何手写语法分析器

陈梓瀚

华南理工大学软件05级本科

vczh@163.com

http://www.cppblog.com/vczh/

 

在写可配置的语法分析器之前,我觉得还是先说说如何手写语法分析器好。因为对于大部分人来说,开发一个可配置的语法分析器并没有什么作用,反而针对某种特定的语法开发特定的语法分析器是特别有必要的。典型的有表达式计算器、某种格式化的文件(HTMLXML等)或者是其他的复杂而且符合树型结构的字符串。根据目前论坛的反应来看,有一些朋友们对如何开发一套自己的脚本引擎比较感兴趣。等基础的文章都写完以后我会考虑撰写一个系列的文章介绍如何开发自己的脚本引擎。

 

这篇文章会附带一些必要的代码以便帮助读者们理解。为了方便,代码使用DevC++开发。

 

一、定义语法

 

在开发语法分析器之前,有必要讲一下语法的定义。这篇文章给出的是一个比较机械化的方法,掌握了这个方法之后手写语法分析器会变成一件没什么挑战性但是很麻烦的工作。因为设计起来太简单,但是代码很多。有些人为了连麻烦的工作也不要会去开发可配置的语法分析器。不过这里先不管这么多,首先给出一个比较使用的语法。

 

我们考虑一个经常从书上或者经常见到的例子:LISP语言。这门语言的表达式相当奇怪,操作符基本上当成函数处理,而且强迫用户给出优先级。因为LISP的操作符是没有优先级的。譬如(1+2)*(3+4)LISP会被写成(* (+ 1 2) (+ 3 4) )

 

让我们看一下这种语法的结构。括号内可以写很多个值,第一个被约定为是函数,之后的是参数。参数的个数是不确定的。一个函数调用的结果仍然是值,这就允许表达式进行嵌套。一个复杂一点的例子:2sinxcosxLISP内被写成(* 2 (sin x) (cos x))。我们注意到最外层的乘法有3个参数,因此代表连乘。其次,(1)1的结果是一样的。

 

于是我们如何规定这种表达式的语法呢?我们可以给出若干条。为了方便我们去掉LISP语言允许的curry属性,也就是说(+ 1 2)等价于( ( (+) 1) 2)

1、  数字可以为值

2、  一个值可以构成参数列表,参数列表后紧接着一个值仍然是参数列表

3、  表达式可以为值,或者是括号内包含操作符或函数名外加可选的参数列表

 

于是我们可以使用一种形式化的方法来写出这个表达式。首先我们可以为表达式命名,譬如表达式我们使用expression或者exp等。其次name=rule代表复杂的rule将会使用一个名字name来代替。最后,a b代表a之后紧接着b

 

这样的话,我们就可以使用一种比较简洁的方法来表示上面提到的简化后的LISP表达式语法:

Operator=”+”

Operator=”-“

Operator=”*”

Operator=”/”

Expression=<数字>

Expression= “(” Operator Expression Expression “)”

Expression=“(”Expression “)”

 

这样写的话觉得很烦,我们可以追加多两种定义语法的语法:

1A | B代表A或者B都可以,并且如果字符串被A匹配成功的话将不会考虑B

2[ A ]代表A是可以存在或者不存在的,但是尽量使其存在

 

于是我们可以把上面的语法改写成如下形式:

1)       Operator=”+” | “-” | “*” | “/”

2)       Expression=<数字> | “(“ Expression “)” | “(“ Operator Expression Expression “)”

 

第一条语法规则说的是Operator,也就是操作符,可以是加号、减号、乘号或者除号。第二条语法规则说的是一条表达式可以只由数字构成、一个加了括号的表达式或者一个加上了括号的操作符和两个参数。

 

二、根据语法写代码

 

到了这里,我们可以考虑一下如何通过语法组织我们的代码了。上面的语法并没有包含如何去除空格的语法,这个事情语法表达只会徒增烦恼,因此我们自己解决可能会更好一点。在语法分析的时候,我们都是一点一点读入字符串的,因此我们的函数的的形式大概如下:

·读入字符串,返回结果或者错误信息

·如果没有错误的话,则将字符指针偏移到尚未读取的位置

·如果有错误的话,保持字符指针不变

 

好了,现在我们来看第一条语法。我们需要一个方法来检查输入是否由我们需要的字符串开头,当然这里仍然需要考虑空格的问题。我们可以写一个函数,输入字符指针和一个字符串。这个函数先过滤掉空格然后检查剩下的地方是不是由指定的字符串开始的。正确的话返回true并将输入的字符指针往后诺到尚未读取的地方:

/*

检查Stream的前缀是否Text

    是返回true并将Stream偏移strlen(Text)个字符

    否则返回false

此函数会过滤Stream开头的空格

*/

bool Is(char*& Stream , const char* Text)

{

    size_t len=strlen(Text);

    /*保存参数*/

    char* Read=Stream;

    /*过滤空格*/

    while(*Read==' ')Read++;

    if(strncmp(Read,Text,len)==0)

    {

        Stream=Read+len;

        return true;

    }

    else

    {

        return false;

    }          

}

代码很短我就不解释了。当然,有了这个函数之后我们可以很轻松地写出一个判断字符串是否由操作符开头的函数:

/*

检查Stream是否操作符

    是的话返回操作符的字符并将Stream偏移至操作符之后

    否则返回

*/

char IsOperator(char*& Stream)

{

    /*A||B操作符的特性是如果A==true则不对B求值

    所以表达式会在一个检查成功后停下来

    */

    if(Is(Stream,"+") || Is(Stream,"-") || Is(Stream,"*") || Is(Stream,"/"))

    {

        /*此时操作符已经被越过,所以返回Read[-1]*/

        return Stream[-1];

    }

    else

    {

        return 0;

    }       

}

第一条语法到了这里就结束了。然后我们考虑第二条语法。这条语法判断一个字符串是否表达式,首先判断一个字符串是否数字,失败的话再检查是否由括号打头。因此我们需要一个判断字符串是否由数字开头。这里我们先引进一个struct

/*表达式分析结果*/

struct Expression

{

    int Result;     /*表达式结果*/

    char* Error;    /*错误信息,没有错误则为*/

    char* Start;    /*错误的位置*/

}; 

这个Expression结构用于表达字符串的分析结果。Result是表达式的计算结果,Error如果非0则保存了错误信息,此时Start保存了错误信息在字符串的什么地方被引发。有了这个Expression之后我们就可以写出如下判断字符串是否由数字开头的函数了。为了方便,这个函数只判断非负整数。

/*

检查Stream是否数字,是的话则将Stream偏移到数字之后

*/

Expression GetNumber(char*& Stream)

{

    /*font-size: 9pt; color:

分享到:
评论

相关推荐

    使用JavaScript编写的语法分析器

    这是一个使用JavaScript编写的基于LL(1)文法的语法分析器

    cminus语法分析器源代码完整版

    编译原理-递归下降语法分析器源代码,手写有注释,能打印出语法树,进行部分错误处理,dev c编写,所有内容皆由一个cpp文件实现

    如何手写语法编译器

    通过简单的例子一步步介绍如何编写一个语法分析器,而且大多通俗易懂,很适合于初学者使用

    parseTINY.rar

    使用python语言写的tiny语言的语法分析器,即通过对tiny语言文法的消除左递归和提前左公因式之后得到的文法进行first函数计算,之后根据其进行源码编写,纯自己手写。未加入TINY的读写文法。压缩包包含,python源...

    pck:解析器构造工具包(“ Puck”):C#中的解析器生成器和语法转换器

    PCK:解析器构建套件 pckedit使用ICSharpCode.TextEditor的语法突出显示技术,并且是的修改版本解析器构造工具包... pck随附的各种工具可用于解析器和词法分析器/令牌生成器。 它可以基于LL(1)算法生成基于FA的词法分

    javascript设计模式之解释器模式详解

    抽象语法树可用一个表驱动的语法分析程序来完成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接client提供。 解析器:指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序。 解释...

    libmarpa-bindings:基于libffi和Kollos的libmarpa绑定

    正在编写一个接口(语法,识别器/词法分析器,评估器)。 Python 可以通过cffi从Python调用libmarpa C函数(并进行错误检查)。 Sample ,也是json.c的端口,带有基于Python正则表达式的手写词法分析器。 C# ...

    容器类编译器初级实现和图像界面规划

    1.创建一个词法分析器来对源输入进行词法分析,它将源代码转换为我们的编译器可以轻松理解的一堆标记。然后,我们通过解析器传递令牌以生成抽象语法树。AST 以逻辑方式描述 C 程序,使我们的编译器更容易理解。以...

    c编译器ucc 编译原理

    一个由清华大学学生完成的C语言编译器...词法分析,语法分析和目标代码生成器都是手写的(其中的代码 生成器本想用burg这样的工具自动生成,但这样可能会给代码的理解带来难度, 最后手写了一个简单的代码生成器)”

    论文研究-密码协议安全性证明系统解析器的设计与实现.pdf

    可证明安全性是密码协议安全性评估的重要依据,但手写安全性证明容易出错且正确性难以判定,利用计算机辅助构造游戏序列进而实现自动化证明是当前一种可行的方法。为此提出一种基于进程演算的密码协议形式化描述模型...

    ParZu:苏黎世的德语依存解析器

    它的体系结构是混合的,由手写语法和统计模块组成,该模块返回最可能的句子分析。 与英语解析器的主要区别是德语语法和统计模块。 在结构上,它的不同之处在于它支持形态信息的使用,并且不使用分块器。 ParZu还...

    parser:一个简单的数学表达式解析器

    该存储库包含一个语法分析器,用于以92行Python代码编写的形式为2*(3+4)简单数学表达式。除了可以在Python标准库中找到的依赖项之外,不使用任何依赖项。它仅出于教育目的而存在。 如何使用它 python3 compute.py '2...

    PyPika是一个python SQL查询构建器,它使用反映结果查询的语法来公开SQL语言的全部功能。 PyPika擅长各种SQL查询,但对数据分析特别有用。-Python开发

    PyPika-Python查询生成器摘要什么是PyPika?...PyPika在设计时就考虑了数据分析,它利用了构建器设计模式来构建查询,以避免混乱的字符串格式和连接。 还可以轻松扩展它以充分利用SQL数据库销售的特定功能

    如何实现一个webpack模块解析器

    最近在学习 webpack源码,由于源码比较复杂,就先梳理了一下整体流程,就参考官网的例子,手写一个最基本的 webpack 模块解析器。 代码很少,github地址:手写webpack模块解析器 整体流程分析 1、读取入口文件。 2...

    rsql-parser:一个用于将rsql表达式解析为谓词的javascript库

    rsql解析器 谓词表达语言RSQL的javascript解析器和... 我们将此语法编译为词法分析器,解析器和基本访问者。 然后,我们用手写代码扩展基本访问者,该代码将已解析的AST构建为一个参数的函数,该参数返回一个布尔值,指

    Matlab代码verilog-work:工作

    Matlab代码verilog ##由Prince ...C#语法分析器的### CompilePrinciple / bison实现 ###乳胶/ 乳胶测试 2016年4月28日星期四18:40:01 CST(Prince Wed) 2016年5月25日16:53:03 CST 2016 by Prince

    霸屏天下源码java-parboiled2:用于Scala2.10+的基于宏的PEG解析器生成器

    构建解析器的“传统”方式相比具有一些优势(例如不需要单独的词法分析器/扫描器阶段)。 parboiled2是 的继承者,它提供了类似的功能(对于 Scala 和 Java),但实际上并不生成解析器。 而是根据输入解释规则树结构...

    Monkey-CSharp:《编写Go口译员》一书中的Monkey编程语言的惯用C#端口

    词法分析器,解析器和评估器的完整实现包括1500行代码以及925行测试。 对于这样一个功能强大的解释器来说,并不是很多,完全没有第三方库就可以实现。 例子 请参阅主页上的“猴子编程语言”部分,并查看此存储库中...

    cddl:简洁的数据定义语言(RFC 8610)实现以及Rust中的JSON和CBOR验证器

    此板条箱包括用于CDDL的手写解析器和词法分析器,其发展受到了Thorsten Ball的书概述的技术的极大启发。 建立AST的目的是紧密匹配规范中由ABNF语法定义的规则。 所有CDDL必须根据规范使用UTF-8进行编码。 此板条箱...

    mu:一种小型的、严格的函数式语言

    词法分析器和解析器已经完成。 Parser 是一个手写的递归下降解析器,具有奇怪的前瞻机制。 在 AST 中完全解析标识符几乎完成 - 尚未实现预编译库支持。 将函数转换为闭包已完成但尚未测试。 生成 C 输出尚未完成...

Global site tag (gtag.js) - Google Analytics