-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathtokenizer.cpp
239 lines (218 loc) · 9.78 KB
/
tokenizer.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#include "tokenizer/tokenizer.h"
#include <cctype>
#include <sstream>
namespace miniplc0 {
std::pair<std::optional<Token>, std::optional<CompilationError>>
Tokenizer::NextToken() {
if (!_initialized) readAll();
if (_rdr.bad())
return std::make_pair(
std::optional<Token>(),
std::make_optional<CompilationError>(0, 0, ErrorCode::ErrStreamError));
if (isEOF())
return std::make_pair(
std::optional<Token>(),
std::make_optional<CompilationError>(0, 0, ErrorCode::ErrEOF));
auto p = nextToken();
if (p.second.has_value()) return std::make_pair(p.first, p.second);
auto err = checkToken(p.first.value());
if (err.has_value()) return std::make_pair(p.first, err.value());
return std::make_pair(p.first, std::optional<CompilationError>());
}
std::pair<std::vector<Token>, std::optional<CompilationError>>
Tokenizer::AllTokens() {
std::vector<Token> result;
while (true) {
auto p = NextToken();
if (p.second.has_value()) {
if (p.second.value().GetCode() == ErrorCode::ErrEOF)
return std::make_pair(result, std::optional<CompilationError>());
else
return std::make_pair(std::vector<Token>(), p.second);
}
result.emplace_back(p.first.value());
}
}
// 注意:这里的返回值中 Token 和 CompilationError 只能返回一个,不能同时返回。
std::pair<std::optional<Token>, std::optional<CompilationError>>
Tokenizer::nextToken() {
// 用于存储已经读到的组成当前token字符
std::stringstream ss;
// 分析token的结果,作为此函数的返回值
std::pair<std::optional<Token>, std::optional<CompilationError>> result;
// <行号,列号>,表示当前token的第一个字符在源代码中的位置
std::pair<int64_t, int64_t> pos;
// 记录当前自动机的状态,进入此函数时是初始状态
DFAState current_state = DFAState::INITIAL_STATE;
// 这是一个死循环,除非主动跳出
// 每一次执行while内的代码,都可能导致状态的变更
while (true) {
// 读一个字符,请注意auto推导得出的类型是std::optional<char>
// 这里其实有两种写法
// 1. 每次循环前立即读入一个 char
// 2. 只有在可能会转移的状态读入一个 char
// 因为我们实现了 unread,为了省事我们选择第一种
auto current_char = nextChar();
// 针对当前的状态进行不同的操作
switch (current_state) {
// 初始状态
// 这个 case 我们给出了核心逻辑,但是后面的 case 不用照搬。
case INITIAL_STATE: {
// 已经读到了文件尾
if (!current_char.has_value())
// 返回一个空的token,和编译错误ErrEOF:遇到了文件尾
return std::make_pair(
std::optional<Token>(),
std::make_optional<CompilationError>(0, 0, ErrEOF));
// 获取读到的字符的值,注意auto推导出的类型是char
auto ch = current_char.value();
// 标记是否读到了不合法的字符,初始化为否
auto invalid = false;
// 使用了自己封装的判断字符类型的函数,定义于 tokenizer/utils.hpp
// see https://en.cppreference.com/w/cpp/string/byte/isblank
if (miniplc0::isspace(
ch)) // 读到的字符是空白字符(空格、换行、制表符等)
current_state = DFAState::
INITIAL_STATE; // 保留当前状态为初始状态,此处直接break也是可以的
else if (!miniplc0::isprint(ch)) // control codes and backspace
invalid = true;
else if (miniplc0::isdigit(ch)) // 读到的字符是数字
current_state =
DFAState::UNSIGNED_INTEGER_STATE; // 切换到无符号整数的状态
else if (miniplc0::isalpha(ch)) // 读到的字符是英文字母
current_state = DFAState::IDENTIFIER_STATE; // 切换到标识符的状态
else {
switch (ch) {
case '=': // 如果读到的字符是`=`,则切换到等于号的状态
current_state = DFAState::EQUAL_SIGN_STATE;
break;
case '-':
// 请填空:切换到减号的状态
case '+':
// 请填空:切换到加号的状态
case '*':
// 请填空:切换状态
case '/':
// 请填空:切换状态
///// 请填空:
///// 对于其他的可接受字符
///// 切换到对应的状态
// 不接受的字符导致的不合法的状态
default:
invalid = true;
break;
}
}
// 如果读到的字符导致了状态的转移,说明它是一个token的第一个字符
if (current_state != DFAState::INITIAL_STATE)
pos = previousPos(); // 记录该字符的的位置为token的开始位置
// 读到了不合法的字符
if (invalid) {
// 回退这个字符
unreadLast();
// 返回编译错误:非法的输入
return std::make_pair(std::optional<Token>(),
std::make_optional<CompilationError>(
pos, ErrorCode::ErrInvalidInput));
}
// 如果读到的字符导致了状态的转移,说明它是一个token的第一个字符
if (current_state != DFAState::INITIAL_STATE) // ignore white spaces
ss << ch; // 存储读到的字符
break;
}
// 当前状态是无符号整数
case UNSIGNED_INTEGER_STATE: {
// 请填空:
// 如果当前已经读到了文件尾,则解析已经读到的字符串为整数
// 解析成功则返回无符号整数类型的token,否则返回编译错误
// 如果读到的字符是数字,则存储读到的字符
// 如果读到的字符不是数字,则回退读到的字符,并解析已经读到的字符串为整数
// 解析成功则返回无符号整数类型的token,否则返回编译错误
break;
}
case IDENTIFIER_STATE: {
// 请填空:
// 如果当前已经读到了文件尾,则解析已经读到的字符串
// 如果解析结果是关键字,那么返回对应关键字的token,否则返回标识符的token
// 如果读到的是字符或字母,则存储读到的字符
// 如果读到的字符不是上述情况之一,则回退读到的字符,并解析已经读到的字符串
// 如果解析结果是关键字,那么返回对应关键字的token,否则返回标识符的token
break;
}
// 如果当前状态是加号
case PLUS_SIGN_STATE: {
// 请思考这里为什么要回退,在其他地方会不会需要
unreadLast(); // Yes, we unread last char even if it's an EOF.
return std::make_pair(std::make_optional<Token>(TokenType::PLUS_SIGN,
'+', pos, currentPos()),
std::optional<CompilationError>());
}
// 当前状态为减号的状态
case MINUS_SIGN_STATE: {
// 请填空:回退,并返回减号token
}
// 请填空:
// 对于其他的合法状态,进行合适的操作
// 比如进行解析、返回token、返回编译错误
// 预料之外的状态,如果执行到了这里,说明程序异常
default:
DieAndPrint("unhandled state.");
break;
}
}
// 预料之外的状态,如果执行到了这里,说明程序异常
return std::make_pair(std::optional<Token>(),
std::optional<CompilationError>());
}
std::optional<CompilationError> Tokenizer::checkToken(const Token& t) {
switch (t.GetType()) {
case IDENTIFIER: {
auto val = t.GetValueString();
if (miniplc0::isdigit(val[0]))
return std::make_optional<CompilationError>(
t.GetStartPos().first, t.GetStartPos().second,
ErrorCode::ErrInvalidIdentifier);
break;
}
default:
break;
}
return {};
}
void Tokenizer::readAll() {
if (_initialized) return;
for (std::string tp; std::getline(_rdr, tp);)
_lines_buffer.emplace_back(std::move(tp + "\n"));
_initialized = true;
_ptr = std::make_pair<int64_t, int64_t>(0, 0);
return;
}
// Note: We allow this function to return a postion which is out of bound
// according to the design like std::vector::end().
std::pair<uint64_t, uint64_t> Tokenizer::nextPos() {
if (_ptr.first >= _lines_buffer.size()) DieAndPrint("advance after EOF");
if (_ptr.second == _lines_buffer[_ptr.first].size() - 1)
return std::make_pair(_ptr.first + 1, 0);
else
return std::make_pair(_ptr.first, _ptr.second + 1);
}
std::pair<uint64_t, uint64_t> Tokenizer::currentPos() { return _ptr; }
std::pair<uint64_t, uint64_t> Tokenizer::previousPos() {
if (_ptr.first == 0 && _ptr.second == 0)
DieAndPrint("previous position from beginning");
if (_ptr.second == 0)
return std::make_pair(_ptr.first - 1,
_lines_buffer[_ptr.first - 1].size() - 1);
else
return std::make_pair(_ptr.first, _ptr.second - 1);
}
std::optional<char> Tokenizer::nextChar() {
if (isEOF()) return {}; // EOF
auto result = _lines_buffer[_ptr.first][_ptr.second];
_ptr = nextPos();
return result;
}
bool Tokenizer::isEOF() { return _ptr.first >= _lines_buffer.size(); }
// Note: Is it evil to unread a buffer?
void Tokenizer::unreadLast() { _ptr = previousPos(); }
} // namespace miniplc0