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>
11 #include <type_traits>
18 /// @brief This is the base class for all sentences.
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;
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.
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
&);
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.
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.
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.
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
);
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
);
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
)
124 if (s
->id() != T::ID
)
125 throw std::bad_cast
{};
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`.
135 /// Function to create sentences, used by the NMEA registry of known sentences.
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.
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
};
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
);
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
);
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
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.
222 /// auto s = nmea::make_sentence("$IIMTW,9.5,C*2F");
223 /// auto mtw = nmea::sentence_cast<nmea::mtw>(s); // OK
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
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`:
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`
253 /// Possible undefined behaviour if not careful:
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.
261 /// This can easily be resolved, when using the raw pointer variant of `sentence_cast`:
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.
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()))
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()))