C语言小项目-词法分析器

1. 什么是词法分析器?

        词法分析器是编译器中的第一个阶段,其主要任务是扫描输入的源代码字符流,并将字符组成的序列转换为有意义的标记(Token)。每个 Token 包含一个词法单元的信息,如关键字、标识符、运算符、常量等。例如,对于表达式 int a = 10;,词法分析器会生成诸如 INTIDENTIFIER(a)ASSIGNNUMBER(10)SEMICOLON 等 Token。

        一个完整的编译器,大致会经历如下几个阶段:

各个阶段的职责,简单描述如下:
1. 词法分析:对源文件进行扫描,将源文件的字符划分为一个一个的记号 (token ) ( 注:类似中文中的分词 )
2. 语法分析:根据语法规则将 Token 序列构造为语法树。
3. 对语法树的各个结点之间的关系进行检查,检查语义规则是否有被违背,同时对语法树进行必要的优化,此为语义分析。
4. 遍历语法树的结点,将各结点转化为中间代码,并按特定的顺序拼装起来,此为中间代码生成。
5. 对中间代码进行优化
6. 将中间代码转化为目标代码
7. 对目标代码进行优化,生成最终的目标程序以上阶段的划分仅仅是逻辑上的划分。实际的编译器中,常常会将几个阶段组合在一起,甚至还可以能省略其中某些阶段。

2.词法分析

编译器扫描源文件的字符流,过滤掉字符流中的空格、注释等,并将其划分为一个个的 token ,生成 token 序列。
例如下面的语句:
a = value + sum(5, 123);
将被拆分为 11 token
a           标识符
=           赋值运算符
value       标识符
+           加号
sum         标识符
(           左括号
5           整数
,           逗号
123         整数
)           右括号
;           分号
本质上,词法分析阶段所做的事情就是模式匹配。判断哪些字符属于标识符,哪些字
符属于关键字,哪些字符属于整数 ...
有限状态机
那么该如何做模式匹配呢?这就要用到有限状态机了 ( 注:术语都是纸老虎,有限状态机一般都是用 switch + while + if 语句实现的 )
单字符 Token ,可以直接识别 : ; ) ( { }
双字符 Token ,需要用 if 语句进行判断: +=, -=, *=, ==, !=
多字符 Token ,需要用 while 语句一直读取到结束标志符 : 标识符,字符串,数字,字符等。
有限状态机如下图所示:

3. 词法分析器的工作流程

步骤一:字符扫描

词法分析器从源代码中逐字符读取输入,构成字符流进行处理。

步骤二:词法规则定义

定义词法规则,包括关键字、运算符、标识符、常量等的正则表达式或者状态机来描述。

步骤三:识别和生成 Token

根据定义的规则,识别输入字符流中的 Token,生成相应的词法单元。

步骤四:错误处理

处理不符合语法的输入或者非法字符,并给出适当的错误提示。

该词法分析器既能交互式地运行,也能够处理 '. c ' 文件。
交互式方式,如下图所示:

整个项目代码如下:

// scanner.h
#ifndef scanner_h
#define scanner_h

//分词的类型
typedef enum {
    // single-character tokens
    TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN,		// '(', ')'
    TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET,	// '[', ']'
    TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE,  		// '{', '}'
    TOKEN_COMMA, TOKEN_DOT, TOKEN_SEMICOLON,	// ',', '.', ';'
    TOKEN_TILDE,  // '~'
    // one or two character tokens
    TOKEN_PLUS, TOKEN_PLUS_PLUS, TOKEN_PLUS_EQUAL, // '+', '++', '+='
    // '-', '--', '-=', '->'
    TOKEN_MINUS, TOKEN_MINUS_MINUS, TOKEN_MINUS_EQUAL, TOKEN_MINUS_GREATER,
    TOKEN_STAR, TOKEN_STAR_EQUAL,			// '*', '*='
    TOKEN_SLASH, TOKEN_SLASH_EQUAL, 		// '/', '/=', 
    TOKEN_PERCENT, TOKEN_PERCENT_EQUAL, 	// '%', '%='
    TOKEN_AMPER, TOKEN_AMPER_EQUAL, TOKEN_AMPER_AMPER, 	// '&', '&=', '&&'
    TOKEN_PIPE, TOKEN_PIPE_EQUAL, TOKEN_PIPE_PIPE,		// '|', '|=', '||'
    TOKEN_HAT, TOKEN_HAT_EQUAL, 		// '^', '^='
    TOKEN_EQUAL, TOKEN_EQUAL_EQUAL, 	// '=', '=='
    TOKEN_BANG, TOKEN_BANG_EQUAL,	  	// '!', '!='
    TOKEN_LESS, TOKEN_LESS_EQUAL, TOKEN_LESS_LESS, 				// '<', '<=', '<<'
    TOKEN_GREATER, TOKEN_GREATER_EQUAL, TOKEN_GREAER_GREATER, 	// '>', '>=', '>>'
    // 字面值: 标识符, 字符, 字符串, 数字
    TOKEN_IDENTIFIER, TOKEN_CHARACTER, TOKEN_STRING, TOKEN_NUMBER,
    // 关键字
    TOKEN_SIGNED, TOKEN_UNSIGNED,
    TOKEN_CHAR, TOKEN_SHORT, TOKEN_INT, TOKEN_LONG,
    TOKEN_FLOAT, TOKEN_DOUBLE,
    TOKEN_STRUCT, TOKEN_UNION, TOKEN_ENUM, TOKEN_VOID,
    TOKEN_IF, TOKEN_ELSE, TOKEN_SWITCH, TOKEN_CASE, TOKEN_DEFAULT,
    TOKEN_WHILE, TOKEN_DO, TOKEN_FOR,
    TOKEN_BREAK, TOKEN_CONTINUE, TOKEN_RETURN, TOKEN_GOTO,
    TOKEN_CONST, TOKEN_SIZEOF, TOKEN_TYPEDEF,
    // 辅助Token
    TOKEN_ERROR, TOKEN_EOF
} TokenType;

//分词的结构体
typedef struct {
    TokenType type;
    const char* start;	// start指向source中的字符,source为读入的源代码。
    int length;		    // length表示这个Token的长度
    int line;		    // line表示这个Token在源代码的哪一行, 方便后面的报错
} Token;

// 对 Scanner 进行初始化 
void initScanner(const char* source);

// 调用scanToken(), 返回下一个Token.
Token scanToken();

#endif
// scanner.c
#include <stdlib.h>
#include "scanner.h"
#include <stdbool.h>

//扫描器结构体
typedef struct {
    const char* start;  // 标记Token的起始位置
    const char* current; // 当前要解析的字符位置
    int line;            // 行数
} Scanner;

// 全局变量
Scanner scanner;

void initScanner(const char* source) {
    // 初始化scanner
    scanner.start = NULL;
    scanner.current = source;
    scanner.line = 1;
}

/***************************************************************************************
 *                                   辅助方法											*
 ***************************************************************************************/
//判断字符是否为大小写字符或下划线
static bool isAlpha(char c) {
    return (c >= 'a' && c <= 'z') ||
        (c >= 'A' && c <= 'Z') ||
        c == '_';
}

//判断字符是否为数字
static bool isDigit(char c) {
    return c >= '0' && c <= '9';
}

//判断扫描器是否到末尾
static bool isAtEnd() {
    return *scanner.current == '\0';
}

//返回当前字符并移动到下一个字符。
static char advance() {
    return *scanner.current++;
}

//返回扫描器当前字符但不移动位置。
static char peek() {
    return *scanner.current;
}

//返回下一个字符(如果有的话),但不移动位置。
static char peekNext() {
    if (isAtEnd()) return '\0';
    return *(scanner.current + 1);
}

//验证当前字符是否符合预期的字符。
static bool match(char expected) {
    if (isAtEnd()) return false;
    if (peek() != expected) return false;
    scanner.current++;
    return true;
}

// 传入TokenType, 创建对应类型的Token,并返回。
static Token makeToken(TokenType type) {
    Token token;
    token.type = type;
    token.start = scanner.start;
    token.length = (int)(scanner.current - scanner.start);
    token.line = scanner.line;
    return token;
}

// 遇到不能解析的情况时,我们创建一个ERROR Token. 比如:遇到@,$等符号时,比如字符串,字符没有对应的右引号时。
static Token errorToken(const char* message) {
    Token token;
    token.type = TOKEN_ERROR;
    token.start = message;
    token.length = (int)strlen(message);
    token.line = scanner.line;
    return token;
}

static void skipWhitespace() {
    // 跳过空白字符: ' ', '\r', '\t', '\n'和注释
    // 注释以'//'开头, 一直到行尾
    // 注意更新scanner.line!
    while (!isAtEnd()) {
        char current = peek();

        //跳过空白字符: ' ', '\r', '\t', '\n'
        if (current == ' ' || current == '\r' || current == '\t' || current == '\n') {
            if (current == '\n') {
                scanner.line++;     //更新行数
            }
            advance();      //移动指针
        }
        else if(current == '/' && peekNext() == '/')    // 跳过注释以'//'开头, 一直到行尾
        {
            // 跳过整行注释
            while (!isAtEnd() && current != '\n')
            {
                advance();      // 继续移动到下一个字符,直到换行符或文件结束
            }
            
        }
        else
        {
            break;      // 如果遇到非空白字符或者非注释内容,则结束跳过过程
        }
    }
}

// 参数说明:
// start: 指定从哪个索引位置开始比较字符串。
// length: 指定要比较的字符长度。
// rest: 要比较的具体内容,通常是关键字的字符串表示。
// type: 如果完全匹配,则返回的 token 类型。
static TokenType checkKeyword(int start, int length, const char* rest, TokenType type) {
    int len = (int)(scanner.current - scanner.start);   // 计算当前 token 的长度
    // start + length: 关键字的长度
    // memcmp: 逐字节比较从 scanner.start + start 开始,长度为 length 的字符与 rest 是否完全一致。
    if (start + length == len && memcmp(scanner.start + start, rest, length) == 0) {
        return type;
    }
    return TOKEN_IDENTIFIER;
}

// 判断当前Token到底是标识符还是关键字
static TokenType identifierType() {
    // 确定identifier类型主要有两种方式:
    // 1. 将所有的关键字放入哈希表中,然后查表确认
    // 2. 将所有的关键字放入Trie树中,然后查表确认
    // Trie树的方式不管是空间上还是时间上都优于哈希表的方式
    char c = scanner.start[0];
    // 用switch语句实现Trie树
    switch (c) {
        // 单字符开头的关键字或标识符
    case 'b': return checkKeyword(1, 4, "reak", TOKEN_BREAK);
    case 'c':
        if (scanner.start[1] == 'a') {
            return checkKeyword(2, 2, "se", TOKEN_CASE);
        }
        else if (scanner.start[1] == 'h') {
            return checkKeyword(2, 2, "ar", TOKEN_CHAR);
        }
        else if (scanner.start[1] == 'o' && scanner.start[2] == 'n' && scanner.start[3] == 't') {
            return checkKeyword(4, 4, "inue", TOKEN_CONTINUE);
        }
        else if (scanner.start[1] == 'o' && scanner.start[2] == 'n' && scanner.start[3] == 's') {
            return checkKeyword(4, 1, "t", TOKEN_CONST);
        }
        else
        {
            return TOKEN_IDENTIFIER;
        }
    case 'd':
        if (scanner.start[1] == 'e') {
            return checkKeyword(2, 5, "fault", TOKEN_DEFAULT);
        }
        else if (scanner.start[1] == 'o') {
            return checkKeyword(2, 4, "uble", TOKEN_DOUBLE);
        }
        else
        {
            return TOKEN_IDENTIFIER;
        }
    case 'e':
        if (scanner.start[1] == 'n') {
            return checkKeyword(2, 2, "um", TOKEN_ENUM);
        }
        else if (scanner.start[1] == 'l') {
            return checkKeyword(2, 2, "se", TOKEN_ELSE);
        }
        else
        {
            return TOKEN_IDENTIFIER;
        }
    case 'f':
        if (scanner.start[1] == 'o') {
            return checkKeyword(2, 1, "r", TOKEN_FOR);
        }
        else if (scanner.start[1] == 'l') {
            return checkKeyword(2, 2, "oat", TOKEN_FLOAT);
        }
        else {
            return TOKEN_IDENTIFIER;
        }
    case 'g': return checkKeyword(1, 2, "oto", TOKEN_GOTO);
    case 'i':
        if (scanner.start[1] == 'f') {
            return checkKeyword(2, 0, "", TOKEN_IF);
        }
        else if (scanner.start[1] == 'n') {
            return checkKeyword(2, 1, "t", TOKEN_INT);
        }
        else {
            return TOKEN_IDENTIFIER;
        }
    case 'l': return checkKeyword(1, 2, "ng", TOKEN_LONG);
    case 'r': return checkKeyword(1, 5, "eturn", TOKEN_RETURN);
    case 's':
        if (scanner.start[1] == 'i' && scanner.start[2] == 'g') {
            return checkKeyword(3, 3, "ned", TOKEN_SIGNED);
        }
        else if (scanner.start[1] == 'i' && scanner.start[2] == 'z') {
            return checkKeyword(3, 3, "eof", TOKEN_SIZEOF);
        }
        else if (scanner.start[1] == 'w') {
            return checkKeyword(2, 4, "itch", TOKEN_SWITCH);
        }
        else if (scanner.start[1] == 't') {
            return checkKeyword(2, 4, "ruct", TOKEN_STRUCT);
        }
        else if (scanner.start[1] == 'h') {
            return checkKeyword(2, 3, "ort", TOKEN_SHORT);
        }
        else {
            return TOKEN_IDENTIFIER;
        }
    case 't': return checkKeyword(1, 6, "ypedef", TOKEN_TYPEDEF);
    case 'u':
        if (scanner.start[1] == 'n' && scanner.start[2] == 's') {
            return checkKeyword(3, 5, "igned", TOKEN_UNSIGNED);
        }
        else if (scanner.start[1] == 'n' && scanner.start[2] == 'i') {
            return checkKeyword(3, 2, "on", TOKEN_UNION);
        }
        else {
            return TOKEN_IDENTIFIER;
        }
    case 'v': return checkKeyword(1, 3, "oid", TOKEN_VOID);
    case 'w': return checkKeyword(1, 4, "hile", TOKEN_WHILE);
    default:
        return TOKEN_IDENTIFIER; // 默认为标识符
    }
}

static Token identifier() {
    // IDENTIFIER包含: 字母,数字和下划线
    // 用来判断下一个字符是否是字母或数字。
    while (isAlpha(peek()) || isDigit(peek())) {
        advance();
    }
    // 这样的Token可能是标识符, 也可能是关键字, identifierType()是用来确定Token类型的
    return makeToken(identifierType());
}

// 创建一个数字类型的token并返回
static Token number() {
    // 简单起见,我们将NUMBER的规则定义如下:
    // 1. NUMBER可以包含数字和最多一个'.'号
    // 2. '.'号前面要有数字
    // 3. '.'号后面也要有数字
    // 这些都是合法的NUMBER: 123, 3.14
    // 这些都是不合法的NUMBER: 123., .14
    // 初始化token
    Token token;
    token.type = TOKEN_NUMBER;
    token.start = scanner.start;
    token.line = scanner.line;
    token.length = 0;
    
    // 扫描整数部分
    while (isDigit(peek()))
    {
        advance();
    }

    // 检查小数点
    if (peek() == '.' && isDigit(peekNext())) {
        // 扫描小数部分
        advance();      // 消耗小数点
        while (isDigit(peek()))
        {
            advance();
        }
    }

    //更新token的长度
    token.length = (int)(scanner.current - token.start);

    return token;
}

static Token string() {
    // 字符串以"开头,以"结尾,而且不能跨行
    // 初始化token
    Token token;
    token.type = TOKEN_STRING;
    token.start = scanner.start;
    token.length = 0;
    token.line = scanner.line;

    // 检查字符串开始的双引号
    if (!match('"')) {
        return errorToken("Unterminated string.");
    }

    // 扫描字符串内容
    while (!isAtEnd()) {
        char c = peek();
        if (c == '"') {
            // 找到字符串结束的双引号
            advance(); // 消耗双引号
            break;
        }
        else if (c == '\n') {
            // 字符串不能跨行
            return errorToken("String literal cannot span multiple lines.");
        }
        else
        {
            advance();  // 继续扫描字符串内容
        }
    }

    //更新token的长度
    token.length = (int)(scanner.current - token.start);

    // 检查是否找到结束的双引号
    if (!match('"')) {
        return errorToken("Unterminated string.");
    }

    return token;

}

static Token character() {
    // 字符'开头,以'结尾,而且不能跨行
    // 初始化token
    Token token;
    token.type = TOKEN_CHARACTER;
    token.start = scanner.start;
    token.length = 0;
    token.line = scanner.line;

    // 检查字符开始的单引号
    if (!match('\''))
    {
        return errorToken("Invalid character literal.");
    }

    // 扫描字符串的内容
    if (isAtEnd()) {
        return errorToken("Unterminated character literal.");
    }

    char c = advance(); // 获取字符内容

    // 检查字符结束的单引号
    if (!match('\'')) {
        return errorToken("Unterminated character literal.");
    }

    // 检查字符是否跨行
    if (c == '\n') {
        return errorToken("Character literal cannot contain newline.");
    }

    // 更新 Token 长度
    token.length = (int)(scanner.current - token.start);

    return token;
}

/***************************************************************************************
 *                                   	分词											  *
 ***************************************************************************************/
Token scanToken() { // 有限状态机
    // 跳过前置空白字符和注释
    skipWhitespace();
    // 记录下一个Token的起始位置
    scanner.start = scanner.current;

    if (isAtEnd()) return makeToken(TOKEN_EOF);

    char c = advance();
    if (isAlpha(c)) return identifier();
    if (isDigit(c)) return number();

    switch (c) {
        // single-character tokens
    case '(': return makeToken(TOKEN_LEFT_PAREN);
    case ')': return makeToken(TOKEN_RIGHT_PAREN);
    case '[': return makeToken(TOKEN_LEFT_BRACKET);
    case ']': return makeToken(TOKEN_RIGHT_BRACKET);
    case '{': return makeToken(TOKEN_LEFT_BRACE);
    case '}': return makeToken(TOKEN_RIGHT_BRACE);
    case ',': return makeToken(TOKEN_COMMA);
    case '.': return makeToken(TOKEN_DOT);
    case ';': return makeToken(TOKEN_SEMICOLON);
    case '~': return makeToken(TOKEN_TILDE);

        // one or two characters tokens
    case '+':
        if (match('+')) return makeToken(TOKEN_PLUS_PLUS);
        else if (match('=')) return makeToken(TOKEN_PLUS_EQUAL);
        else return makeToken(TOKEN_PLUS);
    case '-':
        if (match('-')) return makeToken(TOKEN_MINUS_MINUS);
        else if (match('=')) return makeToken(TOKEN_MINUS_EQUAL);
        else if (match('>')) return makeToken(TOKEN_MINUS_GREATER);
        else return makeToken(TOKEN_MINUS);
    case '/':
        if (match('=')) return makeToken(TOKEN_SLASH_EQUAL);
        else return makeToken(TOKEN_SLASH);
    case '%':
        if (match('=')) return makeToken(TOKEN_PERCENT_EQUAL);
        else return makeToken(TOKEN_PERCENT);
    case '&':
        if (match('=')) return makeToken(TOKEN_AMPER_EQUAL);
        else if (match('&')) return makeToken(TOKEN_AMPER_AMPER);
        else return makeToken(TOKEN_AMPER);
    case '|':
        if (match('=')) return makeToken(TOKEN_PIPE_EQUAL);
        else if (match('|')) return makeToken(TOKEN_PIPE_PIPE);
        else return makeToken(TOKEN_PIPE);
    case '^':
        if (match('=')) return makeToken(TOKEN_HAT_EQUAL);
        else return makeToken(TOKEN_HAT);
    case '=':
        if (match('=')) return makeToken(TOKEN_EQUAL_EQUAL);
        else return makeToken(TOKEN_EQUAL);
    case '!':
        if (match('=')) return makeToken(TOKEN_BANG_EQUAL);
        else return makeToken(TOKEN_BANG);
    case '<':
        if (match('=')) return makeToken(TOKEN_LESS_EQUAL);
        else if (match('<')) return makeToken(TOKEN_LESS_LESS);
        else return makeToken(TOKEN_LESS);
    case '>':
        if (match('=')) return makeToken(TOKEN_GREATER_EQUAL);
        else if (match('>')) return makeToken(TOKEN_GREAER_GREATER);
        else return makeToken(TOKEN_GREATER);

        // various-character tokens
    case '"': return string();
    case '\'': return character();

    default:
        return errorToken("Unexpected character.");
    }
}

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

#include "scanner.h"

static char* strtoken(Token token) {
    switch (token.type) {
        // 单字符Token
    case TOKEN_LEFT_PAREN:      return "(";
    case TOKEN_RIGHT_PAREN:     return ")";
    case TOKEN_LEFT_BRACKET:    return "[";
    case TOKEN_RIGHT_BRACKET:   return "]";
    case TOKEN_LEFT_BRACE:      return "{";
    case TOKEN_RIGHT_BRACE:     return "}";
    case TOKEN_COMMA:           return ",";
    case TOKEN_DOT:             return ".";
    case TOKEN_SEMICOLON:       return ";";
    case TOKEN_TILDE:           return "~";
        // 一个字符或两个字符的Token
    case TOKEN_PLUS:            return "+";
    case TOKEN_PLUS_PLUS:       return "++";
    case TOKEN_PLUS_EQUAL:      return "+=";
    case TOKEN_MINUS:           return "-";
    case TOKEN_MINUS_MINUS:     return "--";
    case TOKEN_MINUS_EQUAL:     return "-=";
    case TOKEN_MINUS_GREATER:   return "->";
    case TOKEN_STAR:            return "*";
    case TOKEN_STAR_EQUAL:      return "*=";
    case TOKEN_SLASH:           return "/";
    case TOKEN_SLASH_EQUAL:     return "/=";
    case TOKEN_PERCENT:         return "%";
    case TOKEN_PERCENT_EQUAL:   return "%=";
    case TOKEN_AMPER:           return "&";
    case TOKEN_AMPER_EQUAL:     return "&=";
    case TOKEN_AMPER_AMPER:     return "&&";
    case TOKEN_PIPE:            return "|";
    case TOKEN_PIPE_EQUAL:      return "|=";
    case TOKEN_PIPE_PIPE:       return "||";
    case TOKEN_HAT:             return "^";
    case TOKEN_HAT_EQUAL:       return "^=";
    case TOKEN_EQUAL:           return "=";
    case TOKEN_EQUAL_EQUAL:     return "==";
    case TOKEN_BANG:            return "!";
    case TOKEN_BANG_EQUAL:      return "!=";
    case TOKEN_LESS:            return "<";
    case TOKEN_LESS_EQUAL:      return "<=";
    case TOKEN_LESS_LESS:       return "<<";
    case TOKEN_GREATER:         return ">";
    case TOKEN_GREATER_EQUAL:   return ">=";
    case TOKEN_GREAER_GREATER: 	return ">>";
        // 字面值: 标识符, 字符, 字符串, 数字
    case TOKEN_IDENTIFIER:      return "IDENTIFIER";
    case TOKEN_CHARACTER:       return "CHARACTER";
    case TOKEN_STRING:          return "STRING";
    case TOKEN_NUMBER:          return "NUMBER";
        // 关键字
    case TOKEN_SIGNED:          return "SIGNED";
    case TOKEN_UNSIGNED:        return "UNSIGNED";
    case TOKEN_CHAR:            return "CHAR";
    case TOKEN_SHORT:           return "SHORT";
    case TOKEN_INT:             return "INT";
    case TOKEN_LONG:            return "LONG";
    case TOKEN_FLOAT:           return "FLOAT";
    case TOKEN_DOUBLE:          return "DOUBLE";
    case TOKEN_STRUCT:          return "STRUCT";
    case TOKEN_UNION:           return "UNION";
    case TOKEN_ENUM:            return "ENUM";
    case TOKEN_VOID:            return "VOID";
    case TOKEN_IF:              return "IF";
    case TOKEN_ELSE:            return "ELSE";
    case TOKEN_SWITCH:          return "SWITCH";
    case TOKEN_CASE:            return "CASE";
    case TOKEN_DEFAULT:         return "DEFAULT";
    case TOKEN_WHILE:           return "WHILE";
    case TOKEN_DO:              return "DO";
    case TOKEN_FOR:             return "FOR";
    case TOKEN_BREAK:           return "BREAK";
    case TOKEN_CONTINUE:        return "CONTINUE";
    case TOKEN_RETURN:          return "RETURN";
    case TOKEN_GOTO:            return "GOTO";
    case TOKEN_CONST:           return "CONST";
    case TOKEN_SIZEOF:          return "SIZEOF";
    case TOKEN_TYPEDEF:         return "TYPEDEF";
        // 辅助Token
    case TOKEN_ERROR:           return "ERROR";
    case TOKEN_EOF:             return "EOF";
    }
}

static void run(const char* source) {
    initScanner(source);
    int line = -1;
    // 打印Token, 遇到TOKEN_EOF为止
    for (;;) {
        Token token = scanToken();
        if (token.line != line) {
            printf("%4d ", token.line);
            line = token.line;
        }
        else {
            printf("   | ");
        }
        printf("%12s '%.*s'\n", strtoken(token), token.length, token.start);

        if (token.type == TOKEN_EOF) break;
    }
}

static void repl() {
    // 与用户交互,用户每输入一行代码,分析一行代码,并将结果输出
    // repl是"read evaluate print loop"的缩写
    char line[1024];
    for (; ;) {
        printf("> ");
        if (!fgets(line, sizeof(line), stdin)) {
            printf("\n");
            break;
        }
        run(line);
    }
}

static char* readFile(const char* path) {
    // 用户输入文件名,将整个文件的内容读入内存,并在末尾添加'\0'
    // 注意: 这里应该使用动态内存分配,因此应该事先确定文件的大小。
    FILE* file = fopen(path, "rb");
    if (file == NULL) {
        fprintf(stderr, "Could not open file '%s'\n", path);
        exit(1);
    }

    fseek(file, 0L, SEEK_END);
    long fileSize = ftell(file);
    rewind(file);

    char* buffer = (char*)malloc(fileSize + 1);
    if (buffer == NULL) {
        fprintf(stderr, "Not enough memory to read '%s'\n", path);
        fclose(file);
        exit(1);
    }

    size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
    if (bytesRead < fileSize) {
        fprintf(stderr, "Could not read file '%s'\n", path);
        fclose(file);
        free(buffer);
        exit(1);
    }

    buffer[bytesRead] = '\0';
    fclose(file);
    return buffer;
}

static void runFile(const char* path) {
    // 处理'.c'文件:用户输入文件名,分析整个文件,并将结果输出
    char* content = readFile(path);
    run(content);
    free(content);
}

int main(int argc, const char* argv[]) {
    if (argc == 1) {
        // ./scanner 没有参数,则进入交互式界面
        repl();
    }
    else if (argc == 2) {
        // ./scanner file 后面跟一个参数,则分析整个文件
        runFile(argv[1]);
    }
    else {
        fprintf(stderr, "Usage: scanner [path]\n");
        exit(1);
    }

    return 0;
}


  • 41
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值