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