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

news2025/6/4 1:11:46

文章目录

  • 一、实现 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, numbers

        if 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, string


def lex_number(string):
    return None, string


def lex_bool(string):
    return None, string


def lex_null(string):
    return None, string


def lex(string):
    tokens = []

    while len(string):
        json_string, string = lex_string(string)
        if json_string is not None:
            tokens.append(json_string)
            continue

        json_number, string = lex_number(string)
        if json_number is not None:
            tokens.append(json_number)
            continue

        json_bool, string = lex_bool(string)
        if json_bool is not None:
            tokens.append(json_bool)
            continue

        json_null, string = lex_null(string)
        if json_null is not None:
            tokens.append(None)
            continue

        if 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, string

    for c in string:
        if c == JSON_QUOTE:
            return json_string, string[len(json_string)+1:]
        else:
            json_string += c

    raise 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 += c
        else:
            break

    rest = string[len(json_number):]

    if not len(json_number):
        return None, string

    if '.' in json_number:
        return float(json_number), rest

    return 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, string


def 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 [], tokens

def parse_object(tokens):
    return {}, tokens

def 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.123
        json_key = tokens[0] # 即 json_key = A
        if 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_value

        t = 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.coloradmin.cn/o/1504697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文阅读:Diffusion Model-Based Image Editing: A Survey

Diffusion Model-Based Image Editing: A Survey 论文链接 GitHub仓库 摘要 这篇文章是一篇基于扩散模型(Diffusion Model)的图片编辑(image editing)方法综述。作者从多个方面对当前的方法进行分类和分析,包括学习…

时间感知自适应RAG(TA-ARE)

原文地址:Time-Aware Adaptive RAG (TA-ARE) 2024 年 3 月 1 日 介绍 随着大型语言模型(LLM)的出现,出现了新兴能力的概念。前提或假设是LLMs具有隐藏的和未知的能力,等待被发现。企业家们渴望在LLMs中发现一些无人知晓…

LLM实施的五个阶段

原文地址:Five Stages Of LLM Implementation 大型语言模型显着提高了对话式人工智能系统的能力,实现了更自然和上下文感知的交互。这导致各个行业越来越多地采用人工智能驱动的聊天机器人和虚拟助手。 2024 年 2 月 20 日 介绍 从LLMs的市场采用情况可以…

Day26:安全开发-PHP应用模版引用Smarty渲染MVC模型数据联动RCE安全

目录 新闻列表 自写模版引用 Smarty模版引用 代码RCE安全测试 思维导图 PHP知识点: 功能:新闻列表,会员中心,资源下载,留言版,后台模块,模版引用,框架开发等 技术:输…

超分辨率(1)--基于GAN网络实现图像超分辨率重建

目录 一.项目介绍 二.项目流程详解 2.1.数据加载与配置 2.2.构建生成网络 2.3.构建判别网络 2.4.VGG特征提取网络 2.5.损失函数 三.完整代码 四.数据集 五.测试网络 一.项目介绍 超分辨率(Super-Resolution),简称超分&#xff08…

React组件(函数式组件,类式组件)

函数式组件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>React Demo</title> <!-- 引…

嵌入式Linux串口和 poll() 函数的使用

一、poll() 函数的介绍 poll() 函数用于监控多个文件描述符的变化的函数。它可以用来检查一个或多个文件描述符的状态是否改变&#xff0c;比如是否可读、可写或有错误发生。它常用于处理 I/O 多路复用&#xff0c;这在需要同时处理多个网络连接或文件操作时非常有用。 头文件…

ZJUBCA研报分享 | 《BTC/USDT周内效应研究》

ZJUBCA研报分享 引言 2023 年 11 月 — 2024 年初&#xff0c;浙大链协顺利举办为期 6 周的浙大链协加密创投训练营 &#xff08;ZJUBCA Community Crypto VC Course&#xff09;。在本次训练营中&#xff0c;我们组织了投研比赛&#xff0c;鼓励学员分析感兴趣的 Web3 前沿话题…

【杂记】IDEA和Eclipse如何查看GC日志

1.Eclipse查看GC日志 1.1 右击代码编辑区 -> Run As -> Run Configurations 1.2 点击Arguments栏 -> VM arguments:区域填写XX参数 -> Run 1.3 控制台输出GC详细日志 2.IDEA查看GC日志 2.1 鼠标右击代码编辑器空白区域&#xff0c;选择Edit 项目名.main()... 2.…

IPsec VPN之安全联盟

一、何为安全联盟 IPsec在两个端点建立安全通信&#xff0c;此时这两个端点被称为IPsec对等体。安全联盟&#xff0c;即SA&#xff0c;是指通信对等体之间对某些要素的约定&#xff0c;定义了两个对等体之间要用何种安全协议、IP报文的封装方式、加密和验证算法。SA是IPsec的基…

【JavaEE初阶 -- 多线程】

认识线程&#xff08;Thread&#xff09;Thread类及常见方法 1.认识线程&#xff08;Thread&#xff09;1.1 线程1.2 进程和线程的关系和区别1.3 Java的线程和操作系统线程的关系1.4 创建线程 2. Thread类及常用的方法2.1 Thread的常见构造方法2.2 Thread的几个常见属性2.3 启动…

在 Python 中 JSON 数据格式的使用

在 Python 中 JSON 数据格式的使用 JSON 简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。它易于阅读和编写&#xff0c;并且与许多编程语言兼容。 Python 中的 JSON 模块 Python 标准库中包含一个 json 模块&#xff0c;用于处理…

docker-compose这下会用了吗?

概要 默认的模板文件是 docker-compose.yml&#xff0c;其中定义的每个服务可以通过 image 指令指定镜像或 build 指令&#xff08;需要 Dockerfile&#xff09;来自动构建。 注意如果使用 build 指令&#xff0c;在 Dockerfile 中设置的选项(例如&#xff1a;CMD, EXPOSE, V…

Normalizer(归一化)和MinMaxScaler(最小-最大标准化)的区别详解

1.Normalizer&#xff08;归一化&#xff09;&#xff08;更加推荐使用&#xff09; 优点&#xff1a;将每个样本向量的欧几里德长度缩放为1&#xff0c;适用于计算样本之间的相似性。 缺点&#xff1a;只对每个样本的特征进行缩放&#xff0c;不保留原始数据的分布形状。 公式…

IM6ULL学习总结(四-七-1)输入系统应用编程

第7章 输入系统应用编程 7.1 什么是输入系统 ⚫ 先来了解什么是输入设备&#xff1f; 常见的输入设备有键盘、鼠标、遥控杆、书写板、触摸屏等等,用户通过这些输入设备与 Linux 系统进行数据交换。 ⚫ 什么是输入系统&#xff1f; 输入设备种类繁多&#xff0c;能否统一它们的…

java 数据结构二叉树

目录 树 树的概念 树的表示形式 二叉树 两种特殊的二叉树 二叉树的性质 二叉树的存储 二叉树的基本操作 二叉树的遍历 二叉树的基本操作 二叉树oj题 树 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次…

ROS——Ubuntu环境搭建

Ubuntu安装 首先下载 Ubuntu 的镜像文件&#xff0c;链接如下:ubuntu-releases-20.04安装包下载_开源镜像站-阿里云ubuntu-releases-20.04安装包是阿里云官方提供的开源镜像免费下载服务&#xff0c;每天下载量过亿&#xff0c;阿里巴巴开源镜像站为包含ubuntu-releases-20.04…

css-通用样式按钮加号

1.实现 2.代码 html <div class"addF">&#xff0b;</div> css .addF{width:40px;font-size:25px;font-weight:600;background-color:rgb(64, 158, 255);text-align:center;color:white;height:34px;border-radius:3px;line-height:34px; }

Spring Boot 自动装配的原理!!!

SpringBootApplication SpringBootConfiguration&#xff1a;标识启动类是一个IOC容器的配置类 EnableAutoConfiguration&#xff1a; AutoConfigurationPackage&#xff1a;扫描启动类所在包及子包中所有的组件&#xff0c;生…

C语言字符串型常量

在C语言中&#xff0c;字符串型常量是由一系列字符组成的常量。字符串常量在C中以双引号&#xff08;"&#xff09;括起来&#xff0c;例如&#xff1a;“Hello, World!”。字符串常量在C中是不可变的&#xff0c;也就是说&#xff0c;一旦定义&#xff0c;就不能修改其内…