KiCad PCB EDA Suite
Loading...
Searching...
No Matches
easypc_archive.h
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
28
29#ifndef EASYPC_ARCHIVE_H
30#define EASYPC_ARCHIVE_H
31
32#include <coroutine>
33#include <cstdint>
34#include <exception>
35#include <functional>
36#include <initializer_list>
37#include <map>
38#include <memory>
39#include <span>
40#include <string>
41#include <string_view>
42#include <utility>
43#include <vector>
44
45#include <wx/string.h>
46
47
48namespace EASYPC
49{
50
51class ARCHIVE;
52
53
55class [[nodiscard]] LOAD_TASK
56{
57public:
59 {
60 // The archive is found among the coroutine's parameters so the task can reach its frame stack
61 template <typename... ARGS>
62 promise_type( ARGS&... aArgs )
63 {
64 ( find( aArgs ), ... );
65 }
66
68 {
69 return LOAD_TASK( std::coroutine_handle<promise_type>::from_promise( *this ) );
70 }
71
72 std::suspend_always initial_suspend() noexcept { return {}; }
73 std::suspend_always final_suspend() noexcept { return {}; }
74 void return_void() noexcept {}
75 void unhandled_exception() noexcept { m_error = std::current_exception(); }
76
77 void find( ARCHIVE& aArchive ) { m_archive = &aArchive; }
78
79 template <typename T>
80 void find( T& )
81 {
82 }
83
84 ARCHIVE* m_archive = nullptr;
85 std::exception_ptr m_error;
86 };
87
88 using HANDLE = std::coroutine_handle<promise_type>;
89
90 LOAD_TASK( LOAD_TASK&& aOther ) noexcept :
91 m_handle( std::exchange( aOther.m_handle, {} ) )
92 {
93 }
94
96
98 {
99 if( m_handle )
100 m_handle.destroy();
101 }
102
103 HANDLE Release() { return std::exchange( m_handle, {} ); }
104
105 bool await_ready() const noexcept { return false; }
106 void await_suspend( std::coroutine_handle<> aAwaiting );
107 void await_resume() const noexcept {}
108
109private:
110 explicit LOAD_TASK( HANDLE aHandle ) :
111 m_handle( aHandle )
112 {
113 }
114
116};
117
118
120struct OBJECT
121{
122 virtual ~OBJECT() = default;
123
125 virtual LOAD_TASK Load( ARCHIVE& aAr );
126
128 virtual int ItemFormat( const ARCHIVE& aAr ) const;
129
130 const char* ClassName = "object";
131 uint16_t Schema = 0;
132 size_t Offset = 0;
133};
134
135
136using REGISTRY = std::map<std::string, std::function<std::unique_ptr<OBJECT>()>, std::less<>>;
137
138
140template <typename T>
141void Register( REGISTRY& aReg, std::initializer_list<const char*> aNames )
142{
143 for( const char* name : aNames )
144 aReg[name] = []() -> std::unique_ptr<OBJECT>
145 {
146 return std::make_unique<T>();
147 };
148}
149
150
151// One per class cluster, in that cluster's easypc_classes_<cluster>.cpp
152void RegisterRootClasses( REGISTRY& aReg );
153void RegisterStyleClasses( REGISTRY& aReg );
155void RegisterLibraryClasses( REGISTRY& aReg );
157
158
169
170
180
181
184{
185public:
187 ARCHIVE( const uint8_t* aData, size_t aSize, DOC_KIND aKind, int32_t aProduct );
188
189 // Every read is bounds checked and throws IO_ERROR past the end
190 uint8_t U8();
191 bool Bool() { return U8() != 0; }
192 bool InBool();
193 uint16_t U16();
194 uint32_t U32();
195 int32_t I32() { return static_cast<int32_t>( U32() ); }
196 uint64_t U64();
197 double F64();
198 uint32_t Count();
199 wxString ReadString();
200 void SkipString();
201 void Skip( size_t aLen );
202
204 void Require( size_t aLen ) const;
205
208 {
209 public:
210 explicit OBJECT_AWAITER( ARCHIVE& aArchive ) :
211 m_archive( aArchive )
212 {
213 }
214
215 bool await_ready();
216 void await_suspend( std::coroutine_handle<> aAwaiting );
217 OBJECT* await_resume() const { return m_object; }
218
219 protected:
221 OBJECT* m_object = nullptr;
222 };
223
224 OBJECT_AWAITER Object() { return OBJECT_AWAITER( *this ); }
225
227 template <typename T>
229 {
230 public:
232
234 {
235 T* typed = dynamic_cast<T*>( m_object );
236
237 if( m_object && !typed )
238 m_archive.throwBadClass( *m_object );
239
240 return typed;
241 }
242 };
243
244 template <typename T>
246 {
247 return TYPED_AWAITER<T>( *this );
248 }
249
252 {
253 public:
254 EMBEDDED_AWAITER( ARCHIVE& aArchive, OBJECT& aObject ) :
255 m_archive( aArchive ),
256 m_object( aObject )
257 {
258 }
259
260 bool await_ready() const { return false; }
261 void await_suspend( std::coroutine_handle<> aAwaiting );
262 void await_resume() const noexcept {}
263
264 private:
267 };
268
269 EMBEDDED_AWAITER Embedded( OBJECT& aObject, const char* aClass )
270 {
271 aObject.ClassName = aClass;
272 return EMBEDDED_AWAITER( *this, aObject );
273 }
274
277 void LoadEmbedded( OBJECT& aObject, const char* aClass );
278
280 void MapObject( OBJECT* aObject );
281
283 static std::unique_ptr<OBJECT> Create( const char* aClass );
284
285 // Stream state that persists across objects: boolean width, shape format and current item format
286 void SetBooleanIsByte( bool aByte ) { m_boolIsByte = aByte; }
287 int ShapeFormat() const { return m_shapeFormat; }
288 void SetShapeFormat( int aFormat ) { m_shapeFormat = aFormat; }
290 void SetReadFormat( int aFormat ) { m_currentReadFormat = aFormat; }
291
293 int OrphanFormat() const;
294
295 int DocumentVersion() const { return m_documentVersion; }
296 void SetDocumentVersion( int aVersion ) { m_documentVersion = aVersion; }
297
298 DOC_KIND Kind() const { return m_kind; }
299 bool IsDesign() const { return m_kind == DOC_KIND::PCB || m_kind == DOC_KIND::SCHEMATIC; }
300 int32_t Product() const { return m_product; }
301 void SetProduct( int32_t aProduct ) { m_product = aProduct; }
302
304 size_t& ParentWalkDepth() const { return m_parentWalkDepth; }
305 size_t LoadArraySize() const { return m_load.size(); }
306
307 size_t Pos() const { return m_pos; }
308 size_t Size() const { return m_size; }
309 bool AtEnd() const { return m_pos == m_size; }
310 uint16_t ObjectSchema() const { return m_objectSchema; }
311
312 std::vector<std::unique_ptr<OBJECT>> TakeObjects() { return std::move( m_objects ); }
313
314 static constexpr int MAX_DEPTH = 100000;
315
316private:
317 friend class LOAD_TASK;
318
319 [[noreturn]] void throwBadClass( const OBJECT& aObject ) const;
320
322 OBJECT* readTag( bool& aIsNew );
323
324 void pushObjectLoad( OBJECT& aObject, uint16_t aSchema );
325 void pushTask( LOAD_TASK::HANDLE aHandle );
326
328 void drive( LOAD_TASK aRoot );
329
331 uint32_t stringLength( bool& aUnicode );
332
333 struct FRAME
334 {
336 OBJECT* Object = nullptr;
337 uint16_t SavedSchema = 0;
338 bool ObjectLoad = false;
339 };
340
341 struct SLOT
342 {
343 const char* ClassName = nullptr;
344 uint16_t Schema = 0;
345 OBJECT* Object = nullptr;
346 };
347
348 const uint8_t* m_data;
349 size_t m_size;
350 size_t m_pos = 0;
352 int32_t m_product;
353
354 std::vector<SLOT> m_load;
355 std::vector<std::unique_ptr<OBJECT>> m_objects;
356 std::vector<FRAME> m_frames;
357 uint16_t m_objectSchema = 0;
358 int m_depth = 0;
359 bool m_errorHasContext = false;
360
361 bool m_boolIsByte = true;
365
366 mutable size_t m_parentWalkDepth = 0;
367};
368
369
375LOAD_TASK SkipFields( ARCHIVE& aAr, std::string_view aOps );
376
377
380{
382 consteval FIELD_ROW( int aSince, int aUntil, const char* aOps ) :
383 Since( aSince ),
384 Until( aUntil ),
385 Ops( aOps )
386 {
387 for( const char* p = aOps; *p; ++p )
388 {
389 if( std::string_view( "b1ifsoa 0123456789" ).find( *p ) == std::string_view::npos )
390 throw "unknown SkipFields op";
391 }
392 }
393
394 int Since;
395 int Until;
396 const char* Ops;
397};
398
399
401LOAD_TASK SkipRows( ARCHIVE& aAr, int aVersion, std::span<const FIELD_ROW> aRows );
402
403
405template <typename BASE>
406struct SKIPPED : BASE
407{
408 LOAD_TASK Load( ARCHIVE& aAr ) override
409 {
410 co_await BASE::Load( aAr );
411 co_await SkipRows( aAr, this->ItemFormat( aAr ), Rows );
412 }
413
414 std::span<const FIELD_ROW> Rows;
415};
416
417
418template <typename BASE>
419void RegisterSkipped( REGISTRY& aReg, const char* aName, std::span<const FIELD_ROW> aRows )
420{
421 aReg[aName] = [aRows]() -> std::unique_ptr<OBJECT>
422 {
423 std::unique_ptr<SKIPPED<BASE>> obj = std::make_unique<SKIPPED<BASE>>();
424 obj->Rows = aRows;
425 return obj;
426 };
427}
428
429} // namespace EASYPC
430
431#endif // EASYPC_ARCHIVE_H
const char * name
An embedded member for a loader to co_await: no tag and no load-array slot.
void await_resume() const noexcept
void await_suspend(std::coroutine_handle<> aAwaiting)
EMBEDDED_AWAITER(ARCHIVE &aArchive, OBJECT &aObject)
The next object tag for a loader to co_await: null, a back reference, or a new object fully loaded.
void await_suspend(std::coroutine_handle<> aAwaiting)
Object that must be a T or null.
OBJECT_AWAITER(ARCHIVE &aArchive)
One archive load stream; it owns every object it creates.
void Skip(size_t aLen)
DOC_KIND Kind() const
size_t & ParentWalkDepth() const
Bounds ItemFormat parent walks, which recurse through file-supplied parent tags.
void pushTask(LOAD_TASK::HANDLE aHandle)
friend class LOAD_TASK
int CurrentReadFormat() const
OBJECT * readTag(bool &aIsNew)
Read a tag; a new object is created and mapped but not loaded (aIsNew)
void SetShapeFormat(int aFormat)
void MapObject(OBJECT *aObject)
A load-array slot for an object not read through a tag.
EMBEDDED_AWAITER Embedded(OBJECT &aObject, const char *aClass)
bool IsDesign() const
static std::unique_ptr< OBJECT > Create(const char *aClass)
An object of a registered class, not yet loaded.
void drive(LOAD_TASK aRoot)
Run aRoot and everything it awaits until it finishes, rethrowing its failure.
void Require(size_t aLen) const
Throw unless aLen more bytes remain.
wxString ReadString()
ANSI (windows-1252) or the 0xFF 0xFFFE UTF-16 form.
void pushObjectLoad(OBJECT &aObject, uint16_t aSchema)
std::vector< std::unique_ptr< OBJECT > > TakeObjects()
uint32_t stringLength(bool &aUnicode)
The string length prefix; aUnicode for the UTF-16 form.
void SetProduct(int32_t aProduct)
TYPED_AWAITER< T > ObjectAs()
static constexpr int MAX_DEPTH
void SetReadFormat(int aFormat)
const uint8_t * m_data
uint16_t ObjectSchema() const
uint16_t m_objectSchema
size_t Pos() const
std::vector< std::unique_ptr< OBJECT > > m_objects
int32_t Product() const
int ShapeFormat() const
void throwBadClass(const OBJECT &aObject) const
bool InBool()
one or four bytes, depending on the active format
int DocumentVersion() const
size_t Size() const
void SetDocumentVersion(int aVersion)
int OrphanFormat() const
A null parent tag is the document: the header version in a design, else the current read format.
std::vector< SLOT > m_load
slot 0 is the null slot
void SetBooleanIsByte(bool aByte)
std::vector< FRAME > m_frames
OBJECT * ReadObject()
ReadObject and LoadEmbedded outside any loader, running the nested loads to completion.
uint32_t Count()
a 16-bit count, 0xFFFF escaping to 32 bits
OBJECT_AWAITER Object()
bool AtEnd() const
void LoadEmbedded(OBJECT &aObject, const char *aClass)
size_t LoadArraySize() const
ARCHIVE(const uint8_t *aData, size_t aSize, DOC_KIND aKind, int32_t aProduct)
aData must outlive the archive; aProduct is the product key in force
A loader coroutine. Awaiting one queues it on its archive's frame stack instead of nesting the call.
LOAD_TASK & operator=(LOAD_TASK &&)=delete
bool await_ready() const noexcept
void await_resume() const noexcept
LOAD_TASK(LOAD_TASK &&aOther) noexcept
std::coroutine_handle< promise_type > HANDLE
LOAD_TASK(HANDLE aHandle)
std::map< std::string, std::function< std::unique_ptr< OBJECT >()>, std::less<> > REGISTRY
LOAD_TASK SkipRows(ARCHIVE &aAr, int aVersion, std::span< const FIELD_ROW > aRows)
SkipFields for every row aVersion falls in, in order.
void RegisterGeometryClasses(REGISTRY &aReg)
void RegisterLibraryClasses(REGISTRY &aReg)
LOAD_TASK SkipFields(ARCHIVE &aAr, std::string_view aOps)
Read and drop a run of fields described by aOps, each letter optionally followed by a repeat count: b...
PRODUCT_KEY
Product keys, the design header's or the library LibFormat's.
@ PRODUCT_DESIGNSPARK
@ PRODUCT_DESIGNSPARK_CREATOR
@ PRODUCT_DESIGNSPARK_PRO
DOC_KIND
Document kind, from the FileType stream or the file extension for projects.
@ SCH_SYMBOL
one item stream of a Schematic Symbol Library
@ COMPONENT
one stream of a Component Library storage
@ PCB_SYMBOL
one item stream of a PCB Symbol Library
void RegisterRootClasses(REGISTRY &aReg)
void RegisterSkipped(REGISTRY &aReg, const char *aName, std::span< const FIELD_ROW > aRows)
void Register(REGISTRY &aReg, std::initializer_list< const char * > aNames)
Register the class names whose objects load as a T.
void RegisterStyleClasses(REGISTRY &aReg)
void RegisterConnectivityClasses(REGISTRY &aReg)
@ BASE
Definition sch_item.h:64
LOAD_TASK::HANDLE Handle
OBJECT * Object
whose Load this is, named in a failure
bool ObjectLoad
counts towards MAX_DEPTH
const char * ClassName
set for a class slot
consteval FIELD_ROW(int aSince, int aUntil, const char *aOps)
Rejects an unknown op at compile time, since a misread op would misalign every later field.
void find(ARCHIVE &aArchive)
std::suspend_always initial_suspend() noexcept
std::suspend_always final_suspend() noexcept
Root of every deserialized object.
virtual LOAD_TASK Load(ARCHIVE &aAr)
Read this object's fields.
size_t Offset
stream offset of the tag or of an embedded member's first field
virtual ~OBJECT()=default
uint16_t Schema
object schema from the class tag, 0 for an embedded member
const char * ClassName
registered class name
virtual int ItemFormat(const ARCHIVE &aAr) const
The version this object gates on; the base is the archive's current read format.
A class no importer reads: its base's fields, then a field program on its format.
LOAD_TASK Load(ARCHIVE &aAr) override
std::span< const FIELD_ROW > Rows