NMEA: remove deprecated type talker_id
[marnav.git] / include / marnav / nmea / sentence.hpp
blobef22a2cd026d80fb9fadf448cdd5e8ff3548f81e
1 #ifndef MARNAV_NMEA_SENTENCE_HPP
2 #define MARNAV_NMEA_SENTENCE_HPP
4 #include <marnav/nmea/constants.hpp>
5 #include <marnav/nmea/talker_id.hpp>
6 #include <marnav/nmea/sentence_id.hpp>
7 #include <marnav/nmea/detail.hpp>
8 #include <functional>
9 #include <memory>
10 #include <string>
11 #include <type_traits>
12 #include <vector>
14 namespace marnav
16 namespace nmea
18 /// @brief This is the base class for all sentences.
19 class sentence
21 public:
22 /// Type for fields to process while reading data from raw sentences.
23 using fields = std::vector<std::string>;
25 /// This signature is used in all subclasses to parse data fields
26 /// of a particular sentence.
27 using parse_function = std::function<std::unique_ptr<sentence>(
28 talker, fields::const_iterator, fields::const_iterator)>;
30 /// Maximum length of a NMEA sentence (raw format as string).
31 constexpr static int max_length = 82;
33 /// The start token of normal NMEA sentences.
34 constexpr static char start_token = '$';
36 /// The start token of AIS related NMEA sentences.
37 constexpr static char start_token_ais = '!';
39 /// The end token (right before the checksum) of all NMEA sentences.
40 constexpr static char end_token = '*';
42 /// Delimiter of fields.
43 constexpr static char field_delimiter = ',';
45 /// The start and end token of a tag block.
46 constexpr static char tag_block_token = '\\';
48 virtual ~sentence() = default;
50 sentence() = delete;
51 sentence(const sentence &) = default;
52 sentence & operator=(const sentence &) = default;
53 sentence(sentence &&) = default;
54 sentence & operator=(sentence &&) = default;
56 sentence_id id() const noexcept { return id_; }
57 std::string tag() const { return tag_; }
58 talker get_talker() const noexcept { return talker_; }
60 /// Sets the talker of the sentence.
61 ///
62 /// @note All subclasses specify a default talker at construction.
63 /// This method is used to override the default or to set the
64 /// talker ID explicitly.
65 void set_talker(const talker & t) { talker_ = t; }
67 /// Sets the tag block. This overwrites a possibly existent block.
68 void set_tag_block(const std::string & t) { tag_block_ = t; }
70 /// Returns the raw tag block string. Since tag blocks are not common
71 /// at the moment, its handling is separated, @see tag_block.
72 const std::string & get_tag_block() const { return tag_block_; }
74 friend std::string to_string(const sentence &);
76 protected:
77 sentence(sentence_id id, const std::string & tag, talker t);
78 virtual char get_start_token() const { return start_token; }
79 virtual char get_end_token() const { return end_token; }
81 /// Lets the concrete sentence append its data (as strings)
82 /// to the specified string.
83 ///
84 /// Benchmark has shown, this method is on average about 7% faster
85 /// than the previous solution (returning a vector of strings).
86 /// However, in some cases this solution is slower (!) and subject
87 /// for investigation.
88 ///
89 /// @note It is recommended to use the static functions `append`
90 /// to append data to the string. This functions take care
91 /// of field delimiters automatically.
92 ///
93 virtual void append_data_to(std::string &) const = 0;
95 static void append(std::string & s, const std::string & t);
96 static void append(std::string & s, const char t);
98 private:
99 sentence_id id_;
100 std::string tag_;
101 talker talker_;
102 std::string tag_block_;
105 // Class `sentence` must be an abstract class, this protectes
106 // against object slicing because it prevents an instance
107 // of `sentence` itself, e.g. const nmea::sentence = nmea::bod{};
108 static_assert(std::is_abstract<sentence>::value, "");
110 /// Renders the specified sentence into a string.
112 /// If the sentence is invalid, the returning string will be empty.
113 std::string to_string(const sentence & s);
115 /// @cond DEV
116 namespace detail
118 /// Checks if the specified cast is valid, throws `std::bad_cast` if not.
119 /// If the pointer is `nullptr`, false returns.
120 template <class T> bool check_cast(const sentence * s)
122 if (!s)
123 return false;
124 if (s->id() != T::ID)
125 throw std::bad_cast{};
126 return true;
129 /// This class provides different means to instantiate subclasses of `sentence`.
131 /// This is based on the fact, that this class is friend of all subclasses of `sentence`.
132 class factory
134 public:
135 /// Function to create sentences, used by the NMEA registry of known sentences.
136 template <class T,
137 typename std::enable_if<std::is_base_of<sentence, T>::value, int>::type = 0>
138 static std::unique_ptr<T> parse(talker talk, sentence::fields::const_iterator first,
139 sentence::fields::const_iterator last)
141 return std::unique_ptr<T>(new T{talk, first, last});
144 /// Helper function to parse a specific sentence.
146 /// @note Only to be used in unit tests.
147 template <class T,
148 typename std::enable_if<std::is_base_of<sentence, T>::value, int>::type = 0>
149 static std::unique_ptr<T> sentence_parse(talker talk, const sentence::fields & f)
151 return parse<T>(talk, begin(f), end(f));
154 /// Creates the configured sentence object from the specified string.
155 /// If the string is invalid or describes a non-configured sentence,
156 /// an exception is thrown.
158 /// @note This function always checks the checksum and throws an
159 /// exception if not correct.
161 /// @tparam T The type of sentence to create. The type must be derived
162 /// from class sentence.
164 /// @param[in] s The raw NMEA sentence.
165 /// @return The initialized sentence derived object.
167 template <typename T,
168 typename std::enable_if<std::is_base_of<sentence, T>::value, int>::type = 0>
169 static T create_sentence(const std::string & s)
171 talker talk{talker::none};
172 std::string tag;
173 std::string tag_block;
174 std::vector<std::string> fields;
175 std::tie(talk, tag, tag_block, fields) = detail::extract_sentence_information(s);
176 T result{talk, std::next(std::begin(fields)), std::prev(std::end(fields))};
177 result.set_tag_block(tag_block);
178 return result;
182 /// @endcond
184 /// Creates the configured sentence object from the specified string.
185 /// If the string is invalid or describes a non-configured sentence,
186 /// an exception is thrown.
188 /// @note This function always checks the checksum and throws an
189 /// exception if not correct.
191 /// @tparam T The type of sentence to create. The type must be derived
192 /// from class sentence.
194 /// @param[in] s The raw NMEA sentence.
195 /// @return The initialized sentence derived object.
197 template <typename T,
198 typename std::enable_if<std::is_base_of<sentence, T>::value, int>::type = 0>
199 T create_sentence(const std::string & s)
201 return detail::factory::create_sentence<T>(s);
204 /// @{
206 /// Casts the specified sentence to the sentence given by the template parameter.
207 /// The object converted only if it is valid and of the correct type. It is not
208 /// possible to cast a sentence into a completley different one.
210 /// @note This function does not use 'dynamic_cast', therefore it does not
211 /// cast subclasses automatically. You will have to do checks and casts
212 /// explicitly.
213 /// This is considered a feature.
215 /// @param[in] s The sentence object to convert.
216 /// @retval nullptr The specified sentence is invalid.
217 /// @return The converted sentence.
218 /// @exception std::bad_cast The specified sentence has the wrong ID.
220 /// Examples:
221 /// @code
222 /// auto s = nmea::make_sentence("$IIMTW,9.5,C*2F");
223 /// auto mtw = nmea::sentence_cast<nmea::mtw>(s); // OK
224 /// @endcode
226 /// @code
227 /// auto s = nmea::make_sentence("$IIMTW,9.5,C*2F");
228 /// auto rmc = nmea::sentence_cast<nmea::rmc>(s); // Error, throws std::bad_cast
229 /// @endcode
230 template <class T> T * sentence_cast(sentence * s)
232 return detail::check_cast<T>(s) ? static_cast<T *>(s) : nullptr;
235 /// Raw pointer const variant.
237 /// @see sentence_cast(sentence * s)
238 template <class T> const T * sentence_cast(const sentence * s)
240 return detail::check_cast<T>(s) ? static_cast<const T *>(s) : nullptr;
243 /// `std::unique_ptr` variant. If the cast is possible, the original `unique_ptr<sentence>`
244 /// will be invalidated and a new `unique_ptr<T>` will be returned. This has implications
245 /// within the calling code.
247 /// Code using a temporary `unique_ptr`:
248 /// @code
249 /// auto q = nmea::sentence_cast<nmea::mtw>(nmea::make_sentence("$IIMTW,9.5,C*2F"));
250 /// // here `q` is valid and an `unique_ptr`
251 /// @endcode
253 /// Possible undefined behaviour if not careful:
254 /// @code
255 /// auto p = nmea::make_sentence("$IIMTW,9.5,C*2F");
256 /// auto q = nmea::sentence_cast<nmea::mtw>(p);
257 /// // here `p` no is no longer valid, `q` is an `unique_ptr`.
258 /// // accessing `p` at this point is undefined behaviour.
259 /// @endcode
261 /// This can easily be resolved, when using the raw pointer variant of `sentence_cast`:
262 /// @code
263 /// auto p = nmea::make_sentence("$IIMTW,9.5,C*2F");
264 /// auto q = nmea::sentence_cast<nmea::mtw>(p.get());
265 /// // here `p` is still valid, `q` is a raw pointer.
266 /// @endcode
268 /// @param[in,out] s The sentence to cast.
269 /// @return The casted sentence. If the specified sentence was `nullptr`, the function
270 /// also returns `nullptr`.
271 /// @exception std::bad_cast This exception is thrown if the specified sentence is
272 /// not castable into the destination type `T`.
274 template <class T> std::unique_ptr<T> sentence_cast(std::unique_ptr<sentence> & s)
276 return detail::check_cast<T>(s.get()) ? std::unique_ptr<T>(static_cast<T *>(s.release()))
277 : nullptr;
280 /// `unique_ptr` ref ref variant.
282 /// @see sentence_cast(std::unique_ptr<sentence> & s)
283 template <class T> std::unique_ptr<T> sentence_cast(std::unique_ptr<sentence> && s)
285 return detail::check_cast<T>(s.get()) ? std::unique_ptr<T>(static_cast<T *>(s.release()))
286 : nullptr;
289 /// @}
293 #endif