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