介绍
更新:在大多数情况下生成更紧凑的代码。
更新 2:修复了 blockend 生成的错误。
更新 3:更改名称XXXX_capture_t
为XXXX_match_t
.
我已经为它们编写了很多正则表达式引擎和代码生成器,这在这一点上几乎是本能的。我这样做是为了好玩。这一次,我这样做是因为它很有用。
我需要从基于 REST 的 Web 服务获得的 JSON 结果中窃取一个字段。我需要通过一个小型物联网设备来完成,当我只需要一个重要但很小的数据时,我不想在整个 JSON 库上浪费宝贵的程序空间。
我真正需要的是正则表达式,但我也不想在正则表达式引擎上浪费空间。
最后,这意味着代码生成器将在给定输入正则表达式的情况下生成匹配器代码。使用这样的动物,我可以获得正则表达式功能,但足以完成这项工作,最终结果中不包含任何非绝对必要的内容。我们没有将正则表达式提供给正则表达式引擎以在运行时进行处理和解释,而是将代码煮熟以匹配硬编码的正则表达式,并且实际上不必在运行时进行解释。
了解这个烂摊子
rxcg 是一个表格驱动的 DFA/非回溯代码生成器。这会生成运行O(n)
复杂的代码,其中n
输入字符串的长度是多少。总的来说,这意味着当它在流中从左到右移动时,它只访问一次。
这也意味着它使用数组/数据表来驱动匹配而不是硬编码goto
。这以一些速度(在某些情况下)换取大小,因为基于阵列的技术呈现更小的闪存占用空间,但运行速度可能不如goto
基于编排的绝对快。
这是一个简洁的设置,以保持最终代码的简单和高效。它不支持子捕获组。您可以分组,但这只会改变运算符的优先级。它不会子捕获到一个组中。它不支持诸如^
or之类的锚点$
。它确实支持 Unicode,一直到 UTF-32,以及常见的字符类,包括 Unicode 的 likeIsLetter
和IsDigit
.
为了灵活性,它使用回调来检索输入流中的下一个 UTF-32 代码点。您负责实现回调函数来检索您的数据,无论是来自文件、字符串还是网络。这也意味着将您的流转换为 UTF-32 代码点是您的工作,无论如何都可以。幸运的是,生成器提供了这样的代码,我在示例中包含了代码,该代码将从 UTF-8 或 ASCII 文件或字符串中执行此操作。
输入文件为 Reggie/Rolex 文件格式。
它是一种基于行的格式,其中每一行的形成类似于以下行:
每个属性都是一个标识符,后跟一个可选=
的 JSON 样式字符串、布尔值、整数或其他值。如果未指定该值,则上面的true
含义ignoreCase
设置为true
。请注意文字表达式是如何用双引号括起来的,而正则表达式是如何用单引号括起来的!
有几个可用的属性,如下所示:
ignoreCase
- 表示表达式应该被解释为不区分大小写blockEnd
- 表示指定符号的多字符结束条件。遇到时,匹配器将继续读取,直到blockEnd
找到该值,并使用它。这对于具有多个字符结尾条件的表达式很有用,例如 C 块注释、HTML/SGML 注释和 XML CDATA 部分。如果是字符串,请记住使用双引号,如果blockEnd
是正则表达式,请记住使用单引号。
正则表达式语言支持如上基本的非回溯构造和常用字符类,以及///[:IsLetter:]
等。映射到他们的 .NET 对应物。确保转义单引号,因为它们在文件中用于分隔表达式。[:IsLetterOrDigit:]
[:IsWhiteSpace:]
该工具是一个基于命令行的应用程序,它接受一个输入文件和一个可选/size <capture_size>
参数,从中生成两个文件:Name.h和Name.c,其中Name
是输入文件的名称。对于MyMatch.rl,该工具将生成MyMatch.h和MyMatch.c。
该/size
参数指示捕获缓冲区的大小。匹配时,将返回匹配的文本代码点,但它们不能大于指示的大小,否则捕获将被剪裁。这可以是一个数值,也可以是一个#define
'd 宏的名称,如CAPTURE_SIZE
.
代码生成器本身是用 C# 编写的,并使用我的 FastFA 正则表达式引擎,我的另一个项目Reggie也使用了该引擎。事实上,我无耻地抄袭了大量用于该项目的代码,因此请访问该链接以了解详细信息。我本可以编写一个 Reggie 插件来做到这一点,但老实说,Reggie 在这方面有点笨拙,我想减少一些,特别是对于物联网,在物联网中做像词法分析这样的事情会适得其反。
编码这个混乱
使用生成的代码首先涉及进行回调。如果您只关心 7 位 ASCII 并且您通过将每个 ASCII 字符转换为 来作弊,这很容易int32_t
,但是对于 Unicode 支持,您需要做一些实际的工作。
示例项目包括两个基本的回调实现——一个用于字符串,一个用于文件。在每种情况下,我们都需要跟踪输入以及我们在其中的位置,以便每次调用时都可以返回下一个字符,或者在到达结尾时返回-1。这些实现也作为生成代码的一部分提供,但已#if 0
被淘汰。
这是字符串实现:
C++
收缩▲
大多数情况下,这个烂摊子是在处理 Unicode。除此之外,我们所做的只是推进state
type的指针string_cb_state_t
,并在完成时返回-1
。
文件一是相似的,虽然在某些方面更简单,因为state
它只是一个FILE
句柄,它保持和前进自己的光标。请注意,在 Arduino 上,您将使用一个File
对象并使用它,但示例是标准 C。
C++
收缩▲
在第一个字符串之后,这个应该很明显。
请注意,所有这些例程都假定流是 7 位 ASCII 或有效的 UTF-8。没有进行检查,因此可能会发生非常糟糕的事情(TM),尤其是在向字符串回调提供其他无效流的情况下。
现在让我们探索如何使用它。
对于输入文件中的每个命名正则表达式,match_Name()
都会生成一个被调用的函数,其中Name
是输入文件中左侧的名称=
。
考虑文件Example.rl中的以下行:
<span style="color:#000000"><span style="background-color:#fbedbb">Identifier = '[_[:IsLetter:]][_[:IsLetterOrDigit:]]*'</span></span>
这将生成一个名为的函数match_Identifier()
,然后可以使用该函数查找与该 Unicode 表达式匹配的字符串。
C++
这里的想法是一遍又一遍地调用match函数,每次都得到一个新的Example_match_t
(从输入文件的名称派生),直到一个返回一个length
为0的a,表示到达了输入的末尾。
请注意,捕获缓冲区本身由int32_t
值组成,这char
与人们可能期望的不同。此匹配器捕获 UTF-32 代码点,而不是字符。这说明了接近末尾的代码循环并打印capture
. 另请注意,捕获缓冲区不是以空值结尾的。
注意ARDUINO
:如果您想将其与 Arduino 框架一起使用,您应该在编译器的命令行上将其定义为常量。或者,将生成的标头修改为 unconditionally #include <Arduino.h>
,或者确保它包含在Arduino.h之后。