简要介绍

构造抽象语法树是构造基于DFA的正则表达式引擎的第一步。目前在我实现的这个正则表达式的雏形中,正则表达式的运算符有3种,表示选择的|运算符,表示星号运算的*运算符,表示连接的运算符cat(在实际正则表达式中被省去)。

例如对于正则表达式a*b|c,在a*和b之间省略了连接运算符cat。其中|、cat运算符是双目运算符,*运算符是单目运算符。


下图来自编译原理一书:

对(a|b)*abb构造语法树,需要注意的是,此图中在原正则表达式的末尾添加了一个#号表示接受状态。在我自己的代码中没有添加最后一个#号,而是用eType_END 类型的词法单元表示正则表达式的末尾和DFA的接受状态。

构造正则表达式的抽象语法树的过程和构造算术表达式的抽象语法树的过程类似,都一样会存在运算符优先级和括号处理的问题。有差异的地方是正则表达式中存在单目运算符*,而普通的算术表达式中都是双目运算符。

构造正则表达式语法树的过程基于词法分析,这里的词法分析就比较简单了,因为一个字符就对应一个词法单元,需要注意的地方是:

1 在两个非运算符、右括号左括号对之间需要添加cat连接运算符。

2 在尾部需要加入一个eType_END 类型的词法单元表示正则表达式的末尾和DFA的接受状态。


语法树展示

根据正则表达式得到的语法树如下所示:

支持转义字符(右斜杠\)的模式串:

在我写的词法分析中支持通配符点号(.),支持转义字符(右斜杠\加特殊字符)。另外这个语法分析树的打印方式大家也可以从我的代码中找到实现方法^_^。

在以上各个语法树中,打印输出时屏蔽了尾部的eType_END节点。

构建语法树主要需要的对象和数据结构

构建语法树主要需要的对象和数据结构如下:

整个语法树的构建过程中需要一个词法分析器Lex,词法分析器从左到右逐个字符地扫描正则表达式,根据遇到的字符返回正确的Token给语法树构建器,对于不合法的正则表达式给出报错信息(例如转义字符\后面跟的不是特殊字符)。

语法树构建器拿到词法分析器返回的词法Token后,开始进行自下而上的建树过程,在不考虑括号的情况下,正确的正则表达式的第一个词法Token应该是一个非运算符,它被包装为语法树节点结构然后被压入语法树构建器的语法树节点栈中。

之后第二个词法Token可能是一个运算符也可能是一个非运算符,如果是非运算符,则需要添加一个表示连接的cat运算符到运算符栈中,并将得到的操作数Token包装为语法树节点压入语法树节点栈中。每次向运算符栈中压入新的运算符new之前,都需要查看当前运算符栈顶的运算符old,和new谁的优先级更高,如果old的优先级较高,则先处理old运算符(会用掉语法树节点栈中的节点,运算得到的节点再压回语法树节点栈),old被处理完后,old出栈,接下来的栈顶元素成为old,再次和new进行比较,重复这个过程,直到old的运算符优先级低于new,再将new运算符压栈。如果遇到了左括号,则先将左括号压入运算符栈中,在遇到右括号时需要将运算符栈中的节点从栈顶开始处理,直到处理到最靠近栈顶的左括号为止。

当正则表达式处理完后,最后再处理运算符栈中剩余的运算符。

正确的结果应该是运算符栈为空,语法树节点栈中有一个节点,这个节点就是整个语法树的根节点。

结合实例介绍构建语法树过程

接下来举一个实例,对正则表达式(a|b)*a|bcd 构造语法树。过程如下:
1 词法分析器从左向右扫描表达式,先得到左括号,将左括号包装成节点,压入运算符栈中;
2 词法分析器获得的下一个节点为字符a,压入语法树节点栈中;
3 词法分析器继续获取词法Token,得到运算符|,压入运算符栈中;
4 下一个字符是b,将b包装成节点压入语法树节点栈中;
5 继续获取字符,得到右括号,此时语法树构建器开始根据语法树节点栈和运算符栈进行运算合并已有节点,直到在语法树节点栈中遇到左括号为止。开始处理时语法树节点栈和运算符栈中内容如下:
运算符栈:(|
语法树节点栈:ab
运算符栈的栈顶运算符出栈,得到|运算符,这是一个双目运算符,所以从语法树节点栈中出栈2个节点b和a,|运算符和节点a节点b,得到新的节点(记为M),M再压入语法树节点栈,此时在运算符栈顶已经是左括号,将其出栈,节点合并结束。
两个栈的内容如下:
运算符栈:空
语法树节点栈:M
6 接下来是*号运算符,因为*号是优先级最高的运算符,所以可以直接处理,无需进行运算符优先级的比较,*号会消耗语法树节点栈中一个节点(也就是M),*号运算符和M节点运算得到新的节点N,重新压入节点栈中。

7 接下来词法分析器得到字符a,但是在节点N和字符a之间需要插入一个连接cat运算符,我们把cat运算符用‘+’来表示,‘+’压入运算符栈,a压入节点栈。
8 词法分析器得到的下一个Token是运算符|,在向运算符栈中压入‘|’运算符之前,我们需要检查运算符栈的栈顶运算符和当前想要压栈的运算符的优先级,如果栈顶运算符的优先级大于等于将要压栈的运算符,则需要先处理栈顶的运算符(这里是一个循环的过程,也就是说处理完栈顶的运算符之后,还要继续比较栈顶的运算符和将要压栈的运算符之间的优先级,以决定接下来该执行什么步骤)。在这里栈顶的运算符‘+’的优先级比运算符‘|’的优先级高,所以先进行栈顶运算符的运算,‘+’连接运算符将节点N和a组成为新的节点(记为P)并重新压入节点栈中。然后运算符栈为空,此时把前面所说的“将要压入运算符栈的‘|’运算符”压入运算符栈。
9 下一个字符是b,此时不需要插入连接运算符,只需要将字符b包装为节点压入节点栈。
10 下一个字符是c,此时同样需要插入一个连接运算符,在向运算符栈中压入‘+’运算符之前,我们需要检查运算符栈的栈顶运算符和当前想要压栈的运算符的优先级。在这里‘+’的优先级高于栈顶的‘|’,所以直接将运算符‘+’压入运算符栈中,并将字符c包装为节点压入节点栈。
11 下一个字符是d,此时同样需要插入一个连接运算符,在向运算符栈中压入‘+’运算符之前,我们需要检查运算符栈的栈顶运算符和当前想要压栈的运算符的优先级。在这里两个运算符相同,所以先处理运算符栈栈顶的运算符,‘+’运算符和节点栈中的b,c字符组成新的节点Q压入节点栈,然后运算符栈顶的运算符为‘|’,‘+’的优先级高于‘|’,所以不在处理运算符栈的栈顶运算符。将‘+’压入运算符栈,将字符d包装为节点压入节点栈。
12 此时词法分析器报告已经到达正则表达式的结尾,所以开始处理运算符栈中剩余的运算符,从栈顶开始依次处理,首先遇到的是‘+’连接符,从节点栈中取出节点Q和字符d生成新的节点R压回节点栈。
13 继续处理运算符栈,栈顶运算符为‘|’,从节点栈中取出节点P和节点R生成新的节点S压回节点栈。
14 此时运算符栈清空,节点栈中只有一个节点S,S就是最终生成的语法树的根节点。(至此大功告成、功德圆满^_^呼呼)
可以看出,我们遇到一个非运算符时,需要检查是否需要添加cat连接符,在向运算符栈中添加一个新的运算符时,需要比较栈顶运算符和将要添加的运算符之间的优先级,以决定是否先进行栈顶运算符的运算。

我们将上面每一个步骤中的运算符栈和节点栈以图形的方式直观地展现出来:

正则表达式引擎的构建——基于编译原理DFA龙书第三章——2 构造抽象语法树的更多相关文章

  1. Html5 canvas实现粒子时钟的示例代码

    这篇文章主要介绍了Html5 canvas实现粒子时钟的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. HTML5数字输入仅接受整数的实现代码

    这篇文章主要介绍了HTML5数字输入仅接受整数的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. ios – 使用大写符号在字符串swift中获取URL的正则表达式

    我尝试在文本中获取URL.所以,在此之前,我使用了这样一个表达式:但是当用户输入带有大写符号的URL时(例如Http://Google.com,它与它不匹配)我遇到了问题.我试过了:但什么都没发生.解决方法您可以使用正则表达式中的i内联标志关闭区分大小写,有关可用正则表达式功能的详细信息,请参阅FoundationFrameworkReference.(?ismwx-ismwx)Flagsetti

  4. iOS推送通知优先级

    我已设置推送通知并正常工作,但是,有时我会遇到终端设备上的延迟交付.有没有办法我可以将推送的“优先级”键设置为10,以便立即发送推送?

  5. 在Xcode4中,你可以更改用于显示隐形字符的字符吗?

    我更喜欢VisualStudio显示隐形的方式……

  6. ios – 何时使用Semaphore而不是Dispatch Group?

    我会假设我知道如何使用DispatchGroup,为了解问题,我尝试过:结果–预期–是:为了使用信号量,我实现了:并在viewDidLoad方法中调用它.结果是:从概念上讲,dispachGroup和Semaphore都有同样的目的.老实说,我不熟悉:什么时候使用信号量,尤其是在与dispachGroup合作时–可能–处理问题.我错过了什么部分?

  7. 如何使用Xcode的自动布局调整视图大小

    解决方法在写这个问题时,我意识到了诀窍是什么:在NSPopUpButton的大小检查器中,我不得不降低内容拥抱优先级.显然,这可以控制视图“拥抱”其内容的紧密程度.因此,当拥抱优先级高于调整大小优先级时,视图将不希望增加其大小,因为这意味着其边界与其内容之间具有更多的空白空间.然后在我的特殊情况下,我也可以将两个NSPopUpButtons固定为具有相同的宽度和vo:popUpButtons将完美地调整大小,同时保持间距不变.

  8. ios – 默认的自动布局内容拥抱和内容压缩阻抗优先级值是什么?

    我正在尝试调试自动布局问题,并且知道内容拥抱和内容压缩阻力优先级的默认值将有所帮助.这些是什么?它们是否特定于特定组件?我可以使用常量来引用它们吗?

  9. ios – 应用程序商店描述特殊字符

    是不是可以在AppStore描述中使用像星星这样的特殊字符了?我得到这个错误:描述不得包含标记语言.说明不得包含以下字符:★提前致谢:)解决方法仍然允许一些unicode字符.以下字符已经过测试并仍然有效:◆√至于现在他们工作正常,但苹果可以随时再次改变条件.

  10. ios – 将数组中的字符转换为整数

    即使我搜索了文档,我似乎无法弄清楚如何做到这一点.我试图弄清楚如何将数组中索引处的字符转换为整数.例如,假设我有一个名为“容器”的字符数组,我无法弄清楚该怎么做:谢谢您的帮助!解决方法Swift并不容易在原始和类型表示之间进行转换.这是一个在此期间应该有所帮助的扩展:这使您可以非常接近您想要的:对于遇到此问题的任何工程师,请参阅rdar://17494834

随机推荐

  1. 法国电话号码的正则表达式

    我正在尝试实施一个正则表达式,允许我检查一个号码是否是一个有效的法国电话号码.一定是这样的:要么:这是我实施的但是错了……

  2. 正则表达式 – perl分裂奇怪的行为

    PSperl是5.18.0问题是量词*允许零空间,你必须使用,这意味着1或更多.请注意,F和O之间的空间正好为零.

  3. 正则表达式 – 正则表达式大于和小于

    我想匹配以下任何一个字符:或=或=.这个似乎不起作用:[/]试试这个:它匹配可选地后跟=,或者只是=自身.

  4. 如何使用正则表达式用空格替换字符之间的短划线

    我想用正则表达式替换出现在带空格的字母之间的短划线.例如,用abcd替换ab-cd以下匹配字符–字符序列,但也替换字符[即ab-cd导致d,而不是abcd,因为我希望]我如何适应以上只能取代–部分?

  5. 正则表达式 – /bb | [^ b] {2} /它是如何工作的?

    有人可以解释一下吗?我在t-shirt上看到了这个:它似乎在说:“成为或不成为”怎么样?我好像没找到’e’?

  6. 正则表达式 – 在Scala中验证电子邮件一行

    在我的代码中添加简单的电子邮件验证,我创建了以下函数:这将传递像bob@testmymail.com这样的电子邮件和bobtestmymail.com之类的失败邮件,但是带有空格字符的邮件会漏掉,就像bob@testmymail也会返回true.我可能在这里很傻……当我测试你的正则表达式并且它正在捕捉简单的电子邮件时,我检查了你的代码并看到你正在使用findFirstIn.我相信这是你的问题.findFirstIn将跳转所有空格,直到它匹配字符串中任何位置的某个序列.我相信在你的情况下,最好使用unapp

  7. 正则表达式对小字符串的暴力

    在测试小字符串时,使用正则表达式会带来性能上的好处,还是会强制它们更快?不会通过检查给定字符串的字符是否在指定范围内比使用正则表达式更快来强制它们吗?

  8. 正则表达式 – 为什么`stoutest`不是有效的正则表达式?

    isthedelimiter,thenthematch-only-onceruleof?PATTERN?

  9. 正则表达式 – 替换..与.在R

    我怎样才能替换..我尝试过类似的东西:但它并不像我希望的那样有效.尝试添加fixed=T.

  10. 正则表达式 – 如何在字符串中的特定位置添加字符?

    我正在使用记事本,并希望使用正则表达式替换在字符串中的特定位置插入一个字符.例如,在每行的第6位插入一个逗号是什么意思?如果要在第六个字符后添加字符,请使用搜索和更换从技术上讲,这将用MatchGroup1替换每行的前6个字符,后跟逗号.

返回
顶部