【编译原理】1、python 实现一个 JSON parser:lex 词法分析、parser 句法分析

文章目录

  • 一、实现 JSON lexer(词法解析器)
  • 二、lex 词法分析
    • 2.1 lex string 解析
    • 2.2 lex number 解析
    • 2.3 lex bool 和 null 解析
  • 三、syntax parser 句法分析
    • 3.1 parse array 解析数组
    • 3.2 parse object 解析对象
  • 四、封装接口

一、实现 JSON lexer(词法解析器)

首先,把输入的 string 拆分为 tokens,过程中会忽略注释、空格。迭代解析字符串流,解析为基本的、非递归定义的语言结构,如正数、字符串、布尔文字。

最终期望实现的效果如下:

assert_equal(lex('{"foo": [1, 2, {"bar": 2}]}'),['{', 'foo', ':', '[', 1, ',', 2, ',', '{', 'bar', ':', 2, '}', ']', '}'])

整体逻辑大概如下:

def lex(string):tokens = []while len(string):json_string, string = lex_string(string)if json_string is not None:tokens.append(json_string)continue# TODO: lex booleans, nulls, numbersif string[0] in JSON_WHITESPACE:string = string[1:]elif string[0] in JSON_SYNTAX:tokens.append(string[0])string = string[1:]else:raise Exception('Unexpected character: {}'.format(string[0]))return tokens

这里的目标是尝试匹配字符串、数字、布尔值和空值并将它们添加到标记列表中。如果这些都不匹配,请检查该字符是否为空格,如果是则将其丢弃。否则,如果它是 JSON 语法的一部分(如左括号),则将其存储为 token。如果字符/字符串与这些模式都不匹配,最后抛出异常。

二、lex 词法分析

整体框架如下:

def lex_string(string):return None, stringdef lex_number(string):return None, stringdef lex_bool(string):return None, stringdef lex_null(string):return None, stringdef lex(string):tokens = []while len(string):json_string, string = lex_string(string)if json_string is not None:tokens.append(json_string)continuejson_number, string = lex_number(string)if json_number is not None:tokens.append(json_number)continuejson_bool, string = lex_bool(string)if json_bool is not None:tokens.append(json_bool)continuejson_null, string = lex_null(string)if json_null is not None:tokens.append(None)continueif string[0] in JSON_WHITESPACE:string = string[1:]elif string[0] in JSON_SYNTAX:tokens.append(string[0])string = string[1:]else:raise Exception('Unexpected character: {}'.format(string[0]))return tokens

2.1 lex string 解析

对于该lex_string函数,要点是检查第一个字符是否是引号。如果是,则迭代输入字符串,直到找到结束引号。

  • 如果没有找到初始 quote,返回 None 和原始列表。
  • 如果找到初始引号和结束引号,返回引号内的字符串以及未检查的输入字符串的其余部分。
def lex_string(string):json_string = ''if string[0] == JSON_QUOTE:string = string[1:]else:return None, stringfor c in string:if c == JSON_QUOTE:return json_string, string[len(json_string)+1:]else:json_string += craise Exception('Expected end-of-string quote')

2.2 lex number 解析

迭代输入,直到找到不能属于数字的字符。

  • 如果已经找到了字符,则返回 float 或 int
  • 否则,返回 None。
def lex_number(string):json_number = ''number_characters = [str(d) for d in range(0, 10)] + ['-', 'e', '.']for c in string:if c in number_characters:json_number += celse:breakrest = string[len(json_number):]if not len(json_number):return None, stringif '.' in json_number:return float(json_number), restreturn int(json_number), rest

2.3 lex bool 和 null 解析

def lex_bool(string):string_len = len(string)if string_len >= TRUE_LEN and string[:TRUE_LEN] == 'true':return True, string[TRUE_LEN:]elif string_len >= FALSE_LEN and string[:FALSE_LEN] == 'false':return False, string[FALSE_LEN:]return None, stringdef lex_null(string):string_len = len(string)if string_len >= NULL_LEN and string[:NULL_LEN] == 'null':return True, string[NULL_LEN:]return None, string

到现在为止, lex 就完成了,完整源码见 https://github.com/eatonphil/pj/blob/master/pj/lexer.py

三、syntax parser 句法分析

句法分析的基本工作是:迭代 一维 的 tokens 数组,匹配为一组组的 token 对。匹配失败时,期望能报错:用户实际的输入,和期望的输入。

对于 JSON parser,就是解析 lex() 函数的结果,匹配为若干 objects、lists、plain values 等。

期望的效果如下:

tokens = lex('{"foo": [1, 2, {"bar": 2}]}')
assert_equal(tokens,['{', 'foo', ':', '[', 1, ',', 2, '{', 'bar', ':', 2, '}', ']', '}'])
assert_equal(parse(tokens),{'foo': [1, 2, {'bar': 2}]})

基本框架如下:

def parse_array(tokens):return [], tokensdef parse_object(tokens):return {}, tokensdef parse(tokens):t = tokens[0]if t == JSON_LEFTBRACKET: # [return parse_array(tokens[1:])elif t == JSON_LEFTBRACE: # {return parse_object(tokens[1:])else:return t, tokens[1:] # 解析出 t,并返回除去 t 之外剩余的部分

该词法分析器和解析器之间的一个关键结构差异是词法分析器返回一个一维标记数组。解析器通常以递归方式定义并返回递归的树状对象。由于 JSON 是一种数据序列化格式而不是一种语言,因此解析器应该生成 Python 中的对象,而不是语法树,您可以在语法树上执行更多分析(或者在编译器的情况下生成代码)。

而且,词法分析独立于解析器进行的好处是,这两段代码都更简单并且只涉及特定元素。

3.1 parse array 解析数组

解析数组,就是解析数组成员,并期望它们之间有逗号标记或指示数组结尾的右括号。

def parse_array(tokens):json_array = []t = tokens[0]if t == JSON_RIGHTBRACKET: # ]   表示第一个字符就直接遇到 ] 了,即数组结尾了return json_array, tokens[1:]while True:json, tokens = parse(tokens) # 解析出一项数组的内容json_array.append(json) # 追加到 json_array 变量中t = tokens[0] # 下一个待解析的字符if t == JSON_RIGHTBRACKET: # ]return json_array, tokens[1:] # 结束匹配elif t != JSON_COMMA: # , 数组各项元素之间应通过逗号分隔raise Exception('Expected comma after object in array')else: # t 为 逗号,则继续循环处理后续字符 tokens[1:]tokens = tokens[1:]raise Exception('Expected end-of-array bracket')

3.2 parse object 解析对象

解析对象就是解析一个键值对,内部用冒号分隔,外部用逗号分隔,直到到达对象的末尾。

def parse_object(tokens):json_object = {}t = tokens[0]if t == JSON_RIGHTBRACE: # }return json_object, tokens[1:]while True:# 期望 json key 是 字符串, 例如 "A": 1.123json_key = tokens[0] # 即 json_key = Aif type(json_key) is str:tokens = tokens[1:]else:raise Exception('Expected string key, got: {}'.format(json_key))# 期望用冒号分隔if tokens[0] != JSON_COLON:raise Exception('Expected colon after key in object, got: {}'.format(t))json_value, tokens = parse(tokens[1:])json_object[json_key] = json_valuet = tokens[0]if t == JSON_RIGHTBRACE:return json_object, tokens[1:]elif t != JSON_COMMA:raise Exception('Expected comma after pair in object, got: {}'.format(t))tokens = tokens[1:]raise Exception('Expected end-of-object brace')

至此,parser 就完成了,源码详见 https://github.com/eatonphil/pj/blob/master/pj/parser.py

四、封装接口

为了提供理想的接口,可以用 from_string 包装 lex 和 parse 函数。

def from_string(string):tokens = lex(string)return parse(tokens)[0]

完整源码见 https://github.com/eatonphil/pj

一些解析器选择在一个阶段中实现词法分析和句法分析。对于某些语言,这可以完全简化解析阶段。或者,在 Common Lisp 等更强大的语言中,它可以允许使用读取器宏一步动态扩展词法分析器和解析器,详见 https://gist.github.com/chaitanyagupta/9324402。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/526242.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运维随录实战(12)之node版本管理工具nvm

1,下载安装nvm 可以去其 github 主页下在,地址为 github.com/coreybutler…会看到有很多个文件可供选择: 这里稍做下解释: nvm-noinstall.zip: 这个是绿色版本,不需要安装,但是使用之前需要配置环境变量;nvm-setup.zip:推荐下载这个包,无需配置就可以使用;Source …

Mint_21.3 drawing-area和goocanvas的FB笔记(七)

FreeBASIC gfx 基本 graphics 绘图 8、ScreenControl与屏幕窗口位置设置 FreeBASIC通过自建屏幕窗口摆脱了原来的屏幕模式限制,既然是窗口,在屏幕坐标中就有它的位置。ScreenControl GET_WINDOW_POS x, y 获取窗口左上角的x, y位置;ScreenC…

深度学习+感知机

深度学习感知机 1感知机总结 2多层感知机1XOR2激活函数3多类分类总结 3代码实现 1感知机 是个很简单的模型,是个二分类的问题。 感知机(perceptron)是Frank Rosenblatt在1957年提出的一种人工神经网络,被视为一种最简单形式的前馈神经网络&…

【亲测有效】解决三月八号ChatGPT 发消息无响应!

背景 今天忽然发现 ChatGPT 无法发送消息,能查看历史对话,但是无法发送消息。 可能的原因 出现这个问题的各位,应该都是点击登录后顶部弹窗邀请 [加入多语言 alapha 测试] 了,并且语言选择了中文,抓包看到 ab.chatg…

Linux篇:文件系统和软硬连接

一 前置知识 文件文件内容文件属性 磁盘上存储文件存文件的内容(数据块)存文件的属性(inode) Linux的文件在磁盘中存储是将属性和内容分开存储的。文件内容的存储是给每个文件分配一块空间,此空间就叫数据块。文件属性…

C++primer -拷贝控制

拷贝控制成员:类通过五种函数来控制拷贝、移动、赋值和销毁:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数 合成拷贝控制成员: 若一个类未显式定义拷贝控制成员,编译器会自动生成合成版本; …

ActiveRAG—主动学习

原文地址:ActiveRAG — Active Learning 2024 年 2 月 26 日 大型语言模型(LLM)的出现开创了对话式人工智能的新时代。这些模型可以生成非常类似人类的文本,并且比以往更好地进行对话。然而,他们仍然面临着仅仅依靠预先…

JVM知识整体学习

前言:本篇没有任何建设性的想法,只是我很早之前在学JVM时记录的笔记,只是想从个人网站迁移过来。文章其实就是对《深入理解JVM虚拟机》的提炼,纯基础知识,网上一搜一大堆。 一、知识点脑图 本文只谈论HotSpots虚拟机。…

TYPE C模拟耳机POP音产生缘由

关于耳机插拔的POP音问题,小白在之前的文章中讲述过关于3.5mm耳机的POP音产生原因。其实这类插拔问题的POP音不仅仅存在于3.5mm耳机,就连现在主流的Type C模拟耳机的插拔也存在此问题,今天小白就来讲一讲这类耳机产生POP音的缘由。 耳机左右…

MyBatisPlus理解

MyBatisPlus是mybatis的增强,mybatis是数据库持久化的框架,但mybatisplus并不是替代mybatis,而是相辅相成的关系 MyBatisPlus不会对以前使用mybatis开发的项目进行影响,引入后仍然正常运行。 使用方法: 1.在引入了对…

数字建筑欢乐颂,智慧工地共筑美好未来!

在解决农民工人欠薪这一长期困扰建筑业的难题上,某建筑公司响应政策,严格按照实名制管理,实施过程中发现并克服了传统管理模式的痛点:聊天群组的信息时,往往会被淹没在“收到”回复中,影响沟通效率&#xf…

人工智能|机器学习——K-means系列聚类算法k-means/ k-modes/ k-prototypes/ ......(划分聚类)

1.k-means聚类 1.1.算法简介 K-Means算法又称K均值算法,属于聚类(clustering)算法的一种,是应用最广泛的聚类算法之一。所谓聚类,即根据相似性原则,将具有较高相似度的数据对象划分至同一类簇,…