已开启
提交第2次作业语法分析实验-杨泽政-43 #34
limn-创建于 2025年12月19日
提交第2次作业语法分析实验-杨泽政-43 #34
已开启
共 13 个文件变更+2148-0
Binary files do not support preview
| @@ -0,0 +1,11 @@ | |||
| 1 | +// 基础语法测试 | ||
| 2 | +func main() { | ||
| 3 | + var num1:int = 8 | ||
| 4 | + while(num1 >=0){ | ||
| 5 | + if(num1 == 2) break | ||
| 6 | + } | ||
| 7 | + let 4um2 = @ | ||
| 8 | +} | ||
| 9 | +/*闭合 | ||
| 10 | +的多行注释*/ | ||
| 11 | +/*未闭合的多行注释 | ||
| @@ -0,0 +1,39 @@ | |||
| 1 | +======================================== | ||
| 2 | +仓颉轻量级词法分析结果 | ||
| 3 | +======================================== | ||
| 4 | +单词类型 | 单词值 | ||
| 5 | +======================================== | ||
| 6 | +func | func | ||
| 7 | +main | main | ||
| 8 | +界符 | ( | ||
| 9 | +界符 | ) | ||
| 10 | +界符 | { | ||
| 11 | +var | var | ||
| 12 | +ID | num1 | ||
| 13 | +界符 | : | ||
| 14 | +int | int | ||
| 15 | +运算符 | = | ||
| 16 | +NUM | 8 | ||
| 17 | +while | while | ||
| 18 | +界符 | ( | ||
| 19 | +ID | num1 | ||
| 20 | +运算符 | >= | ||
| 21 | +NUM | 0 | ||
| 22 | +界符 | ) | ||
| 23 | +界符 | { | ||
| 24 | +if | if | ||
| 25 | +界符 | ( | ||
| 26 | +ID | num1 | ||
| 27 | +运算符 | == | ||
| 28 | +NUM | 2 | ||
| 29 | +界符 | ) | ||
| 30 | +break | break | ||
| 31 | +界符 | } | ||
| 32 | +let | let | ||
| 33 | +Error: 非法标识符(以数字开头),行号:7, 内容:4um2 | ||
| 34 | +运算符 | = | ||
| 35 | +Error: 非法字符 '@',行号:7 | ||
| 36 | +界符 | } | ||
| 37 | +Error: 未闭合的多行注释,行号:11 | ||
| 38 | +======================================== | ||
| 39 | +词法分析完成!共处理 11 行代码 | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | +in.txt为输入文件 | ||
| 2 | +out.txt文件保存词法分析结果 | ||
| 3 | +test1.cpp为词法分析源文件代码 | ||
| @@ -0,0 +1,283 @@ | |||
| 1 | + | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | +// 全局变量:行号(用于错误定位) | ||
| 6 | +int line_num = 1; | ||
| 7 | +char current_char; // 当前读取的字符 | ||
| 8 | + | ||
| 9 | +// 关键字列表(按字典序排序,用于折半二分查找) | ||
| 10 | +char *keywords[] = { | ||
| 11 | + "as", "bool" , "break", "case", "const", "continue", "do", | ||
| 12 | + "else", "float" ,"for", "from", "func", "if", "in", "int", | ||
| 13 | + "let", "main", "match", "var", "where", "while" | ||
| 14 | +}; | ||
| 15 | +int keyword_count = sizeof(keywords) / sizeof(keywords[0]); | ||
| 16 | + | ||
| 17 | +/* | ||
| 18 | + 二分找:判断字符串是否为关键字 | ||
| 19 | + str 待判断字符串 | ||
| 20 | + return 1-是关键字,0-不是 | ||
| 21 | + */ | ||
| 22 | +int is_keyword(char *str) { | ||
| 23 | + int left = 0, right = keyword_count - 1; | ||
| 24 | + while (left <= right) { | ||
| 25 | + int mid = (left + right) / 2; | ||
| 26 | + int cmp = strcmp(str, keywords[mid]); | ||
| 27 | + if (cmp == 0) return 1; // 找到关键字 | ||
| 28 | + else if (cmp < 0) right = mid - 1; | ||
| 29 | + else left = mid + 1; | ||
| 30 | + } | ||
| 31 | + return 0; | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +/* | ||
| 35 | + 读取下一个字符,更新行号和列号 | ||
| 36 | + fp 源文件指针 | ||
| 37 | + return 当前读取的字符(EOF表示文件结束) | ||
| 38 | + */ | ||
| 39 | + | ||
| 40 | +int get_char(FILE *fp) { | ||
| 41 | + current_char = fgetc(fp); | ||
| 42 | + if (current_char == '\n') { // 换行:行号+1 | ||
| 43 | + line_num++; | ||
| 44 | + | ||
| 45 | + } | ||
| 46 | + return current_char; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +/* | ||
| 50 | + 跳过空白字符(空格、制表符、换行符等) | ||
| 51 | + fp 源文件指针 | ||
| 52 | + */ | ||
| 53 | +void skip_whitespace(FILE *fp) { | ||
| 54 | + while (current_char != EOF && isspace(current_char)) { | ||
| 55 | + get_char(fp); | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +/* | ||
| 60 | + 处理注释(单行//和多行/* *\/) | ||
| 61 | + fp 源文件指针 | ||
| 62 | + out 目标文件指针 | ||
| 63 | + */ | ||
| 64 | +bool handle_comment(FILE *fp ,FILE *out) { | ||
| 65 | + bool flag = 0; | ||
| 66 | + | ||
| 67 | + if (current_char == '/') { | ||
| 68 | + get_char(fp); // 读取注释起始符后的字符 | ||
| 69 | + // 单行注释:跳过到行尾 | ||
| 70 | + while (current_char != EOF && current_char != '\n') { | ||
| 71 | + get_char(fp); | ||
| 72 | + } | ||
| 73 | + } else if (current_char == '*') { | ||
| 74 | + // 多行注释:跳过到*/ | ||
| 75 | + get_char(fp); | ||
| 76 | + while (current_char != EOF) { | ||
| 77 | + if (current_char == '*') { | ||
| 78 | + get_char(fp); | ||
| 79 | + if (current_char == '/') { // 找到注释结束符 | ||
| 80 | + | ||
| 81 | + break; | ||
| 82 | + } | ||
| 83 | + } else { | ||
| 84 | + get_char(fp); | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + if (current_char == EOF) { // 未闭合的多行注释 | ||
| 88 | + fprintf(stderr, "\033[31mError: 未闭合的多行注释,行号:%d \033[0m\n", line_num); | ||
| 89 | + fprintf(out, "Error: 未闭合的多行注释,行号:%d \n", line_num); | ||
| 90 | + printf("%c" , current_char); | ||
| 91 | + } | ||
| 92 | + } else { | ||
| 93 | + flag = 1; | ||
| 94 | + // 不是注释,是/运算符:回退字符和列号 | ||
| 95 | + if(current_char != '\n')ungetc(current_char, fp); | ||
| 96 | + | ||
| 97 | + current_char = '/'; | ||
| 98 | + } | ||
| 99 | + return flag; | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +/* | ||
| 103 | +读取标识符/关键字(字母或_开头,后接字母/数字/_) | ||
| 104 | +fp 源文件指针 | ||
| 105 | +out 目标文件指针 | ||
| 106 | +first_char 第一个字符(字母) | ||
| 107 | +return 读取到的标识符字符串 | ||
| 108 | + */ | ||
| 109 | +char *read_identifier(FILE *fp, char first_char) { | ||
| 110 | + static char buf[1024]; // 存储标识符(假设长度不超过1023) | ||
| 111 | + int i = 0; | ||
| 112 | + buf[i++] = first_char; | ||
| 113 | + | ||
| 114 | + get_char(fp); | ||
| 115 | + // 继续读取字母、数字或下划线 | ||
| 116 | + while (current_char != EOF && (isalnum(current_char) || current_char == '_')) { | ||
| 117 | + buf[i++] = current_char; | ||
| 118 | + get_char(fp); | ||
| 119 | + } | ||
| 120 | + buf[i] = '\0'; | ||
| 121 | + | ||
| 122 | + // 回退非标识符字符 | ||
| 123 | + if (current_char != EOF && current_char != '\n') { | ||
| 124 | + ungetc(current_char, fp);//回退该字符到输入流 | ||
| 125 | + | ||
| 126 | + } | ||
| 127 | + return buf; | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +/* | ||
| 131 | + 读取无符号整数(纯数字序列) | ||
| 132 | + fp 源文件指针 | ||
| 133 | + out 目标文件指针 | ||
| 134 | + first_char 第一个字符(数字) | ||
| 135 | + return 读取到的数字字符串(NULL表示非法) | ||
| 136 | + */ | ||
| 137 | +char *read_number(FILE *fp, char first_char , FILE *out) { | ||
| 138 | + static char buf[1024]; // 存储数字(假设长度不超过1023) | ||
| 139 | + int i = 0; | ||
| 140 | + buf[i++] = first_char; | ||
| 141 | + | ||
| 142 | + get_char(fp); | ||
| 143 | + // 继续读取数字 | ||
| 144 | + while (current_char != EOF && isdigit(current_char)) { | ||
| 145 | + buf[i++] = current_char; | ||
| 146 | + get_char(fp); | ||
| 147 | + } | ||
| 148 | + buf[i] = '\0'; | ||
| 149 | + | ||
| 150 | + // 检查是否以数字开头后接字母(非法标识符) | ||
| 151 | + if (current_char != EOF && isalpha(current_char)) { | ||
| 152 | + | ||
| 153 | + // 跳过后续字符 | ||
| 154 | + while( isalpha(current_char) || isdigit(current_char) || current_char == '_' ) { | ||
| 155 | + | ||
| 156 | + buf[i++] = current_char; | ||
| 157 | + get_char(fp); | ||
| 158 | + } | ||
| 159 | + buf[i] = '\0'; | ||
| 160 | + fprintf(stderr, "\033[31mError: 非法标识符(以数字开头),行号:%d, 内容:%s\033[0m\n", | ||
| 161 | + line_num, buf); | ||
| 162 | + fprintf(out, "Error: 非法标识符(以数字开头),行号:%d, 内容:%s \n", | ||
| 163 | + line_num, buf); | ||
| 164 | + if(current_char != '\n')ungetc(current_char,fp); | ||
| 165 | + return NULL; | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + // 回退非数字字符 | ||
| 169 | + if (current_char != EOF && current_char != '\n') { | ||
| 170 | + ungetc(current_char, fp); | ||
| 171 | + } | ||
| 172 | + return buf; | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +/* | ||
| 176 | + 词法分析主函数 | ||
| 177 | + fp 源文件指针 | ||
| 178 | + out 目标文件指针 | ||
| 179 | + */ | ||
| 180 | +void lex_analysis(FILE *fp , FILE *out) { | ||
| 181 | + // 终端输出结果头部 | ||
| 182 | + fprintf(out, "========================================\n"); | ||
| 183 | + fprintf(out, "仓颉轻量级词法分析结果\n"); | ||
| 184 | + fprintf(out, "========================================\n"); | ||
| 185 | + fprintf(out, "%-8s | %-15s \n", "单词类型", "单词值"); | ||
| 186 | + fprintf(out, "========================================\n"); | ||
| 187 | + | ||
| 188 | + current_char = ' '; | ||
| 189 | + while (current_char != EOF) { | ||
| 190 | + get_char(fp); // | ||
| 191 | + skip_whitespace(fp); // 跳过空白字符 | ||
| 192 | + if (current_char == EOF) break; | ||
| 193 | + | ||
| 194 | + // 1. 识别标识符/关键字(字母开头) | ||
| 195 | + if (isalpha(current_char) || current_char == '_') {//是否为字母 或_ | ||
| 196 | + | ||
| 197 | + char *id = read_identifier(fp, current_char); | ||
| 198 | + if (is_keyword(id)) { | ||
| 199 | + fprintf(out, "%-8s | %-15s \n", id, id); | ||
| 200 | + } else { | ||
| 201 | + fprintf(out, "%-8s | %-15s \n", "ID", id); | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + // 2. 识别无符号整数(数字开头) | ||
| 205 | + else if (isdigit(current_char)) { | ||
| 206 | + | ||
| 207 | + char *num = read_number(fp, current_char , out); | ||
| 208 | + if (num != NULL) { | ||
| 209 | + fprintf(out, "%-8s | %-15s \n", "NUM", num); | ||
| 210 | + } | ||
| 211 | + } | ||
| 212 | + // 3. 识别运算符(+、-、*、/、>、<、!、=、>=、<=、!=、==) | ||
| 213 | + else if (current_char == '+' || current_char == '-' || current_char == '*' || | ||
| 214 | + current_char == '/' || current_char == '>' || current_char == '<' || | ||
| 215 | + current_char == '!' || current_char == '=') { | ||
| 216 | + | ||
| 217 | + char op1 = current_char; | ||
| 218 | + get_char(fp); | ||
| 219 | + | ||
| 220 | + // 处理/:可能是注释或运算符 | ||
| 221 | + if (op1 == '/') { | ||
| 222 | + bool is_div = handle_comment(fp,out); | ||
| 223 | + if (is_div) { // 确认为/运算符 | ||
| 224 | + fprintf(out, "%-8s | %-15c \n", "运算符", op1); | ||
| 225 | + //get_char(fp); | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | + // 严格处理双字符运算符:仅当连续两个字符匹配时识别 | ||
| 229 | + else if ((op1 == '>' && current_char == '=') || | ||
| 230 | + (op1 == '<' && current_char == '=') || | ||
| 231 | + (op1 == '!' && current_char == '=') || | ||
| 232 | + (op1 == '=' && current_char == '=')) { | ||
| 233 | + char op_str[3] = {op1, current_char, '\0'}; // 显式创建双字符运算符字符串 | ||
| 234 | + fprintf(out, "%-8s | %-15s \n", "运算符", op_str); | ||
| 235 | + // get_char(fp); | ||
| 236 | + } | ||
| 237 | + // 单字符运算符(包括=):直接输出并回退(避免重复识别) | ||
| 238 | + else { | ||
| 239 | + fprintf(out, "%-8s | %-15c \n", "运算符", op1); | ||
| 240 | + if(current_char != '\n')ungetc(current_char , fp); | ||
| 241 | + } | ||
| 242 | + } | ||
| 243 | + // 4. 识别界符(()、{}、[]、,、;、:) | ||
| 244 | + else if (current_char == '(' || current_char == ')' || current_char == '{' || | ||
| 245 | + current_char == '}' || current_char == '[' || current_char == ']' || | ||
| 246 | + current_char == ',' || current_char == ';' || current_char == ':') { | ||
| 247 | + fprintf(out, "%-8s | %-15c \n", "界符", current_char); | ||
| 248 | + // get_char(fp); | ||
| 249 | + } | ||
| 250 | + // 5. 非法字符与单词 | ||
| 251 | + else { | ||
| 252 | + | ||
| 253 | + fprintf(stderr, "\033[31mError: 非法字符 '%c',行号:%d \033[0m\n", current_char, line_num); | ||
| 254 | + fprintf(out, "Error: 非法字符 '%c',行号:%d \n", current_char, line_num); | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + // 输出分析结束标记 | ||
| 261 | + fprintf(out, "========================================\n"); | ||
| 262 | + fprintf(out, "词法分析完成!共处理 %d 行代码\n", line_num ); | ||
| 263 | +} | ||
| 264 | + | ||
| 265 | +int main() { | ||
| 266 | + // 固定输入文件路径:当前目录下的 "in.txt" | ||
| 267 | + const char *in_path = "in.txt"; | ||
| 268 | + const char *out_path = "out.txt"; | ||
| 269 | + // 打开源文件(只读) | ||
| 270 | + FILE *in_fp = fopen(in_path, "r"); | ||
| 271 | + FILE *out_fp = fopen(out_path, "w"); | ||
| 272 | + if (in_fp == NULL) { | ||
| 273 | + fprintf(stderr, "\033[31m打开文件失败!请确保当前目录下存在 '%s' 文件(需写入仓颉代码)\033[0m\n", in_path); | ||
| 274 | + return 1; | ||
| 275 | + } | ||
| 276 | + | ||
| 277 | + // 执行词法分析(结果直接输出到终端) | ||
| 278 | + lex_analysis(in_fp , out_fp); | ||
| 279 | + | ||
| 280 | + // 关闭源文件 | ||
| 281 | + fclose(in_fp); | ||
| 282 | + return 0; | ||
| 283 | +} | ||