-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.cpp
268 lines (230 loc) · 6.79 KB
/
main.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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#include <string>
#include <vector>
#include <filesystem>
/*
# bash の構文を BNF っぽく定義してみる
* 右辺には正規表現を用いる
* 文字列のクォーテーションには対応しない
* `<JOB> = <CMD>{'|'<CMD>}*{'>'<STR>}?'\n'`
* `<CMD> = <STR>{' '<STR>}*`
* `<STR> = [^ ]+`
*/
// 解析される文字列を表す
class StringToBeParsed final
{
public:
StringToBeParsed(const char *s) : m_string(s){};
// 次の文字に移動し、移動した結果を返す
// すでに文字列末尾に到達していて、移動できない場合は '\n' を返す
// m_string が空文字の場合は '\n' を返す
char NextChar()
{
// m_currentPos == m_string.size() のときに文字列末尾が判別できる
if (m_currentPos < m_string.size())
{
m_currentPos++;
}
return CurrentChar();
};
// すでに文字列末尾に到達していて、移動できない場合は '\n' を返す
// m_string が空文字の場合は '\n' を返す
char CurrentChar() const
{
if (m_string.empty() || m_currentPos == m_string.size())
{
return '\n';
}
return m_string.at(m_currentPos);
};
public:
const std::string m_string;
private:
size_t m_currentPos = 0;
};
struct Command final
{
std::vector<std::string> args;
};
struct Job final
{
// コマンドが複数存在する場合は、パイプで連結する
// リダイレクトが指定されていない場合、最後のコマンド結果は標準出力に出力する
std::vector<Command> commands;
// リダイレクトが指定されている場合に設定される
// リダイレクトが指定されていない場合は空になる
std::filesystem::path redirectFilename;
};
enum Token
{
Pipe,
Redirect,
StrSeparator,
Str,
End,
};
Token ToToken(const char c)
{
switch (c)
{
case '|':
{
return Token::Pipe;
}
case '>':
{
return Token::Redirect;
}
case ' ':
{
return Token::StrSeparator;
}
case '\n':
{
return Token::End;
}
default:
{
return Token::Str;
}
}
}
// p の現在の解析位置から <STR> を取得する
// <STR> を取得できた場合、p の解析地点も移動する
std::string ParseStr(StringToBeParsed &p)
{
std::string str;
while (true)
{
const auto c = p.CurrentChar();
if (ToToken(c) != Token::Str)
{
return str;
}
str += c;
p.NextChar();
}
}
Command NextCmd(StringToBeParsed &p)
{
Command cmd;
// <STR> をすべて読み込む
while (true)
{
// 連続するスペースを飛ばす
while (ToToken(p.CurrentChar()) == Token::StrSeparator)
{
p.NextChar();
}
const auto str = ParseStr(p);
if (!str.empty())
{
cmd.args.push_back(str);
}
// <STR> の次のトークンを調べる
// スペースが存在するなら次の <STR> が存在するかもしれないので続行する
if (ToToken(p.CurrentChar()) == Token::StrSeparator)
{
// 次の <STR> の初めの文字に移動させる
p.NextChar();
}
else
{
return cmd;
}
}
}
Job ParseJob(StringToBeParsed &p)
{
Job job;
// <CMD> をすべて読み込む
while (true)
{
// 連続するスペースを飛ばす
while (ToToken(p.CurrentChar()) == Token::StrSeparator)
{
p.NextChar();
}
Command cmd(NextCmd(p));
if (!cmd.args.empty())
{
job.commands.push_back(cmd);
}
// NextCmd は スペース+<STR> が連続する箇所を読み取るので、NextCmd 後に出現するトークンはスペース以外になる
// そのためスペースを飛ばす処理は不要になる
// <CMD> の次のトークンを調べる
// '|' が存在するなら次の <CMD> が存在するかもしれないので続行する
if (ToToken(p.CurrentChar()) == Token::Pipe)
{
// 次の <CMD> の初めの文字に移動させる
p.NextChar();
}
else
{
break;
}
}
// リダイレクトを読み込む
if (ToToken(p.CurrentChar()) == Token::Redirect)
{
// '>' の次の文字に移動させる
p.NextChar();
// 連続するスペースを飛ばす
while (ToToken(p.CurrentChar()) == Token::StrSeparator)
{
p.NextChar();
}
job.redirectFilename = ParseStr(p);
}
return job;
}
void TestParseJob(const char *in, const std::vector<std::vector<std::string>> expectCommands, const std::filesystem::path expectRedirectFilename)
{
StringToBeParsed str(in);
const auto testeeJob = ParseJob(str);
// コマンド一覧をテストする
std::vector<std::vector<std::string>> testeeCommands;
for (const auto &cmd : testeeJob.commands)
{
std::vector<std::string> args;
for (const auto &arg : cmd.args)
{
args.push_back(arg);
}
testeeCommands.push_back(args);
}
if (testeeCommands != expectCommands)
{
fprintf(stderr, "コマンドテスト失敗, \"%s\"\n", in);
return;
}
// リダイレクトをテストする
if (testeeJob.redirectFilename != expectRedirectFilename)
{
fprintf(stderr, "リダイレクトテスト失敗, \"%s\"\n", in);
return;
}
// OK
printf("テスト成功, \"%s\"\n", in);
}
int main()
{
// 連続したスペースやトークンの間にスペースが出現する
TestParseJob("cmd1 aaa bbb | cmd2 |cmd3|cmd4 xxx>out.txt", {{"cmd1", "aaa", "bbb"}, {"cmd2"}, {"cmd3"}, {"cmd4", "xxx"}}, "out.txt");
TestParseJob(" cmd1 > out.txt", {{"cmd1"}}, "out.txt");
// パイプの右側にコマンドが存在しない
TestParseJob("cmd1|", {{"cmd1"}}, "");
// パイプの右側にリダイレクトが来る
TestParseJob("cmd1|>out.txt", {{"cmd1"}}, "out.txt");
TestParseJob("cmd1| > out.txt", {{"cmd1"}}, "out.txt");
// パイプの左側にコマンドが存在しない
TestParseJob("|cmd1", {{"cmd1"}}, "");
// パイプの左側にリダイレクトが来るとそこで読み込みは終わる
TestParseJob("cmd1>out.txt|cmd2", {{"cmd1"}}, "out.txt");
TestParseJob("cmd1 > out.txt|cmd2", {{"cmd1"}}, "out.txt");
// コマンドは存在せずリダイレクトのみ
TestParseJob("> out.txt", {}, "out.txt");
TestParseJob("| > out.txt", {}, "out.txt");
// 空文字列
TestParseJob("", {}, "");
return EXIT_SUCCESS;
}