可配置语法分析器开发纪事(四) 构造一个真正能用的状态机(上)

本来说这一篇文章要把构造确定性状态机和look ahead讲完的,当我真正要写的时候发现东西太多,只好分成两篇了。上一篇文章说道一个基本的状态机是如何构造出来的,但是根据第一篇文章的说法,这一次设计的文法是为了直接构造出语法树服务的,所以必然在执行状态机的时候就要获得构造语法树的一切信息。如果自己开发过类似的东西就会知道,类似LALR这种东西,你可以很容易的把整个字符串分析完判断他是不是属于这个LALR状态机描述的这个集合,但是你却不能拿到语法分析所走的路径,也就是说你很难直接拿到那颗分析树。没有分析树肯定是做不出语法树的。因此我们得把一些信息插入到状态机里面,才能最终把分析树(并不一定真的要表达成树,像上一篇文章的“分析路径”(其实就是分析树的一种可能的表达形式)所确定的语法树构造出来。

就像《构造正则表达式引擎》一般给状态机添加信息的方法,就是把一些附加的数据加到状态与状态之间的跳转箭头里面去。为了形象的表达这个事情,我就拿第一篇文章的四则运算式子来举例。在这里我为了大家方便,重复一下这个文法的内容(除去了语树书声明):

token NAME = "[a-zA-Z_]/w*";
token NUMBER = "/d+(./d+)";
token ADD = "/+";
token SUB = "-";
token MUL = "/*";
token DIV = "//";
token LEFT = "/(";
token RIGHT = "/)";
token COMMA = ",";

rule NumberExpression Number
        = NUMBER : value;

rule FunctionExpression Call
        = NAME : functionName "(" [ Exp : arguments { "," Exp : arguments } ] ")";

rule Expression Factor
        = !Number | !Call;

rule Expression Term
        = !Factor;
        = Term : firstOperand "*" Factor : secondOperand as BinaryExpression with { binaryOperator = "Mul" };
        = Term : firstOperand "/" Factor : secondOperand as BinaryExpression with { binaryOperator = "Div" };

rule Expression Exp
        = !Term;
        = Exp : firstOperand "+" Term : secondOperand as BinaryExpression with { binaryOperator = "Add" };
        = Exp : firstOperand "-" Term : secondOperand as BinaryExpression with { binaryOperator = "Sub" };

那么我们把这个文发转成状态机之后,要给跳转加上什么呢?从直觉上来说,跳转的时候我们会有六种要干的事情:

1、Create:这个文法创建的语法树节点是某个类型的(区别于在这一刻给这个问法创建一个返回什么类型的语法树节点)

2、Set:给创建的语法树节点的某个成员变量设置一个指定的值

3、Assign:给创建的语法树节点的某个成员变量设置这一次跳转的符号产生的语法树节点(譬如说Exp = Exp: firstOperand “+” Term: secondOperand,走Term的时候,一个语法树节点就会被assign给那个叫做secondOperand的成员变量)

4、Using:使用这一次跳转的符号产生的语法树节点来做这次文法的返回值(譬如说Factor = !Number | !Caller这一条)

5、Shift:略

6、Reduce:略

在这里我们并没有标记整个文法从哪一个非终结符开始,因为在实际过程中,其实分析师可以从任何一个文法开始的。譬如说写IDE的时候,我们可能在某些情况下仅仅只需要分析一个表达式。所以考虑到每一个非终结符都有可能被用到,因此我们的“Token流开始”和“Token流结束”就会在每一个非终结符的状态机中都出现。因此在第一步创建Epsilon PDA(下推自动机)的时候,就可以先直接生成。在这里我们拿Exp做例子:

本栏目更多精彩内容:http://www.bianceng.cn/Programming/cplus/

双虚线代表的是Token流和Token流结束,这并不是我们现在关心的事情。在剩下的转换中,实现是具有输入的转换,而虚线则是没有输入的转换(一般称为epsilon边)。

在这里我们要明确一个概念——分析路径。分析路径代表的是token在“流”过状态机的时候,状态是如何跳转的。因此对于实际的分析过程,分析路径其实就是分析树的一种表达形式。而在状态机里面,分析路径则代表一条从开始到结尾的可能的路径。譬如说在这里,分析路径可以有三条:

$e –> e1 –> e2 –> e$

$e –> e3 –> e8 –> e7 –> e6 –> e5 –> e4 –> e$

$e –> e9 –> e14 –> e13 –> e12 –> e11 –> e10 –> e$

因此我们可以清楚,一条路径上是不能出现多个create的,否则你就不知道应该创建的是什么了。当然create和using都不能同时出现,using也不能有多个。而且由于create和set都是在描述这个非终结符(在这里是Exp)所创建的语法树节点的类型和属性,跟执行他们的时机无关,所以其实在同一条分析路径里面,create和set放在哪里都没关系。就譬如说在上面的第二条分析路径里面,create是在e6->e5里面标记出来的。就算他移动到了e3->e8,做的事情也一样。反正只要一条路径上标记了create,那么他在这条路径被确定之后,就一定会create所指定的具体类型的语法树节点。这是相当重要的,因为在后面的分析中,我们很可能需要移动create和set的具体位置。

跟上一篇文章说的一样,接下来的一步就是去除epsilon边了。结果如下:

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索路径
, 节点
, 状态
, 语法
, 分析
, 语法分析
, 树结构跳转
, 语法篇
, 一个
真正能用
语法树构造、编译原理构造语法树、lex yacc 构造语法树、构造函数语法缺少形参、可配置交换显卡没有了,以便于您获取更多的相关知识。

时间: 2024-12-06 21:03:23

可配置语法分析器开发纪事(四) 构造一个真正能用的状态机(上)的相关文章

可配置语法分析器开发纪事(二) 构造符号表

上一篇博客讲到了构造语法树的问题.有朋友在留言问我,为什么一定要让语法分析器产生语法树,而不是让用户自己决定要怎么办呢?在这里我先解答这个问题. 1.大部分情况下都是真的需要有语法树 2.如果要直接返回计算结果之类的事情的话,只需要写一个visitor运行一下语法树就好了,除去自动生成的代码以外(反正这不用人写,不计入代价),代码量基本上没什么区别 3.加入语法树可以让文法本身描述起来更简单,如果要让程序员把文法单独放在一边,然后自己写完整的语义函数来让他生成语法树的话,会让大部分情况(需要语法

可配置语法分析器开发纪事(一) 构造语法树

就像之前的博客文章所说的,(主要还是)因为GacUI的原因,我决定开发一个更好的可配置轻量级语法分析器来代替之前的落后的版本.在说这个文章之前,我还是想在此向大家推荐一本<编程语言实现模式>,这的确是一本好书,让我相见恨晚. 其实说到开发语法分析器,我从2007年就已经开始在思考类似的问题了.当时C++还处于用的不太熟练的时候,难免会做出一些傻逼的事情,不过总的来说当年的idea还是能用的.从那时候开始,我为了锻炼自己,一直在实现各种不同的语言.所以给自己开发一个可配置语法分析器也是在所难免的

可配置语法分析器开发纪事(六) 构造一个真正能用的状态机(下)

上一篇文章对大部分文法都构造出了一个使用的状态机了,这次主要来讲右递归的情况.右递归不像左递归那么麻烦,因为大部分右递归写成循环也不会过分的让语法树变得难以操作,不过仍然有少数情况是我们仍然希望保留递归的语法树形状,譬如C++的连等操作,因此这里就来讲一下这个问题. 右递归是怎么形成的呢?在这里我们先不想这个问题,我们来看一个普通的文法.在上一篇文章我们已经说过了,如果一条文法有一个非终结符引用了另一条文法,那么就要做一条shift和reduce来从这个状态机穿插到那个状态机上: 开发纪事(六)

可配置语法分析器开发纪事(五) 构造一个真正能用的状态机(中)

上一篇博客写到了如何给一个非终结符的文法规则构造出一个压缩过的下推状态机,那么今天说的就是如何把所有的文法都连接起来.其实主要的idea在(三)和他的勘误(三点五)里面已经说得差不多了.但是今天我们要处理的是带信息的transition,所以还有一些地方要注意. 所以在这里我们先把几条文法的最后的状态机都列出来(大图): 开发纪事(五) 构造一个真正能用的状态机(中)-语法树构造"> 本栏目更多精彩内容:http://www.bianceng.cn/Programming/cplus/

可配置语法分析器开发纪事(三) 生成下推自动机

上一篇博客讲到了构造符号表的事情.构造完符号表之后,就要进入语义分析的后一个阶段了:构造状态机.跟我以前写的如何实现正则表达式引擎的两篇文章讲的一样,自动机先从Epsilon Nondeterministic Automaton开始,然后一步一步构造成Deterministic Automaton.但是语法分析和正则表达式有很大不同,那么这个自动机是什么样子的呢? (对学术感兴趣的人可以去wiki一下"下推自动机") 下推自动机和有限自动机的区别是,下推自动机扩展成普通的自动机的时候,

可配置语法分析器开发纪事(三点五) 生成下推自动机的具体步骤

刚刚发了上一篇文章之后就发现状态机画错了.虽然LiveWriter有打开博客并修改文章的功能,不过为了让我留下一个教训,我还是决定发一篇勘误.这个教训就是,作分析的时候不要随便"跳步",该一步一步来就一步一步来.其实人呢,就是很容易忘掉以前的教训的了.第一个告诉我不能这么干的人其实是小学三年级的数学老师.当时我因为懒得写字,所以计算应用题的时候省了几步,被批评了. 故事就从状态机开始.文法我就不重复了,见上一篇文章.现在我们从状态机开始.第一个状态机是直接从文法变过来的: 开发纪事(三

AppleWatch开发入门四——Table视图的应用

AppleWatch开发入门四--Table视图的应用 一.Watch上的Table         WatchOS中的TableView和iOS中的TableView还是有很大的区别,在开发之前,首先我们应该明白WatchOS中的Table有哪些局限性和特点.下面几点是我总结WatchOS中Table的特殊之处: 1.Table只有行的概念,没有分区的概念,没有头尾视图的概念. 2.可以通过创建多个Table,来实现分区的效果. 3.因为Watch上是通过Gruop进行布局适应的,所以没有行高

《ELK Stack权威指南(第2版)》一1.3 配置语法

1.3 配置语法 Logstash社区通常习惯用Shipper.Broker和Indexer来描述数据流中不同进程各自的角色,如图1-2所示. 不过我见过很多运用场景里都没有用Logstash作为Shipper,或者说没有用Elasticsearch作为数据存储,也就是说也没有Indexer.所以,我们其实不需要这些概念.只需要学好怎么使用和配置Logstash进程,然后把它运用到你的日志管理架构中最合适它的位置就够了. 1.3.1 语法 Logstash设计了自己的DSL,有点像Puppet的

求教:Eclipse如何为新语言配置语法高亮?

问题描述 一个很小众的编程语言,想尝试用Eclipse作为IDE,有关键词若干,希望实现语法高亮和代码提示.请问该如何配置?小弟新手上路,请予指教,烦请讲详细一点.谢谢先 解决方案 解决方案二:eclipse语法提示(红色波浪线)如何开启关闭设置步骤:windows-->perferences-->editors-->texteditor-->annotation在里面选择errors,把其中的三个选项都勾上就可以啦代码提示按alt+/实现语法高亮是系统自带的,不用设置解决方案三: