自己动手写编译器:自顶向下的自动状态机

本节我们介绍编译原理中一种新的数据结构叫自顶向下的自动状态机。前面我们在做词法解析时接触了大量自动状态机,他们存在一个缺陷那就是无法对要识别的字符串进行计数,因此当我们要判断括号对是否匹配时,使用在词法解析的状态机就处理不了,例如给定字符串"((())()))",我们判断其中左右括号是否都能匹配上,以前的状态机就无法处理。

但如果我们匹配上一个数据结构,也就是“栈”,那么问题就能得到解决。我们把状态机跟一个栈组合在一起的情况就叫自顶向下的状态机(push-down automaton)也叫 PDA。这个结构很重要,后续我们的语法解析算法就得依赖它。

我们看看其运行的基本流程。在词法解析中,状态机的当前所处状态由上一个状态和输入字符共同决定,但是在 PDA 中,状态机的状态由堆栈顶部的元素决定,堆栈中存储的是状态机各个状态的状态值,同时状态机在接收到字符输入后,它输出的不再是下一个状态节点,而是对应要采取的行动,下一个状态节点要从堆栈的顶部获取。在状态机中有四种行动可以采取,分别为:
1,接收,当状态机采取该行动时表示当前接收字符所形成的字符串。
2,错误,当状态机采取该行动时表示当前接收字符形成的字符串不符合状态机的规则。
3,push N, 把状态机节点 n压入堆栈顶部。
4,pop, 从堆栈中取出顶部元素,该元素的取值对应状态机所在状态。

我们看看如何使用 PDA 来识别括号字符串是否满足括号匹配。首先状态表如:
请添加图片描述
我们使用 state_table来表示上表,在状态 0 就是起始状态,我们使用如下算法或流程来表示括号识别流程:

将初始状态节点压入堆栈
while(action=state_table[当前堆栈顶部节点数值][输入字符] != accept) {
    if(action == error) {
        print("括号字符串不匹配")
        return 1;
    }
    else {
        执行 action 对应操作
    }
}

我们看看如何使用代码实现上面算法,还是基于前面的dragon-compiler 项目来进行,在项目目录下新建一个 pda 文件夹,然后执行:

init pda

进行初始化模块,然后在给定目录创建代码文件 pda.go,并下输入代码如下:

package pda

import (
	"fmt"
)

const (
	ERROR = iota
	ACCEPT
	PUSH1
	POP
	EOF
)

type StateTable struct{}

func (s *StateTable) get(state int8, symbol int) int8 {
	if state == 0 {
		switch symbol {
		case '(':
			return PUSH1
		case ')':
			return ERROR
		case EOF:
			return ACCEPT
		}
	}

	if state == 1 {
		switch symbol {
		case '(':
			return PUSH1
		case ')':
			return POP
		case EOF:
			return ERROR
		}
	}

	panic("state can only 0 or 1")
}

type BracketPDA struct {
	stateTable *StateTable
	stack      []int8
}

func NewBracketPDA() *BracketPDA {
	pda := &BracketPDA{
		stateTable: &StateTable{},
		stack:      make([]int8, 0),
	}

	pda.stack = append(pda.stack, 0)

	return pda
}

func (b *BracketPDA) Parse(str string) {
	pos := 0
	for true {
		symbol := EOF
		if pos < len(str) {
			symbol = int(str[pos])
		}
		state := b.stack[len(b.stack)-1]
		action := b.stateTable.get(state, symbol)
		switch action {
		case ERROR:
			fmt.Printf("str: %s, is rejected\n", str)
			return
		case ACCEPT:
			fmt.Printf("str: %s, is accept\n", str)
			return
		case PUSH1:
			b.stack = append(b.stack, 1)
		case POP:
			b.stack = b.stack[:len(b.stack)-1]
		}

		pos += 1
		if symbol == EOF {
			return
		}
	}
}

上面代码中StateTable用来模拟状态表,它只有一个方法那就是 get,输入当前状态和读入的字符,它给出要采取的行动,如果返回 PUSH1,那么我们需要将状态值 1 压入堆栈,其他的依次类推。BracketPDA用来模拟整个 PDA,它包含一个堆栈 stack 用来存放状态节点,对应的 Parse 函数在输入括号字符串后启动匹配过程,Parse 函数遍历输入字符串的每个字符,然后获取堆栈顶部的状态节点值,通过 StateTable 的 get 函数获取要采取的动作,如果 get 返回 accept,那么进入接收状态,如果返回 ERROR,那么进入错误状态,返回 PUSH1 则将节点 1 压入堆栈,如果返回 POP,则将堆栈顶部的元素弹开。

在 main.go 中使用调用 PDA 的代码如下:

package main

import (
	"pda"
)

func main() {
	pdaParser := pda.NewBracketPDA()
	pdaParser.Parse("(())")
}

上面代码运行后所得结果如下:

str: (()), is accept

也就是输入的括号字符串"(())“能够匹配,我们可以去掉其中一个括号试试,例如 pdaParser.Parse(”(()"),所得结果如下:

str: ((), is rejected

由此可见我们实现的 PDA 能有效的识别输入的括号字符串是否匹配。更多调试演示和讲解视频请在 b 站搜索"coding 迪斯尼“,代码下载为:
https://github.com/wycl16514/compiler-push-down-automata.git

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

世界人口数据分析与探索

文章目录 世界人口数据集介绍数据集 1&#xff1a;世界国家统计数据&#xff1a;数据集 2&#xff1a;世界人口详细信息&#xff08;2023 年&#xff09;&#xff1a;数据集 3&#xff1a;按年份划分的世界人口&#xff08;1950-2023&#xff09;&#xff1a; 数据分析导入必要…

【金猿CIO展】步长制药信息化管理与建设中心总经理束炼:IT部门既要懂技术,也要懂业务...

‍ 束炼 本文由步长制药信息化管理与建设中心总经理束炼撰写并投递参与“数据猿年度金猿策划活动——2023大数据产业年度优秀CIO榜单及奖项”评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 随着数字化转型的浪潮席卷各行各业&#xff0c;中国数字经济已进入快速发展阶…

Godot之StringName解析

类描述 在Godot中&#xff0c;StringName是唯一字符串的内置类型。 StringName 是不可变的字符串&#xff0c;用于唯一名称的通用表示&#xff08;也叫“字符串内嵌”&#xff09;。值相同的两个 StringName 是同一个对象。进行比较时比普通 String 要快很多。 对于需要 Str…

【密码学】python密码学库pycryptodome

记录了一本几乎是10年前的书&#xff08;python绝技–用python成为顶级黑客&#xff09;中过时的内容 p20 UNIX口令破解机 里面提到了python标准库中自带的crypt库&#xff0c;经验证Python 3.12.1中并没有这个自带的库&#xff0c;密码学相关的库目前&#xff08;2024.1.12&a…

QT周四作业

题目&#xff1a; 代码&#xff1a; widget.cpp #include "widget.h" #include "ui_widget.h" #include <QDebug> Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->lineEditName->setPlac…

JAVA数组以及小练习

目录 数组的概述和静态初始化 数组的地址值和元素访问 数组的遍历 数组的动态初始化 数组练习 数组的概述和静态初始化 package 数组;public class array1 {public static void main(String[] args){//格式//静态初始化//数据类型 [] 数组名 new 数组类型[]{元素1&#xf…

[开发语言][c++][python]:C++与Python中的赋值、浅拷贝与深拷贝

C与Python中的赋值、浅拷贝与深拷贝 1. Python中的赋值、浅拷贝、深拷贝2. C中的赋值、浅拷贝、深拷贝2.1 概念2.2 示例&#xff1a;从例子中理解1) 不可变对象的赋值、深拷贝、浅拷贝2) 可变对象的赋值、浅拷贝与深拷贝3) **可变对象深浅拷贝(外层、内层改变元素)** 写在前面&…

将WAP网站封装成App体验的全新策略

一、传统的App封装方式 传统的App封装技术通常依赖于WebView组件&#xff0c;将WAP内容嵌入到一个原生App框架中。这种方法虽然可以快速实现WAP到App的转换&#xff0c;但存在着明显的缺陷&#xff1a;首先&#xff0c;WebView的性能和用户体验都无法与原生组件相提并论&#x…

2024年1月12日:清爽无糖rio留下唇齿之间的香甜

友利奈绪的时间管理 2024年1月12日08:02:28进行java程序设计的上课准备 2024年1月12日08:02:44知道java的题目有18道 2024年1月12日08:43:07随机数去重比较 2024年1月12日08:54:03C语言题目最小公倍数 2024年1月12日08:58:37C语言题目二维数组变一维数组 2024年1月12日10…

四种无监督聚类算法说明

目录 一、K-Means无监督学习&#xff08;K-Means&#xff09;的认识-CSDN博客​​​​​​ 二、Mini-Batch K-Means -- Centroid models 三、AffinityPropagation (Hierarchical) -- Connectivity models 四、Mean Shift -- Centroid models 无监督聚类是一种机器学习技术&…

Star 8K+,使用.NET开发的开源NoSQL数据库

LiteDB 是一个轻量级、快速、易用的 .NET NoSQL 嵌入式数据库&#xff0c;完全用 C# 托管代码开发&#xff0c;并且是免费和开源的。它非常适合在移动应用&#xff08;Xamarin iOS/Android&#xff09;和小型的桌面/Web 应用中使用。 主要特点 简单易用的 API&#xff0c;类似…

软件项目质量保证措施-word

一、 质量保障措施 二、 项目质量管理保障措施 &#xff08;一&#xff09; 资深的质量经理与质保组 &#xff08;二&#xff09; 全程参与的质量经理 &#xff08;三&#xff09; 合理的质量控制流程 1&#xff0e; 质量管理规范&#xff1a; 2&#xff0e; 加强协调管理&…

BikeDNA(八)外在分析:OSM 与参考数据的比较2

BikeDNA&#xff08;八&#xff09;外在分析&#xff1a;OSM 与参考数据的比较2 1.数据完整性 见链接 2.网络拓扑结构 见链接 3.网络组件 本节仔细研究两个数据集的网络组件特征。 断开连接的组件不共享任何元素&#xff08;节点/边&#xff09;。 换句话说&#xff0c;…

MES生产执行系统在生产车间的主要作用

MES生产执行系统提供从生产订单下达到产品完成全流程的优化管理。实现现场设备、执行系统及管理系统的集成&#xff0c;实时监控生产管理各项绩效指标。 如果说ERP是上层决策&#xff0c;生产车间是下层执行&#xff0c;那么MES就是连接管理软件和一线生产的中间桥梁。 MES也…

c++静态数据成员

目录 静态成员变量 1. 问&#xff1a;这是为什么呢&#xff1f; (以下结束均为个人理解&#xff0c;如有问题&#xff0c;请指教) 2. 使用场景(举一个例子) 代码中需要注意的点&#xff1a; 3.总结&#xff1a; 静态成员函数 使用场景&#xff1a; 静态成员函数中没有…

大模型核心技术原理: Transformer架构详解

在大模型发展历程中&#xff0c;有两个比较重要点&#xff1a;第一&#xff0c;Transformer 架构。它是模型的底座&#xff0c;但 Transformer 不等于大模型&#xff0c;但大模型的架构可以基于 Transformer&#xff1b;第二&#xff0c;GPT。严格意义上讲&#xff0c;GPT 可能…

Linux 内核如何根据设备树文件来匹配内核

一. 简介 上一篇文章学习了 Linux内核如何确定是否支持此设备&#xff0c;如果支持&#xff0c;设备就会启动 Linux 内核。 文章地址如下&#xff1a; 设备树根节点下的compatile属性的作用-CSDN博客 本文继上面文章的学习。这里简单看一下&#xff0c; Linux 内核是如何根…

odoo17 | 模型之间的交互

前言 在前一章中&#xff0c;我们使用继承来修改模块的行为。在我们的房地产场景中&#xff0c;我们希望更进一步&#xff0c;能够为我们的客户生成发票。Odoo提供了一个发票&#xff08;Invoicing&#xff09;模块&#xff0c;所以直接从我们的房地产模块创建一个发票会很简洁…

VS报错:error:LNK2005 _main 已经在 *.obj 中定义

应该是重定义了&#xff0c;但是又解决不了&#xff0c;看似又没有重定义啊&#xff0c;就在一个文件定义了啊&#xff1f;怎么会出现这种情况呢&#xff1f;关键是&#xff0c;编译报错&#xff0c;程序运行不了了。 这里提一下我的前期操作&#xff0c;是因为将一个头文件和…

图像监视:在 Visual Studio 调试器中查看内存中图像

先决条件 本教程假定您具有以下可用项&#xff1a; 安装了 Update 1 的 Visual Studio 2012 Professional&#xff08;或更高版本&#xff09;。更新 1 可在此处下载。在 Windows 计算机上安装 OpenCV&#xff08;教程&#xff1a;在 Windows 中安装&#xff09;。能够在 Visua…