实验环境:
操作系统:window 10
编写环境:Visual Studio 2022
编写语言:c++
分析语言:PL/0
解析:
当我们编写程序时,通常需要从文件或用户输入中获取数据,这些数据需要经过解析才能被程序所理解。而词法分析就是将输入流中的字符序列分解成一个一个的单词,每个单词都有其对应的类型和值。
本文介绍了一个简单的词法分析器程序,它可以读取输入的文件并将其分解为单个单词。程序采用 C++ 语言编写,并使用了文件流、字符串和枚举等多种数据类型和语法。
在程序中,定义了四种单词类型:标识符(ident)、数字(number)、关键字(keyword)和符号(symbol)。其中,标识符和关键字都是由字母和数字组成的字符串,而数字则是由数字组成的字符串。符号则是单个字符,例如加号、减号、乘号、除号等。
程序通过逐个读取输入流中的字符,并根据其类型进行判断,从而将其归类为上述四种类型中的一种。对于标识符和关键字,程序会读取连续的字母和数字,直到遇到一个非字母或数字的字符。程序还定义了一些常见的符号,并根据读取到的字符进行匹配,从而确定其类型和值。
最后,程序将每个单词的类型和值写入输出文件中,以供后续程序使用。这个简单的词法分析器程序可以帮助我们更好地理解程序的输入解析过程,也可以作为其他编译器程序的基础组件进行扩展和优化。
在本程序中,还定义了一个关键字列表,用于判断是否为关键字。程序还实现了一个函数,用于判断一个字符是否是有意义的符号,并通过 switch 语句将其转换为对应的符号类型。同时,程序还处理了注释和空格等无意义字符。
此外,程序还提供了一个函数,用于在控制台输出输入文件的内容,以便我们更好地了解程序的输入数据。最后,程序将每个单词的类型和值写入输出文件中,以供后续程序使用。
在使用这个程序时,我们只需要将需要解析的文件作为输入,运行程序即可得到输出文件。输出文件中的每一行都表示一个单词,其中括号内的第一个值表示单词的类型,第二个值表示单词的值。
总之,词法分析是编译器中的一个重要组成部分,它负责将输入流中的字符序列分解成一个一个的单词,为后续的语法分析和代码生成等工作提供了基础。本文介绍了一个简单的词法分析器程序,它可以帮助我们更好地理解程序的输入解析过程,也可以作为其他编译器程序的基础组件进行扩展和优化。
注意:
input.txt和output.txt 文件要事先创建好,并放在和cpp文件同一目录下,否则运行会报错;
源程序:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;
// 定义单词类型枚举
enum TokenType {
ident, // 标识符
number, // 数字
keyword ,// 关键字
symbol //符号
};
const char* type_names[] = { //定义数组
"ident", // 标识符
"number", // 数字
"keyword" ,// 关键字
"symbol" //符号
};
// 定义符号类型枚举
enum Symbol {
Plus, // "+"
Minus, // "-"
times, // "*"
slash, // "/"
eql, // "="
lss, // "<"
gtr, // ">"
lparen, // "("
rparen, // ")"
comma, // ","
semicolon, // ";"
colon, // ":"
period, // "."
becomes, // ":="
leq, // "<="
geq, // ">="
neq ,// "#"
error//无效字符
};
const char* symbol_names[] = {
"Plus", // "+"
"Minus", // "-"
"times", // "*"
"slash", // "/"
"eql", // "="
"lss", // "<"
"gtr", // ">"
"lparen", // "("
"rparen", // ")"
"comma", // ","
"semicolon", // ";"
"colon", // ":"
"period", // "."
"becomes", // ":="
"leq", // "<="
"geq", // ">="
"neq" ,// "#"
"error"//无效字符
};
// 定义单词结构体
struct Token {
TokenType type; // 单词类型
Symbol symSignal;//符号类型
string value; // 单词值
};
// 定义关键字
vector<string> keywords = { "const", "var", "procedure", "begin", "end", "if", "then", "while", "do", "call", "odd", "write", "read" };
// 判断一个字符串是否是关键字
bool isKeyword(string str) {
for (string keyword : keywords) {
if (str == keyword) {
return true;
}
}
return false;
}
// 判断一个字符是否是有意义的符号
bool isSymbol(char c) {
string symbols = "+-*/=<>(),.;:";
return symbols.find(c) != string::npos;
}
// 获取下一个单词
Token getNextToken(ifstream& ifs) {
Token token;
char c;
// 跳过空格和注释
while (ifs.get(c)) {
if (isspace(c)) {
continue;
}
else if (c == '/') {
if (ifs.get(c) && c == '*') {
// 跳过注释
while (ifs.get(c)) {
if (c == '*' && ifs.get(c) && c == '/') {
break;
}
}
}
else {
ifs.putback(c);
break;
}
}
else {
break;
}
}
// 判断标识符、关键字等类型
if (isalpha(c)) {
token.type = ident;
token.value += c;
while (ifs.get(c) && isalnum(c)) {
token.value += c;
}
ifs.putback(c);
if (isKeyword(token.value)) {
token.type = keyword;
}
}
// 判断数字类型
else if (isdigit(c)) {
token.type = number;
token.value += c;
while (ifs.get(c) && isdigit(c)) {
token.value += c;
}
ifs.putback(c);
}
// 判断符号类型
else if (isSymbol(c)) {
token.type = symbol;
token.value += c;
switch (c) {
case '+': token.symSignal = Plus; token.value = '+'; break;
case '-': token.symSignal = Minus; token.value = '-'; break;
case '*': token.symSignal = times; token.value = '*'; break;
case '/': token.symSignal = slash; token.value = '/'; break;
case '=': token.symSignal = eql; token.value = '='; break;
case '#': token.symSignal = neq; token.value = '#'; break;
case '<': {
if (ifs.get(c) && c == '=') {
token.symSignal = leq;
token.value = "<=";
}
else {
ifs.putback(c);
token.symSignal = lss;
token.value = "<";
}
break;
}
case '>': {
if (ifs.get(c) && c == '=') {
token.symSignal = geq;
token.value = ">=";
}
else {
ifs.putback(c);
token.symSignal = gtr;
token.value = ">";
}
break;
}
case '(': token.symSignal = lparen; token.value = '('; break;
case ')': token.symSignal = rparen; token.value = ')'; break;
case ',': token.symSignal = comma; token.value = ','; break;
case ';': token.symSignal = semicolon; token.value = ';'; break;
case ':': {
if (ifs.get(c) && c == '=') {
token.symSignal = becomes;
token.value = ":=";
}
else {
ifs.putback(c);
token.symSignal = colon;
token.value = ":";
}
break;
}
case '.': token.symSignal = period; token.value = "."; break;
}
}
// 无效字符处理
else {
token.symSignal = error;
token.value += c;
}
return token;
}
void printFileContents(const std::string& filename) { //在控制端打印输入文件数据
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
std::cout << "source program: " << line << std::endl;
}
}
int main() {
// 打开输入,并输出在控制端
//测试文本1
ifstream ifs("input.txt");
std::string filename = "input.txt";
//测试文本2
//ifstream ifs("input2.txt");
//std::string filename = "input2.txt";
printFileContents(filename);
//打开输出
ofstream ofs("output.txt");
// 逐个单词写入到输出文件中
Token token;
while ((token = getNextToken(ifs)).type != symbol || token.value != ".")
{
if (token.type == keyword)
{
ofs << "(" << token.value << ")" << endl;
}
else if (token.type == symbol) {
ofs << "(" << symbol_names[token.symSignal] << ", " << token.value << ")" << endl;
}
else {
ofs << "(" << type_names[token.type] << ", " << token.value << ")" << endl;
}
}
ofs << "(" << symbol_names[token.symSignal] << ", " << token.value << ")" << endl;
// 关闭输入和输出文件
ifs.close();
ofs.close();
return 0;
}
输入测试:
测试1:
input.txt
const a=10;
var b,c;
procedure p;
begin
c:=b+a
end;
begin
read(b);
while b#0 do
begin
call p; write(2*c); read(b)
end
end.
测试2:
input2.txt
const
MaxValue = 100;
var
x, y, result: integer;
procedure addNumbers;
begin
result := x + y;
end;
end.
控制端输出:
测试1
测试2
文件输出:
output.txt
测试1
测试2: