mini-dog-c编译器之词法分析

mini-dog-c 是一个小型的 C 语言编译器,是我学习和理解编译器基本工作原理的实践项目。其词法分析器能够识别 C 语言的基本语法元素,包括常见的标识符、整数和浮点数字面量、布尔字面量以及字符串字面量。此外,它还支持基本的算术和逻辑操作符、比较操作符以及多种分隔符。在关键词方面,mini-dog-c 支持函数定义、变量声明、条件语句和返回语句等基本控制结构。

本项目基于monkey-cpp开发。

mini-dog-c 支持以下语法特性:

标识符和字面量

  • 标识符:由字母、数字和下划线组成,但不能以数字开头(kIdent
  • 字符字面量:支持单引号包围的字符(kChar
  • 布尔字面量:支持 truefalsekBool
  • 整数字面量:支持十进制整数(kInt
  • 浮点数字面量:支持包含小数点的数字(kDouble
  • 字符串字面量:支持双引号包围的字符串(kString

操作符

  • 赋值操作符=
  • 算术操作符+(加)、-(减)、*(乘)、/(除)
  • 逻辑操作符!(逻辑非)、==(等于)、!=(不等于)
  • 比较操作符<(小于)、>(大于)

分隔符

  • 逗号,(用于分隔参数或列表)
  • 分号;(用于语句结束)
  • 冒号:(可能用于标签或类型标注)
  • 圆括号()(用于函数调用或分组)
  • 花括号{}(用于代码块或结构体定义)
  • 方括号[](用于数组或索引)

关键词

  • 函数定义fn(用于定义函数)
  • 变量声明let(用于声明变量)
  • 条件语句if(用于条件判断)、else(用于条件分支)
  • 返回语句return(用于从函数返回值)

token.hpp

#pragma once

#include <string>
#include <unordered_map>


enum class TokenType {
	kIllegal,
	kEOF,

	// 标识符 + 字面量
	kIdent,
	kChar,
	kBool,
	kInt,
	kDouble,
	kString,

	// 操作符
	kAssign,		// =
	kPlus,			// +
	kMinus,			// -
	kBang,			// !
	kAsterisk,		// *
	kSlash,			// /
	kLT,			// <
	kGT,			// >
	kEQ,			// ==
	kNE,			// !=

	// 分隔符
	kComma,			// ,
	kSemicolon,		// ;
	kColon,			// :
	kLParen,		// (
	kRParen,		// )
	kLBrace,		// {
	kRBrace,		// }
	kLBracket,		// [
	kRBracket,		// ]

	// 关键词
	kFunction,		// fn
	kLet,			// let
	kIf,			// if
	kElse,			// else
	kReturn			// return
};

TokenType LookupIdent(std::string ident) {
	static std::unordered_map<std::string, TokenType> keywords = {
		{"fn", TokenType::kFunction},
		{"let", TokenType::kLet},
		{"if", TokenType::kIf},
		{"return", TokenType::kReturn},
		{"else", TokenType::kElse},
		{"true", TokenType::kBool},
		{"false", TokenType::kBool}
	};

	auto it = keywords.find(ident);
	if (it != keywords.end())
		return it->second;
	else
		return TokenType::kIdent;
}

struct Token {
	Token() {
		type = TokenType::kIllegal;
	}

	Token(TokenType type, std::string literal) {
		this->type = type;
		this->literal = literal;
	}

	TokenType type;
	std::string literal;
};

lexer.hpp

#pragma once

#include <string>
#include "token.hpp"


class Lexer {
public:
	Lexer(const std::string input)
		: input_(input)
		, pos_(0)
		, next_pos_(0)
		, ch_(' ') {
	}

	void SkipWhitespace() {
		while (ch_ == ' ' || ch_ == '\t' || ch_ == '\n' || ch_ == '\r')
			ReadChar();
	}

	void ReadChar() {
		ch_ = next_pos_ >= input_.size() ? 0 : input_[next_pos_];
		pos_ = next_pos_++;
	}

	char PeekChar() {
		return next_pos_ >= input_.size() ? 0 : input_[next_pos_];
	}

	std::string ReadIdentifier() {
		size_t old_pos = pos_;
		while (std::isalpha(ch_) || std::isdigit(ch_) || ch_ == '_')
			ReadChar();

		return input_.substr(old_pos, pos_ - old_pos);
	}

	std::string ReadString() {
		size_t old_pos = pos_ + 1;
		while (true) {
			ReadChar();
			if (ch_ == '"' || ch_ == 0)
				break;
		}

		return input_.substr(old_pos, pos_ - old_pos);
	}

	std::string ReadNumber() {
		size_t old_pos_ = pos_;
		if (old_pos_ == '.') {
			while (std::isdigit(ch_))
				ReadChar();
		}
		else {
			int dot_count = 0;
			while (std::isdigit(ch_) || (ch_ == '.' && (dot_count++) == 0))
				ReadChar();
		}

		return input_.substr(old_pos_, pos_ - old_pos_);
	}

	Token NextToken()
	{
		SkipWhitespace();

		Token tok;
		switch (ch_) {
		case '=':
		{
			if (PeekChar() == '=') {
				ReadChar();
				std::string literal("==");
				tok.type = TokenType::kEQ;
				tok.literal = literal;
			} else {
				tok = Token(TokenType::kAssign, std::string(1, ch_));
			}
			break;
		}
		case '+':
		{
			tok = Token(TokenType::kPlus, std::string(1, ch_));
			break;
		}
		case '-':
		{
			tok = Token(TokenType::kMinus, std::string(1, ch_));
			break;
		}
		case '!':
		{
			if (PeekChar() == '=')
			{
				ReadChar();
				tok.type = TokenType::kNE;
				tok.literal = "!=";
			}
			else
			{
				tok = Token(TokenType::kBang, std::string(1, ch_));
			}
			break;
		}
		case '/':
			tok = Token(TokenType::kSlash, std::string(1, ch_));
			break;
		case '*':
			tok = Token(TokenType::kAsterisk, std::string(1, ch_));
			break;
		case '<':
			tok = Token(TokenType::kLT, std::string(1, ch_));
			break;
		case '>':
			tok = Token(TokenType::kGT, std::string(1, ch_));
			break;
		case ';':
			tok = Token(TokenType::kSemicolon, std::string(1, ch_));
			break;
		case ',':
			tok = Token(TokenType::kComma, std::string(1, ch_));
			break;
		case '{':
			tok = Token(TokenType::kLBrace, std::string(1, ch_));
			break;
		case '}':
			tok = Token(TokenType::kRBrace, std::string(1, ch_));
			break;
		case '(':
			tok = Token(TokenType::kLParen, std::string(1, ch_));
			break;
		case ')':
			tok = Token(TokenType::kRParen, std::string(1, ch_));
			break;
		case '[':
			tok = Token(TokenType::kLBracket, std::string(1, ch_));
			break;
		case ']':
			tok = Token(TokenType::kRBracket, std::string(1, ch_));
			break;
		case ':':
			tok = Token(TokenType::kColon, std::string(1, ch_));
			break;
		case '"':
		{
			tok.type = TokenType::kString;
			tok.literal = ReadString();
			break;
		}
		case '.':
		{
			tok.type = TokenType::kDouble;
			tok.literal = ReadNumber();
			break;
		}
		case 0:
		{
			tok.literal = "";
			tok.type = TokenType::kEOF;
			break;
		}
		default:
		{
			if (std::isalpha(ch_) || ch_ == '_')
			{
				tok.literal = ReadIdentifier();
				tok.type = LookupIdent(tok.literal);
				return tok;
			}
			else if (std::isdigit(ch_))
			{
				tok.literal = ReadNumber();
				tok.type = tok.literal.find('.') == std::string::npos ? TokenType::kInt : TokenType::kDouble;
				return tok;
			}
			else
			{
				tok = Token(TokenType::kIllegal, std::string(1, ch_));
			}
		}
		}

		ReadChar();
		return tok;
	}

private:
	std::string input_;		// 源码
	size_t pos_;			// 当前位置
	size_t next_pos_;		// 下一位置
	char ch_;				// 当前字符
};

main.cpp

#include "lexer.hpp"
#include <iostream>


int main(int argc, char** argv) {
	std::string input = R"(
int add(int a, int b) {
	return a + b;
}

double add(double a, double b) {
	return a + b;
}

int main() {
	int a = 100;
	int b = 200;
	println(add(a, b));

	double c1 = 123.;
	double _d1 = .456;
	println(add(c1, _d1));

	println("Hello, world!\nThis is mini-dog-c.");

	return 0;
}
)";
	Lexer lexer(input);
	while (true) {
		auto token = lexer.NextToken();
		if(token.type == TokenType::kEOF)
			break;
		std::cout << token.literal << std::endl;
	}

	return 0;
}

运行结果:

int
add
(
int
a
,
int
b
)
{
return
a
+
b
;
}
double
add
(
double
a
,
double
b
)
{
return
a
+
b
;
}
int
main
(
)
{
int
a
=
100
;
int
b
=
200
;
println
(
add
(
a
,
b
)
)
;
double
c1
=
123.
;
double
_d1
=
.456
println
(
add
(
c1
,
_d1
)
)
;
println
(
Hello, world!\nThis is mini-dog-c.
)
;
return
0
;
}

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

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

相关文章

sys.dm_exec_connections:查询与 SQL Server 实例建立的连接有关的信息以及每个连接的详细信息(客户端ip)

文章目录 引言I 基于dm_exec_connections查询客户端ip权限物理联接时间范围dm_exec_connections表see also: 监视SQL Server 内存使用量资源信号灯 DMV sys.dm_exec_query_resource_semaphores( 确定查询执行内存的等待)引言 查询历史数据库客户端ip应用场景: 安全分析缺乏…

vscode如何离线安装插件

在没有网络的时候,如果要安装插件,就会麻烦一些,需要通过离线安装的方式进行。下面记录如何在vscode离线安装插件。 一、下载离线插件 在一台能联网的电脑中,下载好离线插件,拷贝到无法联网的电脑上。等待安装。 vscode插件商店地址:https://marketplace.visualstudio.co…

基于ADAS 与关键点特征金字塔网络融合的3D LiDAR目标检测原理与算法实现

一、概述 3D LiDAR目标检测是一种在三维空间中识别和定位感兴趣目标的技术。在自动驾驶系统和先进的空间分析中&#xff0c;目标检测方法的不断演进至关重要。3D LiDAR目标检测作为一种变革性的技术&#xff0c;在环境感知方面提供了前所未有的准确性和深度信息. 在这里&…

【玩转全栈】----Django连接MySQL

阅前先赞&#xff0c;养好习惯&#xff01; 目录 1、ORM框架介绍 选择建议 2、安装mysqlclient 3、创建数据库 4、修改settings&#xff0c;连接数据库 5、对数据库进行操作 创建表 删除表 添加数据 删除数据 修改&#xff08;更新&#xff09;数据&#xff1a; 获取数据 1、OR…

基于Android的疫苗预约系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实…

基于 Apache Commons Pool 实现的 gRPC 连接池管理类 GrpcChannelPool 性能分析与优化

基于 Apache Commons Pool 实现的 gRPC 连接池管理类 GrpcChannelPool 性能分析与优化 1. 输出关键信息的代码示例 日志记录方法 使用以下代码记录连接池的关键信息&#xff0c;帮助分析连接池的状态和性能瓶颈&#xff1a; import org.apache.commons.pool2.impl.GenericO…

矩阵碰一碰发视频的视频剪辑功能源码搭建,支持OEM

在短视频创作与传播领域&#xff0c;矩阵碰一碰发视频结合视频剪辑功能&#xff0c;为用户带来了高效且富有创意的内容产出方式。这一功能允许用户通过碰一碰 NFC 设备触发视频分享&#xff0c;并在分享前对视频进行个性化剪辑。以下将详细阐述该功能的源码搭建过程。 一、技术…

CClinkIEfield Basic转Modbus TCP网关模块连接三菱FX5U PLC

捷米特JM-CCLKIE-TCP是自主研发的一款CCLINK IE FB从站功能的通讯网关。该产品主要功能是将各种 MODBUS-TCP 设备接入到 CCLINK IE FB网络中。 捷米特JM-CCLKIE-TCP网关连接到CCLINK IE FB总线中做为从站使用&#xff0c;连接到 MODBUS-TCP 总线中做为主站或从站使用。 为了打破…

农产品智慧物流系统

本文结尾处获取源码。 本文结尾处获取源码。 本文结尾处获取源码。 一、相关技术 后端&#xff1a;Java、JavaWeb / Springboot。前端&#xff1a;Vue、HTML / CSS / Javascript 等。数据库&#xff1a;MySQL 二、相关软件&#xff08;列出的软件其一均可运行&#xff09; I…

设计模式-结构型-桥接模式

1. 什么是桥接模式&#xff1f; 桥接模式&#xff08;Bridge Pattern&#xff09; 是一种结构型设计模式&#xff0c;它旨在将抽象部分与实现部分分离&#xff0c;使它们可以独立变化。通过这种方式&#xff0c;系统可以在抽象和实现两方面进行扩展&#xff0c;而无需相互影响…

后台管理系统引导功能的实现

引导是软件中经常见到的一个功能&#xff0c;无论是在后台项目还是前台或者是移动端项目中。 那么对于引导页而言&#xff0c;它是如何实现的呢&#xff1f;通常情况下引导页是通过 聚焦 的方式&#xff0c;高亮一块视图&#xff0c;然后通过文字解释的形式来告知用户该功能的作…

现场展示deepseek VS openAI o1模型大对比

DeepSeek-V3 模型的发布在 AI 领域引起了广泛关注。作为一款拥有 6850 亿参数的混合专家&#xff08;MoE&#xff09;语言模型&#xff0c;DeepSeek-V3 在多个基准测试中表现出色&#xff0c;甚至超越了一些闭源模型。其在 Aider 代码能力排行榜上的正确率达到 48.4%&#xff0…

Golang的并发编程框架比较

# Golang的并发编程框架比较 中的并发编程 在现代软件开发中&#xff0c;处理高并发的能力愈发重要。Golang作为一门支持并发编程的编程语言&#xff0c;提供了丰富的并发编程框架和工具&#xff0c;使得开发者能够更轻松地处理并发任务。本文将介绍Golang中几种常用的并发编程…

SSL,TLS协议分析

写在前面 工作中总是会接触到https协议&#xff0c;也知道其使用了ssl&#xff0c;tls协议。但对其细节并不是十分的清楚。所以&#xff0c;就希望通过这篇文章让自己和读者朋友们都能对这方面知识有更清晰的理解。 1&#xff1a;tls/ssl协议的工作原理 1.1&#xff1a;设计的…

网络安全-XSS跨站脚本攻击(基础篇)

漏洞扫描的原理 1.跨站脚本攻击介绍 xss跨站脚本攻击&#xff1a; xSS 全称&#xff08;Cross site Scripting &#xff09;跨站脚本攻击&#xff0c;是最常见的Web应用程序安全漏洞之一&#xff0c;位于OWASP top 10 2013/2017年度分别为第三名和第七名&#xff0c;XSS是指攻…

SpringBoot之核心配置

学习目标&#xff1a; 1.熟悉Spring Boot全局配置文件的使用 2.掌握Spring Boot配置文件属性值注入 3.熟悉Spring Boot自定义配置 4.掌握Profile多环境配置 5.了解随机值设置以及参数间引用 1.全局配置文件 Spring Boot使用 application.properties 或者application.yaml 的文…

【Word_笔记】Word的修订模式内容改为颜色标记

需求如下&#xff1a;请把修改后的部分直接在原文标出来&#xff0c;不要采用修订模式 步骤1&#xff1a;打开需要转换的word后&#xff0c;同时按住alt和F11 进入&#xff08;Microsoft Visual Basic for Appliations&#xff09; 步骤2&#xff1a;插入 ---- 模块 步骤3&…

【C++】字符数|组与字符串的深度解析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;一、字符数组的基本概念1. 什么是字符数组&#xff1f;2. C语言风格字符串的特点 &#x1f4af;二、字符数组的初始化1. 字符串直接赋值2. 按字符逐个赋值数据对比示例 &am…

计算机网络——网络层—IP数据报与分片

一、IP 数据报的格式 • 一个 IP 数据报由首部和数据两部分组成。 • 首部的前一部分是固定长度&#xff0c;共 20 字节&#xff0c;是所有 IP 数据报必须具有的。 • 在首部的固定部分的后面是一些可选字段&#xff0c;其长度是可变的。 IP 数据报首部的固定部分中的各字段 版…

【Python学习(八)——异常处理】

Python学习&#xff08;八&#xff09;——异常处理 本文介绍了异常处理的知识&#xff0c;仅作为本人学习时记录&#xff0c;感兴趣的初学者可以一起看看&#xff0c;欢迎评论区讨论&#xff0c;一起加油鸭~~~ 心中默念&#xff1a;Python 简单好学&#xff01;&#xff01;&…