1 /*
2 * LibrePCB - Professional EDA for everyone!
3 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4 * https://librepcb.org/
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (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 <http://www.gnu.org/licenses/>.
18 */
19
20 #ifndef LIBREPCB_SEXPRESSION_H
21 #define LIBREPCB_SEXPRESSION_H
22
23 /*******************************************************************************
24 * Includes
25 ******************************************************************************/
26 #include "../exceptions.h"
27 #include "filepath.h"
28
29 #include <QtCore>
30 #include <QtWidgets>
31
32 /*******************************************************************************
33 * Namespace / Forward Declarations
34 ******************************************************************************/
35 namespace librepcb {
36
37 class SExpression;
38 class Version;
39
40 /**
41 * Serialize an object to a ::librepcb::SExpression
42 *
43 * @tparam T Type of object to serialize.
44 * @param obj Object to serialize.
45 * @return Serialized S-Expression.
46 * @throws ::librepcb::Exception in case of an error.
47 */
48 template <typename T>
49 SExpression serialize(const T& obj);
50
51 /**
52 * Deserialize an ::librepcb::SExpression to an object
53 *
54 * @tparam T Type of object to deserialize.
55 * @param sexpr S-Expression to deserialize.
56 * @param fileFormat The file format version of the passed S-Expression.
57 * If this is older than the latest file format version,
58 * a migration might need to be performed.
59 * @return Deserialized object.
60 * @throws ::librepcb::Exception in case of an error.
61 */
62 template <typename T>
63 T deserialize(const SExpression& sexpr, const Version& fileFormat);
64
65 /*******************************************************************************
66 * Class SExpression
67 ******************************************************************************/
68
69 /**
70 * @brief The SExpression class
71 */
72 class SExpression final {
73 Q_DECLARE_TR_FUNCTIONS(SExpression)
74
75 public:
76 // Types
77 enum class Type {
78 List, ///< has a tag name and an arbitrary number of children
79 Token, ///< values without quotes (e.g. `-12.34`)
80 String, ///< values with double quotes (e.g. `"Foo!"`)
81 LineBreak, ///< manual line break inside a List
82 };
83
84 // Constructors / Destructor
85 SExpression() noexcept;
86 SExpression(const SExpression& other) noexcept;
87 ~SExpression() noexcept;
88
89 // Getters
getFilePath()90 const FilePath& getFilePath() const noexcept { return mFilePath; }
getType()91 Type getType() const noexcept { return mType; }
isList()92 bool isList() const noexcept { return mType == Type::List; }
isToken()93 bool isToken() const noexcept { return mType == Type::Token; }
isString()94 bool isString() const noexcept { return mType == Type::String; }
isLineBreak()95 bool isLineBreak() const noexcept { return mType == Type::LineBreak; }
96 bool isMultiLineList() const noexcept;
97 const QString& getName() const;
98 const QString& getValue() const;
getChildren()99 const QList<SExpression>& getChildren() const noexcept { return mChildren; }
100 QList<SExpression> getChildren(const QString& name) const noexcept;
101
102 /**
103 * @brief Get a child by path
104 *
105 * This method allows to get a specific child, even nested child.
106 * Consider this S-Expression:
107 *
108 * @verbatim
109 * (netsegment 3115f409-5e6c-4023-a8ab-06428ed0720a
110 * (via 2cc45b07-1bef-4340-9292-b54b011c70c5
111 * (position 35.91989 46.0375) (size 0.7) (drill 0.3) (shape round)
112 * )
113 * )
114 * @endverbatim
115 *
116 * - To get the UUID of the net segment, use the path "@0" (first child).
117 * - To get the whole "via" element (incl. children), use the path "via".
118 * - To get the Y coordinate of the via, use the path "via/position/@1".
119 *
120 * @attention If there exist several childs with (the begin of) the specified
121 * path, only the first match is returned! So if the example above
122 * had more "via" elements, all after the first one would be
123 * ignored. And for example if the first "via" element had no
124 * "position" child, an exception is raised even if the following
125 * "via" elements do have a "position" child.
126 *
127 * @param path The path to the child to get, separated by forward slashes
128 * '/'. To specify a child by index, use '@' followed by the
129 * index (e.g. '@1' to get the second child).
130 *
131 * @return A reference to the child of the specified path.
132 *
133 * @throws ::librepcb::Exception if the specified child does not exist.
134 */
135 const SExpression& getChild(const QString& path) const;
136
137 /**
138 * @brief Try get a child by path
139 *
140 * This is exactly the same as #getChild(), but returns `nullptr` if the
141 * specified child does not exist (instead of throwing an exception).
142 *
143 * @param path See documentation of #getChild().
144 *
145 * @return A pointer to the child of the specified path, if found. If no
146 * such child exists, `nullptr` is returned.
147 */
148 const SExpression* tryGetChild(const QString& path) const noexcept;
149
150 // General Methods
151 SExpression& appendLineBreak();
152 SExpression& appendList(const QString& name, bool linebreak);
153 SExpression& appendChild(const SExpression& child, bool linebreak);
154 template <typename T>
appendChild(const T & obj)155 SExpression& appendChild(const T& obj) {
156 appendChild(serialize(obj), false);
157 return *this;
158 }
159 template <typename T>
appendChild(const QString & child,const T & obj,bool linebreak)160 SExpression& appendChild(const QString& child, const T& obj, bool linebreak) {
161 return appendList(child, linebreak).appendChild(obj);
162 }
163 void removeLineBreaks() noexcept;
164 QByteArray toByteArray() const;
165
166 // Operator Overloadings
167 SExpression& operator=(const SExpression& rhs) noexcept;
168
169 // Static Methods
170 static SExpression createList(const QString& name);
171 static SExpression createToken(const QString& token);
172 static SExpression createString(const QString& string);
173 static SExpression createLineBreak();
174 static SExpression parse(const QByteArray& content, const FilePath& filePath);
175
176 private: // Methods
177 SExpression(Type type, const QString& value);
178
179 static SExpression parse(const QString& content, int& index,
180 const FilePath& filePath);
181 static SExpression parseList(const QString& content, int& index,
182 const FilePath& filePath);
183 static QString parseToken(const QString& content, int& index,
184 const FilePath& filePath);
185 static QString parseString(const QString& content, int& index,
186 const FilePath& filePath);
187 static void skipWhitespaceAndComments(const QString& content, int& index);
188 static QString escapeString(const QString& string) noexcept;
189 static bool isValidToken(const QString& token) noexcept;
190 static bool isValidTokenChar(const QChar& c) noexcept;
191 QString toString(int indent) const;
192
193 private: // Data
194 Type mType;
195 QString mValue; ///< either a list name, a token or a string
196 QList<SExpression> mChildren;
197 FilePath mFilePath;
198 };
199
200 /*******************************************************************************
201 * Serialization Methods
202 ******************************************************************************/
203
204 template <>
serialize(const QString & obj)205 inline SExpression serialize(const QString& obj) {
206 return SExpression::createString(obj);
207 }
208
209 template <>
serialize(const bool & obj)210 inline SExpression serialize(const bool& obj) {
211 return SExpression::createToken(obj ? "true" : "false");
212 }
213
214 template <>
serialize(const int & obj)215 inline SExpression serialize(const int& obj) {
216 return SExpression::createToken(QString::number(obj));
217 }
218
219 template <>
serialize(const uint & obj)220 inline SExpression serialize(const uint& obj) {
221 return SExpression::createToken(QString::number(obj));
222 }
223
224 template <>
serialize(const QColor & obj)225 inline SExpression serialize(const QColor& obj) {
226 return SExpression::createString(obj.isValid() ? obj.name(QColor::HexArgb)
227 : "");
228 }
229
230 template <>
serialize(const QUrl & obj)231 inline SExpression serialize(const QUrl& obj) {
232 return SExpression::createString(
233 obj.isValid() ? obj.toString(QUrl::PrettyDecoded) : "");
234 }
235
236 template <>
serialize(const QDateTime & obj)237 inline SExpression serialize(const QDateTime& obj) {
238 return SExpression::createToken(obj.toUTC().toString(Qt::ISODate));
239 }
240
241 template <>
serialize(const SExpression & obj)242 inline SExpression serialize(const SExpression& obj) {
243 return obj;
244 }
245
246 /*******************************************************************************
247 * Deserialization Methods
248 ******************************************************************************/
249
250 template <>
deserialize(const SExpression & sexpr,const Version & fileFormat)251 inline QString deserialize(const SExpression& sexpr,
252 const Version& fileFormat) {
253 Q_UNUSED(fileFormat);
254 return sexpr.getValue(); // can throw
255 }
256
257 template <>
deserialize(const SExpression & sexpr,const Version & fileFormat)258 inline bool deserialize(const SExpression& sexpr, const Version& fileFormat) {
259 Q_UNUSED(fileFormat);
260 if (sexpr.getValue() == "true") {
261 return true;
262 } else if (sexpr.getValue() == "false") {
263 return false;
264 } else
265 throw RuntimeError(__FILE__, __LINE__,
266 SExpression::tr("Not a valid boolean."));
267 }
268
269 template <>
deserialize(const SExpression & sexpr,const Version & fileFormat)270 inline int deserialize(const SExpression& sexpr, const Version& fileFormat) {
271 Q_UNUSED(fileFormat);
272 bool ok = false;
273 int value = sexpr.getValue().toInt(&ok);
274 if (ok) {
275 return value;
276 } else
277 throw RuntimeError(__FILE__, __LINE__,
278 SExpression::tr("Not a valid integer."));
279 }
280
281 template <>
deserialize(const SExpression & sexpr,const Version & fileFormat)282 inline uint deserialize(const SExpression& sexpr, const Version& fileFormat) {
283 Q_UNUSED(fileFormat);
284 bool ok = false;
285 uint value = sexpr.getValue().toUInt(&ok);
286 if (ok) {
287 return value;
288 } else
289 throw RuntimeError(__FILE__, __LINE__,
290 SExpression::tr("Not a valid unsigned integer."));
291 }
292
293 template <>
deserialize(const SExpression & sexpr,const Version & fileFormat)294 inline QDateTime deserialize(const SExpression& sexpr,
295 const Version& fileFormat) {
296 Q_UNUSED(fileFormat);
297 QDateTime obj =
298 QDateTime::fromString(sexpr.getValue(), Qt::ISODate).toLocalTime();
299 if (obj.isValid())
300 return obj;
301 else
302 throw RuntimeError(__FILE__, __LINE__,
303 SExpression::tr("Not a valid datetime."));
304 }
305
306 template <>
deserialize(const SExpression & sexpr,const Version & fileFormat)307 inline QColor deserialize(const SExpression& sexpr, const Version& fileFormat) {
308 Q_UNUSED(fileFormat);
309 QColor obj(sexpr.getValue());
310 if (obj.isValid()) {
311 return obj;
312 } else
313 throw RuntimeError(__FILE__, __LINE__,
314 SExpression::tr("Not a valid color."));
315 }
316
317 template <>
deserialize(const SExpression & sexpr,const Version & fileFormat)318 inline QUrl deserialize(const SExpression& sexpr, const Version& fileFormat) {
319 Q_UNUSED(fileFormat);
320 QUrl obj(sexpr.getValue(), QUrl::StrictMode);
321 if (obj.isValid()) {
322 return obj;
323 } else
324 throw RuntimeError(__FILE__, __LINE__, SExpression::tr("Not a valid URL."));
325 }
326
327 /*******************************************************************************
328 * End of File
329 ******************************************************************************/
330
331 } // namespace librepcb
332
333 #endif // LIBREPCB_SEXPRESSION_H
334