一、程序功能描述
[实验项目]
以下为正则文法所描述的 C 语言子集单词符号的示例,请补充单词符号:
++,–, >>, <<, += , -= ,*=, /= ,&&(逻辑与),||(逻辑或),!(逻辑非)等等,给出补充后描述 C 语言子集单词符号的正则文法,设计并实现其词法分析程序。
以下文法为左线性正则文法:
<标识符>→字母︱ <标识符>字母︱ <标识符>d
<无符号整数>→数字︱ <无符号整数>数字
<单字符分界符>→+ ︱- ︱* ︱;︱, ︱(︱) ︱{︱}
<双字符分界符>→<大于>=︱<小于>=︱<小于>>︱<感叹号>=︱<等于>=︱<斜竖>*
<小于>→< <等于>→= <大于>→> <斜竖> →/
<感叹号>→!
该语言的保留字 :void、int、float、double、if、else、for、do、while 等等(也可补充)。
[设计说明]
(1)可将该语言设计成大小写不敏感,也可设计成大小写敏感,用户定义的标识符最长不超过 32 个字符;
(2)字母为 a-z A-Z,数字为 0-9;
(3)可以对上述文法进行扩充和改造;
(4)“/*……*/”和“//”(一行内)为程序的注释部分。
[设计要求]
(1)给出各单词符号的类别编码;
(2)词法分析程序应能发现输入串中的错误;
(3)词法分析作为单独一遍编写,词法分析结果为二元式序列组成的中间文件;
(4)设计至少 4 个测试用例(尽可能完备),并给出测试结果。
二、主要数据结构
变量及类型 | 用途 |
枚举类型 TokenType
|
定义了词法分析器中可能出现的各种记号类型。
每个枚举值代表一种特定的记号类型,例如 IDENTIFIER 表示标识符,INTEGER 表示整数,PLUS 表示加号等。 |
结构体 Token
|
表示一个记号(token),包含两个成员变量:
type:记号的类型,使用 TokenType 枚举类型表示。 value:记号的具体值,使用 std::string 表示。 提供了一个构造函数,用于初始化 Token 对象。 |
关键字映射表 keywords
|
使用 std::unordered_map 存储关键字及其对应的记号类型。
在词法分析器中,当遇到一个标识符时,可以通过查找这个映射表来确定它是否是一个关键字。 |
类 Lexer
|
实现了一个词法分析器类,用于将输入字符串转换为记号序列。
包含以下成员变量: input:输入的源代码字符串。 position:当前解析的位置。 current_char:当前解析的字符。 包含以下成员方法: advance:向前移动一个字符。 skip_whitespace:跳过空白字符。 identifier_or_keyword:处理标识符或关键字。 number:处理数字。 single_char_token:处理单字符记号。 double_char_token:处理双字符记号。 peek:查看下一个字符而不改变当前位置。 get_next_token:获取下一个记号。 |
三、程序结构描述
设计方法
总体设计思路:
词法分析器(Lexer):将输入的源代码字符串分解成一系列记号(tokens)。
记号(Token):表示源代码中的每个基本单元,包括类型和值。
关键字映射表:用于识别关键字并将其转换为相应的记号类型。
主函数:提供用户界面,允许用户选择测试用例或手动输入代码,并调用词法分析器进行处理。
函数定义及函数间的调用关系
函数名称 | 函数功能描述 |
main() | 程序入口点,负责初始化程序环境,显示菜单选项,接收用户输入,并根据用户选择调用相应的处理函数。 |
display_menu() | 显示程序的菜单选项,供用户选择操作。 |
get_user_choice() | 获取用户的输入选择,用于决定接下来要执行的操作。 |
Lexer(input_string) | Lexer 类的构造函数,接收输入字符串并初始化词法分析器的状态。 |
Get_next_token() | 从输入字符串中获取下一个记号(token),并返回该记号。这是词法分析的核心函数。 |
skip_whitespace() | 跳过输入字符串中的所有空白字符,包括空格、制表符、换行符等。 |
identifier_or_keyword() | 识别输入字符串中的标识符或关键字,并返回相应的记号。 |
number() | 识别输入字符串中的数字,并返回相应的记号。 |
single_char_token() | 处理输入字符串中的单个字符记号,如括号、运算符等。 |
double_char_token() | 处理输入字符串中的双字符记号,例如“==”、“!=”等。 |
peek() | 查看输入字符串中的下一个字符,但不改变当前的位置指针。 |
advance() | 在输入字符串中向前移动一个字符的位置指针。 |
is_alpha(char c) | 检查给定字符是否为字母。 |
is_digit(char c) | 检查给定字符是否为数字。 |
is_alphanumeric(char c) | 检查给定字符是否为字母或数字。 |
四、程序测试
测试用例1:
#include <stdio.h> int main() { printf("Hello, World!"); return 0; }
程序执行结果:
(28, #include) (2, <stdio.h>) (1, int) (2, main) (8, () (9, )) (10, { (1, printf) (8, ( (2, "Hello, World!") (9, )) (11, ; (12, return) (2, 0) (11, ; (13, })
测试用例2:
int main() { char c = 'A'; char str[] = "Hello, World!"; return 0; }
程序执行结果:
(1, int) (2, main) (8, () (9, )) (10, { (1, char) (1, c) (6, = (2, 'A') (11, ; (1, char) (1, str) (26, []) (6, = (2, "Hello, World!") (11, ; (12, return) (2, 0) (11, ; (13, })
测试用例3:
int main() { int a = 10; a = a + 1; return 0 }
程序执行结果:
(0, int) (0, main) (8, () (9, )) (22, {) (0, int) (0, a) (6, =) (1, 10) (7, ;) (0, a) (6, =) (0, a) (2, +) (1, 1) (7, ;) (0, return) (1, 0) (23, })
五、源代码附录
#include #include #include #include #include #include #include enum class TokenType { IDENTIFIER, INTEGER, PLUS, MINUS, TIMES, DIVIDE, ASSIGN, SEMICOLON, LPAREN, RPAREN, EOF_TOKEN, INCREMENT, DECREMENT, LSHIFT, RSHIFT, PLUS_ASSIGN, MINUS_ASSIGN, TIMES_ASSIGN, DIVIDE_ASSIGN, AND, OR, NOT, LBRACE, RBRACE, // 可以继续添加其他类型 }; struct Token { TokenType type; std::string value; Token(TokenType t, std::string v) : type(t), value(v) {} }; std::ostream& operator<<(std::ostream& os, const Token& token) { return os << "(" << static_cast(token.type) << ", " << token.value << ")"; } const std::unordered_map<std::string, TokenType> keywords = { {"void", TokenType::IDENTIFIER}, // 这里假设关键字也作为标识符处理,可以根据需求调整 {"int", TokenType::IDENTIFIER}, {"float", TokenType::IDENTIFIER}, {"double", TokenType::IDENTIFIER}, {"if", TokenType::IDENTIFIER}, {"else", TokenType::IDENTIFIER}, {"for", TokenType::IDENTIFIER}, {"do", TokenType::IDENTIFIER}, {"while", TokenType::IDENTIFIER} }; class Lexer { public: Lexer(const std::string& input) : input(input), position(0), current_char(input[position]) {} Token get_next_token(); private: void advance(); void skip_whitespace(); Token identifier_or_keyword(); Token number(); Token single_char_token(char ch, TokenType type); Token double_char_token(std::string chars, TokenType type); char peek(); std::string input; size_t position; char current_char; }; void Lexer::advance() { position++; if (position < input.length()) { current_char = input[position]; } else { current_char = '\0'; // 表示已到达输入末尾 } } void Lexer::skip_whitespace() { while (current_char != '\0' && isspace(current_char)) { advance(); } } Token Lexer::identifier_or_keyword() { std::string value; while (isalnum(current_char) || current_char == '_') { value += current_char; advance(); } auto it = keywords.find(value); if (it != keywords.end()) { return Token(it->second, value); } else { return Token(TokenType::IDENTIFIER, value); } } Token Lexer::number() { std::string value; while (isdigit(current_char)) { value += current_char; advance(); } if (current_char == '.') { value += current_char; advance(); while (isdigit(current_char)) { value += current_char; advance(); } return Token(TokenType::INTEGER, value); // 假设这里处理的是浮点数 } else { return Token(TokenType::INTEGER, value); } } Token Lexer::single_char_token(char ch, TokenType type) { advance(); return Token(type, std::string(1, ch)); } Token Lexer::double_char_token(std::string chars, TokenType type) { advance(); advance(); return Token(type, chars); } Token Lexer::get_next_token() { while (current_char != '\0') { if (isspace(current_char)) { skip_whitespace(); continue; } if (isalpha(current_char) || current_char == '_') { return identifier_or_keyword(); } if (isdigit(current_char)) { return number(); } if (current_char == '+') { if (peek() == '+') { return double_char_token("++", TokenType::INCREMENT); } else if (peek() == '=') { return double_char_token("+=", TokenType::PLUS_ASSIGN); } else { return single_char_token('+', TokenType::PLUS); } } if (current_char == '-') { if (peek() == '-') { return double_char_token("--", TokenType::DECREMENT); } else if (peek() == '=') { return double_char_token("-=", TokenType::MINUS_ASSIGN); } else { return single_char_token('-', TokenType::MINUS); } } if (current_char == '*') { if (peek() == '=') { return double_char_token("*=", TokenType::TIMES_ASSIGN); } else { return single_char_token('*', TokenType::TIMES); } } if (current_char == '/') { if (peek() == '=') { return double_char_token("/=", TokenType::DIVIDE_ASSIGN); } else { return single_char_token('/', TokenType::DIVIDE); } } if (current_char == '&') { if (peek() == '&') { return double_char_token("&&", TokenType::AND); } } if (current_char == '|') { if (peek() == '|') { return double_char_token("||", TokenType::OR); } } if (current_char == '!') { if (peek() == '=') { return double_char_token("!=", TokenType::NOT); } else { return single_char_token('!', TokenType::NOT); } } if (current_char == '=') { if (peek() == '=') { return double_char_token("==", TokenType::ASSIGN); } else { return single_char_token('=', TokenType::ASSIGN); } } if (current_char == ';') { return single_char_token(';', TokenType::SEMICOLON); } if (current_char == '(') { return single_char_token('(', TokenType::LPAREN); } if (current_char == ')') { return single_char_token(')', TokenType::RPAREN); } if (current_char == '{') { return single_char_token('{', TokenType::LBRACE); } if (current_char == '}') { return single_char_token('}', TokenType::RBRACE); } std::cout << "未识别的字符: '" << current_char << "'" << std::endl; throw std::runtime_error("非法字符"); } return Token(TokenType::EOF_TOKEN, ""); } char Lexer::peek() { if (position + 1 < input.length()) { return input[position + 1]; } else { return '\0'; } } int main() { // 从 example.txt 文件中读取内容 std::ifstream input_file("example.txt"); if (!input_file.is_open()) { std::cerr << "无法打开 example.txt 文件" << std::endl; return 1; } std::string input((std::istreambuf_iterator(input_file)), std::istreambuf_iterator()); input_file.close(); // 创建 Lexer 对象 Lexer lexer(input); // 打开 result.txt 文件,准备写入结果 std::ofstream output_file("result.txt", std::ios::out | std::ios::trunc); if (!output_file.is_open()) { std::cerr << "无法打开 result.txt 文件" << std::endl; return 1; } // 获取并输出记号 Token token = lexer.get_next_token(); while (token.type != TokenType::EOF_TOKEN) { output_file << token << std::endl; token = lexer.get_next_token(); } output_file.close(); std::cout << "词法分析完成,结果已写入 result.txt 文件" << std::endl; return 0; }