Compiler Lab2- 自制极简编译器

news2024/12/26 2:22:16

笔者实现的这个超级迷你版编译器(词法分析、语法分析、生成中间代码(cpp))仅支持四则运算,功能真的是非常非常简单,不过其中的流程(词法分析 -> 语法分析 -> 中间代码生成)还是有一定的学习价值的,可以把这些阶段都给串起来,看看不同的处理阶段是如何衔接的,而不是仅仅停留在教科书中各个分裂的模块里。

myCompiler.cpp

该文件涵盖了词法分析,语法分析,中间代码生成这三个部分,可以更清楚地观察源语言是怎么一步步通过词法分析器转换为token序列,再由token序列根据递归下降算法构建抽象语法树,最后根据抽象语法树生成中间代码的。(注:笔者将此编译器的中间代码选为cpp,并不是传统意义上的汇编代码哈,主要是汇编代码不大熟悉

#include <iostream>
#include <iterator>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>

using namespace std;

enum class TokenType
{
    Keyword,
    Identifier,
    Number,
    Operator,
    Separator,
    Comment
};

struct Token
{
    TokenType type;
    string value;
};

vector<Token> lex(const string &input)
{
    vector<Token> tokens;
    size_t i = 0;

    while (i < input.length())
    {
        char c = input[i];

        if (isspace(c))
        {
            i++;
        }
        else if (isalpha(c) || c == '_')
        {
            string identifier;
            while (i < input.length() && (isalnum(input[i]) || input[i] == '_'))
            {
                identifier.push_back(input[i]);
                i++;
            }
            if (identifier == "int")
            {
                tokens.push_back({TokenType::Keyword, identifier});
            }
            else
            {
                tokens.push_back({TokenType::Identifier, identifier});
            }
        }
        else if (isdigit(c))
        {
            string number;
            while (i < input.length() && isdigit(input[i]))
            {
                number.push_back(input[i]);
                i++;
            }
            tokens.push_back({TokenType::Number, number});
        }
        else if (c == '+' || c == '-' || c == '*' || c == '/' || c == '=')
        {
            tokens.push_back({TokenType::Operator, string(1, c)});
            i++;
        }
        else if (c == ';' || c == ',')
        {
            tokens.push_back({TokenType::Separator, string(1, c)});
            i++;
        }
        else if (c == '/')
        {
            if (i + 1 < input.length() && input[i + 1] == '/')
            {
                i += 2;
                string comment;
                while (i < input.length() && input[i] != '\n')
                {
                    comment.push_back(input[i]);
                    i++;
                }
                tokens.push_back({TokenType::Comment, comment});
            }
            else if (i + 1 < input.length() && input[i + 1] == '*')
            {
                i += 2;
                string comment;
                while (i + 1 < input.length() && !(input[i] == '*' && input[i + 1] == '/'))
                {
                    comment.push_back(input[i]);
                    i++;
                }
                if (i + 1 < input.length())
                {
                    i += 2;
                    // comment.push_back(input[i]);
                    // i++;
                    // comment.push_back(input[i]);
                    // i++;
                }
                tokens.push_back({TokenType::Comment, comment});
            }
            else
            {
                tokens.push_back({TokenType::Operator, string(1, c)});
                i++;
            }
        }
        else
        {
            throw runtime_error(" Invalid character: " + string(1, c));
        }
    }

    return tokens;
}

enum class ASTNodeType
{
    Program,
    Declaration,
    Assignment,
    BinaryExpression,
    Identifier,
    IntegerLiteral
};

struct ASTNode
{
    ASTNodeType type;
    string value;
    vector<ASTNode> children;
};

class Parser
{
public:
    Parser(const vector<Token> &tokens) : tokens(tokens), index(0) {}

    ASTNode parse()
    {
        ASTNode program{ASTNodeType::Program, "", {}};
        while (index < tokens.size())
        {
            program.children.push_back(parseStatement());
        }
        return program;
    }

private:
    const vector<Token> &tokens;
    size_t index;

    ASTNode parseStatement()
    {
        ASTNode statement;
        if (tokens[index].type == TokenType::Keyword && tokens[index].value == "int")
        {
            statement = parseDeclaration();
        }
        else
        {
            statement = parseAssignment();
        }

        // 跳过语句后面的分号
        if (tokens[index].type == TokenType::Separator && tokens[index].value == ";")
        {
            index++;
        }
        else
        {
            throw runtime_error(" Expected ';' at the end of the statement");
        }

        return statement;
    }

    ASTNode parseDeclaration()
    {
        ASTNode declaration{ASTNodeType::Declaration, "", {}};

        // 跳过关键字int
        index++;

        if (tokens[index].type == TokenType::Identifier)
        {
            // 将声明节点的值设置为标识符的名字
            declaration.value = tokens[index].value;
            index++;
        }
        else
        {
            throw runtime_error(" Expected an identifier in declaration");
        }

        return declaration;
    }

    ASTNode parseAssignment()
    {
        ASTNode assignment{ASTNodeType::Assignment, "", {}};

        if (tokens[index].type == TokenType::Identifier)
        {
            // 添加标识符作为子节点
            assignment.children.push_back({ASTNodeType::Identifier, tokens[index].value, {}});
            index++;
        }
        else
        {
            throw runtime_error(" Expected an identifier in assignment");
        }

        if (tokens[index].type == TokenType::Operator && tokens[index].value == "=")
        {
            // 跳过赋值操作的"="
            index++;
        }
        else
        {
            throw runtime_error(" Expected '=' in assignment");
        }

        assignment.children.push_back(parseExpression());

        return assignment;
    }

    ASTNode parseExpression()
    {
        ASTNode expression{ASTNodeType::BinaryExpression, "", {}};

        // 处理第一个操作数(数字或标识符)
        if (tokens[index].type == TokenType::Number)
        {
            // 添加整数字面量作为子节点
            expression.children.push_back({ASTNodeType::IntegerLiteral, tokens[index].value, {}});
        }
        else if (tokens[index].type == TokenType::Identifier)
        {
            // 添加标识符作为子节点
            expression.children.push_back({ASTNodeType::Identifier, tokens[index].value, {}});
        }
        else
        {
            throw runtime_error(" Expected a number or an identifier in expression");
        }
        index++;

        if (tokens[index].type == TokenType::Operator &&
            (tokens[index].value == "+" || tokens[index].value == "-" ||
             tokens[index].value == "*" || tokens[index].value == "/"))
        {
            // 将二元表达式节点的值设置为运算符
            expression.value = tokens[index].value;
            index++;
        }
        else
        {
            throw runtime_error(" Expected an operator in expression");
        }

        // 处理第二个操作数(数字或标识符)
        if (tokens[index].type == TokenType::Number)
        {
            // 添加整数字面量作为子节点
            expression.children.push_back({ASTNodeType::IntegerLiteral, tokens[index].value, {}});
        }
        else if (tokens[index].type == TokenType::Identifier)
        {
            // 添加标识符作为子节点
            expression.children.push_back({ASTNodeType::Identifier, tokens[index].value, {}});
        }
        else
        {
            throw runtime_error(" Expected a number or an identifier in expression");
        }
        index++;

        return expression;
    }
};

void printTokens(const vector<Token> &tokens)
{
    cout << "Tokens:" << endl;
    for (const auto &token : tokens)
    {
        cout << token.value << " ";
    }
    cout << endl;
}

/*
enum class ASTNodeType
{
    Program,
    Declaration,
    Assignment,
    BinaryExpression,
    Identifier,
    IntegerLiteral
};
*/
void printAST(const ASTNode &node, int indent = 0)
{
    string indent_str(indent, ' ');
    cout << indent_str << "Node: type=";
    //  << static_cast<int>(node.type) << ", value=" << node.value << endl;
    switch (node.type)
    {
    case ASTNodeType::Program:
        cout << "Program";
        break;
    case ASTNodeType::Declaration:
        cout << "Declaration";
        break;
    case ASTNodeType::Assignment:
        cout << "Assignment";
        break;
    case ASTNodeType::BinaryExpression:
        cout << "BinaryExpression";
        break;
    case ASTNodeType::Identifier:
        cout << "Identifier";
        break;
    case ASTNodeType::IntegerLiteral:
        cout << "IntegerLiteral";
        break;
    }
    cout << ", value=" << node.value << endl;

    for (const auto &child : node.children)
    {
        printAST(child, indent + 2);
    }
}

// string generateCode(const ASTNode &node)
// {
//     string code;
//     switch (node.type)
//     {
//     case ASTNodeType::Declaration:
//         code += "int " + node.value + ";\n";
//         break;
//     case ASTNodeType::Assignment:
//         code += node.children[0].value + " = " + generateCode(node.children[1]) + ";\n";
//         break;
//     case ASTNodeType::BinaryExpression:
//         code += generateCode(node.children[0]) + " " + node.value + " " + generateCode(node.children[1]);
//         break;
//     case ASTNodeType::IntegerLiteral:
//         code += node.value;
//         break;
//     case ASTNodeType::Identifier:
//         code += node.value;
//         break;
//     default:
//         throw runtime_error("Unknown AST node type in code generation");
//     }
//     return code;
// }

string generateCode(const ASTNode &node)
{
    string code;
    switch (node.type)
    {
    case ASTNodeType::Program:
        for (const auto &child : node.children)
        {
            code += generateCode(child);
        }
        break;

    case ASTNodeType::Declaration:
        code += "int " + node.value + ";\n";
        break;

    case ASTNodeType::Assignment:
        code += generateCode(node.children[0]) + " = " + generateCode(node.children[1]) + ";\n";
        break;

    case ASTNodeType::BinaryExpression:
        code += "(" + generateCode(node.children[0]) + " " + node.value + " " + generateCode(node.children[1]) + ")";
        break;

    case ASTNodeType::IntegerLiteral:
        code += node.value;
        break;

    case ASTNodeType::Identifier:
        code += node.value;
        break;
        
    default:
        throw runtime_error(" Unknown AST node type in code generation");
    }

    // cout << "here :" << endl;
    // for (size_t i = 2; i < node.children.size(); ++i)
    // {
    //     cout << static_cast<int>(node.children[i].type) << ", value=" << node.children[i].value << endl;
    //     code += generateCode(node.children[i]);
    // }

    return code;
}

int main(int argc, char** argv)
{
    if (argc <= 1)
    {
        cout << "mjn reminds you:\nplease input filename like './myCompiler filename'" << endl;
        return 0;
    }

    string filename = argv[1];
    ifstream infile(filename);
    if (!infile)
    {
        cerr << "mjn reminds you:\nError opening input file" << endl;
        return 1;
    }
    string input((istreambuf_iterator<char>(infile)), istreambuf_iterator<char>());

    vector<Token> tokens;
    try
    {
        tokens = lex(input);
    }
    catch (const runtime_error &e)
    {
        cerr << "mjn reminds you~\nLexer error: " << e.what() << endl;
        return 1;
    }

    printTokens(tokens);

    Parser parser(tokens);
    ASTNode ast;

    string generatedCode;

    try
    {
        ast = parser.parse();
        generatedCode = generateCode(ast);
    }
    catch (const runtime_error &e)
    {
        cerr << "mjn reminds you~\nParser error: " << e.what() << endl;
        return 1;
    }

    cout << "Abstract Syntax Tree:" << endl;
    printAST(ast);

    ofstream outputFile("output.cpp");
    outputFile << generatedCode;
    outputFile.close();

    cout << "mjn reminds you:\nCode generation complete. Output written to 'output.cpp'." << endl;

    return 0;
}

input.txt

此编译器的输入文件(注意,仅支持声明单个变量,不能同时声明多个哈,比如,int a, b; 就不可以哈,还有只能是二元表达式,仅支持 a = b op c; 的形式哈,所以说,这个编译器真的是非常非常简单hh~)

int a;
int b;
a = 3 + 5;
b = a * 2;

还有一点需要注意的是,在执行生成的myCompiler时,后面需要跟上待编译文件的名称,不然会弹出如下提示hh~ 

如果输入的文件名不存在,则会提示找不到该文件

正确输入文件名后,打印出token序列,构建好的抽象语法树,并提示输出中间代码到新文件output.cpp中

output.cpp中的内容如下:

如果输入文件中的语法不正确,比如,同时声明了多个变量(如下所示)

int a;
int b, c, d;
a = 3 + 5;
b = a * 2;

就会报语法解析错误,没有按照规定(即只能声明单个变量)来做,int b后面必须跟上";" 


总结:通过自己构建这样一个功能超级简单的编译器,加深了对龙书上面的知识点的理解,最重要的是,对于编译器的各个处理模块之间的数据流向及其转换形式有了一个深刻、直观的认识。

再次说明一下,笔者写的这个编译器(我甚至都不愿称之为编译器,因为功能实在是太简单了,呜呜呜)功能极其简单,感兴趣的读者可以在此基础上肆意发挥哈,尽情地扩充其他功能。

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

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

相关文章

各SQL引擎的SQL转换过程对比

SQL引擎 参考文档:高级语言的解析过程—解析树 从 MySQL、Oracle、TiDB、CK,到 Hive、HBase、Spark,从关系型数据库到大数据计算引擎,他们大都可以借助 SQL 引擎,实现 “接受一条 sql 语句然后返回查询结果” 的功能。 他们核心的执行逻辑都是一样的,大致可以通过下面…

【五一创作】《嵌入式系统》知识总结7:GPIO寄存器

总述 每组端口具有7个寄存器 • 实现对GPIO端口初始化配置和数据输入输出控制 1. 配置寄存器&#xff1a;GPIOx_CRL、GPIOx_CRH 用来选择引脚功能&#xff0c;例如输入或输出 2. 数据寄存器&#xff1a;GPIOx_IDR、GPIOx_ODR 用来保存引脚输入电平或输出电平 3. 位控寄存器…

数据库管理-第七十一期 五一,休息?(20230503)

数据库管理 2023-05-03 第七十一期 五一&#xff0c;休息&#xff1f;1 备份2 两个DDL3 问题处理4 问题排查总结 第七十一期 五一&#xff0c;休息&#xff1f; 好不容易&#xff0c;熬过万恶的6天班来到了五一假期&#xff0c;想着好好休息&#xff0c;顺便把绝地幸存者给通关…

Java 基础进阶篇(九)—— 常用 API

文章目录 一、Object 类二、Objects 工具类三、Math 类四、System 类五、BigDecimal 类 一、Object 类 一个类要么默认继承了 Object 类&#xff0c;要么间接继承了 Object 类&#xff0c;Object 类是 java 中的祖宗类。Object 类的方法是一切子类都可以直接使用的。 因此&…

Obsidian +Obsidian Git插件 + Gitee 自动同步笔记

在Obsidian 关闭安全模式 然后再插件市场里面搜索并下载Obsidian Git 这个插件 注意&#xff1a;这里需要科学上网才能搜索并下载&#xff0c;我看很多博主都没说这件事情 然后再你的Gitee中&#xff0c;新建一个仓库 把这两个勾选上&#xff0c;然后随便选个语言&#xff0…

【JavaEE】应用层UDP协议

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 本篇文章将为大家介绍应用层中UDP协议~~ 在应用层这里&#xff0c;虽然存在一些现有的协议&#xff08;HTTP&#xff09;&#xff0c;但是也有很多情况&#xff0c;需要程序猿自定制协议&a…

Windeployqt 打包,缺少DLL 的原因分析,解决方法

很多同学使用工具windeployqt进行打包发布后&#xff0c;运行exe文件时&#xff0c;还是会出现下图所示的系统错误提示&#xff0c;这种情况就表示相关的DLL 库文件没有被正确打包。可是windeployqt明确显示运行正常啊&#xff0c;难道是QT自家的windeployqt这个工具有bug&…

2024年浙大MBA创客班项目提面如何申请?

2024年浙大MBA创客班提面如何申请&#xff1f;在目前的提前批面试申请过程中不少考生都不太清楚&#xff0c;专注浙大的杭州达立易考教育本期将项目的提前批面试和常规批复试申请基本流程和关键信息整理出来&#xff0c;帮助考生更精准的进行面试准备。 一、浙大MBA创客班项目…

基于比较排序算法总结

1.数据无法全部放入内存 2.数据以数据流形式

操作系统2(多处理器编程)

一、并发 1.操作系统是最早的并发程序之一 2.并发的基本单位&#xff1a;线程 共享内存的多个执行流 执行流拥有独立的堆栈/寄存器共享全部的内存&#xff08;指针可以互相引用&#xff09; 3.实现原子性 lock(&lk)unlock(&lk) 实现临界区(critical section)之间…

keepalived脑裂现象

Keepealived最常见的问题是会出现脑裂现象&#xff1a; Master一直发送心跳消息给backup主机&#xff0c;如果中间的链路突然断掉&#xff0c;backup主机将无法收到master主机发送过来的心跳消息&#xff08;也就是vrrp报文&#xff09;&#xff0c;backup这时候会立即抢占mas…

Snmputil和Snmputilg工具的下载和基本使用 SNMP协议 Windows系统SNMP服务的安装教程

⬜⬜⬜ ---&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; (*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;---⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &#x1f381;欢迎各位→…

现代CMake高级教程 - 第 7 章:变量与缓存

双笙子佯谬老师的【公开课】现代CMake高级教程课程笔记 第 7 章&#xff1a;变量与缓存 重复执行 cmake -B build 会有什么区别&#xff1f; ❯ cmake -B build -- The C compiler identification is GNU 11.3.0 -- The CXX compiler identification is GNU 11.3.0 -- Detec…

C++:分治算法之输油管道问题

目录 描述 输入 输出 输入样例 输出样例 分析 代码 运行结果 描述 ¢ 某石油公司计划建造一条 由东向西 的主输油管道。该管道要穿过一个有n口油井的油田。从每口油井都要有一条输油管道沿最短路经&#xff08;或南或北&#xff09;与主管道相连。 ¢ 如果给定…

如何区分GPT3.5和4?

切换模型 前两天申请的GPT 4的API调用权限终于申请下来了。 这两天我也是抓紧开发&#xff0c;让自己搭建的国内网站&#xff08;aichatroom.cn&#xff09;可以快速支持上使用GPT 4。 GPT 3.5和GPT4的区别 GPT-3.5 和 GPT-4 分别代表了 OpenAI 发布的两个不同版本的自然语言处…

现代CMake高级教程 - 第 4 章:对象的属性

双笙子佯谬老师的【公开课】现代CMake高级教程课程笔记 第 4 章&#xff1a;对象的属性 除了 POSITION_INDEPENDENT_CODE 还有哪些这样的属性&#xff1f; add_executable(main main.cpp)set_property(TARGET main PROPERTY CXX_STANDARD 17) # 采用 C17 标准进行编译&am…

STC15W104 8脚单片机串口下载程序

单片机串口下载是一种常见的单片机程序下载方式&#xff0c;它通过串口线连接单片机的串口引脚和电脑的串口接口实现。下面是单片机串口下载的基本原理和操作方法&#xff1a; 原理 确定下载模式&#xff1a;大多数单片机芯片都支持串口下载模式&#xff0c;需要在程序中设置…

常识性概念图谱建设与应用

目录 一、知识图谱背景介绍 &#xff08;一&#xff09;基本背景 &#xff08;二&#xff09;与NLP的关系 &#xff08;三&#xff09;常识性概念图谱的引入对比 二、常识性概念图谱介绍 &#xff08;一&#xff09;常识性概念图谱关系图示例 &#xff08;二&#xff09…

深度学习-tensorflow 使用keras进行深度神经网络训练

概要 深度学习网络的训练可能会很慢、也可能无法收敛&#xff0c;本文介绍使用keras进行深度神经网络训练的加速技巧&#xff0c;包括解决梯度消失和爆炸问题的策略&#xff08;参数初始化策略、激活函数策略、批量归一化、梯度裁剪&#xff09;、重用预训练层方法、更快的优化…

linux进程描述指令:ps与top

这里写自定义目录标题 一 ps指令1 ps -aux2. ps -a3. ps -u4.ps -x 二 top指令1 top2 top -d 时间3. top -i4 top -p ID 一 ps指令 1 ps -aux 显示系统中的所有进程 PID就是进程的唯一编号&#xff0c;操作系统书里有一个PCD的概念&#xff0c;就是一个标识性的控制单元 [ro…