blob: 2be9a8ba781c34043920245fa314904eb5b64cec [file] [log] [blame] [view]
Ben Olmsteadc0d77842019-07-31 17:34:05 -07001# Emboss C++ User Guide
2
3[TOC]
4
5## General Principles
6
7In C++, Emboss generates *view* classes which *do not* take ownership of any
8data. Application code is expected to manage the actual binary data. However,
9Emboss views are extremely cheap to construct (often free when optimizations are
10turned on), so it is expected that applications can pass around pointers to
11binary data and instantiate views as needed.
12
13All of the generated C++ code is in templates, so only code that is actually
14called will be linked into your application.
15
16Unless otherwise noted, all code for a given Emboss module will be generated in
17the namespace given by the module's `[(cpp) namespace]` attribute.
18
19
20### Read-Only vs Read-Write vs C++ `const`
21
22Emboss views can be applied to read-only or read-write storage:
23
24```c++
25void CopyX(const std::vector<char> &src, std::vector<char> *dest) {
26 auto source_view = MakeXView(&src);
27 auto dest_view = MakeXView(dest);
28 dest_view.x().Write(source_view.x().Read());
29}
30```
31
32When applied to read-only storage, methods like `Write()` or
33`UpdateFromTextStream()` won't compile:
34
35```c++
36void WontCompile(const std::vector<char> &data) {
37 auto view = MakeXView(&data);
38 view.x().Write(10); // Won't compile.
39}
40```
41
42This is separate from the C++ `const`ness of the view itself! For example, the
43following will work with no issue:
44
45```c++
46void WillCompileAndRun(std::vector<char> *data) {
47 const auto view = MakeXView(&data);
48 view.x().Write(10);
49}
50```
51
52This works because views are like pointers. In C++, you can have any
53combination of `const`/non-`const` pointer to `const`/non-`const` data:
54
55```c++
56char * ncnc; // Pointer is mutable, and points to mutable data.
57const char * ncc; // Point is mutable, but points to const data.
58char const * ncc2; // Another way of writing const char *
59char *const cnc; // Pointer is constant, but points to mutable data.
60using char_p = char *;
61const char_p cnc2; // Another way of writing char *const
62const char *const cc; // Pointer is constant, and points to constant data.
63using c_char_p = const char *;
64const c_char_p * cc2; // Another way of writing const char *const
65```
66
67The Emboss view equivalents are:
68
69```c++
70GenericMyStructView<ContiguousBuffer<char, ...>> ncnc;
71GenericMyStructView<ContiguousBuffer<const char, ...>> ncc;
72GenericMyStructView<ContiguousBuffer<char const, ...>> ncc2;
73GenericMyStructView<ContiguousBuffer<char, ...>> const cnc;
74const GenericMyStructView<ContiguousBuffer<char, ...>> cnc2;
75GenericMyStructView<ContiguousBuffer<const char, ...>> const cc;
76const GenericMyStructView<ContiguousBuffer<const char, ...>> cc2;
77```
78
79For this reason, `const` methods of views work on `const` *views*, not
80necessarily on `const` data: for example, `UpdateFromTextStream()` is a `const`
81method, because it does not modify the view itself, but it will not work if the
82view points to `const` data. This is analogous to writing through a constant
83pointer, like: `char *const p = &some_char; *p = 'z';`.
84
85Conversely, non-`const` methods, such as `operator=`, still work on views of
86`const` data. This is analogous to `pointer_to_const_char =
87other_pointer_to_const_char`.
88
89
90## Example: Fixed-Size `struct`
91
92Given a simple, fixed-size `struct`:
93
94```
95[(cpp) namespace = "example"]
96
97struct MyStruct:
98 0 [+4] UInt field_a
99 4 [+4] Int field_b
100 8 [+4] Bcd field_c
101```
102
103Emboss will generate code with this public C++ interface:
104
105```c++
106namespace example {
107
108// The view class for the struct. Views are like pointers: they do not own
109// their storage.
110//
111// `Storage` is typically some ::emboss::support::ContiguousBuffer (which uses
112// contiguous memory as backing storage), but you would typically just use
113// `auto`:
114//
115// auto view = MakeMyStructView(&container);
116//
117// If you need to make a view of some non-RAM backing storage (e.g., a register
118// file on a remote device, accessed via SPI), you can provide your own Storage.
119template <class Storage>
120class GenericMyStructView final {
121 public:
122 // Typically, you do not need to explicitly call any of the constructors.
123
124 // The default constructor gives you a "null" view: you cannot read or write
125 // through the view, Ok() and IsComplete() return false, and so on.
126 GenericMyStructView();
127
128 // A non-"null" view must be constructed with an appropriate Storage.
129 explicit GenericMyStructView(Storage bytes);
130
131 // Views can be copy-constructed and assigned from views of "compatible"
132 // Storage. For ContiguousBuffer, that means ContiguousBuffer over any of the
133 // char types -- char, unsigned char, and signed char. std::uint8_t and
134 // std::int8_t are typically aliases of char types, but are not required to
135 // be by the C++ standard.
136 template <typename OtherStorage>
137 GenericMyStructView(const GenericMyStructView<OtherStorage> &other);
138
139 template <typename OtherStorage>
140 GenericMyStructView<Storage> &operator=(
141 const GenericMyStructView<OtherStorage> &other);
142
143
144 // Ok() returns true if the Storage is big enough for the struct (for
145 // MyStruct, at least 12 bytes), and all fields are Ok(). For this struct,
146 // the Int and UInt fields are always Ok(), and the Bcd field is Ok() if none
147 // of its nibbles has a value greater than 9.
148 bool Ok() const;
149
150 // IsComplete() returns true if the Storage is big enough for the struct.
151 // This is most useful when you are reading bytes from some stream: you can
152 // read until IsComplete() is true, and then use IntrinsicSizeInBytes() to
153 // find out how many bytes are actually used by the struct, and Ok() to find
154 // out if the bytes are correct.
155 //
156 // An alternate way of thinking about it is: Ok() tells you if you can read a
157 // structure; IsComplete() tells you if you can write to it.
158 bool IsComplete() const;
159
160
161 // The Equals() and UncheckedEquals() methods check if two structs are
162 // *logically* equal. Equals() performs Ok() and bounds checks,
163 // UncheckedEquals() does not: UncheckedEquals() is useful when you need
164 // maximum performance, and can guarantee that your structures are Ok()
165 // before calling UncheckedEquals().
166 template <typename OtherStorage>
167 bool Equals(GenericMyStructView<OtherStorage> other) const;
168 template <typename OtherStorage>
169 bool UncheckedEquals(GenericMyStructView<OtherStorage> other) const;
170
171 // CopyFrom() and UncheckedCopyFrom() copy the bytes of the source structure
172 // directly from its Storage. CopyFrom() performs bounds checks to ensure
173 // that there are enough bytes available in the source; UncheckedCopyFrom()
174 // does not. With ContiguousBuffer storage, these should have essentially
175 // identical performance to memcpy().
176 template <typename OtherStorage>
177 void CopyFrom(GenericMyStructView<OtherStorage> other) const;
178 template <typename OtherStorage>
179 void UncheckedCopyFrom(GenericMyStructView<OtherStorage> other) const;
180
181
182 // UpdateFromTextStream() attempts to update the structure from text format.
183 // The Stream class provides a simple interface for getting and ungetting
184 // characters; typically, you would use ::emboss::UpdateFromText(view,
185 // some_string) instead of calling this yourself.
186 template <class Stream>
187 bool UpdateFromTextStream(Stream *stream) const;
188
189 // WriteToTextStream() writes a textual representation of the structure to the
190 // provided stream. Typically, you would use ::emboss::WriteToString(view)
191 // instead.
192 template <class Stream>
193 void WriteToTextStream(Stream *stream,
194 ::emboss::TextOutputOptions options) const;
195
196
197 // Each field in the struct will have a method to get its corresponding view.
198 //
199 // The exact types of the returned views are not contractual.
200 ::emboss::prelude::UIntView<...> field_a() const;
201 ::emboss::prelude::IntView<...> field_b() const;
202 ::emboss::prelude::BcdView<...> field_c() const;
203
204
205 // The built-in virtual fields also have methods to get their views:
206 // $size_in_bytes has IntrinsicSizeInBytes(), $max_size_in_bytes has
207 // MaxSizeInBytes(), and $min_size_in_bytes has MinSizeInBytes().
208 //
209 // Because $min_size_in_bytes and $max_size_in_bytes are always constant,
210 // their corresponding field methods are always static constexpr. Because
211 // $size_in_bytes is also constant for MyStruct, IntrinsicSizeInBytes() will
212 // also be static constexpr for GenericMyStructView:
213 //
214 // For any virtual field, you can use its Ok() method to find out if you can
215 // Read() its value:
216 //
217 // if (view.IntrinsicSizeInBytes().Ok()) {
218 // // The size of the struct is known.
219 // DoSomethingWithNBytes(view.IntrinsicSizeInBytes().Read());
220 // }
221 //
222 // For constant values, Ok() will always return true.
223 //
224 // For MyStruct, my_struct_view.IntrinsicSizeInBytes().Read(),
225 // my_struct_view.MinSizeInBytes().Read(), and
226 // my_struct_view.MaxSizeInBytes().Read() will all return 12.
227 //
228 // For constexpr fields, you can also get their values from functions in the
229 // structure's namespace, which also lets you skip the Read():
230 //
231 // MyStruct::IntrinsicSizeInBytes()
232 // MyStruct::MaxSizeInBytes()
233 // MyStruct::MinSizeInBytes()
234 static constexpr IntrinsicSizeInBytesView IntrinsicSizeInBytes();
235 static constexpr MinSizeInBytesView MinSizeInBytes();
236 static constexpr MaxSizeInBytesView MaxSizeInBytes();
237
238 // The IntrinsicSizeInBytes() method returns the view of the $size_in_bytes
239 // virtual field. Because $size_in_bytes is constant, this is a static
240 // constexpr method.
241 //
242 // Typically, you would use IntrinsicSizeInBytes().Ok() and
243 // IntrinsicSizeInBytes().Read():
244 //
245 // if (view.IntrinsicSizeInBytes().Ok()) {
246 // // The size of the struct is known.
247 // DoSomethingWithNBytes(view.IntrinsicSizeInBytes().Read());
248 // }
249 //
250 // Because MyStruct is always 12 bytes,
251 // GenericMyStructView::IntrinsicSizeInBytes().Ok() will always be true.
252 static constexpr UIntView<...> IntrinsicSizeInBytes();
253
254 // If you need to get at the raw bytes underneath the view, you can get the
255 // view's Storage.
256 Storage BackingStorage() const;
257};
258
259
260// An overload of MakeMyStructView is provided which accepts a pointer to a
261// container type: this generally works with STL and STL-like containers of
262// chars, that have size() and data() methods. This is known to work with
263// std::vector<char>, std::array<char>, std::string, absl:: and
264// std::string_view, and some others. Note that you need to call this with a
265// pointer to the container:
266//
267// auto view = MakeMyStructView(&container);
268//
269// IMPORTANT: this does *not* keep a reference to the actual container, so if
270// you call a container method that invalidates data() (such as
271// std::vector<>::reserve()), you will have to make a new view.
272template <typename Container>
273inline GenericMyStructView<...> MakeMyStructView(Container *arg);
274
275// Alternately, a "C-style" overload is provided, if you just have a pointer and
276// length:
277template <typename CharType>
278inline GenericMyStructView<...> MakeMyStructView(CharType *buffer,
279 std::size_t length);
280
281
282// In addition to the View class, a namespace will be generated with the
283// compile-time constant elements of the class. This is a convenience, so that
284// you can write something like:
285//
286// std::array<char, MyStruct::IntrinsicSizeInBytes()>
287//
288// instead of:
289//
290// std::array<char, GenericMyStructView<ContiguousBuffer<
291// char>>::IntrinsicSizeInBytes().Read()>
292namespace MyStruct {
293
294// Because MyStruct only has some constant virtual fields, the namespace
295// MyStruct only contains a few corresponding functions. Note that the
296// functions here return values, not views:
297inline constexpr unsigned int IntrinsicSizeInBytes();
298inline constexpr unsigned int MaxSizeInBytes();
299inline constexpr unsigned int MinSizeInBytes();
300
301} // namespace MyStruct
302} // namespace example
303```
304
305
306## TODO(bolms): Example: Variable-Size `struct`
307
308
309## TODO(bolms): Example: `enum`
310
311
312## TODO(bolms): Example: `bits`
313
314