1. 定义词法单元Tag

  首先要将可能出现的词进行分类,可以有不同的分类方式。如多符一类:将所有逗号、分号、括号等都归为一类,或者一符一类,将一个符号归为一类。我这里采用的是一符一类的方式。C代码如下:

    #ifndef TAG_H
#define TAG_H namespace Tag {
//保留字
const int
INT = 1, BOOL = 2, MAIN = 3, IF = 4,
ELSE = 5, FOR = 6, WHILE = 7, FALSE = 8,
BREAK = 9, RETURN = 10, TRUE = 11 ; //运算符
const int
NOT = 20, NE = 21, AUTOMINUS =22, MINUS = 23,
AUTOADD = 24, ADD = 25, OR = 26,
AND = 27, MUTIPLY = 28, DIVIDE = 29, MOD = 30,
EQ = 31, ASSIN = 32, GE = 33, GT = 34,
LE = 35, LS = 36; //分界符
const int
COMMA = 40, SEMICOLON = 41, LLBRACKET = 42,
RLBRACKET = 43, LMBRACKET = 44, RMBRACKET = 45,
LGBRACKET = 46, RGBRACKET = 47; //整数常数
const int NUM = 50; //标识符
const int ID = 60; //错误
const int ERROR = 404; //空
const int EMPTY = 70; } #endif

2. 具体步骤

  • 一个一个字符地扫描测试代码,忽略空白字符,遇到回车时,记录行数加1
  • 要进行区分标识符(即普通变量名字)和保留字
  • 因为将标识符和常数都guiwe各自归为一类,所以要有算法能够识别出一整个常数和完整的标识符
  • 加入适当的非法词检测

3. 设计词法分析类

  设计一个词法分析器,当然要包括如何存储一个词法单元,如何扫描(scan)测试代码等,直接上代码:

myLexer.h

    #ifndef MYLEXER_H
#define MYLEXER_H #include <fstream>
#include <string>
#include <unordered_map>
#include "tag.h" /*
* 主要是定义基本的词法单元类,
* 声明了词法分析类
*/ //存储词法单元
class Word {
public:
Word() = default;
Word(std::string s, int t) : lexeme(s), tag(t) {};
std::string getLexeme() { return lexeme; };
int getTag() { return tag; }
void setTag(int t) { tag = t; }
void setLexeme(std::string s) { lexeme = s; }
private:
std::string lexeme;
int tag;
}; //词法分析器类
class Lexer {
public:
Lexer();
void reserve(Word w);
bool readnext(char c, std::ifstream &in);
Word scan(std::ifstream &in);
int getLine() { return line; }
private:
char peek;
std::unordered_map<std::string, Word> words;
int line;
}; #endif

myLexer.cpp

    #include <iostream>
#include <cctype>
#include <sstream>
#include "myLexer.h" void Lexer::reserve(Word w) {
words.insert({w.getLexeme(), w});
} Lexer::Lexer() {
//存入保留字,为了区分标识符
reserve( Word("int", Tag::INT) );
reserve( Word("bool", Tag::BOOL) );
reserve( Word("main", Tag::MAIN) );
reserve( Word("if", Tag::IF) );
reserve( Word("else", Tag::ELSE) );
reserve( Word("for", Tag::FOR) );
reserve( Word("while", Tag::WHILE) );
reserve( Word("break", Tag::BREAK) );
reserve( Word("return", Tag::RETURN) );
reserve( Word("true", Tag::TRUE) );
reserve( Word("false", Tag::FALSE) ); peek = ' ';
line = 1; } //方便处理像>=,++等这些两个字符连在一起的运算符
bool Lexer::readnext(char c, std::ifstream &in) {
in >> peek;
if( peek != c)
return false;
peek = ' ';
return true;
} Word Lexer::scan(std::ifstream &in) {
//跳过空白符
while(!in.eof()) {
if(peek == ' ' || peek == '\t') {
in >> peek;
continue;
}
else if(peek == '\n')
++line;
else
break;
in >> peek;
} //处理分界符、运算符等
switch(peek) {
case '!':
if(readnext('=', in))
return Word("!=", Tag::NE);
else
return Word("!", Tag::NOT);
case '-':
if(readnext('-', in))
return Word("--", Tag::AUTOMINUS);
else
return Word("-", Tag::MINUS);
case '+':
if(readnext('+', in))
return Word("++", Tag::AUTOADD);
else
return Word("+", Tag::ADD);
case '|':
if(readnext('|', in))
return Word("||", Tag::OR);
else
return Word("error", Tag::ERROR);
case '&':
if(readnext('&', in))
return Word("&&", Tag::AND);
else
return Word("error", Tag::ERROR);
case '*':
in >> peek;
return Word("*", Tag::MUTIPLY);
case '/':
in >> peek;
return Word("/", Tag::DIVIDE);
case '%':
in >> peek;
return Word("%", Tag::MOD);
case '=':
if(readnext('=', in))
return Word("==", Tag::EQ);
else
return Word("=", Tag::ASSIN);
case '>':
if(readnext('=', in))
return Word(">=", Tag::GE);
else
return Word(">", Tag::GT);
case '<':
if(readnext('=', in))
return Word("<=", Tag::LE);
else
return Word("<", Tag::LS);
case ',':
in >> peek;
return Word(",", Tag::COMMA);
case ';':
in >> peek;
return Word(";", Tag::SEMICOLON);
case '(':
in >> peek;
return Word("(", Tag::LLBRACKET);
case ')':
in >> peek;
return Word(")", Tag::RLBRACKET);
case '[':
in >> peek;
return Word("[", Tag::LMBRACKET);
case ']':
in >> peek;
return Word("]", Tag::RMBRACKET);
case '{':
in >> peek;
return Word("{", Tag::LGBRACKET);
case '}':
in >> peek;
return Word("}", Tag::RGBRACKET);
} //处理常数
if(isdigit(peek)) {
int v = 0;
do {
v = 10*v + peek - 48;
in >> peek;
} while(isdigit(peek));
if(peek != '.')
return Word(std::to_string(v), Tag::NUM);
} //处理标识符
if(isalpha(peek)) {
std::ostringstream b;
do {
b << peek;
in >> peek;
} while(isalnum(peek) || peek == '_'); std::string tmp = b.str(); //判断是否为保留字
if(words.find(tmp) != words.end())
return words[tmp];
else
return Word(tmp, Tag::ID);
}
if(peek != ' ' && peek != '\t' && peek != '\n')
return Word("error", Tag::ERROR);
return Word("empty", Tag::EMPTY);
}

  设计完成后,自己写一个Main函数,在while循环中调用scan函数,每次打印出Word内容,就能够得到

简单的C语言编译器--词法分析器的更多相关文章

  1. 简单的C语言编译器--概述

      在学习了编译原理的相关知识后,逐渐的掌握一个编译器的结构.作用和实现方法.同时,希望自己在不断的努力下写出一个简单的C语言编译器. 实现步骤 词法分析器:将C语言测试代码分解成一个一个的词法单元: ...

  2. 简单的C语言编译器--语义制导翻译

      语法分析是最难写的,而这部分确实最伤脑的.大量的语义动作分析差点把我逼疯.   简而言之,这部分的作用就是在每次归约之后,都进行一些语义动作,最终让我们得到测试程序的三地址码,即中间代码. 1. ...

  3. 简单的C语言编译器--语法分析器

      语法分析算是最难的一部分了.总而言之,语法分析就是先设计一系列语法,然后再用设计好的语法去归约词法分析中的结果.最后将归约过程打印出来,或者生成抽象语法树. 1. 设计文法 以下是我的文法(引入的 ...

  4. 02.从0实现一个JVM语言之词法分析器-Lexer-03月02日更新

    从0实现JVM语言之词法分析器-Lexer 本次有较大幅度更新, 老读者如果对前面的一些bug, 错误有疑问可以复盘或者留言. 源码github仓库, 如果这个系列文章对你有帮助, 希望获得你的一个s ...

  5. 第一个C语言编译器是怎样编写的?

    首先向C语言之父Dennis MacAlistair Ritchie致敬! 当今几乎所有的实用的编译器/解释器(以下统称编译器)都是用C语言编写的,有一些语言比如Clojure,Jython等是基于J ...

  6. 【转】自己动手写SC语言编译器

    自序 编译原理与技术的一整套理论在整个计算机科学领域占有相当重要的地位,学习它对程序设计人员有很大的帮助.我们考究历史会发现那些人人称颂的程序设 计大师都是编译领域的高手,像写出BASIC语言的BIL ...

  7. 在线C语言编译器/解释器

    在线C语言编译器/解释器 本文介绍两个C语言在线解释器/编译器,这些工具可以提高代码片段检测方便的工作效率,并可以保证这些代码的正确性,而且还可以和别人一起编辑/分享之间的代码,这样可以共同分析代码并 ...

  8. 一个简单的C语言程序(详解)

    C Primer Plus之一个简单的C语言程序(详解) #include <stdio.h> int main(void) //一个简单的 C程序 { int num; //定义一个名为 ...

  9. C语言编译器和IDE的选择

    什么是编译器: CPU只认识几百个二进制形式的指令,C语言对CPU而言简直就是天书.C语言是用固定的词汇与格式组织起来,简单直观,程序员容易识别和理解. 这时候就需要一个工具,将C语言代码转换成CPU ...

随机推荐

  1. C#图解教程 第八章 表达式和运算符

    表达式和运算符 表达式字面量 整数字面量实数字面量字符字面量字符串字面量 求值顺序 优先级结合性 简单算术运算符求余运算符关系比较运算符和相等比较运算符递增运算符和递减运算符条件逻辑运算符逻辑运算符移 ...

  2. 自己写的,然后配合zepto+iscroll的上拉加载

    /** * Created by jl on 2016/3/28. *///初始化绑定iScroll控件var actHtml="";var myScroll,    pullUp ...

  3. RBM如何训练?

    RBM(Restricted Boltzman Machine,受限玻尔兹曼机)是深度学习的基础,虽然原理比较简单,但实际训练中用到了很多trick,在参考文献中,Hinton为我们披露了几个训练的细 ...

  4. TP90 95 99指标

    备忘下: 保证90%请求都能被响应的最小耗时 The tp90 is a minimum time under which 90% of requests have been served. tp90 ...

  5. SQL Server将自己的查询结果作为待查询数据子列之一

    嵌套子查询是SQL语句中比较常用的一种查询方法,开发过程中遇到查询需要将自己的某列作为待查询的数据,在参考别人的SQL语句的写法终于实现了自己需要的功能. 查询语句如下: SELECT DISTINC ...

  6. Hadoop的Archive归档命令使用指南

    hadoop不适合小文件的存储,小文件本省就占用了很多的metadata,就会造成namenode越来越大.Hadoop Archives的出现视为了缓解大量小文件消耗namenode内存的问题. 采 ...

  7. H5页面基于接口实现数据交互

    对于现在APP开发来说,目前流行的两个方式是原生和H5.就如同之前业界程序猿争论的BS和CS之争一样,业界对于H5和原生也有不小的争论.对于前者的争论在于PC端,后者在于移动端上体现. 那一个APP适 ...

  8. 原生jdbc操作mysql数据库详解

    首先给大家说一下使用JDBC链接数据库的步骤 1.加载链接数据库驱动 2.建立数据库链接 3.创建数据库操作对象 4.编写sql语句,执行sql语句 5.获取结果集 6.释放资源 我这边采用的是mav ...

  9. 如何彻底关闭windows defender

    我是一个喜欢裸奔的人,我不喜欢使用那些安全软件,什么360啊,什么毒霸啊让我深恶痛绝,就连windows自带的杀软我都不能忍啊,因为我平时喜欢找一下软件,很多的补丁和注册机,这些安全软件都会误报,所以 ...

  10. 3D模型展示以及体积、表面积计算

    本文原创 如转载请注明出处!!! 本博客地址http://www.cnblogs.com/we-jack 本文原创,如果有同样需求的小伙伴请第一时间联系我 或者在留言区留言 上次为大家提供了3D模型的 ...