Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
Parser.hpp
Go to the documentation of this file.
1// The Reactive C++ Toolbox.
2// Copyright (C) 2013-2019 Swirly Cloud Limited
3// Copyright (C) 2021 Reactive Markets Limited
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17#ifndef TOOLBOX_RESP_PARSER_HPP
18#define TOOLBOX_RESP_PARSER_HPP
19
21
22#include <toolbox/util/Enum.hpp>
24
25#include <boost/container/small_vector.hpp>
26
27#include <cstdint>
28#include <stack>
29
30namespace toolbox {
31inline namespace resp {
32
34enum class Type : char {
35 None = '\0',
36 CommandLine = '\1',
38 SimpleString = '+',
40 Error = '-',
42 Integer = ':',
44 BulkString = '$',
46 Array = '*',
47};
48
50template <typename DerivedT>
52 using Stack = std::stack<int, boost::container::small_vector<int, 8>>;
53
54 public:
55 BasicParser() = default;
56 ~BasicParser() = default;
57
58 // Copy.
59 BasicParser(const BasicParser&) = default;
60 BasicParser& operator=(const BasicParser&) = default;
61
62 // Move.
65
67 void put(char c)
68 {
69 switch (type_) {
70 case Type::None:
71 switch (c) {
73 case unbox(Type::Error):
74 case unbox(Type::Integer):
76 case unbox(Type::Array):
77 type_ = Type{c};
78 break;
79 default:
80 type_ = Type::CommandLine;
81 tok_ += c;
82 break;
83 }
84 return;
86 put_command_line(c);
87 break;
89 put_simple_string(c);
90 break;
91 case Type::Error:
92 put_error(c);
93 break;
94 case Type::Integer:
95 put_integer(c);
96 break;
98 put_bulk_string(c);
99 break;
100 case Type::Array:
101 put_array(c);
102 break;
103 }
104 }
105
106 private:
107 void put_command_line(char c)
108 {
109 put_string(c, [this]() { static_cast<DerivedT*>(this)->on_resp_command_line(tok_); });
110 }
111 void put_simple_string(char c)
112 {
113 put_string(c, [this]() { static_cast<DerivedT*>(this)->on_resp_string(tok_); });
114 }
115 void put_error(char c)
116 {
117 put_string(c, [this]() { static_cast<DerivedT*>(this)->on_resp_error(tok_); });
118 }
119 void put_integer(char c)
120 {
121 if (sign_ == 0) {
122 if (c == '+') {
123 sign_ = 1;
124 return;
125 }
126 if (c == '-') {
127 sign_ = -1;
128 return;
129 }
130 // Imply positive sign.
131 sign_ = 1;
132 }
133 if (c == '\r') {
134 // Ignore.
135 return;
136 }
137 if (c != '\n') {
138 if (c < '0' || c > '9') {
139 // Fatal protocol exception.
140 throw Exception{"invalid integer"};
141 }
142 num_ = num_ * 10 + (c - '0');
143 return;
144 }
145 flush([this]() { static_cast<DerivedT*>(this)->on_resp_integer(sign_ * num_); });
146 }
147 void put_bulk_string(char c)
148 {
149 // TODO: add support for null bulk strings.
150 if (sign_ == 0) {
151 switch (c) {
152 case '\r':
153 // Ignore.
154 break;
155 case '\n':
156 sign_ = 1;
157 break;
158 default:
159 if (c < '0' || c > '9') {
160 // Fatal protocol exception.
161 throw Exception{"invalid length"};
162 }
163 num_ = num_ * 10 + (c - '0');
164 break;
165 }
166 return;
167 }
168 if (num_ > 0) {
169 tok_ += c;
170 --num_;
171 return;
172 }
173 // End of line.
174 if (c == '\r') {
175 // Ignore.
176 return;
177 }
178 if (c != '\n') {
179 // Fatal protocol exception.
180 throw Exception{"invalid bulk string"};
181 }
182 flush([this]() { static_cast<DerivedT*>(this)->on_resp_string(tok_); });
183 }
184 void put_array(char c)
185 {
186 if (c == '\r') {
187 // Ignore.
188 return;
189 }
190 if (c != '\n') {
191 if (c < '0' || c > '9') {
192 // Fatal protocol exception.
193 throw Exception{"invalid length"};
194 }
195 num_ = num_ * 10 + (c - '0');
196 return;
197 }
198 bool ok{false};
199 int popped{0};
200 if (num_ > 0) {
201 stack_.push(num_);
202 } else {
203 popped = 1 + pop_if_end();
204 }
205 const auto finally = make_finally([&]() noexcept {
206 if (!ok) {
207 if (!is_top_level()) {
208 bad_ = true;
209 } else if (popped > 0) {
210 assert(!ok && is_top_level() && popped > 0);
211 // Callback must be noexcept.
212 static_cast<DerivedT*>(this)->on_resp_reset(); // noexcept
213 }
214 }
215 clear_tok();
216 });
217 // NOLINTNEXTLINE(readability-braces-around-statements)
218 if (bad_) [[unlikely]] {
219 if (is_top_level() && popped > 0) {
220 bad_ = false;
221 static_cast<DerivedT*>(this)->on_resp_reset(); // noexcept
222 }
223 } else {
224 static_cast<DerivedT*>(this)->on_resp_array_begin(num_);
225 for (int i{0}; i < popped; ++i) {
226 static_cast<DerivedT*>(this)->on_resp_array_end();
227 }
228 }
229 ok = true;
230 }
231 template <typename FnT>
232 void put_string(char c, FnT fn)
233 {
234 if (c != '\n') {
235 tok_ += c;
236 return;
237 }
238 if (!tok_.empty() && tok_.back() == '\r') {
239 tok_.pop_back();
240 }
241 flush(fn);
242 }
243 template <typename FnT>
244 void flush(FnT fn)
245 {
246 bool ok{false};
247 const int popped{pop_if_end()};
248 const auto finally = make_finally([&]() noexcept {
249 if (!ok) {
250 if (!is_top_level()) {
251 bad_ = true;
252 } else if (popped > 0) {
253 assert(!ok && is_top_level() && popped > 0);
254 // Callback must be noexcept.
255 static_cast<DerivedT*>(this)->on_resp_reset(); // noexcept
256 }
257 }
258 clear_tok();
259 });
260 // NOLINTNEXTLINE(readability-braces-around-statements)
261 if (bad_) [[unlikely]] {
262 if (is_top_level() && popped > 0) {
263 bad_ = false;
264 static_cast<DerivedT*>(this)->on_resp_reset(); // noexcept
265 }
266 } else {
267 fn();
268 for (int i{0}; i < popped; ++i) {
269 static_cast<DerivedT*>(this)->on_resp_array_end();
270 }
271 }
272 ok = true;
273 }
274 bool is_top_level() const noexcept { return stack_.empty(); }
275 void clear_tok() noexcept
276 {
277 type_ = Type::None;
278 sign_ = 0;
279 num_ = 0;
280 tok_.clear();
281 }
283 int pop_if_end() noexcept
284 {
285 int n{0};
286 while (!stack_.empty() && --stack_.top() == 0) {
287 stack_.pop();
288 ++n;
289 }
290 return n;
291 }
294 bool bad_{false};
295 Type type_{Type::None};
296 int sign_{0};
297 std::int64_t num_{0};
298 std::string tok_;
299 Stack stack_;
300};
301
302} // namespace resp
303} // namespace toolbox
304
305#endif // TOOLBOX_RESP_PARSER_HPP
BasicParser is a class template for RESP (REdis Serialization Protocol) parsers.
Definition Parser.hpp:51
BasicParser(BasicParser &&) noexcept=default
BasicParser & operator=(const BasicParser &)=default
BasicParser(const BasicParser &)=default
Type
Type is an enumeration of the RESP data-types currently supported.
Definition Parser.hpp:34
@ BulkString
For Bulk Strings the first byte of the reply is "$".
@ Array
For Arrays the first byte of the reply is "*".
@ Error
For Errors the first byte of the reply is "-".
@ Integer
For Integers the first byte of the reply is ":".
@ SimpleString
For Simple Strings the first byte of the reply is "+".
auto make_finally(FnT fn) noexcept
Definition Finally.hpp:48
constexpr std::underlying_type_t< EnumT > unbox(EnumT val) noexcept
Definition Enum.hpp:34
constexpr auto bind() noexcept
Definition Slot.hpp:92