编译原理——扫描器设计与实现

news2024/10/13 2:17:10

非常详细(包括跳过注释部分),不多说直接上代码(结合代码讲解)

#include<bits/stdc++.h>

using namespace std;

#define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))

//关键词集合
string KEY_WORD[] = {"int","char","string","bool","float","double","true","false","return","if","else","while","for","default","do","public","static","switch","case","private","protected"};
//界符集合,我将'/'也加到界符表中用于判断是否是注释
char BOUND_CHAR[] = {',', ';', '(', ')', '{', '}', '[', ']', '\'', '\"', '/'};
int pos = 0;//当前读到的数的下标
short in_annotation = 0;//当前是否在注释的范围,1表示在并且是"//"这种类型,2表示在并且是"/**/"这种注释类型,默认不在(因为两种注释的退出条件不同)

// 关键字,标识符,运算符,界符和常量
enum WordTypeKind{
    KEYWORD, IDENTIFIER, CONSTANT, OPERATOR, DELIMITER, ANNOTATION, ERROR
};

//定义词
struct WORD {
    WordTypeKind wordType;
    string value;
};

//读取文件中的内容
string openFile(string fileName) {
    ifstream readFile(fileName);
    //文件是否打开
    if(!readFile.is_open()) {
        cerr << "无法打开文件!" << '\n';
        return "";
    }

    string content, temp;
    while(getline(readFile, temp)) {
        // cout << content << endl;
        content += temp;
        content += '\n';
    }

    //关闭资源
    readFile.close();
    return content;
}

//将字符串写入文件
bool writeFile(string fileName, string content) {
    ofstream writeFile(fileName);
    if(!writeFile.is_open()) {
        cerr << "无法打开文件!" << '\n';
        return false;
    }

    writeFile << content << endl;

    writeFile.close();
    return true;
}

//判断一个单词是不是关键字,是返回true
bool isKeyWord(string word) {
    for(int i = 0; i < ARRAY_LENGTH(KEY_WORD); i++) {
        if(word == KEY_WORD[i]) return true;
    }
    return false;
}

//分词器,将字符串分成最小单位(关键字,标识符,运算符,界符和常量),关键字我们可以自己根据使用的高级语句自定义
WORD getNextWord(string str) {
    string tempStr = "";//暂存这个单词
    WORD newWord;//返回的词
    newWord.wordType = ERROR;//方便后续退出循环
    while (pos < str.length() && std::isspace(str[pos])) {
        if(in_annotation == 1 && str[pos] == '\n') in_annotation = 0;//退出注释状态
        ++pos; // 跳过空白字符
    }
    if (pos >= str.length()) {
        return newWord; // 结束
    }
    if(pos < str.length()) {//不越界
        char c = str[pos++];
        tempStr += c;
        //标识符的命名规范:只能以字母或'_'开头
        if(isalpha(c) || c == '_') {//这个词是关键词或标识符
            // isalnum(str[pos])这个函数用来检查传递给它的字符是否是字母(isalpha)或者是数字(isdigit)
            while(pos < str.length() && (isalnum(str[pos]) || str[pos] == '_')) {
                tempStr += str[pos++];
            }
            //判断这个单词是标识符or关键字
            if(isKeyWord(tempStr)) {//是关键字
                newWord.wordType = KEYWORD;
            } else {//标识符
                newWord.wordType = IDENTIFIER;
            }
        } else if(isdigit(c)) {//数字开头只可能是常数,我们把所有数字读完
            while(pos < str.length() && isdigit(str[pos])) {
                tempStr += str[pos++];
            }
            newWord.wordType = CONSTANT;
        } else {//只可能是运算符或界符
            for(int i = 0; i < ARRAY_LENGTH(BOUND_CHAR); i++) {//是不是界符
                if(c == BOUND_CHAR[i]) {
                    //遇到'/'判断是不是注释和注释类型
                    if(c == '/' && pos < str.length() && str[pos] == '/') {//是'//'类型
                        in_annotation = 1; 
                        newWord.wordType = ANNOTATION;
                        pos++;
                        break;
                    } else if(c == '/' && pos < str.length() && str[pos] == '*') {//是'/**/'类型
                        in_annotation = 2;
                        newWord.wordType = ANNOTATION;
                        pos++;
                        break;
                    }
                    newWord.wordType = DELIMITER;
                    break;
                }
            }
            //是运算符,注意双目运算符(三目运算符我们就不考虑了)
            if(pos < str.length()) {//注意不要越界
                newWord.wordType = OPERATOR;
                char nextChar = str[pos];
                //特判一下'*/'的情况,因为这是第二种注释的退出标识
                if(c == '*' && nextChar == '/') {//退出注释状态,下面也不用看了
                    in_annotation = 0;
                    tempStr += nextChar;
                    newWord.wordType = ANNOTATION;
                    pos++;
                    newWord.value = tempStr;
                    return newWord;
                }
                //考虑一下所有的双目运算符
                if((c == '+' || c == '-' || c == '*' || c == '/' || c == '!' || c == '^' || c == '%' || c == '=' || c == '<' || c == '>') && nextChar == '=') {
                    tempStr += nextChar;//更新一下nextChar
                    pos++;
                }
                if(c == '+' && nextChar == '+') {
                    tempStr += nextChar;//更新一下nextChar
                    pos++;
                }
                if(c == '-' && nextChar == '-') {
                    tempStr += nextChar;//更新一下nextChar
                    pos++;
                }
                if(c == '&' && nextChar == '&') {
                    tempStr += nextChar;//更新一下nextChar
                    pos++;
                }
                if(c == '|' && nextChar == '|') {
                    tempStr += nextChar;//更新一下nextChar
                    pos++;
                }
                if(c == '<' && nextChar == '<') {
                    tempStr += nextChar;//更新一下nextChar
                    pos++;
                }
                if(c == '>' && nextChar == '>') {
                    tempStr += nextChar;//更新一下nextChar
                    pos++;
                }
            }
        }
        newWord.value = tempStr;
    }
    return newWord;
}

//换行符占一个长度!!!

int main() {
    //先读取txt文件
    string fileName = "E:\\program\\bianyiyuanli\\1.txt";
    string str = openFile(fileName);
    WORD word;
    while((word = getNextWord(str)).wordType != ERROR) {
        if(word.wordType != ANNOTATION && in_annotation == 0){//我们只有在词语不是注释类型和不在注释状态才输出它
            std::cout << "[" << word.wordType << ",   " << word.value << "]" << std::endl;
        }
    }
    return 1;
}

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

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

相关文章

vue+ant解决弹窗可以拖动的问题

通过自定义指令实现拖拽功能 在main.js里加入drag自定义指令 Vue.directive(drag, {bind(el) {// 获取弹窗头部const header el.querySelector(.ant-modal-header)const modal el.querySelector(.ant-modal)// 弹窗头部鼠标变为移动header.style.cursor move// 头部鼠标按…

心觉:别再让你的精力流浪,精准掌控每一刻

Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作197/1000天 昨天写了一篇文章心觉&#xff1a;生理欲望转化的奥秘&#xff1a;提升创造力的法则 核心观点是来自于拿破仑希尔的《…

ICML 2024 | 牛津提出合作图神经网络Co-GNNs,更灵活的消息传递新范式

引用次数:9 引用格式:Finkelshtein B, Huang X, Bronstein M, et al. Cooperative graph neural networks[J]. arXiv preprint arXiv:2310.01267, 2023. 一、摘要 本文提出了一种训练图神经网络的新框架“合作图神经网络”(Co-GNNs),其中每一个节点可以被看作一个独立的玩…

【数据结构】邻接表

一、概念 邻接表是一个顺序存储与链式存储相结合的数据结构&#xff0c;用于描述一个图中所有节点之间的关系。 若是一个稠密图&#xff0c;我们可以选择使用邻接矩阵&#xff1b;但当图较稀疏时&#xff0c;邻接矩阵就显得比较浪费空间了&#xff0c;此时我们就可以换成邻接…

grpc的python使用

RPC 什么是 RPC &#xff1f; RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;是一种计算机通信协议&#xff0c;允许一个程序&#xff08;客户端&#xff09;通过网络向另一个程序&#xff08;服务器&#xff09;请求服务&#xff0c;而无需了解…

JDK17常用新特性

目前国内大部分开发人员都是在使用jdk8&#xff0c;甚至是jdk6&#xff0c;但是随着jdk的更新迭代&#xff0c;jdk8我觉得可能就会慢慢的淡出舞台&#xff0c;随着目前主流框架最新版推出明确说明了不再支持jdk8&#xff0c;也促使我不得不抓紧学习了解一波jdk17的新特性&#…

手写mybatis之解析和使用ResultMap映射参数配置

前言 学习源码是在学习什么呢&#xff1f; 就是为了通过这些源码级复杂模型中&#xff0c;学习系统框架的架构思维、设计原则和设计模式。在这些源码学习手写的过程中&#xff0c;感受、吸收并也是锻炼一种思维习惯&#xff0c;并尝试把这些思路技术迁移到平常的复杂业务设计开…

MD5消息摘要算法学习

MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种广泛使用的哈希函数&#xff0c;它用于生成128位的哈希值&#xff08;也称为消息摘要&#xff09;。MD5主要用于确保信息的完整性&#xff0c;即可以通过对数据生成的哈希值来验证数据是否被篡改。尽管MD5在过去被…

60. 排列序列【回溯】

文章目录 60. 排列序列解题思路Go代码 60. 排列序列 60. 排列序列 给出集合 [1,2,3,...,n]&#xff0c;其所有元素共有 n! 种排列。 按大小顺序列出所有排列情况&#xff0c;并一一标记&#xff0c;当 n 3 时, 所有排列如下&#xff1a; “123”“132”“213”“231”“31…

SpringBoot 集成GPT实战,超简单详细

Spring AI 介绍 在当前的AI应用开发中&#xff0c;像OpenAI这样的GPT服务提供商主要通过HTTP接口提供服务&#xff0c;这导致大部分Java开发者缺乏一种标准化的方式来接入这些强大的语言模型。Spring AI Alibaba应运而生&#xff0c;它作为Spring团队提供的一个解决方案&…

Spring Boot 3 文件管理:上传、下载、预览、查询与删除(全网最全面教程)

在现代Web应用中&#xff0c;文件管理是一个非常重要的功能。Spring Boot作为Java开发领域的热门框架&#xff0c;提供了丰富的工具和API来简化文件管理的操作。本文将详细介绍如何使用Spring Boot 3进行文件的上传、下载、预览、查询与删除操作&#xff0c;并提供一个完整的示…

OpenCV 环境配置

首先下载opencv&#xff0c;在opencv官网进行下载。 按照上面的步骤&#xff0c;点击进去 滑至底部&#xff0c;不切换至不同页&#xff0c;选择合适的版本进行下载(Window系统选择Windows版本进行下载)。 接下来以4.1.2版本为例&#xff1a; 点击之后会进入这个页面&#xff…

聚类分析 | NRBO-GMM聚类优化算法

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 (创新)NRBO-GMM聚类优化算法 (NRBO聚类优化&#xff0c;创新&#xff0c;独家) 牛顿-拉夫逊优化算法优化GMM高斯混合聚类优化算法 matlab语言&#xff0c;一键出图&#xff0c;直接运行 1.牛顿-拉夫逊优化算法(New…

STM32—BKP备份寄存器RTC实时时钟

1.BKP简介 BKP(Backup Registers)备份寄存器BKP可用于存储用户应用程序数据。当VDD&#xff08;2.0~3.6V&#xff09;电源被切断&#xff0c;他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒&#xff0c;或系统复位或电源复位时&#xff0c;他们也不会被复位TAMP…

教培机构如何向知识付费转型

在数字化时代&#xff0c;知识付费已成为一股不可忽视的潮流。面对这一趋势&#xff0c;教育培训机构必须积极应对&#xff0c;实现向知识付费的转型&#xff0c;以在新的市场环境中立足。 一、教培机构应明确自身的知识定位。 在知识付费领域&#xff0c;专业性和独特性是关键…

【Python】selenium获取鼠标在网页上的位置,并定位到网页位置模拟点击的方法

在使用Selenium写自动化爬虫时&#xff0c;遇到验证码是常事了。我在写爬取测试的时候&#xff0c;遇到了点击型的验证码&#xff0c;例如下图这种&#xff1a; 这种看似很简单&#xff0c;但是它居然卡爬虫&#xff1f;用简单的点触验证码的方法来做也没法实现 平常的点触的方…

数据迁移:如何保证在不停机的情况下平滑的迁移数据

1. 引言 数据迁移是一个常见的需求&#xff0c;比如以下的场景&#xff0c;我们都需要进行数据迁移。 大表修改表结构单表拆分进行分库分表、扩容系统重构&#xff0c;使用新的表结构来存储数据 2. 迁移准备 2.1 备份工具 2.1.1 mysqldump mysqldump 是 MySQL 自带的用于…

【计网】从零开始认识https协议 --- 保证安全的网络通信

在每个死胡同的尽头&#xff0c; 都有另一个维度的天空&#xff0c; 在无路可走时迫使你腾空而起&#xff0c; 那就是奇迹。 --- 廖一梅 --- 从零开始认识https协议 1 什么是https协议2 https通信方案2.1 只使用对称加密2.2 只使用非对称加密2.3 双方都使用非对称加密2.4 …

Winform和WPF的技术对比

WinForms&#xff08;Windows Forms&#xff09;和WPF&#xff08;Windows Presentation Foundation&#xff09;是用于创建桌面应用程序的两种技术。尽管两者都可以用于开发功能强大的Windows应用程序&#xff0c;但它们的设计理念、功能和开发体验都有显著区别。在本文中&…

(亲测可行)ubuntu下载安装c++版opencv4.7.0和4.5.0 安装opencv4.5.0报错及解决方法

文章目录 &#x1f315;系统配置&#x1f315;打开终端&#xff0c;退出anacoda激活环境(如果有的话)&#x1f315;安装依赖&#x1f319;安装g, cmake, make, wget, unzip&#xff0c;若已安装&#xff0c;此步跳过&#x1f319;安装opencv依赖的库&#x1f319;安装可选依赖 …