博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

编译原理实验一:词法分析

Posted on 2025-04-23 10:24  steve.z  阅读(224)  评论(0)    收藏  举报

要求:
任务描述
本关任务:使用C语言编写一个对C语言进行词法分析的程序。

编程要求
根据提示,在右侧编辑器补充代码,从标准输入流读取字符,并完成词法分析任务。
其中,单词可以分为以下几类
第1类:标识符,由字母开头,由字母和数字组成
第2类:保留字,共32个,已存储到keyword_list数组中
第3类:无符号整数,如123【不考虑科学记数法】
第4类,无符号实数,如123.456【不考虑科学记数法】
第5类,操作符,包括以下符号
算术运算符:+,-,,/,%,++,--
关系运算符:>,<,==,>=,<=,!=,
赋值运算符: =
逻辑运算符: &&,||,!
其他运算符: &(返回变量地址),
(指向一个变量)
第6类,分隔符,包括:
, ; ( ) [ ] { } ' "
第7类,字符串,由" "包起来的字符(不包括"),如"abcde"中的abcde

测试说明
平台会对你编写的代码进行测试:

输入的数据为一段C语言代码,包括了上述所涉及到的单词,代码以#符号作为输入结束的标志。

输出的数据为一个二元组,包括单词的名称和单词的类别例如:
(sum,1)
(int,2)
(123,3)
(123.456,4)
(++,5)
(;,6)
(sum:%d,7)

样例测试输入:printf("hello world")#
预期输出:
(printf,1)
((,6)
(",6)
(hello world,7)
(",6)
(),6)
(;,6)

代码:

//
//  main.c
//  LexiAnalyze
//
//  Created by steve xiaohu zhao on 2025/4/23.
//

#include <ctype.h>
#include <stdio.h>
#include <string.h>

// 定义 C 语言关键字列表
const char *keyword_list[] = {
    "auto",     "break",  "case",    "char",     "const",  "continue",
    "default",  "do",     "double",  "else",     "enum",   "extern",
    "float",    "for",    "goto",    "if",       "int",    "long",
    "register", "return", "short",   "signed",   "sizeof", "static",
    "struct",   "switch", "typedef", "unsigned", "union",  "void",
    "volatile", "while"};
// 定义关键字数量
#define KEYWORD_COUNT 32
// 定义最大令牌长度
#define MAX_TOKEN 256
// 词法分析函数, 读取输入并进行令牌分类
void readsym(void) {
    char ch;               // 当前读取的字符
    char token[MAX_TOKEN]; // 存储当前令牌的字符数组
    int pos = 0;           // 令牌字符数组的当前位置

    // 循环读取字符, 直到遇到 '#'
    while ((ch = getchar()) != '#') {
        // 跳过空白字符(空格、换行、tab等)
        if (isspace(ch)) {
            continue;
        }

        pos = 0;         // 重置令牌位置
        token[0] = '\0'; // 初始化令牌为空字符串

        // 1. 处理 标识符 or 保留字
        if (isalpha(ch) || ch == '_') {
            token[pos++] = ch; // 记录首字符
            // 继续读取字母、数字或下划线, 组成标识符
            while (((ch = getchar()) && (isalnum(ch))) || ch == '_') {
                token[pos++] = ch;
            }

            token[pos] = '\0'; // 结束令牌字符串
            ungetc(ch, stdin); // 将非标识符字符放回输入流

            // 检查是否为保留字
            int is_keyword = 0;
            for (int i = 0; i < KEYWORD_COUNT; i++) {
                if (strcmp(token, keyword_list[i]) == 0) {
                    // 如果是保留字, 输出格式为(令牌, 2)
                    printf("(%s,2)\n", token);
                    is_keyword = 1;
                    break;
                }
            }

            // 如果不是保留字, 则为标识符, 输入格式为(令牌, 1)
            if (!is_keyword) {
                printf("(%s,1)\n", token);
            }
        } else if (isdigit(ch)) {
            // 2. 处理无符号整数 or 无符号实数
            token[pos++] = ch; // 记录首字符
            int is_float = 0;  // 标记是否为实数(包含小数点)

            // 继续读取数字或小数点
            while (((ch = getchar()) && isdigit(ch)) || ch == '.') {
                if (ch == '.') {
                    if (is_float) {
                        break; // 表示遇到多个小数点, 退出循环
                    }
                    is_float = 1; // 标记为实数
                }
                token[pos++] = ch; // 记录字符
            }

            token[pos] = '\0'; // 结束令牌字符串
            ungetc(ch, stdin); // 将非数字字符放回输入流

            // 根据是否包含小数点, 输出 (令牌, 3) or (令牌, 4)
            printf("(%s,%d)\n", token, is_float ? 4 : 3);
        } else if (ch == '"') {
            
            // 先处理 " 分隔符
            printf("(\",6)\n");
            // 3. 处理字符串
            // 读取字符串内容, 直到遇到结束引号或终止符
            while ((ch = getchar()) && ch != '"' && ch != '#') {
                token[pos++] = ch;
            }
            token[pos] = '\0'; // 结束令牌字符串

            if (ch == '"') {
                // 正常结束的字符串, 输出 (令牌, 7)
                printf("(%s,7)\n", token);
                // 字符串结束前先处理分隔符
                printf("(\",6)\n");
            } else {
                // 未闭合的字符串, 仍然输出 (令牌, 7),并退出
                printf("(%s,7)\n", token);
                break;
            }
        } else {
            // 处理 操作符 or 分隔符
            token[pos++] = ch; // 记录当前字符
            token[pos] = '\0';

            // 检查是否构成双字符操作符(如 ++, --, >= 等)
            if (ch == '+' || ch == '-' || ch == '>' || ch == '<' || ch == '=' ||
                ch == '!' || ch == '&' || ch == '|') {
                // 继续读取一个字符
                ch = getchar();
                // 判断是否构成双字符操作符
                if ((token[0] == '+' && ch == '+') ||
                    (token[0] == '-' && ch == '-') ||
                    (token[0] == '&' && ch == '&') ||
                    (token[0] == '|' && ch == '|') ||
                    (ch == '=' && ((token[0] == '>') || (token[0] == '<') ||
                                   (token[0] == '!')))) {
                    // 记录第二个字符
                    token[pos++] = ch; // 记录第二个字符
                    token[pos] = '\0'; // 结束令牌字符串
                } else {
                    ungetc(ch, stdin); // 非双字符操作符,放回输入流
                }
            }

            // 判断是操作符还是分隔符
            if (strchr("+-*/%=><!&", token[0]) || strcmp(token, "++") == 0 ||
                strcmp(token, "--") == 0 || strcmp(token, "==") == 0 ||
                strcmp(token, ">=") == 0 || strcmp(token, "<=") == 0 ||
                strcmp(token, "!=") == 0 || strcmp(token, "&&") == 0 ||
                strcmp(token, "||") == 0) {
                // 表示是操作符,输出 (令牌, 5)
                printf("(%s,5)\n", token);
            } else if (strchr(",;()[]{}'\"", token[0])) {
                // 表示是分隔符,输出 (令牌, 6)
                printf("(%s,6)\n", token);
            }
        }
    }
}


int main(int argc, const char *argv[]) {
    // 调用读取下一个字符函数, 进行词法分析
    readsym();
    return 0;
}

/*
 测试输入1:
 int num1,num2,sum;
 sum = add(num1,num2);
 printf("sum:%d",sum);
 #
 
 预期输出:
 (int,2)
 (num1,1)
 (,,6)
 (num2,1)
 (,,6)
 (sum,1)
 (;,6)
 (sum,1)
 (=,5)
 (add,1)
 ((,6)
 (num1,1)
 (,,6)
 (num2,1)
 (),6)
 (;,6)
 (printf,1)
 ((,6)
 (",6)
 (sum:%d,7)
 (",6)
 (,,6)
 (sum,1)
 (),6)
 (;,6)
 
 
 测试输入2:
 a=2*sin(b)+exp(b)/5+12.34;
 #
 
 预期输出:
 (a,1)
 (=,5)
 (2,3)
 (*,5)
 (sin,1)
 ((,6)
 (b,1)
 (),6)
 (+,5)
 (exp,1)
 ((,6)
 (b,1)
 (),6)
 (/,5)
 (5,3)
 (+,5)
 (12.34,4)
 (;,6)
 
 
 测试输入3:
 for(i=0;i<=100;i++)
 {sum = sum + i;}
 #
 
 预期输出:
 (for,2)
 ((,6)
 (i,1)
 (=,5)
 (0,3)
 (;,6)
 (i,1)
 (<=,5)
 (100,3)
 (;,6)
 (i,1)
 (++,5)
 (),6)
 ({,6)
 (sum,1)
 (=,5)
 (sum,1)
 (+,5)
 (i,1)
 (;,6)
 (},6)
 
 测试输入4:
 int var=10;
 int *p;
 p=&var;
 printf("var变量的地址是:%p",p);
 #
 
 预期输出:
 (int,2)
 (var,1)
 (=,5)
 (10,3)
 (;,6)
 (int,2)
 (*,5)
 (p,1)
 (;,6)
 (p,1)
 (=,5)
 (&,5)
 (var,1)
 (;,6)
 (printf,1)
 ((,6)
 (",6)
 (var变量的地址是:%p,7)
 (",6)
 (,,6)
 (p,1)
 (),6)
 (;,6)
 */