一、程序功能描述
[实验项目]
以下为正则文法所描述的 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;
}
	
