愚蠢的解析方式

解析文本的明智方法是使用ANTLR,yacc或解析器组合器甚至Xtext之类的工具。 但是,要聪明就意味着要研究那些工具和框架。 如果您不想学习,该怎么办?如果您只想编写一个解析器而又不花很多时间在学习“ 正确的 ”工具上,该怎么办? 比您应该继续阅读,因为这正是本文的主题。

看起来我可以解析文本!!!

通常,我们不仅仅为了踢球而解析文本。 我们进行分析,因为我们需要从文本表示中提取一些数据。 在本文中,我将使用:https://github.com/mzaks/FlatBuffersSwiftCodeGen

作为如何在Swift中扮演自己的解析器角色的示例。 我的代码不是超级健壮和干净的代码,但是,嘿,它是分析文章的愚蠢方式 。 目的是激发您学习我的错误并做得更好。

另外,如果您有兴趣全面了解文本解析术语和技术,我建议您阅读以下内容:

解析指南:算法和术语

我们已经介绍了一些解析术语,同时列出了用于Java解析的主要工具和库…

tomassetti.me


无论如何,让我们从愚蠢的解析方式开始。 首先,我们需要了解文本是一维数字数组。 基于不同的编码,它可以是代表相同文本的不同数字。 因此,为了能够解析文本,我们需要就一种编码达成一致。 我定义了要解析的文本以UTF-8表示。

现在,正确的文本解析方法基于词法分析和词法分析两个阶段(有关更多详细信息,请参阅我之前提到的“分析指南”)。 我说搞砸了,我们处于愚蠢的解析状态 ,所以我们只有一个阶段,我们称之为吃eat

请看以下文件:

mzaks / FlatBuffersSwiftCodeGen

FlatBuffersSwift的代码生成器。 通过在以下位置创建帐户来为mzaks / FlatBuffersSwiftCodeGen开发做出贡献

github.com

在那里,我们有一个eat函数,它具有:

  • 静态字符串-我们要吃的字符串
  • 指向我们要开始进食的记忆的指针
  • 和长度,这告诉我们我们想吃多远

它返回一个可选的指针,这意味着如果我们能够食用该字符串,那么我们将返回停止进食的指针,如果我们无法食用,则将返回nil

但是,在我们开始吃弦本身之前,我们先吃了空白。 空格是字符,对人类没有任何语义含义。 如果我们看一下前128个ASCII字符的字符编码表,它等于UTF8:

ascii代码表新的优秀ascii家庭设计符号发生器代码表ascii代码tabelle.png

我们看到前0..<33是不可见的字符,可以视为空白。 因此, eatWhiteSpace函数的愚蠢实现:

 公共功能eatWhiteSpace( 
_ p:UnsafePointer ,
长度:整数
)-> UnsafePointer ? {
变量p1 = p
而p1.pointee <33 {
p1 = p1.advanced(by:1)
如果p.distance(to:p1)> length {
返回零
}
}
返回p1
}

它前进通过小于33数字,并将指针返回到大于或等于33的第一个数字。 它还可以检查我们是否吃得太多。 吃东西应该是安全的经历!

首先谈到安全性,如果您已经决定采用愚蠢的解析方式,则至少应该对解析器进行单元测试,即btw。 一个非常愉快的经历。 这是我们饮食逻辑的一些单元测试:

mzaks / FlatBuffersSwiftCodeGen

FlatBuffersSwift的代码生成器。 通过在以下位置创建帐户来为mzaks / FlatBuffersSwiftCodeGen开发做出贡献

github.com


正如我之前提到的,我们不仅仅为了踢球而解析文本。 我们想从文本表示中提取数据。 但是在进行数据提取之前,让我们定义将保存数据的数据结构。 在我的示例中,我正在解析FlatBuffers的IDL(接口定义语言)。 可以在以下位置找到FlatBuffers IDL的语法:

FlatBuffers:模式语言的语法

schema = include *(namespace_decl | type_decl | enum_decl | root_decl | file_extension_decl | file_identifier_decl |…

google.github.io

这是一个相当大的时间,而且也不总是最新的。 因此,让我们从中选择一个简单的声明,以了解如何以愚蠢的方式解析它。

让我们选择enum_decl —这是enum_decl的一个枚举类型的声明。 首先,我们要为枚举声明定义数据模型:

  struct Enum { 
命名:Ident
让类型:类型
让案例:[EnumCase]
让metaData:MetaData吗?
让评论:[评论]
}

在这里,我们看到一个Enum的名称是Ident它具有类型,案例数组,元数据和注释数组。

让我们从后面开始,让我们看看Comment的全部含义:

mzaks / FlatBuffersSwiftCodeGen

FlatBuffersSwift的代码生成器。 通过在以下位置创建帐户来为mzaks / FlatBuffersSwiftCodeGen开发做出贡献

github.com

注释是一个仅包含字符串值的简单结构。 它实现了ASTNode协议,该协议迫使我们将解析逻辑实现为静态方法。 此方法接收指针和长度,并返回一个可选的元组:

  • 评论实例
  • 指向我们停止吃字符的地方的指针

这是一个可选的元组,因为如果我们无法生成注释,它会返回nil 。 现在无法发表评论可能是合法的事情,也可能是例外。 如果此方法可以抛出,我想我会更好,但是我决定只在内部raise并崩溃。 取决于您自己,以使您的实施更好。

在函数内部,我们//吃了字符串(不要忘记内部的eat也吃掉了字符串前的空白),然后我们将在字符串中前进,直到达到10 13 0或字符串的末尾。 数字代表行的结尾或零终止字符串的结尾。 现在我们有两个指针,一个指向//之后的字符串,另一个指向停止使用时的指针。 两者之间的数字需要转换为String实例,并将成为Comment的值。

以下是单元测试,用于检查我们是否能够根据自己的喜好生成Comment实例。

mzaks / FlatBuffersSwiftCodeGen

FlatBuffersSwift的代码生成器。 通过在以下位置创建帐户来为mzaks / FlatBuffersSwiftCodeGen开发做出贡献

github.com


让我们快速浏览一下Enum引用的其他模型类:

  struct Ident { 
let值:字符串
}

非常简单的结构,只需将名称标题为字符串即可。 标识可以以小写/大写字母或_字符开头,然后是字母, _字符或数字。 解析逻辑可以在这里找到:

mzaks / FlatBuffersSwiftCodeGen

FlatBuffersSwift的代码生成器。 通过在以下位置创建帐户来为mzaks / FlatBuffersSwiftCodeGen开发做出贡献

github.com


 结构类型{ 
让标量:标量?
让向量:布尔
让参考:身份?
让字符串:布尔
}

Type是一个relativley复杂的构造,代表值的类型。 对于FlatBuffers中的Enum ,我们定义枚举对应的数字类型:

  • byteuint8
  • shortuint16
  • 等等…

标量类型表示为快速枚举。 对其他复杂类型(如表,结构枚举或联合)的引用通过其Ident表示。 我将字符串定义为特例,每种类型都可以是向量,除了标量,字符串或ref。 正如我之前提到的涉及更多示例的示例,但在解析方面并不那么重要。


  struct EnumCase { 
让ident:ident
让价值:ValueLiteral?
}

EnumCase具有名称/标识和可选的ValueLiteral 。 当您在FlatBuffers IDL中定义枚举案例时,可以选择添加= 4表达式,该表达式等于4 。 这就是ValueLiteral的全部意义。 它只是将字符串存储在=字符之后。


  struct MetaData { 
let值:[(Ident,ValueLiteral?)]
}

MetaData是一种将一些元信息添加到FlatBuffers IDL中的定义的方法。 它由一组键值对组成,其中value是可选的。 键用Ident表示,值用ValueLiteral


为了完成深入研究,我想简要解释将文本转换为Enum实例的逻辑,您可以在这里找到:

mzaks / FlatBuffersSwiftCodeGen

FlatBuffersSwift的代码生成器。 通过在以下位置创建帐户来为mzaks / FlatBuffersSwiftCodeGen开发做出贡献

github.com

与往常一样,我们收到一个指针和长度。

首先,我们将尝试构造尽可能多的Comment对象,并将它们全部放入数组中。 这意味着在实际枚举之前的注释语句将属于枚举定义。

比我们尝试吃enum字符串。 如果不成功,则返回nil 。 意味着曾经希望构造该枚举的人都知道,在提供给Enum构造函数的指针上没有Enum定义。 因此,它可以尝试使用相同的指针来构造其他对象。

万一我们可以吃enum字符串,现在我们需要构造一个Ident作为枚举名称,而不是eat : character并构造Type 。 比我们尝试构造MetaData 。 在这种情况下,如果得到nil很好,因为MetaData是可选的。

我们继续吃{性格。 并构造一个EnumCase对象数组。

比吃}字符,我们基本完成了。 如果所有这些美味字节进餐都成功了,我们可以构造一个Enum对象,并在停下的指针处将其返回。 这样,调用方将获取Enum实例和指向可以继续解析的位置的指针。


如果您想查看所有工作,请查看Enum的测试:

mzaks / FlatBuffersSwiftCodeGen

FlatBuffersSwift的代码生成器。 通过在以下位置创建帐户来为mzaks / FlatBuffersSwiftCodeGen开发做出贡献

github.com


这就是我要与您分享的全部。 我认为这种解析方式并不明智,也无法向所有人推荐。 但是它的学习曲线确实很浅,抽象很薄-您有一个字节流,并且正在吃掉这些字节以生成模型实例。 您的模型就是您的语法,也没有代码生成,没有monad和没有库可供学习。 这是关于您弄清楚什么是模型以及如何吃文本以产生文本。 对于复杂的解析任务,我仍然不建议使用它,但是对于“简单”的任务,它很有趣。


PS:该示例在Swift中,但我也在C#项目中使用了此技术😉