├── .gitignore ├── experiment_1 ├── .tours │ └── .tour ├── demo │ ├── categoryCode.txt │ ├── code.txt │ ├── main.cpp │ └── 词法分析.xls ├── readme.md └── 参考资料 │ ├── 1. 实验1 词法分析(设计性实验)-实验指导书.docx │ ├── 2. 实验1 词法分析(设计性实验)-实验报告模板.docx │ └── 4. 实验1 词法分析-预习提示.docx ├── experiment_2 ├── demo │ ├── exp.txt │ └── main.cpp ├── readme.md └── 参考资料 │ ├── 1. 实验2 LL(1)分析法-预习提示.docx │ ├── 2. 实验2 LL(1)分析法-实验报告模板.docx │ └── 3. 实验2 LL(1)分析法-参考程序.txt ├── experiment_3 ├── demo │ ├── exp.txt │ ├── function3.drawio.png │ ├── main.cpp │ ├── main.drawio.png │ └── output.txt ├── readme.md └── 参考资料 │ ├── 1. 实验3 逆波兰式的产生及计算-预习提示.docx │ ├── 2. 实验3 逆波兰式的产生及计算-实验报告模板.docx │ └── 3. 实验3 逆波兰式的产生及计算-参考程序.txt ├── experiment_4 ├── demo │ ├── LR.xls │ ├── action.txt │ ├── exp.txt │ ├── goto.txt │ └── main.cpp ├── readme.md └── 参考资料 │ ├── 1. 实验4 LR(1)分析法-预习提示.docx │ ├── 2. 实验4 LR(1)分析法-实验报告模板.docx │ └── 3. 实验4 LR(1)分析法-参考程序.txt └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | else/ -------------------------------------------------------------------------------- /experiment_1/.tours/.tour: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://aka.ms/codetour-schema", 3 | "title": "词法分析器设计", 4 | "steps": [ 5 | { 6 | "file": "demo/main.cpp", 7 | "description": "# 头文件声明和全局变量定义\r\n定义关键字表、运算符表和界符表", 8 | "line": 27, 9 | "selection": { 10 | "start": { 11 | "line": 9, 12 | "character": 1 13 | }, 14 | "end": { 15 | "line": 27, 16 | "character": 118 17 | } 18 | }, 19 | "title": "1:头文件声明和全局变量定义" 20 | }, 21 | { 22 | "file": "demo/main.cpp", 23 | "description": "# 生成的数据结构——Token流\r\n包括了种别码、值和种别码对应的类型名称\r\n", 24 | "line": 37, 25 | "selection": { 26 | "start": { 27 | "line": 28, 28 | "character": 1 29 | }, 30 | "end": { 31 | "line": 37, 32 | "character": 3 33 | } 34 | }, 35 | "title": "2:生成的数据结构——Token流" 36 | }, 37 | { 38 | "file": "demo/main.cpp", 39 | "description": "# 所需全局变量\r\n`code` 为待识别的字符串,`pos` 为当前识别的位置,`len`为 `code`的长度(均在init函数中进行初始化),`tempToken` 存放临时的字符串(变量值、常量等),`tokenList` 存放识别出的token", 40 | "line": 40, 41 | "selection": { 42 | "start": { 43 | "line": 38, 44 | "character": 1 45 | }, 46 | "end": { 47 | "line": 40, 48 | "character": 41 49 | } 50 | } 51 | }, 52 | { 53 | "file": "demo/main.cpp", 54 | "description": "# 读取文件函数 readFile\r\n根据文件名读取文件,返回一个string动态数组(`vector`),若过程中出错则报错", 55 | "line": 55, 56 | "selection": { 57 | "start": { 58 | "line": 41, 59 | "character": 1 60 | }, 61 | "end": { 62 | "line": 55, 63 | "character": 2 64 | } 65 | } 66 | }, 67 | { 68 | "file": "demo/main.cpp", 69 | "description": "# init 初始化函数\r\n读取包含关键字、界符表等的种别文件,初始化对应表,并读入待识别的code文件", 70 | "line": 78, 71 | "selection": { 72 | "start": { 73 | "line": 56, 74 | "character": 1 75 | }, 76 | "end": { 77 | "line": 78, 78 | "character": 2 79 | } 80 | } 81 | }, 82 | { 83 | "file": "demo/main.cpp", 84 | "description": "# peek函数 探测下一个字符\r\n探测下一个字符,若存在则返回该字符,否则返回\\0即字符串结束符", 85 | "line": 82, 86 | "selection": { 87 | "start": { 88 | "line": 79, 89 | "character": 1 90 | }, 91 | "end": { 92 | "line": 82, 93 | "character": 2 94 | } 95 | } 96 | }, 97 | { 98 | "file": "demo/main.cpp", 99 | "description": "# isxxx函数 判断类别\r\n判断是否为字符、数字或者关键字等\r\n", 100 | "line": 108, 101 | "selection": { 102 | "start": { 103 | "line": 83, 104 | "character": 1 105 | }, 106 | "end": { 107 | "line": 108, 108 | "character": 2 109 | } 110 | } 111 | }, 112 | { 113 | "file": "demo/main.cpp", 114 | "description": "# judge函数 `_SPACE_`\r\n## `_SPACE_`\r\n若为回车或者空格等空白符,忽略不计返回枚举量`_SPACE_", 115 | "line": 110, 116 | "selection": { 117 | "start": { 118 | "line": 110, 119 | "character": 1 120 | }, 121 | "end": { 122 | "line": 111, 123 | "character": 1 124 | } 125 | } 126 | }, 127 | { 128 | "file": "demo/main.cpp", 129 | "description": "## `_DOUBLE_` or `_INT_`\r\n若第一个字符为数字,则进行实型或整型的判断\r\n", 130 | "line": 151, 131 | "selection": { 132 | "start": { 133 | "line": 111, 134 | "character": 1 135 | }, 136 | "end": { 137 | "line": 151, 138 | "character": 6 139 | } 140 | } 141 | }, 142 | { 143 | "file": "demo/main.cpp", 144 | "description": "## `_KEYWORD_` or `_ID_`\r\n若第一个字符为字母或者下划线,则返回`_KEYWORD_` or `_ID_`", 145 | "line": 161, 146 | "selection": { 147 | "start": { 148 | "line": 160, 149 | "character": 39 150 | }, 151 | "end": { 152 | "line": 160, 153 | "character": 56 154 | } 155 | } 156 | }, 157 | { 158 | "file": "demo/main.cpp", 159 | "description": "## `_STRING_`\r\n第一个字符为 `\"` 的情况,进入字符串常量的判断", 160 | "line": 175, 161 | "selection": { 162 | "start": { 163 | "line": 162, 164 | "character": 4 165 | }, 166 | "end": { 167 | "line": 175, 168 | "character": 6 169 | } 170 | } 171 | }, 172 | { 173 | "file": "demo/main.cpp", 174 | "description": "# `_CHAR_`\r\n第一个字符为 `'`,进入单个字符常量判断,多于一个字符则报错。", 175 | "line": 200, 176 | "selection": { 177 | "start": { 178 | "line": 176, 179 | "character": 1 180 | }, 181 | "end": { 182 | "line": 200, 183 | "character": 6 184 | } 185 | } 186 | }, 187 | { 188 | "file": "demo/main.cpp", 189 | "description": "# `_COMMENT_`\r\n第一个字符为 `/`,进入注释判断,详见DFA", 190 | "line": 223, 191 | "selection": { 192 | "start": { 193 | "line": 201, 194 | "character": 2 195 | }, 196 | "end": { 197 | "line": 223, 198 | "character": 6 199 | } 200 | } 201 | }, 202 | { 203 | "file": "demo/main.cpp", 204 | "description": "## `_OPERATOR_`\r\n第一个字符为运算符,进入运算符判断", 205 | "line": 236, 206 | "selection": { 207 | "start": { 208 | "line": 225, 209 | "character": 1 210 | }, 211 | "end": { 212 | "line": 236, 213 | "character": 6 214 | } 215 | } 216 | }, 217 | { 218 | "file": "demo/main.cpp", 219 | "description": "## `_DELIMITER_`\r\n第一个字符为界符,进入界符判断", 220 | "line": 241, 221 | "selection": { 222 | "start": { 223 | "line": 240, 224 | "character": 16 225 | }, 226 | "end": { 227 | "line": 240, 228 | "character": 27 229 | } 230 | } 231 | }, 232 | { 233 | "file": "demo/main.cpp", 234 | "description": "# read_next函数 读取下一个字符\r\n## `_SPACE_`\r\n空白符则继续读取直到没有下一个字符了或者不是空白符。", 235 | "line": 249, 236 | "selection": { 237 | "start": { 238 | "line": 246, 239 | "character": 1 240 | }, 241 | "end": { 242 | "line": 249, 243 | "character": 6 244 | } 245 | } 246 | }, 247 | { 248 | "file": "demo/main.cpp", 249 | "description": "## `_EOF_`\r\n读完空白符后无下一个字符了。返回`_EOF_`", 250 | "line": 251, 251 | "selection": { 252 | "start": { 253 | "line": 250, 254 | "character": 1 255 | }, 256 | "end": { 257 | "line": 251, 258 | "character": 11 259 | } 260 | } 261 | }, 262 | { 263 | "file": "demo/main.cpp", 264 | "description": "## `_ERROR_` 直接返回,其他类型进行相应处理", 265 | "line": 271, 266 | "selection": { 267 | "start": { 268 | "line": 252, 269 | "character": 1 270 | }, 271 | "end": { 272 | "line": 271, 273 | "character": 6 274 | } 275 | } 276 | }, 277 | { 278 | "file": "demo/main.cpp", 279 | "description": "# main函数 主程序入口\r\n不断读取直到结束,读取中若有错误不中断,而是从下一个不出错的字符继续往后读取。最后输出token流", 280 | "line": 282, 281 | "selection": { 282 | "start": { 283 | "line": 276, 284 | "character": 1 285 | }, 286 | "end": { 287 | "line": 282, 288 | "character": 27 289 | } 290 | } 291 | } 292 | ] 293 | } -------------------------------------------------------------------------------- /experiment_1/demo/categoryCode.txt: -------------------------------------------------------------------------------- 1 | int 2 | float 3 | const 4 | bool 5 | void 6 | char 7 | double 8 | struct 9 | return 10 | if 11 | else 12 | while 13 | do 14 | static 15 | break 16 | for 17 | switch 18 | case 19 | default 20 | continue 21 | true 22 | false 23 | + 24 | - 25 | * 26 | / 27 | % 28 | ++ 29 | -- 30 | = 31 | += 32 | -= 33 | *= 34 | /= 35 | %= 36 | &= 37 | |= 38 | ^= 39 | >> 40 | << 41 | ! 42 | & 43 | | 44 | && 45 | || 46 | < 47 | > 48 | <= 49 | >= 50 | == 51 | ; 52 | , 53 | \' 54 | \" 55 | /* 56 | */ 57 | ? 58 | : 59 | ( 60 | ) 61 | [ 62 | ] 63 | } 64 | { 65 | . -------------------------------------------------------------------------------- /experiment_1/demo/code.txt: -------------------------------------------------------------------------------- 1 | int main() { 2 | char ch = 'ss'; 3 | string str = "Hello, World!" 4 | char ch2 = 's'; 5 | init(); 6 | double x = 10.31;/* some comment */ 7 | int m = 0; 8 | int y = 310, m = 0.31; 9 | while(pos < len) { 10 | int flag = read_next(); 11 | if(flag == _EOF_) break; 12 | if(flag != _ERROR_) { 13 | Token t(flag, tempToken); 14 | tokenList.push_back(t); 15 | cout << t << endl; 16 | } else cout << "Error!" << endl; 17 | } 18 | return 0; 19 | } -------------------------------------------------------------------------------- /experiment_1/demo/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: cos 3 | * @Date: 2022-04-05 00:10:59 4 | * @LastEditTime: 2022-04-08 17:27:03 5 | * @LastEditors: cos 6 | * @Description: 词法分析器设计实现 7 | * @FilePath: \experiment_1\demo\main.cpp 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | using namespace std; 15 | const string CategoryFileName = "./categoryCode.txt"; 16 | const string CodeFileName = "./code.txt"; 17 | string keywords[22]; // 关键字表 种别码1-22 18 | string operate[28]; // 运算符表 种别码23-50 19 | string delimiter[15]; // 界符表 种别码51-65 20 | map categoryCode; // 种别码表 21 | const string op = "+-*/%=!&|<>"; 22 | const int _EOF_ = -2; 23 | const int _ERROR_ = -1; 24 | enum { 25 | _ID_, _INT_, _DOUBLE_, _OPERATOR_, _DELIMITER_, _KEYWORD_, _CHAR_, _STRING_, _COMMENT_, _SPACE_ 26 | }; // 类型 27 | string cat[10] = { "id", "int", "double", "operator", "delimiter", "keyword", "char", "string", "comment", "space" }; 28 | struct Token { 29 | int type; // 种别码 30 | string value; // 值 关键字/变量名/数字/运算符/界符 31 | string category; // 种别码对应的类型名称 32 | Token(int type, string value, string category) : type(type), value(value), category(category) {} 33 | friend ostream& operator<<(ostream& os, const Token& t) { 34 | os << t.category << ", type: " << t.type << ", value: " << t.value; 35 | return os; 36 | } 37 | }; 38 | int pos, len; // 当前字符位置和长度 39 | string code, tempToken; // 当前识别的字符串 40 | vector tokenList; // 存储识别出的token 41 | // 读文件 42 | vector readFile(string fileName) { 43 | vector res; 44 | try { 45 | ifstream fin; 46 | fin.open(fileName); 47 | string temp; 48 | while (getline(fin, temp)) 49 | res.push_back(temp); 50 | return res; 51 | } catch(const exception& e) { 52 | cerr << e.what() << '\n'; 53 | return res; 54 | } 55 | } 56 | void init() { 57 | vector res = readFile(CategoryFileName); 58 | // cout << "len:" << len << endl; 59 | for(int i = 0; i < 22; ++i) { 60 | keywords[i] = res[i]; 61 | categoryCode[res[i]] = i+1; 62 | // cout << "keyword:" << res[i] << endl; 63 | } 64 | for(int i = 0; i < 28; ++i) { 65 | operate[i] = res[i + 22]; 66 | categoryCode[res[i+22]] = i+23; 67 | // cout << "operate:" << res[i + 22] << endl; 68 | } 69 | for(int i = 0; i < 15; ++i) { 70 | delimiter[i] = res[i + 50]; 71 | categoryCode[res[i+50]] = i+51; 72 | // cout << "delimiter:" << res[i + 50] << endl; 73 | } 74 | res = readFile(CodeFileName); 75 | for(int i = 0; i < res.size(); ++i) 76 | code += res[i]+'\n'; 77 | len = code.size(); 78 | } 79 | char peek() { 80 | if (pos+1 < len) return code[pos+1]; 81 | else return '\0'; 82 | } 83 | inline bool isDigit(char c) { 84 | return c >= '0' && c <= '9'; 85 | } 86 | // 是否为字母或下划线 87 | inline bool isLetter(char c) { 88 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; 89 | } 90 | bool isKeyword(string s) { 91 | for(int i = 0; i < 22; ++i) 92 | if (s == keywords[i]) 93 | return true; 94 | return false; 95 | } 96 | bool isOP(char ch) { 97 | return op.find(ch) != string::npos; 98 | } 99 | bool isOperator(string s) { 100 | for(int i = 0; i < 28; ++i) 101 | if (s == operate[i]) return true; 102 | return false; 103 | } 104 | bool isDelimiter(char ch) { 105 | for(int i = 0; i < 15; ++i) 106 | if (ch == delimiter[i][0]) return true; 107 | return false; 108 | } 109 | int judge(char ch) { 110 | if(ch == '\n' || ch == ' ') return _SPACE_; 111 | if(isDigit(ch)) { 112 | char nextChar = peek(); 113 | if(ch == '0' && nextChar == '.') { // 0.多少 114 | ++pos; 115 | if(!isDigit(peek())) // .后面不是数字 116 | return _ERROR_; 117 | tempToken = "0."; 118 | while(isDigit(peek())) { 119 | tempToken += peek(); 120 | ++pos; 121 | } 122 | return _DOUBLE_; // 8 123 | } else if(ch == '0' && !isDigit(nextChar)) { // 不是数字也不是.,说明是单纯的一个0 124 | tempToken = "0"; 125 | return _INT_; // 5 126 | } else if(ch != '0') { // digit1 127 | tempToken = ch; 128 | while(isDigit(peek())) { 129 | tempToken += peek(); 130 | ++pos; 131 | } 132 | char nextChar = peek(); 133 | if(nextChar == '.') { 134 | tempToken += nextChar; 135 | ++pos; 136 | nextChar = peek(); 137 | if(isDigit(nextChar)) { 138 | tempToken += peek(); 139 | ++pos; 140 | while(isDigit(peek())) { 141 | tempToken += peek(); 142 | ++pos; 143 | } 144 | return _DOUBLE_; // 8 145 | } else return _ERROR_; 146 | } else return _INT_; // 6 147 | } else { // 0+数字 148 | ++pos; 149 | return _ERROR_; // ERROR 150 | } 151 | } 152 | if(isLetter(ch)) { 153 | tempToken = ch; 154 | char nextChar = peek(); 155 | while( isLetter(nextChar) || isDigit(nextChar) ) { // 标识符~ 156 | tempToken += nextChar; 157 | ++pos; 158 | nextChar = peek(); 159 | } 160 | return isKeyword(tempToken) ? _KEYWORD_ : _ID_; 161 | } 162 | if(ch == '\"') { 163 | tokenList.push_back(Token(54, "\"", cat[_DELIMITER_])); 164 | tempToken = ""; 165 | char nextChar = peek(); 166 | while(nextChar != '\"') { 167 | tempToken += nextChar; 168 | ++pos; 169 | nextChar = peek(); 170 | } 171 | tokenList.push_back(Token(69, tempToken, cat[_STRING_])); 172 | tokenList.push_back(Token(54, "\"", cat[_DELIMITER_])); 173 | pos += 2; 174 | return _STRING_; 175 | } 176 | if(ch == '\'') { 177 | tempToken = ""; 178 | ++pos; 179 | char nextChar = peek(); 180 | if(nextChar == '\'') { 181 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 182 | tempToken += code[pos]; 183 | tokenList.push_back(Token(68, tempToken, cat[_CHAR_])); 184 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 185 | ++pos; 186 | return _CHAR_; 187 | } else if(code[pos] == '\'') { 188 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 189 | tokenList.push_back(Token(68, tempToken, cat[_CHAR_])); // 空字符串 190 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 191 | return _CHAR_; 192 | } else { 193 | while(pos < len && nextChar != '\'') { 194 | ++pos; 195 | nextChar = peek(); 196 | } 197 | ++pos; 198 | return _ERROR_; 199 | } 200 | } 201 | if(ch == '/') { 202 | if(peek() == '*') { 203 | ++pos; 204 | char nextChar = peek(); 205 | ++pos; 206 | tempToken = ""; 207 | while(pos < len) { 208 | if(nextChar == '*' && peek() == '/') { 209 | tokenList.push_back(Token(55, "/*", cat[_DELIMITER_])); 210 | tokenList.push_back(Token(71, tempToken, cat[_COMMENT_])); 211 | tokenList.push_back(Token(56, "*/", cat[_DELIMITER_])); 212 | ++pos; 213 | ++pos; 214 | return _COMMENT_; 215 | } else { 216 | tempToken += nextChar; 217 | nextChar = peek(); 218 | ++pos; 219 | } 220 | } 221 | return _ERROR_; 222 | } 223 | } 224 | 225 | if(isOP(ch)) { // op运算符 226 | tempToken = ""; 227 | tempToken += ch; 228 | char nextChar = peek(); 229 | if(isOP(nextChar)) { 230 | if(isOperator(tempToken + nextChar)) { 231 | tempToken += nextChar; 232 | ++pos; 233 | return _OPERATOR_; // 15 234 | } else return _OPERATOR_; // 14 235 | } else return _OPERATOR_; // 14 236 | } 237 | if(isDelimiter(ch)) { 238 | tempToken = ""; 239 | tempToken += ch; 240 | return _DELIMITER_; 241 | } 242 | return _ERROR_; 243 | } 244 | int read_next() { 245 | int type = judge(code[pos]); 246 | while(pos < len && type == _SPACE_) { 247 | ++pos; 248 | type = judge(code[pos]); 249 | } 250 | if(pos >= len) return _EOF_; 251 | ++pos; 252 | if(type == _ERROR_) return _ERROR_; 253 | if(type == _DOUBLE_) { 254 | // cout << "double: " << tempToken << endl; 255 | tokenList.push_back(Token(67, tempToken, cat[_DOUBLE_])); 256 | return _DOUBLE_; 257 | } 258 | if(type == _INT_) { 259 | // cout << "int: " << tempToken << endl; 260 | tokenList.push_back(Token(66, tempToken, cat[_INT_])); 261 | return _INT_; 262 | } 263 | if(type == _ID_) { // 标识符 264 | // cout << "id: " << tempToken << endl; 265 | tokenList.push_back(Token(70, tempToken, cat[_ID_])); 266 | return _ID_; 267 | } 268 | if(type == _OPERATOR_ || type == _KEYWORD_ || type == _DELIMITER_) { 269 | tokenList.push_back(Token(categoryCode[tempToken], tempToken, cat[type])); 270 | return type; 271 | } 272 | return _ERROR_; 273 | } 274 | int main() { 275 | init(); 276 | while(pos < len) { 277 | int flag = read_next(); 278 | if(flag == _EOF_) break; 279 | else if(flag == _ERROR_) tokenList.push_back(Token(_ERROR_, "ERROR!", "ERROR")); 280 | } 281 | for(auto t : tokenList) 282 | cout << t << endl; 283 | return 0; 284 | } -------------------------------------------------------------------------------- /experiment_1/demo/词法分析.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_1/demo/词法分析.xls -------------------------------------------------------------------------------- /experiment_1/readme.md: -------------------------------------------------------------------------------- 1 | 源代码在demo文件夹中~ 2 | # 一. 实验目的 3 | 4 | 1. 深入理解有限自动机及其应用 5 | 2. 掌握根据语言的词法规则构造识别其单词的有限自动机的方法 6 | 3. 基本掌握词法分析程序的开发方法 7 | 4. 能够设计词法扫描器程序,对源程序进行词法分析,并输出单词序列 8 | 9 | # 二. 实验内容及要求 10 | 11 | 编写识别单词的词法分析程序。 12 | 13 | 已知某语言中各类单词的DFA如下图,编写程序实现: 14 | 15 | 1、输入:txt文件(存放要分析的源程序) 16 | 17 | 2、输出:从输入的源程序中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的种别码及单词符号自身值。(遇到错误时可显示“Error”,然后跳过错误部分继续显示)。 18 | 19 | 输出格式:每个单词的表示:(种别码,单词符号自身值) 20 | 21 | 要求:对识别出的每一单词均单行输出。 22 | 23 | 源程序中每类单词都要有 24 | 25 | 26 | # 三. 实验过程 27 | 28 | ## 1、设计的DFA转换图 29 | 30 | 字母与下划线:`letter -> A|B|…|Z|a|b|c|d…|y|z|_` 31 | 32 | 数字:`digit1 -> 1~9 digit-> 0~9` 33 | 34 | 标识符定义:`id -> letter(letter|digit)*` 35 | 36 | 运算符定义:`op -> +-*/%=!&|<>` 37 | 38 | 关键字定义:`keyword -> int float const bool void char double struct return if else while do static break for switch case default continue true false ` 39 | 40 | 界符定义:`delimiter -> ; , ' " * */ ? : ( ) [ ] } { .` 41 | 42 | 整型定义:`int -> (+|-)(0 | digit1 digit*) ` 43 | 44 | 字符常量:`char -> letter|digit|……` 45 | 46 | 字符串常量:`string -> char*` 47 | 48 | 实型定义:`double-> (0|(+|-)digit1 digit*)(.digit*)` 49 | 50 | 我画的DFA如图 51 | 52 | ![myDFA.png](https://img-blog.csdnimg.cn/img_convert/6b190d7bfbbbf46ee2a839bd20c042d4.png) 53 | ## 2、采用的数据结构 54 | 55 | 输出Token流为类型名称+种别码+值(该关键字/变量名/数字/运算符/界符),重载输出流。 56 | 57 | ```cpp 58 | struct Token { 59 | int type; // 种别码 60 | string value; // 值 关键字/变量名/数字/运算符/界符 61 | string category; // 种别码对应的类型名称 62 | Token(int type, string value, string category) : type(type), value(value), category(category) {} 63 | friend ostream& operator<<(ostream& os, const Token& t) { 64 | os << t.category << ", type: " << t.type << ", value: " << t.value; 65 | return os; 66 | } 67 | }; 68 | ``` 69 | 70 | ![keyword.png](https://img-blog.csdnimg.cn/img_convert/9e3a5b13718813da233c99b3adaf8ea3.png) 71 | 72 | ![operator.png](https://img-blog.csdnimg.cn/img_convert/37c133139afdf1e2d7a00a9aad6ccd79.png) 73 | 74 | ![delimiter.png](https://img-blog.csdnimg.cn/img_convert/b11f845d3963fe8c9f03329f4a1e9d3b.png) 75 | 76 | ![else.png](https://img-blog.csdnimg.cn/img_convert/74358fddb970de5001fd2ed991a7b1ec.png) 77 | ## 3、头文件声明和全局变量定义 78 | 如下,应该非常的一目了然吧。 79 | ```cpp 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | using namespace std; 86 | const string CategoryFileName = "./categoryCode.txt"; 87 | const string CodeFileName = "./code.txt"; 88 | string keywords[22]; // 关键字表 种别码1-22 89 | string operate[28]; // 运算符表 种别码23-50 90 | string delimiter[15]; // 界符表 种别码51-65 91 | map categoryCode; // 种别码表 92 | const string op = "+-*/%=!&|<>"; 93 | const int _EOF_ = -2; 94 | const int _ERROR_ = -1; 95 | enum { 96 | _ID_, _INT_, _DOUBLE_, _OPERATOR_, _DELIMITER_, _KEYWORD_, _CHAR_, _STRING_, _COMMENT_, _SPACE_ 97 | }; // 类型 98 | string cat[10] = { "id", "int", "double", "operator", "delimiter", "keyword", "char", "string", "comment", "space" }; 99 | struct Token { 100 | int type; // 种别码 101 | string value; // 值 关键字/变量名/数字/运算符/界符 102 | string category; // 种别码对应的类型名称 103 | Token(int type, string value, string category) : type(type), value(value), category(category) {} 104 | friend ostream& operator<<(ostream& os, const Token& t) { 105 | os << t.category << ", type: " << t.type << ", value: " << t.value; 106 | return os; 107 | } 108 | }; 109 | int pos, len; // 当前字符位置和长度 110 | string code, tempToken; // 当前识别的字符串 111 | vector tokenList; // 存储识别出的token 112 | ``` 113 | 114 | 115 | ## 4、函数汇总 116 | 117 | ### (1)函数汇总表 118 | 函数名称 | 功能简述 | 119 | | ----------- | ---------------------------------------------------------------------------- | 120 | | `readFile` | 读取文件函数,返回一个string动态数组 | 121 | | `init` | 初始化函数,在该函数中进行读取种别码文件、关键字文件,并进行相应赋值与初始化 | 122 | | `peek` | 探测下一个字符,若存在则返回该字符,否则返回\0即字符串结束符 | 123 | | `isDigit` | 判断字符ch是否为数字0-9 | 124 | | `isLetter` | 判断字符ch是否为字母或下划线(即A-Z a-z _ ) | 125 | | `isKeyword` | 判断字符串s是否为关键字(在关键字表中) | 126 | | `isOP` | 判断字符ch是否为单个运算符(在op中) | 127 | | `isOperator` | 判断字符串s是否为运算符(运算符表中) | 128 | | `isDelimiter` | 判断字符ch是否为界符(在operate中) | 129 | | `judge` | 核心函数,判断并返回当前字符(`code[pos]`)的枚举类型,并对一些特殊的token进行处理后直接放入`tokenList`(如注释、字符和字符串常量) | 130 | | `read_next` | 核心函数,读取下一个字符,根据返回的枚举类型,将对应的token放入`tokenList ` | 131 | | `main` | 主程序入口,从此进入,调用`init`函数初始化 132 | 133 | ### (2)函数的调用关系 134 | ![function.png](https://img-blog.csdnimg.cn/img_convert/aff661018b1f196d8c6165e924db2cd1.png) 135 | ## 5、实验结果 136 | ### 输入 137 | code.txt 138 | ``` 139 | int main() { 140 | char ch = 'ss'; 141 | string str = "Hello, World!" 142 | char ch2 = 's'; 143 | init(); 144 | double x = 10.31;/* some comment */ 145 | int m = 0; 146 | int y = 310, m = 0.31; 147 | while(pos < len) { 148 | int flag = read_next(); 149 | if(flag == _EOF_) break; 150 | if(flag != _ERROR_) { 151 | Token t(flag, tempToken); 152 | tokenList.push_back(t); 153 | cout << t << endl; 154 | } else cout << "Error!" << endl; 155 | } 156 | return 0; 157 | } 158 | ``` 159 | ### 输出 160 | ![0I3RK{8`N%~J~NPNA`(WB_R.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a2c8cbe0d32a4149a4d8ec9525d9b39a~tplv-k3u1fbpfcp-watermark.image?) 161 | 162 | 163 | # 四、实验总结 164 | 此次实验还是很有意思的,最终跑通的时候也是非常有成就感,个人感觉不用拘泥于用什么算法,只需要捋清楚自己的思路,如何设计才能使这个程序能正确识别?主要有一个优先级的思路,空格和换行符会被跳过,然后先判断是否为数字或者字母,在进行相应处理,然后进行一些特殊界符的判断,如字符串、注释等。我认为代码就足以很好的说清楚这个流程。这个程序暂时只使用常用符号(.)来支持小数,如果需要更多,可以在judge中的isdigit()后进行修改,改起来并不困难。显然,judge函数中的函数还可以拆成更细致的几个函数,但这就等以后再补全了。 165 | # 五、思考题回答 166 | ## 程序设计中哪些环节影响词法分析的效率?如何提高效率? 167 | 答:有待优化的部分还有不少,例如在判断是否为关键字时,目前的方法是把可能为标识符或者关键字的字符串读取完后存放在一个字符数组后再逐个与关键字表进行匹配,可改为在读取的同时判断,这样会提高效率。还有就是界符匹配也是同理。 168 | 169 | # 完整代码 170 | ```cpp 171 | /* 172 | * @Author: cos 173 | * @Date: 2022-04-05 00:10:59 174 | * @LastEditTime: 2022-04-08 02:37:49 175 | * @LastEditors: cos 176 | * @Description: 词法分析器设计实现 177 | * @FilePath: \CS\experiment_1\demo\main.cpp 178 | */ 179 | #include 180 | #include 181 | #include 182 | #include 183 | #include 184 | using namespace std; 185 | const string CategoryFileName = "./categoryCode.txt"; 186 | const string CodeFileName = "./code.txt"; 187 | string keywords[22]; // 关键字表 种别码1-22 188 | string operate[28]; // 运算符表 种别码23-50 189 | string delimiter[15]; // 界符表 种别码51-65 190 | map categoryCode; // 种别码表 191 | const string op = "+-*/%=!&|<>"; 192 | const int _EOF_ = -2; 193 | const int _ERROR_ = -1; 194 | enum { 195 | _ID_, _INT_, _DOUBLE_, _OPERATOR_, _DELIMITER_, _KEYWORD_, _CHAR_, _STRING_, _COMMENT_, _SPACE_ 196 | }; // 类型 197 | string cat[10] = { "id", "int", "double", "operator", "delimiter", "keyword", "char", "string", "comment", "space" }; 198 | struct Token { 199 | int type; // 种别码 200 | string value; // 值 关键字/变量名/数字/运算符/界符 201 | string category; // 种别码对应的类型名称 202 | Token(int type, string value, string category) : type(type), value(value), category(category) {} 203 | friend ostream& operator<<(ostream& os, const Token& t) { 204 | os << t.category << ", type: " << t.type << ", value: " << t.value; 205 | return os; 206 | } 207 | }; 208 | int pos, len; // 当前字符位置和长度 209 | string code, tempToken; // 当前识别的字符串 210 | vector tokenList; // 存储识别出的token 211 | // 读文件 212 | vector readFile(string fileName) { 213 | vector res; 214 | try { 215 | ifstream fin; 216 | fin.open(fileName); 217 | string temp; 218 | while (getline(fin, temp)) 219 | res.push_back(temp); 220 | return res; 221 | } catch(const exception& e) { 222 | cerr << e.what() << '\n'; 223 | return res; 224 | } 225 | } 226 | void init() { 227 | vector res = readFile(CategoryFileName); 228 | // cout << "len:" << len << endl; 229 | for(int i = 0; i < 22; ++i) { 230 | keywords[i] = res[i]; 231 | categoryCode[res[i]] = i+1; 232 | // cout << "keyword:" << res[i] << endl; 233 | } 234 | for(int i = 0; i < 28; ++i) { 235 | operate[i] = res[i + 22]; 236 | categoryCode[res[i+22]] = i+23; 237 | // cout << "operate:" << res[i + 22] << endl; 238 | } 239 | for(int i = 0; i < 15; ++i) { 240 | delimiter[i] = res[i + 50]; 241 | categoryCode[res[i+50]] = i+51; 242 | // cout << "delimiter:" << res[i + 50] << endl; 243 | } 244 | res = readFile(CodeFileName); 245 | for(int i = 0; i < res.size(); ++i) 246 | code += res[i]+'\n'; 247 | len = code.size(); 248 | } 249 | char peek() { 250 | if (pos+1 < len) return code[pos+1]; 251 | else return '\0'; 252 | } 253 | inline bool isDigit(char c) { 254 | return c >= '0' && c <= '9'; 255 | } 256 | // 是否为字母或下划线 257 | inline bool isLetter(char c) { 258 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; 259 | } 260 | bool isKeyword(string s) { 261 | for(int i = 0; i < 22; ++i) 262 | if (s == keywords[i]) 263 | return true; 264 | return false; 265 | } 266 | bool isOP(char ch) { 267 | return op.find(ch) != string::npos; 268 | } 269 | bool isOperator(string s) { 270 | for(int i = 0; i < 28; ++i) 271 | if (s == operate[i]) return true; 272 | return false; 273 | } 274 | bool isDelimiter(char ch) { 275 | for(int i = 0; i < 15; ++i) 276 | if (ch == delimiter[i][0]) return true; 277 | return false; 278 | } 279 | int judge(char ch) { 280 | if(ch == '\n' || ch == ' ') return _SPACE_; 281 | if(isDigit(ch)) { 282 | char nextChar = peek(); 283 | if(ch == '0' && nextChar == '.') { // 0.多少 284 | ++pos; 285 | if(!isDigit(peek())) // .后面不是数字 286 | return _ERROR_; 287 | tempToken = "0."; 288 | while(isDigit(peek())) { 289 | tempToken += peek(); 290 | ++pos; 291 | } 292 | return _DOUBLE_; // 8 293 | } else if(ch == '0' && !isDigit(nextChar)) { // 不是数字也不是.,说明是单纯的一个0 294 | tempToken = "0"; 295 | return _INT_; // 5 296 | } else if(ch != '0') { // digit1 297 | tempToken = ch; 298 | while(isDigit(peek())) { 299 | tempToken += peek(); 300 | ++pos; 301 | } 302 | char nextChar = peek(); 303 | if(nextChar == '.') { 304 | tempToken += nextChar; 305 | ++pos; 306 | nextChar = peek(); 307 | if(isDigit(nextChar)) { 308 | tempToken += peek(); 309 | ++pos; 310 | while(isDigit(peek())) { 311 | tempToken += peek(); 312 | ++pos; 313 | } 314 | return _DOUBLE_; // 8 315 | } else return _ERROR_; 316 | } else return _INT_; // 6 317 | } else { // 0+数字 318 | ++pos; 319 | return _ERROR_; // ERROR 320 | } 321 | } 322 | if(isLetter(ch)) { 323 | tempToken = ch; 324 | char nextChar = peek(); 325 | while( isLetter(nextChar) || isDigit(nextChar) ) { // 标识符~ 326 | tempToken += nextChar; 327 | ++pos; 328 | nextChar = peek(); 329 | } 330 | return isKeyword(tempToken) ? _KEYWORD_ : _ID_; 331 | } 332 | if(ch == '\"') { 333 | tokenList.push_back(Token(54, "\"", cat[_DELIMITER_])); 334 | tempToken = ""; 335 | char nextChar = peek(); 336 | while(nextChar != '\"') { 337 | tempToken += nextChar; 338 | ++pos; 339 | nextChar = peek(); 340 | } 341 | tokenList.push_back(Token(69, tempToken, cat[_STRING_])); 342 | tokenList.push_back(Token(54, "\"", cat[_DELIMITER_])); 343 | pos += 2; 344 | return _STRING_; 345 | } 346 | if(ch == '\'') { 347 | tempToken = ""; 348 | ++pos; 349 | char nextChar = peek(); 350 | if(nextChar == '\'') { 351 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 352 | tempToken += code[pos]; 353 | tokenList.push_back(Token(68, tempToken, cat[_CHAR_])); 354 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 355 | ++pos; 356 | return _CHAR_; 357 | } else if(code[pos] == '\'') { 358 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 359 | tokenList.push_back(Token(68, tempToken, cat[_CHAR_])); // 空字符串 360 | tokenList.push_back(Token(53, "\'", cat[_DELIMITER_])); 361 | return _CHAR_; 362 | } else { 363 | while(pos < len && nextChar != '\'') { 364 | ++pos; 365 | nextChar = peek(); 366 | } 367 | ++pos; 368 | return _ERROR_; 369 | } 370 | } 371 | if(ch == '/') { 372 | if(peek() == '*') { 373 | ++pos; 374 | char nextChar = peek(); 375 | ++pos; 376 | tempToken = ""; 377 | while(pos < len) { 378 | if(nextChar == '*' && peek() == '/') { 379 | tokenList.push_back(Token(55, "/*", cat[_DELIMITER_])); 380 | tokenList.push_back(Token(71, tempToken, cat[_COMMENT_])); 381 | tokenList.push_back(Token(56, "*/", cat[_DELIMITER_])); 382 | ++pos; 383 | ++pos; 384 | return _COMMENT_; 385 | } else { 386 | tempToken += nextChar; 387 | nextChar = peek(); 388 | ++pos; 389 | } 390 | } 391 | return _ERROR_; 392 | } 393 | } 394 | 395 | if(isOP(ch)) { // op运算符 396 | tempToken = ""; 397 | tempToken += ch; 398 | char nextChar = peek(); 399 | if(isOP(nextChar)) { 400 | if(isOperator(tempToken + nextChar)) { 401 | tempToken += nextChar; 402 | ++pos; 403 | return _OPERATOR_; // 15 404 | } else return _OPERATOR_; // 14 405 | } else return _OPERATOR_; // 14 406 | } 407 | if(isDelimiter(ch)) { 408 | tempToken = ""; 409 | tempToken += ch; 410 | return _DELIMITER_; 411 | } 412 | return _ERROR_; 413 | } 414 | int read_next() { 415 | int type = judge(code[pos]); 416 | while(pos < len && type == _SPACE_) { 417 | ++pos; 418 | type = judge(code[pos]); 419 | } 420 | if(pos >= len) return _EOF_; 421 | ++pos; 422 | if(type == _ERROR_) return _ERROR_; 423 | if(type == _DOUBLE_) { 424 | // cout << "double: " << tempToken << endl; 425 | tokenList.push_back(Token(67, tempToken, cat[_DOUBLE_])); 426 | return _DOUBLE_; 427 | } 428 | if(type == _INT_) { 429 | // cout << "int: " << tempToken << endl; 430 | tokenList.push_back(Token(66, tempToken, cat[_INT_])); 431 | return _INT_; 432 | } 433 | if(type == _ID_) { // 标识符 434 | // cout << "id: " << tempToken << endl; 435 | tokenList.push_back(Token(70, tempToken, cat[_ID_])); 436 | return _ID_; 437 | } 438 | if(type == _OPERATOR_ || type == _KEYWORD_ || type == _DELIMITER_) { 439 | tokenList.push_back(Token(categoryCode[tempToken], tempToken, cat[type])); 440 | return type; 441 | } 442 | return _ERROR_; 443 | } 444 | int main() { 445 | init(); 446 | while(pos < len) { 447 | int flag = read_next(); 448 | if(flag == _EOF_) break; 449 | else if(flag == _ERROR_) tokenList.push_back(Token(_ERROR_, "ERROR!", "ERROR")); 450 | } 451 | for(auto t : tokenList) 452 | cout << t << endl; 453 | return 0; 454 | } 455 | ``` 456 | -------------------------------------------------------------------------------- /experiment_1/参考资料/1. 实验1 词法分析(设计性实验)-实验指导书.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_1/参考资料/1. 实验1 词法分析(设计性实验)-实验指导书.docx -------------------------------------------------------------------------------- /experiment_1/参考资料/2. 实验1 词法分析(设计性实验)-实验报告模板.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_1/参考资料/2. 实验1 词法分析(设计性实验)-实验报告模板.docx -------------------------------------------------------------------------------- /experiment_1/参考资料/4. 实验1 词法分析-预习提示.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_1/参考资料/4. 实验1 词法分析-预习提示.docx -------------------------------------------------------------------------------- /experiment_2/demo/exp.txt: -------------------------------------------------------------------------------- 1 | i*(i+i)+(i*i)# 2 | i*i-i/1# 3 | i+i*i+i*(i+i*i)# 4 | i+*i(i)+i(i+i*i)# 5 | i+i(i)# -------------------------------------------------------------------------------- /experiment_2/demo/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: cos 3 | * @Date: 2022-04-12 23:03:36 4 | * @LastEditTime: 2022-04-13 01:32:58 5 | * @LastEditors: cos 6 | * @Description: 7 | * @FilePath: \CS\experiment_2\main.cpp 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | using namespace std; 15 | const string ExpFileName = "./exp.txt"; 16 | char analyeStack[20]; /*分析栈*/ 17 | char restStack[20]; /*剩余栈*/ 18 | const string v1 = "i+*()#"; /*终结符 */ 19 | const string v2 = "EGTSF"; /*非终结符 */ 20 | int top, ridx, len; /*len为输入串长度 */ 21 | struct type { /*产生式类型定义 */ 22 | char origin; /*产生式左侧字符 大写字符 */ 23 | string array; /*产生式右边字符 */ 24 | int length; /*字符个数 */ 25 | type():origin('N'), array(""), length(0) {} 26 | void init(char a, string b) { 27 | origin = a; 28 | array = b; 29 | length = array.length(); 30 | } 31 | }; 32 | type e, t, g, g1, s, s1, f, f1; /* 产生式结构体变量 */ 33 | type C[10][10]; /* 预测分析表 */ 34 | void print() {/*输出分析栈和剩余栈 */ 35 | for(int i = 0; i <= top + 1; ++i) /*输出分析栈 */ 36 | cout << analyeStack[i]; 37 | cout << "\t\t"; 38 | 39 | for(int i = 0; i < ridx; ++i) /*输出对齐符*/ 40 | cout << ' '; 41 | for(int i = ridx; i <= len; ++i) /*输出剩余串*/ 42 | cout << restStack[i]; 43 | cout << "\t\t\t"; 44 | } 45 | // 读文件 46 | vector readFile(string fileName) { 47 | vector res; 48 | try { 49 | ifstream fin; 50 | fin.open(fileName); 51 | string temp; 52 | while (getline(fin, temp)) 53 | res.push_back(temp); 54 | return res; 55 | } catch(const exception& e) { 56 | cerr << e.what() << '\n'; 57 | return res; 58 | } 59 | } 60 | bool isTerminator(char c) { // 判断是否是终结符 61 | return v1.find(c) != string::npos; 62 | } 63 | void init(string exp) { 64 | top = ridx = 0; 65 | len = exp.length(); /*分析串长度*/ 66 | for(int i = 0; i < len; ++i) 67 | restStack[i] = exp[i]; 68 | } 69 | void analyze(string exp) { // 分析一个文法 70 | init(exp); 71 | int k = 0; 72 | analyeStack[top] = '#'; 73 | analyeStack[++top] = 'E'; /*'#','E'进栈*/ 74 | cout << "步骤\t\t分析栈 \t\t剩余字符 \t\t所用产生式 " << endl; 75 | while(true) { 76 | char ch = restStack[ridx]; 77 | char x = analyeStack[top--]; /*x为当前栈顶字符*/ 78 | cout << ++k << "\t\t"; 79 | if(x == '#') { 80 | cout << "分析成功!AC!\n" << endl; /*接受 */ 81 | return; 82 | } 83 | if(isTerminator(x)) { 84 | if (x == ch) { // 匹配上了 85 | print(); 86 | cout << ch << "匹配" << endl; 87 | ch = restStack[++ridx]; /*下一个输入字符*/ 88 | } else { /*出错处理*/ 89 | print(); 90 | cout << "分析出错,错误终结符为" << ch << endl; /*输出出错终结符*/ 91 | return; 92 | } 93 | } else { /*非终结符处理*/ 94 | int m, n; // 非终结符下标, 终结符下标 95 | v2.find(x) != string::npos ? m = v2.find(x) : -1; // m为-1则说明找不到该非终结符,出错 96 | v1.find(ch) != string::npos ? n = v1.find(ch) : -1; // n为-1则说明找不到该终结符,出错 97 | if(m == -1 || n == -1) { /*出错处理*/ 98 | print(); 99 | cout << "分析出错,错误非终结符为" << x << endl; /*输出出错非终结符*/ 100 | return; 101 | } 102 | type nowType = C[m][n];/*用来接受C[m][n]*/ 103 | if(nowType.origin != 'N') {/*判断是否为空*/ 104 | print(); 105 | cout << nowType.origin << "->" << nowType.array << endl; /*输出产生式*/ 106 | for (int j = (nowType.length - 1); j >= 0; --j) /*产生式逆序入栈*/ 107 | analyeStack[++top] = nowType.array[j]; 108 | if (analyeStack[top] == '^') /*为空则不进栈*/ 109 | top--; 110 | } else { /*出错处理*/ 111 | print(); 112 | cout << "分析出错,错误非终结符为" << x << endl; /*输出出错非终结符*/ 113 | return; 114 | } 115 | } 116 | } 117 | } 118 | int main() { 119 | e.init('E', "TG"), t.init('T', "FS"); 120 | g.init('G', "+TG"), g1.init('G', "^"); 121 | s.init('S', "*FS"), s1.init('S', "^"); 122 | f.init('F', "(E)"), f1.init('F', "i"); /* 结构体变量 */ 123 | /*填充分析表*/ 124 | C[0][0] = C[0][3] = e; 125 | C[1][1] = g; 126 | C[1][4] = C[1][5] = g1; 127 | C[2][0] = C[2][3] = t; 128 | C[3][2] = s; 129 | C[3][4] = C[3][5] = C[3][1] = s1; 130 | C[4][0] = f1; C[4][3] = f; 131 | cout << "LL(1)分析程序分析程序,编制人:xxx xxx 计科xxxx班" << endl; 132 | cout << "提示:本程序只能对由'i','+','*','(',')'构成的以'#'结束的字符串进行分析,每行一个读入的字符串" << endl; 133 | cout << "读取的文件名为:" << ExpFileName << endl; 134 | vector exps = readFile(ExpFileName); 135 | int len = exps.size(); 136 | for(int i = 0; i < len; i++) { 137 | string exp = exps[i]; 138 | cout << "------------------待分析字符串" << i+1 << ":"<< exp << "--------------------" << endl; 139 | bool flag = true; 140 | for(int j = 0; j < exp.length(); j++) { 141 | if(!isTerminator(exp[j])) { 142 | cout << "第"<< i+1 << "行输入的字符串不合法,请重新输入" << endl; 143 | flag = false; 144 | break; 145 | } 146 | } 147 | if(flag) { 148 | cout << "字符串" << i+1 << ":" << exp << endl; 149 | analyze(exp); 150 | } 151 | } 152 | return 0; 153 | } 154 | -------------------------------------------------------------------------------- /experiment_2/readme.md: -------------------------------------------------------------------------------- 1 | 源代码在demo文件夹中 2 | 3 | # 一. 实验目的 4 | 5 | 1. 掌握LL(1)分析法的基本原理 6 | 2. 掌握LL(1)分析表的构造方法 7 | 3. 掌握LL(1)驱动程序的构造方 8 | 9 | # 二. 实验内容及要求 10 | 11 | 编写识别单词的词法分析程序。 12 | 13 | 根据某一文法编制调试LL(1)分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对预测分析LL(1)分析法的理解。 14 | 15 | 例:对下列文法,用LL(1)分析法对任意输入的符号串进行分析: 16 | 17 | (1)E->TG 18 | 19 | (2)G->+TG|—TG 20 | 21 | (3)G->ε 22 | 23 | (4)T->FS 24 | 25 | (5)S->*FS|/FS 26 | 27 | (6)S->ε 28 | 29 | (7)F->(E) 30 | 31 | (8)F->i 32 | 33 | 输出的格式如下: 34 | 35 | (1) `LL(1)分析程序,编制人:姓名,学号,班级` 36 | 37 | (2) 输入一以#结束的符号串(包括 `+*()i#`):i*(i+i)+(i*i)# 38 | 39 | (3) 输出过程步骤如下: 40 | 41 | | **步骤** | **分析栈** | **剩余输入串** | **所用产生式** | 42 | | -------------- | ---------------- | -------------------- | -------------------- | 43 | | 1 | E | i+i*i# | E->TG | 44 | 45 | (4)输入符号串为非法符号串(或者为合法符号串) 46 | 47 | 备注: 48 | 49 | (1) 在“ **所用产生式** ”一列中如果对应有推导则写出所用产生式;如果为匹配终结符则写明匹配的终结符;如分析异常出错则写为“分析出错”;若成功结束则写为“分析成功”。 50 | 51 | (2)在此位置输入符号串为用户自行输入的符号串。 52 | 53 | (3)上述描述的输出过程只是其中一部分的。 54 | 55 | 注意:1.表达式中允许使用运算符(+-*/)、分割符(括号)、字符i,结束符#; 56 | 57 | 2.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好); 58 | 59 | 3.对学有余力的同学,测试用的表达式事先放在文本文件中,一行存放一个表达式,同时以分号分割。同时将预期的输出结果写在另一个文本文件中,以便和输出进行对照; 60 | 61 | 4.可采用的其它的文法 62 | 63 | # 三. 实验过程 64 | 65 | ## 1、采用的数据结构 66 | 67 | 产生式类型定义为type,左侧为origin,大写字符,右侧产生的字符 68 | 69 | ```cpp 70 | 71 | struct type { /*产生式类型定义 */ 72 | char origin; /*产生式左侧字符 大写字符 */ 73 | string array; /*产生式右边字符 */ 74 | int length; /*字符个数 */ 75 | type():origin('N'), array(""), length(0) {} 76 | void init(char a, string b) { 77 | origin = a; 78 | array = b; 79 | length = array.length(); 80 | } 81 | }; 82 | ``` 83 | 84 | 初始化数据结构如下: 85 | 86 | ```cpp 87 | e.init('E', "TG"), t.init('T', "FS"); 88 | g.init('G', "+TG"), g1.init('G', "^"); 89 | s.init('S', "*FS"), s1.init('S', "^"); 90 | f.init('F', "(E)"), f1.init('F', "i"); 91 | ``` 92 | 93 | ## 2、头文件声明和全局变量定义 94 | 95 | 如下 96 | 97 | ```cpp 98 | #include 99 | #include 100 | #include 101 | #include 102 | #include 103 | using namespace std; 104 | const string ExpFileName = "./exp.txt"; 105 | char analyeStack[20]; /*分析栈*/ 106 | char restStack[20]; /*剩余栈*/ 107 | const string v1 = "i+*()#"; /*终结符 */ 108 | const string v2 = "EGTSF"; /*非终结符 */ 109 | const string acceptStr = "i+*()#"; // 接受的字符串 110 | int top, ridx, len; /*len为输入串长度 */ 111 | struct type { /*产生式类型定义 */ 112 | char origin; /*产生式左侧字符 大写字符 */ 113 | string array; /*产生式右边字符 */ 114 | int length; /*字符个数 */ 115 | type():origin('N'), array(""), length(0) {} 116 | void init(char a, string b) { 117 | origin = a; 118 | array = b; 119 | length = array.length(); 120 | } 121 | }; 122 | type e, t, g, g1, s, s1, f, f1; /* 产生式结构体变量 */ 123 | type C[10][10]; /* 预测分析表 */ 124 | ``` 125 | 126 | ## 4、函数汇总 127 | 128 | ### (1)函数汇总表 129 | 130 | | 函数名称 | 功能简述 | 131 | | ------------ | -------------------------------------- | 132 | | `readFile` | 读取文件函数,返回一个string动态数组,以行数分割 | 133 | | `init` | 初始化函数,在该函数中进行分析栈、剩余栈的初始化 | 134 | | `print` | 输出当前分析栈和剩余栈 | 135 | | `isTerminator` | 判断当前字符c是否是终结符 | 136 | | `analyze` | 分析字符串s,输出其分析步骤 | 137 | | `main` | 主程序入口,从此进入,填充初始分析表及产生式初始化,调用读取文件函数开始分析 | 138 | 139 | ### (2)函数的调用关系 140 | ![function](https://img-blog.csdnimg.cn/abc7e8b3a3284171aa084634eb198772.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5L2ZY29z,size_12,color_FFFFFF,t_70,g_se,x_16) 141 | 142 | ## 5、实验结果 143 | 144 | ### 输入 145 | 146 | 文件exp.txt 147 | 148 | ```txt 149 | i*(i+i)+(i*i)# 150 | i*i-i/1# 151 | i+i*i+i*(i+i*i)# 152 | i+*i(i)+i(i+i*i)# 153 | i+i(i)#code.txt 154 | ``` 155 | 156 | ### 输出 157 | 158 | ![实验结果](https://img-blog.csdnimg.cn/76ca3a03665a4aa8a7c8a4ac59057b54.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5L2ZY29z,size_20,color_FFFFFF,t_70,g_se,x_16) 159 | 160 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/f17a8979097b4880bebcb857e849445e.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5L2ZY29z,size_20,color_FFFFFF,t_70,g_se,x_16) 161 | 162 | 163 | 164 | # 完整代码 165 | 166 | ```cpp 167 | /* 168 | * @Author: cos 169 | * @Date: 2022-04-12 23:03:36 170 | * @LastEditTime: 2022-04-13 01:32:58 171 | * @LastEditors: cos 172 | * @Description: 173 | * @FilePath: \CS\experiment_2\main.cpp 174 | */ 175 | #include 176 | #include 177 | #include 178 | #include 179 | #include 180 | using namespace std; 181 | const string ExpFileName = "./exp.txt"; 182 | char analyeStack[20]; /*分析栈*/ 183 | char restStack[20]; /*剩余栈*/ 184 | const string v1 = "i+*()#"; /*终结符 */ 185 | const string v2 = "EGTSF"; /*非终结符 */ 186 | int top, ridx, len; /*len为输入串长度 */ 187 | struct type { /*产生式类型定义 */ 188 | char origin; /*产生式左侧字符 大写字符 */ 189 | string array; /*产生式右边字符 */ 190 | int length; /*字符个数 */ 191 | type():origin('N'), array(""), length(0) {} 192 | void init(char a, string b) { 193 | origin = a; 194 | array = b; 195 | length = array.length(); 196 | } 197 | }; 198 | type e, t, g, g1, s, s1, f, f1; /* 产生式结构体变量 */ 199 | type C[10][10]; /* 预测分析表 */ 200 | void print() {/*输出分析栈和剩余栈 */ 201 | for(int i = 0; i <= top + 1; ++i) /*输出分析栈 */ 202 | cout << analyeStack[i]; 203 | cout << "\t\t"; 204 | 205 | for(int i = 0; i < ridx; ++i) /*输出对齐符*/ 206 | cout << ' '; 207 | for(int i = ridx; i <= len; ++i) /*输出剩余串*/ 208 | cout << restStack[i]; 209 | cout << "\t\t\t"; 210 | } 211 | // 读文件 212 | vector readFile(string fileName) { 213 | vector res; 214 | try { 215 | ifstream fin; 216 | fin.open(fileName); 217 | string temp; 218 | while (getline(fin, temp)) 219 | res.push_back(temp); 220 | return res; 221 | } catch(const exception& e) { 222 | cerr << e.what() << '\n'; 223 | return res; 224 | } 225 | } 226 | bool isTerminator(char c) { // 判断是否是终结符 227 | return v1.find(c) != string::npos; 228 | } 229 | void init(string exp) { 230 | top = ridx = 0; 231 | len = exp.length(); /*分析串长度*/ 232 | for(int i = 0; i < len; ++i) 233 | restStack[i] = exp[i]; 234 | } 235 | void analyze(string exp) { // 分析一个文法 236 | init(exp); 237 | int k = 0; 238 | analyeStack[top] = '#'; 239 | analyeStack[++top] = 'E'; /*'#','E'进栈*/ 240 | cout << "步骤\t\t分析栈 \t\t剩余字符 \t\t所用产生式 " << endl; 241 | while(true) { 242 | char ch = restStack[ridx]; 243 | char x = analyeStack[top--]; /*x为当前栈顶字符*/ 244 | cout << ++k << "\t\t"; 245 | if(x == '#') { 246 | cout << "分析成功!AC!\n" << endl; /*接受 */ 247 | return; 248 | } 249 | if(isTerminator(x)) { 250 | if (x == ch) { // 匹配上了 251 | print(); 252 | cout << ch << "匹配" << endl; 253 | ch = restStack[++ridx]; /*下一个输入字符*/ 254 | } else { /*出错处理*/ 255 | print(); 256 | cout << "分析出错,错误终结符为" << ch << endl; /*输出出错终结符*/ 257 | return; 258 | } 259 | } else { /*非终结符处理*/ 260 | int m, n; // 非终结符下标, 终结符下标 261 | v2.find(x) != string::npos ? m = v2.find(x) : -1; // m为-1则说明找不到该非终结符,出错 262 | v1.find(ch) != string::npos ? n = v1.find(ch) : -1; // n为-1则说明找不到该终结符,出错 263 | if(m == -1 || n == -1) { /*出错处理*/ 264 | print(); 265 | cout << "分析出错,错误非终结符为" << x << endl; /*输出出错非终结符*/ 266 | return; 267 | } 268 | type nowType = C[m][n];/*用来接受C[m][n]*/ 269 | if(nowType.origin != 'N') {/*判断是否为空*/ 270 | print(); 271 | cout << nowType.origin << "->" << nowType.array << endl; /*输出产生式*/ 272 | for (int j = (nowType.length - 1); j >= 0; --j) /*产生式逆序入栈*/ 273 | analyeStack[++top] = nowType.array[j]; 274 | if (analyeStack[top] == '^') /*为空则不进栈*/ 275 | top--; 276 | } else { /*出错处理*/ 277 | print(); 278 | cout << "分析出错,错误非终结符为" << x << endl; /*输出出错非终结符*/ 279 | return; 280 | } 281 | } 282 | } 283 | } 284 | int main() { 285 | e.init('E', "TG"), t.init('T', "FS"); 286 | g.init('G', "+TG"), g1.init('G', "^"); 287 | s.init('S', "*FS"), s1.init('S', "^"); 288 | f.init('F', "(E)"), f1.init('F', "i"); /* 结构体变量 */ 289 | /*填充分析表*/ 290 | C[0][0] = C[0][3] = e; 291 | C[1][1] = g; 292 | C[1][4] = C[1][5] = g1; 293 | C[2][0] = C[2][3] = t; 294 | C[3][2] = s; 295 | C[3][4] = C[3][5] = C[3][1] = s1; 296 | C[4][0] = f1; C[4][3] = f; 297 | cout << "LL(1)分析程序分析程序,编制人:xxx xxx 计科xxxx班" << endl; 298 | cout << "提示:本程序只能对由'i','+','*','(',')'构成的以'#'结束的字符串进行分析,每行一个读入的字符串" << endl; 299 | cout << "读取的文件名为:" << ExpFileName << endl; 300 | vector exps = readFile(ExpFileName); 301 | int len = exps.size(); 302 | for(int i = 0; i < len; i++) { 303 | string exp = exps[i]; 304 | cout << "------------------待分析字符串" << i+1 << ":"<< exp << "--------------------" << endl; 305 | bool flag = true; 306 | for(int j = 0; j < exp.length(); j++) { 307 | if(!isTerminator(exp[j])) { 308 | cout << "第"<< i+1 << "行输入的字符串不合法,请重新输入" << endl; 309 | flag = false; 310 | break; 311 | } 312 | } 313 | if(flag) { 314 | cout << "字符串" << i+1 << ":" << exp << endl; 315 | analyze(exp); 316 | } 317 | } 318 | return 0; 319 | } 320 | 321 | ``` 322 | -------------------------------------------------------------------------------- /experiment_2/参考资料/1. 实验2 LL(1)分析法-预习提示.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_2/参考资料/1. 实验2 LL(1)分析法-预习提示.docx -------------------------------------------------------------------------------- /experiment_2/参考资料/2. 实验2 LL(1)分析法-实验报告模板.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_2/参考资料/2. 实验2 LL(1)分析法-实验报告模板.docx -------------------------------------------------------------------------------- /experiment_2/参考资料/3. 实验2 LL(1)分析法-参考程序.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_2/参考资料/3. 实验2 LL(1)分析法-参考程序.txt -------------------------------------------------------------------------------- /experiment_3/demo/exp.txt: -------------------------------------------------------------------------------- 1 | (28+68)*2 2 | (12-10+10*2)/2 3 | 32+12/(42-4*10) 4 | 12/0+12 5 | (1+1)+1) 6 | hauihd1@! -------------------------------------------------------------------------------- /experiment_3/demo/function3.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_3/demo/function3.drawio.png -------------------------------------------------------------------------------- /experiment_3/demo/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: cos 3 | * @Date: 2022-04-18 00:46:57 4 | * @LastEditTime: 2022-04-18 20:52:52 5 | * @LastEditors: cos 6 | * @Description: 编译原理实验三 逆波兰表达式计算及求值 7 | * @FilePath: \CompileTheory\experiment_3\demo\main.cpp 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | using namespace std; 16 | const int Error = -1; 17 | const string ExpFileName = "./exp.txt"; 18 | const string existChars = "+-*/()0123456789"; 19 | const string op = "+-*/"; 20 | map opMap = { 21 | {"+", 1}, 22 | {"-", 1}, 23 | {"*", 2}, 24 | {"/", 2}, 25 | }; 26 | // 读文件 27 | vector readFile(string fileName) { 28 | vector res; 29 | try { 30 | ifstream fin; 31 | fin.open(fileName); 32 | string temp; 33 | while (getline(fin, temp)) 34 | res.push_back(temp); 35 | return res; 36 | } catch(const exception& e) { 37 | cerr << e.what() << '\n'; 38 | return res; 39 | } 40 | } 41 | 42 | bool isValid(string exp) { 43 | if(exp.find_first_not_of(existChars) != string::npos){ 44 | cout << "Error:输入的表达式不合法(只能包含+—*/()数字)!" << endl; 45 | return false; 46 | } 47 | stack s; // ()??? 48 | for (auto i : exp) { 49 | if(i == '(') 50 | s.push(i); 51 | else if(i == ')') { 52 | if(s.empty()){ 53 | cout << "Error: ) without (!\n"; 54 | return false; 55 | } 56 | s.pop(); 57 | } 58 | } 59 | return true; 60 | } 61 | class ReversePolishExp { 62 | private: 63 | int len; 64 | string exp; 65 | vector postfixExp; 66 | stack s; 67 | public: 68 | ReversePolishExp(string exp) { 69 | this->exp = exp; 70 | this->len = exp.length(); 71 | this->postfixExp = getPostfixExp(exp); 72 | } 73 | // 判断是否为数字 74 | bool isDigit(char c) { 75 | return c >= '0' && c <= '9'; 76 | } 77 | // 判断是否为+-*/ 78 | bool isOperate(char c) { 79 | return op.find(c) != string::npos; 80 | } 81 | // 将字符串转换为整数 82 | int s2i(string s) { 83 | int res = 0; 84 | for (auto i : s) 85 | res = res * 10 + (i - '0'); 86 | return res; 87 | } 88 | // 读入一个完整的整数。 89 | string readNum(string exp, int& idx) { 90 | string res = ""; 91 | while(idx < len && isDigit(exp[idx])) { 92 | res += exp[idx]; 93 | idx++; 94 | } 95 | --idx; 96 | return res; 97 | } 98 | // 根据中缀计算后缀表达式 99 | vector getPostfixExp(string exp) { 100 | vector res; 101 | for(int i = 0; i < len; ++i) { 102 | char nowChar = exp[i]; 103 | if(isDigit(nowChar)) { 104 | res.push_back(readNum(exp, i)); 105 | } else if(isOperate(nowChar)) { 106 | string temp = string(1, nowChar); 107 | if(opMap[temp] == 1) { // + - 108 | while(!s.empty() && s.top() != "(") { 109 | res.push_back(s.top()); 110 | s.pop(); 111 | } 112 | } else { // * / 113 | while(!s.empty() && (s.top() == "*" || s.top() == "/")) { 114 | res.push_back(s.top()); 115 | s.pop(); 116 | } 117 | } 118 | s.push(temp); 119 | } else if(nowChar == '(') { 120 | s.push("("); 121 | } else if(nowChar == ')') { 122 | while(!s.empty() && s.top() != "(") { 123 | res.push_back(s.top()); 124 | s.pop(); 125 | } 126 | s.pop(); 127 | } 128 | } 129 | while(!s.empty()) { 130 | res.push_back(s.top()); 131 | s.pop(); 132 | } 133 | return res; 134 | } 135 | // 计算后缀表达式的值 136 | double compute() { 137 | stack s2; 138 | for(auto item: postfixExp) { 139 | if(isDigit(item[0])) { // 是数字的话 直接入栈 140 | s2.push(s2i(item)*1.0); 141 | } else { 142 | double b = s2.top(); 143 | s2.pop(); 144 | double a = s2.top(); 145 | s2.pop(); 146 | switch(item[0]) { 147 | case '+': s2.push(a + b); break; 148 | case '-': s2.push(a - b); break; 149 | case '*': s2.push(a * b); break; 150 | case '/': 151 | if(b == 0) { 152 | cout << "Error: denominator is 0!" << endl; 153 | return Error; 154 | } 155 | s2.push(a / b); 156 | break; 157 | } 158 | } 159 | } 160 | return s2.top(); 161 | } 162 | void printPostfixExp() { 163 | if(this->postfixExp.empty()) { 164 | cout << "postfixExp is empty" << endl; 165 | return; 166 | } 167 | cout << "输出的逆波兰表达式为: "; 168 | for (auto i : this->postfixExp) 169 | cout << i << " "; 170 | cout << endl; 171 | } 172 | void printExp() { 173 | if(this->exp.empty()) { 174 | cout << "exp is empty" << endl; 175 | return; 176 | } 177 | cout << "输入的中缀表达式为: " << this->exp << endl; 178 | } 179 | }; 180 | int main(){ 181 | 182 | cout << "逆波兰式的生成及计算程序,编制人: 201916010728 余思娴 计科F1901班" << endl; 183 | cout << "读取的文件名为:" << ExpFileName << endl; 184 | vector exps = readFile(ExpFileName); 185 | int len = exps.size(); 186 | for(int i = 0; i < len; i++) { 187 | string exp = exps[i]; 188 | cout << "\n------------------样例" << i+1 << ": "<< exp << "--------------------" << endl; 189 | if(!isValid(exp)) continue; 190 | cout << "第"<< i+1 << "行输入的表达式合法,开始计算" << endl; 191 | ReversePolishExp res(exp); 192 | res.printExp(); 193 | res.printPostfixExp(); 194 | cout << "该逆波兰表达式的计算值为:" << res.compute() << endl; 195 | } 196 | return 0; 197 | } 198 | -------------------------------------------------------------------------------- /experiment_3/demo/main.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_3/demo/main.drawio.png -------------------------------------------------------------------------------- /experiment_3/demo/output.txt: -------------------------------------------------------------------------------- 1 | ------------------样例1:(28+68)*2-------------------- 2 | 第1行输入的表达式合法,开始计算 3 | 输入的中缀表达式为: (28+68)*2 4 | 输出的逆波兰表达式为: 28 68 + 2 * 5 | 该逆波兰表达式的计算值为:192 6 | ------------------样例3:32+12/(42-4*10)-------------------- 7 | 第3行输入的表达式合法,开始计算 8 | 输入的中缀表达式为: 32+12/(42-4*10) 9 | 输出的逆波兰表达式为: 32 12 42 4 10 * - / + 10 | 该逆波兰表达式的计算值为:38 11 | 12 | ------------------样例4:12/0+12-------------------- 13 | 第4行输入的表达式合法,开始计算 14 | 输入的中缀表达式为: 12/0+12 15 | 输出的逆波兰表达式为: 12 0 / 12 + 16 | 该逆波兰表达式的计算值为:Error:除数不能为0! 17 | -1 18 | 19 | ------------------样例5:(1+1)+1)-------------------- 20 | error: ) without ( 21 | 22 | ------------------样例6:hauihd1@!-------------------- 23 | error:输入的表达式不合法(只能包含+—*/()数字#) -------------------------------------------------------------------------------- /experiment_3/readme.md: -------------------------------------------------------------------------------- 1 | 源代码在demo文件夹中~ 2 | # 一. 实验目的 3 | 将非后缀式用来表示的算术表达式转换为用逆波兰式来表示的算术表达式,并计算用逆波兰式来表示的算术表达式的值。 4 | 5 | # 二. 实验内容及要求 6 | 将非后缀式用来表示的算术表达式转换为用逆波兰式来表示的算术表达式,并计算用逆波兰式来表示的算术表达式的值。 7 | 程序输入/输出示例: 8 | 输出的格式如下: 9 | (1)逆波兰式的生成及计算程序,编制人:姓名,学号,班级 10 | (2)输入一以#结束的中缀表达式(包括+—*/()数字#):在此位置输入符号串如(28+68)*2# 11 | (3)逆波兰式为:28&68+2* 12 | (4)逆波兰式28&68+2*计算结果为192 13 | > 备注:\ 14 | (1) 在生成的逆波兰式中如果两个数相连则用&分隔,如28和68,中间用&分隔\ 15 | (2)在此位置输入符号串为用户自行输入的符号串。 16 | 17 | ## 注意 18 | 1. 表达式中允许使用运算符(+-*/)、分割符(括号)、数字,结束符# 19 | 2. 如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好) 20 | 3. 对学有余力的同学,测试用的表达式事先放在文本文件中,一行存放一个表达式,同时以分号分割。同时将预期的输出结果写在另一个文本文件中,以便和输出进行对照 21 | 22 | # 三. 实验过程 23 | 24 | ## 1、功能描述 25 | 读取文件exp.txt中每一行表达式,输出其逆波兰表达式以及根据逆波兰表达式求值。 26 | ## 2、采用的数据结构 27 | 28 | ```cpp 29 | class ReversePolishExp { 30 | private: 31 | int len; 32 | string exp; 33 | vector postfixExp; 34 | stack s; 35 | public: 36 | ReversePolishExp(string exp) { 37 | this->exp = exp; 38 | this->len = exp.length(); 39 | this->postfixExp = getPostfixExp(exp); 40 | } 41 | // 判断是否为数字 42 | bool isDigit(char c) { 43 | return c >= '0' && c <= '9'; 44 | } 45 | // 判断是否为+-*/ 46 | bool isOperate(char c) { 47 | return op.find(c) != string::npos; 48 | } 49 | // 将字符串转换为整数 50 | int s2i(string s) { 51 | int res = 0; 52 | for (auto i : s) 53 | res = res * 10 + (i - '0'); 54 | return res; 55 | } 56 | // 读入一个完整的整数。 57 | string readNum(string exp, int& idx) { 58 | string res = ""; 59 | while(idx < len && isDigit(exp[idx])) { 60 | res += exp[idx]; 61 | idx++; 62 | } 63 | --idx; 64 | return res; 65 | } 66 | // 根据中缀计算后缀表达式 67 | vector getPostfixExp(string exp) { 68 | vector res; 69 | for(int i = 0; i < len; ++i) { 70 | char nowChar = exp[i]; 71 | if(isDigit(nowChar)) { 72 | res.push_back(readNum(exp, i)); 73 | } else if(isOperate(nowChar)) { 74 | string temp = string(1, nowChar); 75 | if(opMap[temp] == 1) { // + - 76 | while(!s.empty() && s.top() != "(") { 77 | res.push_back(s.top()); 78 | s.pop(); 79 | } 80 | } else { // * / 81 | while(!s.empty() && (s.top() == "*" || s.top() == "/")) { 82 | res.push_back(s.top()); 83 | s.pop(); 84 | } 85 | } 86 | s.push(temp); 87 | } else if(nowChar == '(') { 88 | s.push("("); 89 | } else if(nowChar == ')') { 90 | while(!s.empty() && s.top() != "(") { 91 | res.push_back(s.top()); 92 | s.pop(); 93 | } 94 | s.pop(); 95 | } 96 | } 97 | while(!s.empty()) { 98 | res.push_back(s.top()); 99 | s.pop(); 100 | } 101 | return res; 102 | } 103 | // 计算后缀表达式的值 104 | double compute() { 105 | stack s2; 106 | for(auto item: postfixExp) { 107 | if(isDigit(item[0])) { // 是数字的话 直接入栈 108 | s2.push(s2i(item)*1.0); 109 | } else { 110 | double b = s2.top(); 111 | s2.pop(); 112 | double a = s2.top(); 113 | s2.pop(); 114 | switch(item[0]) { 115 | case '+': s2.push(a + b); break; 116 | case '-': s2.push(a - b); break; 117 | case '*': s2.push(a * b); break; 118 | case '/': 119 | if(b == 0) { 120 | cout << "Error: denominator is 0!" << endl; 121 | return Error; 122 | } 123 | s2.push(a / b); 124 | break; 125 | } 126 | } 127 | } 128 | return s2.top(); 129 | } 130 | void printPostfixExp() { 131 | if(this->postfixExp.empty()) { 132 | cout << "postfixExp is empty" << endl; 133 | return; 134 | } 135 | cout << "输出的逆波兰表达式为: "; 136 | for (auto i : this->postfixExp) 137 | cout << i << " "; 138 | cout << endl; 139 | } 140 | void printExp() { 141 | if(this->exp.empty()) { 142 | cout << "exp is empty" << endl; 143 | return; 144 | } 145 | cout << "输入的中缀表达式为: " << this->exp << endl; 146 | } 147 | }; 148 | ``` 149 | ## 3、头文件声明和全局变量定义 150 | 如下。 151 | ```cpp 152 | /* 153 | * @Author: cos 154 | * @Date: 2022-04-18 00:46:57 155 | * @LastEditTime: 2022-04-18 20:40:38 156 | * @LastEditors: cos 157 | * @Description: 编译原理实验三 逆波兰表达式计算及求值 158 | * @FilePath: \CompileTheory\experiment_3\demo\main.cpp 159 | */ 160 | #include 161 | #include 162 | #include 163 | #include 164 | #include 165 | #include 166 | using namespace std; 167 | const int Error = -1; 168 | const string ExpFileName = "./exp.txt"; 169 | const string existChars = "+-*/()0123456789"; 170 | const string op = "+-*/"; 171 | map opMap = { 172 | {"+", 1}, 173 | {"-", 1}, 174 | {"*", 2}, 175 | {"/", 2}, 176 | }; 177 | ``` 178 | ## 4、函数汇总 179 | 180 | ### (1)函数汇总表 181 | 182 | | 函数名称 | 功能简述 | 183 | | --------------- | --------------------------- | 184 | | `readFile` | 读取文件函数,返回一个string动态数组,以行数分割 | 185 | | `isValid` | 判断表达式是否有效(只能包含`+-* /()数字`) | 186 | | `isDigit` | 判断字符是否为数字 | 187 | | `isOperate` | 判断是否为 + - * / | 188 | | `s2i` | 字符串转数字 | 189 | | `readNum` | 读取连续的数字构成整数返回 | 190 | | `getPostfixExp` | 中缀表达式转逆波兰表达式 | 191 | | `compute` | 计算逆波兰表达式的值 | 192 | | `printExp` | 输出中缀表达式 | 193 | | `printPostfixExp` | 输出逆波兰表达式 194 | 195 | ### (2)函数的调用关系 196 | ![function3.drawio.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6c67d4e323742a9b2c0c5a9569d01b4~tplv-k3u1fbpfcp-watermark.image?) 197 | ### (3)流程图 198 | ![main.drawio.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/772d9329232c42eab2ae9f3ec1760db8~tplv-k3u1fbpfcp-watermark.image?) 199 | ## 5、实验结果 200 | ### 输入 201 | exp.txt 202 | ```txt 203 | (28+68)*2 204 | (12-10+10*2)/2 205 | 32+12/(42-4*10) 206 | 12/0+12 207 | (1+1)+1) 208 | hauihd1@! 209 | ``` 210 | ### 输出 211 | ``` 212 | 读取的文件名为:./exp.txt 213 | 214 | ------------------样例1: (28+68)*2-------------------- 215 | 第1行输入的表达式合法,开始计算 216 | 输入的中缀表达式为: (28+68)*2 217 | 第2行输入的表达式合法,开始计算 218 | 输入的中缀表达式为: (12-10+10*2)/2 219 | 输出的逆波兰表达式为: 12 10 - 10 2 * + 2 / 220 | ./../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-crt0_c.o):crt0_c.c:(.text+0x46): undefined reference to `WinMain' 221 | collect2.exe: error: ld returned 1 exit status 222 | PS C:\Users\34504\OneDrive - nahcox\Programming\CS\CompileTheory\experiment_3\demo> cd "c:\Users\34504\OneDrive - nahcox\Programming\CS\CompileTheory\experiment_3\demo\" ; if ($?) { g++ main.cpp -o main } ; if ($?) { .\main } 223 | 逆波兰式的生成及计算程序,编制人: 201916010728 余思娴 计科F1901班 224 | 读取的文件名为:./exp.txt 225 | 226 | ------------------样例1: (28+68)*2-------------------- 227 | 第1行输入的表达式合法,开始计算 228 | 输入的中缀表达式为: (28+68)*2 229 | 输出的逆波兰表达式为: 28 68 + 2 * 230 | 该逆波兰表达式的计算值为:192 231 | 232 | ------------------样例2: (12-10+10*2)/2-------------------- 233 | 第2行输入的表达式合法,开始计算 234 | 输入的中缀表达式为: (12-10+10*2)/2 235 | 输出的逆波兰表达式为: 12 10 - 10 2 * + 2 / 236 | 该逆波兰表达式的计算值为:11 237 | 238 | ------------------样例3: 32+12/(42-4*10)-------------------- 239 | 第3行输入的表达式合法,开始计算 240 | 输入的中缀表达式为: 32+12/(42-4*10) 241 | 输出的逆波兰表达式为: 32 12 42 4 10 * - / + 242 | 该逆波兰表达式的计算值为:38 243 | 244 | ------------------样例4: 12/0+12-------------------- 245 | 第4行输入的表达式合法,开始计算 246 | 输入的中缀表达式为: 12/0+12 247 | 输出的逆波兰表达式为: 12 0 / 12 + 248 | 该逆波兰表达式的计算值为:Error: denominator is 0! 249 | -1 250 | 251 | ------------------样例5: (1+1)+1)-------------------- 252 | Error: ) without (! 253 | 254 | ------------------样例6: hauihd1@!-------------------- 255 | Error:输入的表达式不合法(只能包含+—*/()数字)! 256 | ``` 257 | 258 | # 完整代码 259 | ```cpp 260 | /* 261 | * @Author: cos 262 | * @Date: 2022-04-18 00:46:57 263 | * @LastEditTime: 2022-04-18 20:40:38 264 | * @LastEditors: cos 265 | * @Description: 编译原理实验三 逆波兰表达式计算及求值 266 | * @FilePath: \CompileTheory\experiment_3\demo\main.cpp 267 | */ 268 | #include 269 | #include 270 | #include 271 | #include 272 | #include 273 | #include 274 | using namespace std; 275 | const int Error = -1; 276 | const string ExpFileName = "./exp.txt"; 277 | const string existChars = "+-*/()0123456789"; 278 | const string op = "+-*/"; 279 | map opMap = { 280 | {"+", 1}, 281 | {"-", 1}, 282 | {"*", 2}, 283 | {"/", 2}, 284 | }; 285 | // 读文件 286 | vector readFile(string fileName) { 287 | vector res; 288 | try { 289 | ifstream fin; 290 | fin.open(fileName); 291 | string temp; 292 | while (getline(fin, temp)) 293 | res.push_back(temp); 294 | return res; 295 | } catch(const exception& e) { 296 | cerr << e.what() << '\n'; 297 | return res; 298 | } 299 | } 300 | 301 | bool isValid(string exp) { 302 | if(exp.find_first_not_of(existChars) != string::npos){ 303 | cout << "Error:输入的表达式不合法(只能包含+—*/()数字)!" << endl; 304 | return false; 305 | } 306 | stack s; // ()??? 307 | for (auto i : exp) { 308 | if(i == '(') 309 | s.push(i); 310 | else if(i == ')') { 311 | if(s.empty()){ 312 | cout << "Error: ) without (!\n"; 313 | return false; 314 | } 315 | s.pop(); 316 | } 317 | } 318 | return true; 319 | } 320 | class ReversePolishExp { 321 | private: 322 | int len; 323 | string exp; 324 | vector postfixExp; 325 | stack s; 326 | public: 327 | ReversePolishExp(string exp) { 328 | this->exp = exp; 329 | this->len = exp.length(); 330 | this->postfixExp = getPostfixExp(exp); 331 | } 332 | // 判断是否为数字 333 | bool isDigit(char c) { 334 | return c >= '0' && c <= '9'; 335 | } 336 | // 判断是否为+-*/ 337 | bool isOperate(char c) { 338 | return op.find(c) != string::npos; 339 | } 340 | // 将字符串转换为整数 341 | int s2i(string s) { 342 | int res = 0; 343 | for (auto i : s) 344 | res = res * 10 + (i - '0'); 345 | return res; 346 | } 347 | // 读入一个完整的整数。 348 | string readNum(string exp, int& idx) { 349 | string res = ""; 350 | while(idx < len && isDigit(exp[idx])) { 351 | res += exp[idx]; 352 | idx++; 353 | } 354 | --idx; 355 | return res; 356 | } 357 | // 根据中缀计算后缀表达式 358 | vector getPostfixExp(string exp) { 359 | vector res; 360 | for(int i = 0; i < len; ++i) { 361 | char nowChar = exp[i]; 362 | if(isDigit(nowChar)) { 363 | res.push_back(readNum(exp, i)); 364 | } else if(isOperate(nowChar)) { 365 | string temp = string(1, nowChar); 366 | if(opMap[temp] == 1) { // + - 367 | while(!s.empty() && s.top() != "(") { 368 | res.push_back(s.top()); 369 | s.pop(); 370 | } 371 | } else { // * / 372 | while(!s.empty() && (s.top() == "*" || s.top() == "/")) { 373 | res.push_back(s.top()); 374 | s.pop(); 375 | } 376 | } 377 | s.push(temp); 378 | } else if(nowChar == '(') { 379 | s.push("("); 380 | } else if(nowChar == ')') { 381 | while(!s.empty() && s.top() != "(") { 382 | res.push_back(s.top()); 383 | s.pop(); 384 | } 385 | s.pop(); 386 | } 387 | } 388 | while(!s.empty()) { 389 | res.push_back(s.top()); 390 | s.pop(); 391 | } 392 | return res; 393 | } 394 | // 计算后缀表达式的值 395 | double compute() { 396 | stack s2; 397 | for(auto item: postfixExp) { 398 | if(isDigit(item[0])) { // 是数字的话 直接入栈 399 | s2.push(s2i(item)*1.0); 400 | } else { 401 | double b = s2.top(); 402 | s2.pop(); 403 | double a = s2.top(); 404 | s2.pop(); 405 | switch(item[0]) { 406 | case '+': s2.push(a + b); break; 407 | case '-': s2.push(a - b); break; 408 | case '*': s2.push(a * b); break; 409 | case '/': 410 | if(b == 0) { 411 | cout << "Error: denominator is 0!" << endl; 412 | return Error; 413 | } 414 | s2.push(a / b); 415 | break; 416 | } 417 | } 418 | } 419 | return s2.top(); 420 | } 421 | void printPostfixExp() { 422 | if(this->postfixExp.empty()) { 423 | cout << "postfixExp is empty" << endl; 424 | return; 425 | } 426 | cout << "输出的逆波兰表达式为: "; 427 | for (auto i : this->postfixExp) 428 | cout << i << " "; 429 | cout << endl; 430 | } 431 | void printExp() { 432 | if(this->exp.empty()) { 433 | cout << "exp is empty" << endl; 434 | return; 435 | } 436 | cout << "输入的中缀表达式为: " << this->exp << endl; 437 | } 438 | }; 439 | int main(){ 440 | cout << "读取的文件名为:" << ExpFileName << endl; 441 | vector exps = readFile(ExpFileName); 442 | int len = exps.size(); 443 | for(int i = 0; i < len; i++) { 444 | string exp = exps[i]; 445 | cout << "\n------------------样例" << i+1 << ": "<< exp << "--------------------" << endl; 446 | if(!isValid(exp)) continue; 447 | cout << "第"<< i+1 << "行输入的表达式合法,开始计算" << endl; 448 | ReversePolishExp res(exp); 449 | res.printExp(); 450 | res.printPostfixExp(); 451 | cout << "该逆波兰表达式的计算值为:" << res.compute() << endl; 452 | } 453 | return 0; 454 | } 455 | 456 | ``` 457 | -------------------------------------------------------------------------------- /experiment_3/参考资料/1. 实验3 逆波兰式的产生及计算-预习提示.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_3/参考资料/1. 实验3 逆波兰式的产生及计算-预习提示.docx -------------------------------------------------------------------------------- /experiment_3/参考资料/2. 实验3 逆波兰式的产生及计算-实验报告模板.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_3/参考资料/2. 实验3 逆波兰式的产生及计算-实验报告模板.docx -------------------------------------------------------------------------------- /experiment_3/参考资料/3. 实验3 逆波兰式的产生及计算-参考程序.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_3/参考资料/3. 实验3 逆波兰式的产生及计算-参考程序.txt -------------------------------------------------------------------------------- /experiment_4/demo/LR.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_4/demo/LR.xls -------------------------------------------------------------------------------- /experiment_4/demo/action.txt: -------------------------------------------------------------------------------- 1 | N N s4 N s5 N 2 | s6 N N N N acc 3 | r2 s7 N r2 N r2 4 | r4 r4 N r4 N r4 5 | N N s4 N s5 N 6 | r6 r6 N r6 N r6 7 | N N s4 N s5 N 8 | N N s4 N s5 N 9 | s6 N N s11 N N 10 | r1 s7 N r1 N r1 11 | r3 r3 N r3 N r3 12 | r5 r5 N r5 N r5 -------------------------------------------------------------------------------- /experiment_4/demo/exp.txt: -------------------------------------------------------------------------------- 1 | i+(i*i)*(i+i)# 2 | i*i+i*i# 3 | i+i*i+i*(i+i*i)# 4 | i+*(i)+i(i+i*i)# 5 | i+i(i)# -------------------------------------------------------------------------------- /experiment_4/demo/goto.txt: -------------------------------------------------------------------------------- 1 | 1 2 3 2 | N N N 3 | N N N 4 | N N N 5 | 8 2 3 6 | N N N 7 | N 9 3 8 | N N 10 9 | N N N 10 | N N N 11 | N N N 12 | N N N -------------------------------------------------------------------------------- /experiment_4/demo/main.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_4/demo/main.cpp -------------------------------------------------------------------------------- /experiment_4/readme.md: -------------------------------------------------------------------------------- 1 | 源代码仓库:[CompilePrincipleLearning/experiment_4 · yusixian/CompilePrincipleLearning (github.com)](https://github.com/yusixian/CompilePrincipleLearning/tree/master/experiment_4) 2 | 3 | 源代码在demo文件夹中~ 4 | 5 | # 一. 实验目的 6 | 1. 掌握LR(1)分析法的基本原理 7 | 2. 掌握LR(1)分析表的构造方法 8 | 3. 掌握LR(1)驱动程序的构造方法 9 | 10 | # 二. 实验内容及要求 11 | 构造LR(1)分析程序,利用它进行语法分析,判断给出的符号串是否为该文法识别的句子,了解LR(K)分析方法是严格的从左向右扫描,和自底向上的语法分析方法。 12 | 13 | 根据某一文法编制调试LR(1)分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对LR(1)分析法的理解。 14 | 15 | **对下列文法,用LR(1)分析法对任意输入的符号串进行分析**: 16 | ```txt 17 | (0)S’->E 18 | 19 | (1)E->E+T 20 | 21 | (2)E->T 22 | 23 | (3)T->T*F 24 | 25 | (4)T->F 26 | 27 | (5)F->(E) 28 | 29 | (6)F->i 30 | ``` 31 | 32 | 输出的格式如下: 33 | 34 | (1)LR(1)分析程序,编制人:姓名,学号,班级 35 | 36 | (2)输入一以#结束的符号串(包括+-*/()i#):在此位置输入符号串 37 | 38 | (3)输出过程如下: 39 | 40 | | **步骤** | **状态栈** | **符号栈****** | **剩余输入串** | **动作** | 41 | | ------ | ------- | ----------- | --------- | ------ | 42 | | 1 | 0 | # | i+i*i# | 移进 | 43 | 44 | (4)输入符号串为非法符号串或合法符号串 45 | 46 | 注意: 47 | 48 | 1.表达式中允许使用运算符(+|*)、分割符(括号)、字符i,结束符#; 49 | 50 | 2.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好); 51 | 52 | 3.对学有余力的同学,测试用的表达式事先放在文本文件中,一行存放一个表达式,同时以分号分割。同时将预期的输出结果写在另一个文本文件中,以便和输出进行对照; 53 | 54 | 4.可采用的其它的文法,但是必须是LR1分析方法。 55 | 56 | # 三. 实验过程 57 | 58 | ## 1、构造识别LR(1)文法活前缀的DFA 59 | 如图:新标签页打开,不糊的。 60 | 61 | ![LR1_DFA.drawio.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b812acc3e4e4ea9929eef56cebea020~tplv-k3u1fbpfcp-watermark.image?) 62 | 63 | action表和goto表如下: 64 | 65 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5dbe68ae771f4a89a6dca82976915452~tplv-k3u1fbpfcp-watermark.image?) 66 | 67 | ## 2、采用的数据结构 68 | 69 | ```cpp 70 | // ACTION表 71 | // + * ( ) i # 72 | string action[12][6]; 73 | // goto表 74 | // a b # 75 | int _goto[12][3]; 76 | string vt = "+*()i#"; // 终结符表 77 | string vn = "ETF"; // 非终结符表 78 | string LR[6] = { "E->E+T", "E->T", "T->T*F", "T->F", "F->(E)", "F->i" }; // 存放产生式 79 | stack chars; // 符号栈 80 | stack state; // 状态栈 81 | ``` 82 | ## 3、头文件声明和全局变量定义 83 | 如下。 84 | ```cpp 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | using namespace std; 91 | const string ExpFileName = "./exp.txt"; 92 | const string GotoFileName = "./goto.txt"; 93 | const string ActionFileName = "./action.txt"; 94 | const int Null = -1; 95 | // ACTION表 96 | // + * ( ) i # 97 | string action[12][6]; 98 | // goto表 99 | // a b # 100 | int _goto[12][3]; 101 | string vt = "+*()i#"; // 终结符表 102 | string vn = "ETF"; // 非终结符表 103 | string LR[6] = { "E->E+T", "E->T", "T->T*F", "T->F", "F->(E)", "F->i" }; // 存放产生式 104 | ``` 105 | ## 4、函数汇总 106 | 107 | ### (1)函数汇总表 108 | | 函数名称 | 功能简述 | 109 | | ------------------------- | ------------------------------ | 110 | | `readFile` | 读取文件函数,返回一个string动态数组,以行数分割 | 111 | | `init` | 初始化函数,在该函数中进行goto表和action表的初始化 | 112 | | `printActions / printGotos` | 输出goto表与action表 | 113 | | `isTerminator` | 判断当前字符c是否是终结符 | 114 | | `findTerminator` | 返回终结符所处下标 | 115 | | `findNonTerminator` | 返回非终结符所处下标 | 116 | | `s2string` | 将栈转换为字符串返回,方便输出步骤 | 117 | | `analyzeLR1` | 利用LR1分析法分析字符串exp,输出其分析步骤 | 118 | | `main ` | 主程序入口,调用读取文件函数开始分析 | 119 | 120 | 121 | ### (2)函数的调用关系 122 | 123 | ![function4.drawio.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b99924fde594015aac1b75d324243c7~tplv-k3u1fbpfcp-watermark.image?) 124 | ### (3)流程图 125 | 126 | ![main.drawio.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fbd377b4720c450894253ad587ca8c3f~tplv-k3u1fbpfcp-watermark.image?) 127 | 128 | ## 5、实验结果 129 | ### 输入 130 | action.txt文件 131 | ```txt 132 | N N s4 N s5 N 133 | s6 N N N N acc 134 | r2 s7 N r2 N r2 135 | r4 r4 N r4 N r4 136 | N N s4 N s5 N 137 | r6 r6 N r6 N r6 138 | N N s4 N s5 N 139 | N N s4 N s5 N 140 | s6 N N s11 N N 141 | r1 s7 N r1 N r1 142 | r3 r3 N r3 N r3 143 | r5 r5 N r5 N r5 144 | ``` 145 | 146 | goto.txt文件 147 | ```txt 148 | 1 2 3 149 | N N N 150 | N N N 151 | N N N 152 | 8 2 3 153 | N N N 154 | N 9 3 155 | N N 10 156 | N N N 157 | N N N 158 | N N N 159 | N N N 160 | ``` 161 | 162 | exp.txt文件 163 | ```txt 164 | i+(i*i)*(i+i)# 165 | i*i+i*i# 166 | i+i*i+i*(i+i*i)# 167 | i+*(i)+i(i+i*i)# 168 | i+i(i)# 169 | ``` 170 | ### 输出 171 | 172 | ![image1.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00706fefb691427592dcf885bd5729a7~tplv-k3u1fbpfcp-watermark.image?) 173 | 174 | ![image2.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ca94078bbded4bf5802757568e8221b4~tplv-k3u1fbpfcp-watermark.image?) 175 | 176 | ![image3.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/26196777c3a241799dcd13ea1a30debc~tplv-k3u1fbpfcp-watermark.image?) 177 | 178 | # 完整代码 179 | 180 | ```cpp 181 | /* 182 | * @Author: cos 183 | * @Date: 2022-04-30 14:20:51 184 | * @LastEditTime: 2022-05-01 02:34:12 185 | * @LastEditors: cos 186 | * @Description: 实验4 LR(1) 分析法 187 | * @FilePath: \CompileTheory\experiment_4\demo\main.cpp 188 | */ 189 | #include 190 | #include 191 | #include 192 | #include 193 | #include 194 | using namespace std; 195 | const string ExpFileName = "./exp.txt"; 196 | const string GotoFileName = "./goto.txt"; 197 | const string ActionFileName = "./action.txt"; 198 | const int Null = -1; 199 | // ACTION表 200 | // + * ( ) i # 201 | string action[12][6]; 202 | // goto表 203 | // a b # 204 | int _goto[12][3]; 205 | string vt = "+*()i#"; // 终结符表 206 | string vn = "ETF"; // 非终结符表 207 | string LR[6] = { "E->E+T", "E->T", "T->T*F", "T->F", "F->(E)", "F->i" }; // 存放产生式 208 | // 读文件 209 | vector readFile(string fileName) { 210 | vector res; 211 | try { 212 | ifstream fin; 213 | fin.open(fileName); 214 | string temp; 215 | while (getline(fin, temp)) 216 | res.push_back(temp); 217 | return res; 218 | } 219 | catch (const exception& e) { 220 | cerr << e.what() << '\n'; 221 | return res; 222 | } 223 | } 224 | void printActions() { 225 | cout << "-----------------ACTION表------------------" << endl; 226 | cout << "+\t*\t(\t)\ti\t$" << endl; 227 | for (int i = 0; i < 12; ++i) { 228 | for (int j = 0; j < 6; ++j) 229 | cout << action[i][j] << "\t"; 230 | cout << endl; 231 | } 232 | } 233 | void printGotos() { 234 | cout << "-----------------GOTO表------------------" << endl; 235 | cout << "E\tT\tF" << endl; 236 | for (int i = 0; i < 12; ++i) { 237 | for (int j = 0; j < 3; ++j) 238 | cout << _goto[i][j] << "\t"; 239 | cout << endl; 240 | } 241 | } 242 | void init() { 243 | vector actions = readFile(ActionFileName); 244 | for (int i = 0; i < 12; ++i) { 245 | int cnt = 0; 246 | string row = actions[i]; 247 | int len = actions[i].length(); 248 | for (int j = 0; j < len; ++j) { 249 | string temp = ""; 250 | while (j < len && row[j] != ' ' && row[j] != '\t') { 251 | temp += row[j]; 252 | ++j; 253 | } 254 | while (j < len && (row[j] == ' ' || row[j] == '\t')) 255 | ++j; 256 | --j; 257 | action[i][cnt++] = temp; 258 | } 259 | 260 | } 261 | printActions(); 262 | vector gotos = readFile(GotoFileName); 263 | for (int i = 0; i < 12; ++i) { 264 | int cnt = 0; 265 | string row = gotos[i]; 266 | int len = row.length(); 267 | for (int j = 0; j < len; ++j) { 268 | string temp = ""; 269 | while (j < len && row[j] != ' ' && row[j] != '\t') { 270 | temp += row[j]; 271 | ++j; 272 | } 273 | while (j < len && (row[j] == ' ' || row[j] == '\t')) 274 | ++j; 275 | --j; 276 | _goto[i][cnt++] = (temp == "N") ? Null : stoi(temp); 277 | } 278 | } 279 | printGotos(); 280 | } 281 | bool isTerminator(char c) { 282 | return vt.find(c) != string::npos; 283 | } 284 | int findTerminator(char c) { // 返回终结符所处下标 285 | return vt.find(c); 286 | } 287 | int findNonTerminator(char c) { // 返回非终结符的下标 288 | return vn.find(c); 289 | } 290 | // 将栈转换为字符串返回 291 | string s2string(stack s) { 292 | string str = ""; 293 | while(!s.empty()) { 294 | str += to_string(s.top()) + " "; 295 | s.pop(); 296 | } 297 | return str; 298 | } 299 | // 输出剩余输入串 300 | void printRestInput(string exp, int start, int len) { 301 | for(int i = start; i < len; ++i) 302 | cout << exp[i]; 303 | cout << '\t'; 304 | } 305 | void analyzeLR1(string exp) { // 分析一个表达式 306 | int len = exp.length(); 307 | stack chars; // 符号栈 308 | stack state; // 状态栈 309 | state.push(0); // 初始状态为0 310 | chars.push('#'); // 初始符号为# 311 | string charsStr = "#"; 312 | stack copyState; 313 | copyState.push(0); 314 | int cnt = 0; // 序号 315 | int idx = 0; // 当前输入指针 316 | cout << "序号\t\t状态栈\t\t符号栈\t\t输入串\t\t描述" << endl; 317 | cout << cnt++ << '\t' << s2string(copyState) << '\t' << charsStr << '\t' << exp << '\t' << " 初始状态 " << endl; 318 | while(1) { 319 | int nowState = state.top(); 320 | char nowChar = exp[idx]; // 当前输入字符 321 | int isT = findTerminator(nowChar); 322 | if(isT == Null) { // 非终结符 323 | cout << "Error!" << "出现非法字符,程序错误退出" < exps = readFile(ExpFileName); 378 | int len = exps.size(); 379 | for (int i = 0; i < len; i++) { 380 | string exp = exps[i]; 381 | cout << "\n------------------待分析表达式" << i+1 << ":"<< exp << "--------------------" << endl; 382 | bool flag = true; 383 | for (int j = 0; j < exp.length(); j++) { 384 | if (!isTerminator(exp[j])) { 385 | cout << "第 "<< i+1 << "行输入的字符串不合法,请重新输入" << endl; 386 | flag = false; 387 | break; 388 | } 389 | } 390 | if (flag) { 391 | cout << "表达式" << i+1 << ":" << exp << "分析开始" << endl; 392 | analyzeLR1(exp); 393 | } 394 | } 395 | return 0; 396 | } 397 | ``` 398 | -------------------------------------------------------------------------------- /experiment_4/参考资料/1. 实验4 LR(1)分析法-预习提示.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_4/参考资料/1. 实验4 LR(1)分析法-预习提示.docx -------------------------------------------------------------------------------- /experiment_4/参考资料/2. 实验4 LR(1)分析法-实验报告模板.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_4/参考资料/2. 实验4 LR(1)分析法-实验报告模板.docx -------------------------------------------------------------------------------- /experiment_4/参考资料/3. 实验4 LR(1)分析法-参考程序.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusixian/CompilePrincipleLearning/362de163caca2b674f4827cb8b086d2a079b5197/experiment_4/参考资料/3. 实验4 LR(1)分析法-参考程序.txt -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 编译原理实验报告及代码 2 | 3 | 此仓库记录编译原理的学习 4 | 5 | | 实验 | 博客链接 | 源代码 | 6 | | ----------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | 7 | | 实验1-词法分析器 | [编译原理 实验一 词法分析器设计](https://blog.csdn.net/qq_45890533/article/details/124030827) | [CompilePrincipleLearning/experiment_1 (github)](https://github.com/yusixian/CompilePrincipleLearning/tree/master/experiment_1) | 8 | | 实验2-LL(1)分析法 | [编译原理 实验二 LL(1)分析法程序实现](https://blog.csdn.net/qq_45890533/article/details/124193342) | [CompilePrincipleLearning/experiment_2(github)](https://github.com/yusixian/CompilePrincipleLearning/tree/master/experiment_2) | 9 | | 实验3-逆波兰式的生成及计算 | [编译原理 实验三 逆波兰式的生成及计算程序](https://blog.csdn.net/qq_45890533/article/details/124259427) | [CompilePrincipleLearning/experiment_3(github)](https://github.com/yusixian/CompilePrincipleLearning/tree/master/experiment_3) 10 | | 实验4-LR(1)分析法 | [编译原理 实验四 LR(1)分析法程序](https://blog.csdn.net/qq_45890533/article/details/124522187) | [CompilePrincipleLearning/experiment_4(github)](https://github.com/yusixian/CompilePrincipleLearning/tree/master/experiment_4) | 11 | --------------------------------------------------------------------------------