KiCad PCB EDA Suite
Loading...
Searching...
No Matches
spice_grammar.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 (C) 2022 Mikolaj Wielgus
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#ifndef SPICE_GRAMMAR_H
22#define SPICE_GRAMMAR_H
23
24#include <sim/sim_value.h>
25
26
28{
29 using namespace SIM_VALUE_GRAMMAR;
30
31
32 struct garbage : plus<one<' ', '\t', '=', '(', ')', ','>> {};
33 struct leaders : plus<one<' ', '\t'>> {};
34 struct trailers : plus<one<' ', '\t', '\v', '\f'>> {};
35
36 struct garbageOrEolf : sor<garbage, eolf> {};
37
38 // NOTE: In Ngspice, a '$' opening a comment must be preceded by ' ', ',', or '\t'. We don't
39 // implement that here - this may cause problems in the future.
40 // Ngspice supports '//' for comments.
41 struct eolfCommentStart : sor<one<';', '$'>,
42 string<'/', '/'>> {};
43
44 struct eolfComment : seq<eolfCommentStart,
45 until<eolf>> {};
46
47
48 struct commentLine : seq<opt<garbage>,
49 one<'*'>,
50 until<eolf>> {};
51
52
53 struct newline : seq<sor<eolf,
54 eolfComment>,
55 not_at<one<'+'>>> {};
56
57 struct backslashContinuation : seq<string<'\\', '\\'>,
58 opt<trailers>,
59 eolf> {};
60
61 struct commentBackslashContinuation : seq<eolfCommentStart,
62 seq<star<not_at<eolf>,
63 not_at<string<'\\', '\\'>,
64 opt<trailers>,
65 eolf>,
66 any>,
67 string<'\\', '\\'>,
68 opt<trailers>,
69 eolf>> {};
70
71
72 struct plusContinuation : seq<sor<eolf,
73 eolfComment>,
74 star<commentLine>,
75 opt<leaders>,
76 one<'+'>> {};
77
78 struct continuation : seq<opt<garbage>,
79 sor<backslashContinuation,
80 commentBackslashContinuation,
81 plusContinuation>,
82 opt<garbage>> {};
83
84
85 // Token separator.
86 struct sep : sor<plus<continuation>,
87 garbage> {};
88
89 struct modelName : plus<not_at<garbageOrEolf>, any> {};
90
91 struct dotModelType : sor<// VDMOS models have a special syntax.
92 seq<TAO_PEGTL_ISTRING( "vdmos" ),
93 sep,
94 sor<TAO_PEGTL_ISTRING( "nchan" ),
95 TAO_PEGTL_ISTRING( "pchan" )>>,
96 plus<not_at<garbageOrEolf>, any>> {};
97
98 struct vectorExpr : seq<one<'['>,
99 star<not_one<']'>>,
100 one<']'>> {};
101
102 struct bracedExpr : seq<one<'{'>,
103 star<not_one<'}'>>,
104 one<'}'>> {};
105
106 // Ngspice has some heuristic logic to allow + and - in tokens. We replicate that here.
107 struct tokenStart : seq<opt<one<'+', '-'>>,
108 opt<seq<star<sor<tao::pegtl::digit,
109 one<'.'>>>,
110 one<'e', 'E'>,
111 opt<one<'+', '-'>>>>> {};
112
113 struct tokenChar : seq<not_at<eolf>,
114 not_at<backslashContinuation>,
115 not_one<' ', '\t', '=', '(', ')', ',', ';'>> {};
116
117 struct token : seq<tokenStart,
118 star<tokenChar>> {};
119
120 // Param names cannot be `token` because LTspice models contain spurious values without
121 // parameter names, which we need to skip, and because tokens can include a very limited
122 // subset of un-braced expressions
123 struct param : identifier {};
124 struct paramValue : sor<bracedExpr,
125 vectorExpr,
126 token> {};
127
128 struct paramValuePair : seq<param,
129 sep,
130 paramValue> {};
131 struct paramValuePairs : list<paramValuePair, sep> {};
132
133 struct cplSep : opt<leaders> {};
134
135 // A CPL parameter value is a whitespace-separated list of atoms, where an atom is a braced
136 // expression or a bare token, freely intermixed (e.g. "{R11} 0 {R22}"). The bare token uses
137 // plus<> so every atom consumes at least one character; otherwise the list could spin forever
138 // because cplSep matches empty. The lookahead stops the list before a following "name=" so a
139 // bare value does not swallow the next parameter.
140 struct cplTokenValue : seq<not_at<identifier, opt<leaders>, one<'='>>,
141 tokenStart,
142 plus<tokenChar>> {};
143 struct cplParamValueAtom : sor<bracedExpr,
144 vectorExpr,
145 cplTokenValue> {};
146 struct cplParamValue : list<cplParamValueAtom, cplSep> {};
147 struct cplParamValuePair : seq<param,
148 sep,
149 cplParamValue> {};
150 struct cplParamValuePairs : list<cplParamValuePair, sep> {};
151
152 struct dotModelAko : seq<opt<sep>,
153 if_must<seq<TAO_PEGTL_ISTRING( ".model" ),
154 sep,
155 modelName,
156 sep,
157 TAO_PEGTL_ISTRING( "ako:" )>,
158 opt<sep>,
159 modelName,
160 opt<sep,
161 dotModelType>,
162 opt<sep,
163 paramValuePairs>,
164 opt<sep>,
165 newline>> {};
166
167 struct dotModelCPL : seq<opt<sep>,
168 if_must<seq<TAO_PEGTL_ISTRING( ".model" ),
169 sep,
170 modelName,
171 sep,
172 TAO_PEGTL_ISTRING( "CPL" ),
173 at<garbageOrEolf>>,
174 opt<sep,
175 cplParamValuePairs>,
176 opt<sep>,
177 newline>> {};
178
179 struct dotModel : seq<opt<sep>,
180 if_must<TAO_PEGTL_ISTRING( ".model" ),
181 sep,
182 modelName,
183 sep,
184 dotModelType,
185 opt<sep,
186 paramValuePairs>,
187 opt<sep>,
188 newline>> {};
189
190 struct dotSubcktParamValuePair : seq<not_at<eolfComment>,
191 param,
192 // TODO: Check if these `star<space>`s match Ngspice's
193 // behavior.
194 star<space>,
195 opt<plusContinuation>,
196 one<'='>,
197 opt<plusContinuation>,
198 star<space>,
199 paramValue> {};
200
201 struct dotSubcktParamValuePairs : list<dotSubcktParamValuePair, sep> {};
202
203 struct dotSubcktParamsStart : sor<TAO_PEGTL_ISTRING( "params:" ),
204 seq<newline,
205 TAO_PEGTL_ISTRING( ".param" )>> {};
206
207 struct dotSubcktParams : seq<opt<dotSubcktParamsStart>,
208 opt<sep>,
209 dotSubcktParamValuePairs> {};
210
211 struct dotSubcktPinName : seq<not_at<dotSubcktParams>,
212 not_at<eolfComment>,
213 plus<not_at<space>, any>> {};
214
215 struct dotSubcktPinSequence : list<dotSubcktPinName, sep> {};
216
217 struct dotSubcktEnd : seq<opt<sep>,
218 TAO_PEGTL_ISTRING( ".ends" ),
219 until<newline>> {};
220
221 struct spiceUnit;
222 struct dotSubckt : seq<opt<sep>,
223 if_must<TAO_PEGTL_ISTRING( ".subckt" ),
224 sep,
225 modelName,
226 opt<sep,
227 dotSubcktPinSequence>,
228 opt<sep,
229 dotSubcktParams>,
230 opt<sep>,
231 newline,
232 until<dotSubcktEnd,
233 spiceUnit>>> {};
234
235
236 struct modelUnit : seq<star<commentLine>,
237 sor<dotModelAko,
238 dotModelCPL,
239 dotModel,
240 dotSubckt>> {};
241
242
243 // Intentionally no if_must<>.
244 struct dotControl : seq<opt<sep>,
245 TAO_PEGTL_ISTRING( ".control" ),
246 until<TAO_PEGTL_ISTRING( ".endc" )>,
247 until<newline>> {};
248
249
250 struct dotTitleTitle : star<not_at<newline>, any> {};
251 // Intentionally no if_must<>.
252 struct dotTitle : seq<opt<sep>,
253 TAO_PEGTL_ISTRING( ".title" ),
254 sep,
255 dotTitleTitle,
256 newline> {};
257
258
259 struct dotIncludePathWithoutQuotes : star<not_one<'"'>> {};
260 struct dotIncludePathWithoutApostrophes : star<not_one<'\''>> {};
261 struct dotIncludePath : star<not_at<newline>, any> {};
262 // Intentionally no if_must<>.
263 struct dotInclude : seq<opt<sep>,
264 TAO_PEGTL_ISTRING( ".inc" ),
265 star<not_at<garbageOrEolf>, any>,
266 sep,
267 sor<seq<one<'\"'>,
268 dotIncludePathWithoutQuotes,
269 one<'\"'>>,
270 seq<one<'\''>,
271 dotIncludePathWithoutApostrophes,
272 one<'\''>>,
273 dotIncludePath>,
274 opt<sep>,
275 newline> {};
276
277
278 // Intentionally no if_must<>.
279 struct dotLine : seq<opt<sep>,
280 one<'.'>,
281 until<newline>> {};
282
283
284 // Intentionally no if_must<>.
285 struct kLine : seq<opt<sep>,
286 one<'K'>,
287 until<sep>,
288 one<'L'>,
289 until<sep>,
290 one<'L'>,
291 until<sep>,
292 until<newline>> {};
293
294 struct unknownLine : seq<plus<not_at<newline>, any>,
295 until<newline>> {};
296
297
298 struct spiceUnit : sor<modelUnit,
299 dotControl,
300 dotTitle,
301 dotInclude,
302 dotLine,
303 kLine,
304 eol, // Empty line. This is necessary to terminate on EOF.
305 unknownLine> {};
306 struct spiceUnitGrammar : must<spiceUnit> {};
307
308
309 struct spiceSource : star<spiceUnit> {};
310 struct spiceSourceGrammar : must<spiceSource> {};
311
312
313 template <typename> inline constexpr const char* errorMessage = nullptr;
314 template <> inline constexpr auto errorMessage<newline> =
315 "expected newline";
316 template <> inline constexpr auto errorMessage<sep> =
317 "expected token separator (one or more whitespace, parenthesis, '=', ',', or line continuation)";
318 template <> inline constexpr auto errorMessage<opt<sep>> =
319 "";
320 template <> inline constexpr auto errorMessage<modelName> =
321 "expected model name";
322 template <> inline constexpr auto errorMessage<dotModelType> =
323 "expected model type";
324 template <> inline constexpr auto errorMessage<opt<sep, dotModelType>> =
325 "";
326 template <> inline constexpr auto errorMessage<opt<sep, paramValuePairs>> =
327 "";
328 template <> inline constexpr auto errorMessage<opt<sep, cplParamValuePairs>> =
329 "";
330 template <> inline constexpr auto errorMessage<opt<sep, dotSubcktPinSequence>> =
331 "";
332 template <> inline constexpr auto errorMessage<opt<sep, dotSubcktParams>> =
333 "";
334 template <> inline constexpr auto errorMessage<until<dotSubcktEnd, spiceUnit>> =
335 "expected (possibly empty) sequence of Spice lines followed by an .ends line";
336 template <> inline constexpr auto errorMessage<spiceUnit> =
337 "expected Spice directive, item, subcircuit definitions, or empty or commented-out line";
338 template <> inline constexpr auto errorMessage<spiceSource> =
339 "expected zero or more Spice directives, items, subcircuit definitions, or empty or commented-out lines";
340
341 // We create a custom PEGTL control to modify the parser error messages.
342 struct error
343 {
344 template <typename Rule> static constexpr bool raise_on_failure = false;
345 template <typename Rule> static constexpr auto message = errorMessage<Rule>;
346 };
347
348 template <typename Rule> using control = must_if<error>::control<Rule>;
349}
350
351#endif // SPICE_GRAMMAR_H
must_if< error >::control< Rule > control
constexpr const char * errorMessage
static constexpr auto message
static constexpr bool raise_on_failure