-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathLineReader.cpp
250 lines (238 loc) · 10.2 KB
/
LineReader.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
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
**/
#include "cli/LineReader.hpp"
#include <algorithm>
#include <cctype>
#include <string>
#include "glog/logging.h"
using std::ispunct;
using std::size_t;
using std::string;
namespace quickstep {
std::string LineReader::getNextCommand() {
string multiline_buffer(leftover_);
// Whether we are continuing to read a command across multiple lines of
// input.
bool continuing = false;
// The state of our micro command-scanner. Either normal SQL (where the whole
// command terminates with a semicolon), various types of quoted strings
// (which terminate with a closing quote, putting the state back to normal),
// or SQL comments (which terminate with a newline, putting the state back to
// normal).
LineState line_state = kNormal;
// The position we are scanning the multiline_buffer from.
size_t scan_position = 0;
// The position of a special character, which might terminate a statement or
// signify a change in the line_state.
for (;;) {
size_t special_char_location = string::npos;
switch (line_state) {
case kNormal:
// A semicolon to end the SQL command, or any character which might
// put us into a different mode.
special_char_location = multiline_buffer.find_first_of(";'\"-eE.\\", scan_position);
break;
case kSingleQuote:
// A single quote which ends the string (note that two successive
// single-quotes in a string, which the lexer will make into one
// single-quote, will still be fine here: we will go into kNormal
// and then, on the next iteration, immediately back into
// kSingleQuote).
special_char_location = multiline_buffer.find_first_of('\'', scan_position);
break;
case kSingleQuoteWithEscape:
// A single quote which ends the string, or the beginning of an escape
// sequence \' which does not end the string.
special_char_location = multiline_buffer.find_first_of("'\\", scan_position);
break;
case kDoubleQuote:
// A double quote which ends the string. As above, two successive
// double-quotes will still get handled properly.
special_char_location = multiline_buffer.find_first_of('"', scan_position);
break;
case kDoubleQuoteWithEscape:
// A single quote which ends the string, or the beginning of an escape
// sequence \" which does not end the string.
special_char_location = multiline_buffer.find_first_of("\"\\", scan_position);
break;
case kComment:
case kCommand: // Fall Through.
// A newline which ends the command and comment and resumes normal SQL parsing.
special_char_location = multiline_buffer.find_first_of('\n', scan_position);
break;
}
if (special_char_location == string::npos) {
// No special character found in the buffer. Get some more input.
scan_position = multiline_buffer.size();
if (!getMoreInput(&multiline_buffer, &continuing)) {
return string("");
}
} else {
switch (line_state) {
case kNormal:
// In kNormal, we may end the SQL command or switch into one of the
// other states.
switch (multiline_buffer[special_char_location]) {
case ';':
// Command finished. Return it.
leftover_ = multiline_buffer.substr(special_char_location + 1);
// Clear 'leftover_' if it is blank to avoid counting the remaining
// lines in the previous command in computing the positions of each parser node.
if (std::all_of(leftover_.begin(), leftover_.end(), ::isspace)) {
leftover_.clear();
}
return multiline_buffer.substr(0, special_char_location + 1);
case '\'':
// Starting a single-quote string.
line_state = kSingleQuote;
scan_position = special_char_location + 1;
break;
case '"':
// Starting a double-quote string.
line_state = kDoubleQuote;
scan_position = special_char_location + 1;
break;
case '-':
// Possibly starting a comment. We must peek ahead to the next
// character to be sure.
if (multiline_buffer.size() > special_char_location + 1) {
if (multiline_buffer[special_char_location + 1] == '-') {
line_state = kComment;
scan_position = special_char_location + 2;
} else {
// False alarm.
scan_position = special_char_location + 1;
}
} else {
// No next character is available. Get more input and try
// again.
scan_position = special_char_location;
if (!getMoreInput(&multiline_buffer, &continuing)) {
return string("");
}
}
break;
case 'e':
case 'E':
// e' or E' begins a quoted string with escape sequences. First,
// check that the e or E either begins the string or is
// immediately preceded by whitespace or punctuation.
if ((special_char_location == 0)
|| ::isspace(multiline_buffer[special_char_location - 1])
|| ::ispunct(multiline_buffer[special_char_location - 1])) {
// Peek ahead to see if the next character is a single-quote.
if (multiline_buffer.size() > special_char_location + 1) {
if (multiline_buffer[special_char_location + 1] == '\'') {
line_state = kSingleQuoteWithEscape;
scan_position = special_char_location + 2;
} else if (multiline_buffer[special_char_location + 1] == '\"') {
line_state = kDoubleQuoteWithEscape;
scan_position = special_char_location + 2;
} else {
// False alarm. Just a normal character.
scan_position = special_char_location + 1;
}
} else {
// Can't peek ahead yet. Get more input and re-scan from
// current position.
scan_position = special_char_location;
if (!getMoreInput(&multiline_buffer, &continuing)) {
return string("");
}
}
} else {
scan_position = special_char_location + 1;
}
break;
case '.':
case '\\': // Fall Through.
// If the dot or forward slash begins the line, begin a command search.
if (special_char_location == multiline_buffer.find_first_not_of(" \t\r\n")) {
line_state = kCommand;
} else {
// This is a regular character, so skip over it.
scan_position = special_char_location + 1;
}
break;
default:
FATAL_ERROR("Unexpected special character in LineReader::getNextCommand()");
}
break;
case kSingleQuote:
case kDoubleQuote:
case kComment:
// Reached the terminal character for this state. Go back to normal
// SQL mode.
line_state = kNormal;
scan_position = special_char_location + 1;
break;
case kSingleQuoteWithEscape:
if (multiline_buffer[special_char_location] == '\'') {
line_state = kNormal;
scan_position = special_char_location + 1;
} else {
// Skip past an escape character.
scan_position = special_char_location + 2;
}
break;
case kDoubleQuoteWithEscape:
if (multiline_buffer[special_char_location] == '\"') {
line_state = kNormal;
scan_position = special_char_location + 1;
} else {
// Skip past an escape character.
scan_position = special_char_location + 2;
}
break;
case kCommand:
if (multiline_buffer[special_char_location] == '\n') {
// Command finished. Return it.
leftover_ = multiline_buffer.substr(special_char_location + 1);
// Clear 'leftover_' if it is blank to avoid counting the remaining
// lines in the previous command in computing the positions of each parser node.
if (std::all_of(leftover_.begin(), leftover_.end(), ::isspace)) {
leftover_.clear();
}
// Skip all the whitespaces before the command.
const std::size_t start_position =
multiline_buffer.find_first_not_of(" \t\r\n");
DCHECK_LE(start_position, special_char_location + 1);
return multiline_buffer.substr(start_position,
special_char_location + 1 - start_position);
}
break;
}
}
}
}
bool LineReader::getMoreInput(std::string *input_buffer, bool *continuing) {
string nextline = getLineInternal(*continuing);
if (nextline.empty()) {
return false;
} else {
if (*continuing
|| !std::all_of(nextline.begin(), nextline.end(), ::isspace)) {
// Don't show the continuing prompt if a blank line was entered.
*continuing = true;
}
input_buffer->append(nextline);
return true;
}
}
} // namespace quickstep