首页 > 其他分享 >C语言小项目-词法分析器

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

时间:2024-07-02 22:31:41浏览次数:23  
标签:case return EQUAL makeToken 分析器 C语言 词法 TOKEN Token

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;
}


标签:case,return,EQUAL,makeToken,分析器,C语言,词法,TOKEN,Token
From: https://blog.csdn.net/qq_42037383/article/details/140136618

相关文章

  • C语言编程-基于单链表实现贪吃蛇游戏
    基于单链表实现贪吃蛇游戏1.定义结构体参数蛇行走的方向蛇行走的状态蛇身节点类维护蛇的结构体型2.游戏运行前预备工作定位光标位置游戏欢迎界面绘制游戏地图(边界)初始化游戏中的蛇身创建食物3.游戏运行下一个位置是食物,就吃掉食物,释放该节点下一个位置不是......
  • c语言函数指针和指针函数的区别及代码示例
    c语言函数指针和指针函数的区别及代码示例在C或C++中,函数指针和指针函数是两个容易混淆但本质不同的概念。理解它们的区别对于深入掌握C/C++的指针和函数特性至关重要。1.函数指针定义:函数指针是指向函数的指针。它存储了函数的地址,通过它可以调用该函数。语法:返回类型(*指......
  • C语言打印倒三角形,底边长n作为参数输入,从键盘输入
    打印倒三角形,底边长n作为参数输入,从键盘输入。#include<stdio.h>intmain(intargc,charconst*argv[]){  inti,j,k,l,n;  printf("请输入底边长:\n");  scanf("%d",&n);  while(getchar()!='\n');  printf("输出图形如下:\......
  • 7.2面试错+C语言复习
    7.2面试错题设有如下定义:structsk{inta;floatb;}data,*p;若有p=&data;,则对data中的a域的正确引用是(B)A.(*p).data.aB.(*p).aC.p->data.aD.p.data.a1.请简要叙述全局变量和局部变量的区别*存储位置:全局变量存储在静态存储区,而局部变量存储在栈上。**作用范围:全......
  • Python123:找出不是两个数组共有的元素、矩阵运算、方阵循环右移(C语言)
    文章目录1、找出不是两个数组共有的元素2、矩阵运算3、方阵循环右移1、找出不是两个数组共有的元素题目:给定两个整型数组,本题要求找出不是两者共有的元素。输入格式:输入分别在两行中给出两个整型数组,每行先给出正整数N(≤20),随后是N个整数,其间以空格分隔。‪‬‪......
  • 【C语言入门】C语言入门:探索编程世界的基础概念
    ......
  • C语言复习
    C语言必问(待更新)1、变量的声明和定义有什么区别为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,但是只在一个地方定义。加入extern修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。2、C语言中,变量的作用域在C语言中,......
  • #C语言基础 笔记二
    强制转换inta=5;floatb=a/2;//2.000000floatb=(float)a/2;//2.500000#include<stdio.h>intmain(intargc,charconst*argv[]){inta=5;floatb=(float)a/2;printf("%d%f\n",a,b);return0;}分支语句if......
  • 墨烯的C语言技术栈-C语言基础-002
    二.第一个C语言写第一个C语言程序环境VS20221.打开VS20222.创建项目3.创建源文件.c源文件.h头文件.cpp编译器会按照C++的语法编译代码.c编译器会按照C语言的语法编译代码C语言的源文件必须要有main方法//标准的主函数的写法intmain(){printf("hehe\n"......
  • 大话C语言:第26篇 静态库
    1静态库概述C语言静态库(StaticLibrary)是一种包含一组目标文件的归档文件,这些目标文件通常是由多个C语言源文件编译而成的。静态库在程序编译时被链接到目标程序中,成为程序的一部分,因此在运行时不再需要额外的库文件。与动态库(DynamicLibrary)不同,静态库在编译时就已经被完全......