揭秘 JSON.Parse()
揭秘 JSON.Parse()
软件工程的一个令人着迷的方面是将文本文件中的一系列字符转换为一组指令,这些指令可以在 CPU 上执行以产生有意义的结果。这对于阅读代码的人来说是非常明显的(好吧,也许不是那么明显, 取决于语言 ) 期望的结果是什么。
那是因为人们的思维已经发展到能够有效地将字符流构造成有意义的组,这些组以有意义的顺序出现。这使人们能够进行交流和响应,并且通常被认为是理所当然的活动。
这完全是针对计算机的另一个故事。他们没有自动构建可用于指导其操作的内容的心理过程;字符流只是字符流。为了让它成为其他任何东西,必须教计算机如何理解它。
软件工程师每天使用的多种编程语言、文件格式和反序列化数据的方法证明了这不是一项艰巨的任务(至少不再是一项艰巨的任务,这要归功于开创这一领域的前几代计算机科学家和工程师)解决问题。任何工程师都可以参与在此列表中添加一种编程语言或一种文件格式或一种序列化技术。唯一真正的先决条件是对文本解析有一定的了解和经验。
解析
解析本质上是将一系列字符(或更一般地,位)转换为结构化数据。出于本文的目的,它将用于处理字符,尽管文件格式之类的东西处理的是位。
简单地说,将字符串转换为结构化和有意义的内容的过程有两个步骤:
- 词法分析:将单个字符流转换为标记流( IE , 分组字符)
- 句法分析:将标记流转换为 抽象语法树 (一种数据结构,表达文本中标记之间的关系,也可以记录关于它们的重要元数据)
通常,解析只是更大努力的第一步。例如,将 Golang 文件编译为可执行文件需要解析作为第一步,但还有更多可以进一步将 AST 转换为 CPU 指令。
不管最终结果是什么,任何成功的解析阶段都需要一个 形式语法 被指定。这是一个人类可读的规范,它解释了相关标记是什么以及它们应该以什么顺序出现。
在最简单的形式中,语法可以描述为重写字符串的一组规则。某些字符串可以用其他字符串重写,而某些字符串不能再重写——它们分别称为非终结符号和终结符号。在词汇阶段识别的标记包括该语法的终结符号集,该集合进一步包括其非终结符号集(非终结符号与终结符号之间存在一对多的关系)。
计算器可以用来解析数学表达式的语法的简单示例可能如下所示
表达
: 表达式 '+' 表达式
|表达式 '-' 表达式
|表达式 '*' 表达式
|表达式'/'表达式
|数字
;
(这实际上是一个糟糕的语法,原因超出了本文的范围;它仅用于说明稍后将使用的那种形式。)
此语法指定终端符号 +
, -
, *
, /
, 和 数字
并表示它们可以用五种不同的方式组织——其中四种允许递归组织。举个例子, 表达式 '+' 表达式
可以扩展为 表达式 '+' 表达式 '-' 表达式
.这允许以非常少的规则表达复杂的令牌组。在这种语法下,标准算术表达式如 12 + 41 * 3
可以通过以下重写作为表达式匹配:
EXPRESSION '+' EXPRESSION(改写最右边的EXPRESSION)
-> 表达式 '+' 表达式 * 表达式
-> 数字 '+' 数字 '*' 数字
如何 解析发生,如何使用语法来识别有效的数学表达式,是本文的主要重点。有两种主要的解析方法: 使用众多解析器生成器库之一 (它实现了很好的解析算法),或者手工编写。使用生成器可以从开发人员那里抽象出很多工作,并且非常适合复杂的情况,但是当试图了解该过程的工作原理时,最好手动编写
为此,本文将为 JavaScript 开发人员每天使用的东西创建一个解析器:JSON。在本文结束时,将会有一个工作版本 JSON.parse
该方法探索了将 JSON 文本转换为 JavaScript 值必须采取的各种路径和必须做出的决定。
解析 JSON(精简版)
为了在本文的其余部分保持词汇一致,“解析”将指将非结构化字符串转换为结构化数据的整个过程。解析将包括标记化步骤(词法分析)和(句法)分析步骤。
语法
为了为构建标记器和分析器奠定基础,将给出表达要解析的 JSON 的精确语法。此语法不会读取与完整 JSON 规范匹配的所有可能字符串;这样做将需要深入研究细节并偏离广泛理解解析管道的更高层次的目标。
JSON 字符串可以表示的可能值是字符串、数字、布尔值、空值以及对象和数组(两者都可以包含任意数量的 JSON 值)。这给了语法它的最高规则:
JSON
: JSON_STRING
|数字
|无效的
| JSON_BOOL
| JSON_ARRAY
| JSON_OBJECT
;
在此语法中用于命名符号的约定将是:
- 非终结符将全部大写
- 代表一类字符的终端符号将全部小写
- 代表文字字符的终端符号将被写在单引号之间
为了完整起见,这个文法的四个非终结符号需要指定它们的规则。
JSON 中的字符串将是两个引号之间的任何内容。数组将是两个方括号之间的任何 JSON 值的逗号分隔列表。对象将是花括号之间的键/值对的逗号分隔列表。这将语法扩展为以下内容:
JSON
: JSON_STRING
|数字
|无效的
| JSON_BOOL
| JSON_ARRAY
| JSON_OBJECT
; JSON_STRING
: '“' 细绳 '”'
; JSON_BOOL
: 真的
|错误的
; JSON_ARRAY
:'['JSON_ARRAY_ELS']'
; JSON_ARRAY_ELS
: JSON_ARRAY_ELS ',' JSON
| JSON
| ε
; JSON_OBJECT
: '{' JSON_OBJECT_KV_PAIRS '}'
; JSON_OBJECT_KV_PAIRS
: JSON_OBJECT_KV_PAIRS ',' JSON_OBJECT_KV_PAIR
| JSON_OBJECT_KV_PAIR
| ε
; JSON_OBJECT_KV_PAIR
: JSON_STRING ':' JSON
;
在这个语法中有几个有趣的特征需要指出。
一个特殊的符号叫做 ε
已介绍。该符号表示空字符串,在此语法中用于表示数组或对象可以为空。这将允许 “[]”
和 “{}”
都是有效的 JSON 字符串。
语法也是递归的。这反映了 JSON 值本身的递归性质,因为可以嵌套在数组和对象中的内容没有限制。这允许 “[1,[真,[]]]”
是一个有效的 JSON 字符串,将被解析为一系列嵌套数组。此外,其中一些递归规则是 剩下 递归的,这意味着生产规则将自身作为规则的开始:
JSON_ARRAY_ELS
: JSON_ARRAY_ELS ',' JSON
;
对于使用某些解析算法的分析器,这可能会导致无限循环,因为解析器会解析 JSON_ARRAY_ELS
,首先要看看是否匹配 JSON_ARRAY_ELS。
但那得看看是否 那 火柴 JSON_ARRAY_ELS
, 到无穷远 .由于本文是手动实现分析器,因此解析器处理左递归的方式具有很大的灵活性:它可以很容易地重写为迭代。
分词器
分词器的重点是将要解析的文本分块成字符组(即使其中一些组只包含一个字符)。这些标记代表语法中的终结符号,通常由正则表达式定义并命名。某些标记也可以被识别为无意义并完全跳过——这是通常用于空白字符的方法。
上面定义的语法给出了以下标记:
常量标记 = [
[/^\s+/, null], // 跳过空格
[/^\[/, '['],
[/^]/, ']'],
[/^\{/, '{'],
[/^}/, '}'],
[/^:/, ':'],
[/^,/, ','],
[/^"/, '"'],
[/^\d+/, '数字'],
[/^null\b/, 'null'], // 添加单词边界以确保
[/^true\b/, 'true'], // `null` 等,是完全匹配的。
[/^false\b/, '假'],
[/^[^"]*/, 'string'], // 匹配除引号以外的任何内容。
];
每个元组代表一个模式/名称对。每个模式都锚定在字符串的开头,以确保标记器始终生成 下一个 应馈送到分析器的令牌。通过分配空白 无效的
名称,这将向标记器指示应忽略所有空格。
标记器的逻辑是遍历标记列表并找到第一个与字符串开头匹配的标记。这意味着以更具体的令牌排在更通用的令牌之前的方式订购令牌很重要。例如,如果一个
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明