1 /*******************************************************************************
2 * jArduino: Arduino C++ Code Generation From Java
3 * Copyright 2020 Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 ******************************************************************************/
17 package net.sourceforge.jarduino.message;
18
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.nio.charset.Charset;
26 import java.text.NumberFormat;
27 import java.text.ParseException;
28 import java.util.Locale;
29
30 import net.sourceforge.jarduino.ArduinoException;
31
32 /**
33 * Parser.
34 */
35 public final class ArduinoParser {
36 /**
37 * Number format (US format to match .dbc standard).
38 */
39 private static final NumberFormat FORMAT = NumberFormat.getInstance(Locale.US);
40
41 /**
42 * Private constructor.
43 */
44 private ArduinoParser() {
45 }
46
47 /**
48 * parse a file.
49 * @param pDBCFile the DBCFile
50 * @param pCharSet the character set to use
51 * @return the parsed system
52 * @throws ArduinoException on error
53 */
54 public static ArduinoSystem parseFile(final File pDBCFile,
55 final Charset pCharSet) throws ArduinoException {
56 try (FileInputStream myStream = new FileInputStream(pDBCFile)) {
57 return parseStream(pDBCFile.getName().replace(".dbc", ""), pCharSet, myStream);
58 } catch (IOException e) {
59 throw new ArduinoException("Failed to open file", e);
60 }
61 }
62
63 /**
64 * parse an input stream.
65 * @param pName the name
66 * @param pCharSet the character set to use
67 * @param pInput the input stream
68 * @return the parsed system
69 * @throws ArduinoException on error
70 */
71 public static ArduinoSystem parseStream(final String pName,
72 final Charset pCharSet,
73 final InputStream pInput) throws ArduinoException {
74 /* Protect against exceptions */
75 try (InputStreamReader myInputReader = new InputStreamReader(pInput, pCharSet);
76 BufferedReader myReader = new BufferedReader(myInputReader)) {
77
78 /* The current message */
79 ArduinoSystem mySystem = null;
80 ArduinoMessage myMessage = null;
81
82 /* Read the header entry */
83 boolean systemFound = false;
84 for (;;) {
85 /* Read next line */
86 String myLine = readNextLine(myReader);
87 if (myLine == null) {
88 break;
89 }
90 myLine = myLine.trim();
91
92 /* If we have not yet found the system */
93 if (!systemFound) {
94 /* if we have found the system */
95 if (myLine.startsWith(ArduinoNode.MARKER + ArduinoChar.COLON)) {
96 /* Allocate the system and note the fact */
97 mySystem = ArduinoSystem.parseSystem(pName, myLine);
98 systemFound = true;
99 }
100
101 /* else if we have a message */
102 } else {
103 /* Parse the line */
104 myMessage = parseLine(mySystem, myMessage, myLine);
105 }
106 }
107
108 /* return the system */
109 return mySystem;
110
111 /* Handle exceptions */
112 } catch (IOException e) {
113 throw new ArduinoException("Failed to read file", e);
114 }
115 }
116
117 /**
118 * read next line allowing for LF embedded in quotes.
119 * @param pReader the input reader
120 * @return the next line
121 * @throws ArduinoException on error
122 */
123 private static String readNextLine(final BufferedReader pReader) throws ArduinoException {
124 /* Protect against exceptions */
125 try {
126 String myLine = null;
127
128 /* Loop reading lines */
129 for (;;) {
130 /* Read next line */
131 final String myPart = pReader.readLine();
132 if (myPart == null) {
133 return myLine;
134 }
135
136 /* Append to any existing line */
137 myLine = myLine == null
138 ? myPart
139 : myLine + ArduinoChar.LF + myPart;
140
141 /* Count the number of quotes in the line */
142 final int myCount = countQuotes(myLine);
143
144 /* Line is complete if all quotes are completed */
145 if (myCount % 2 == 0) {
146 return myLine;
147 }
148 }
149
150 /* Handle exceptions */
151 } catch (IOException e) {
152 throw new ArduinoException("Failed to read line", e);
153 }
154 }
155
156 /**
157 * Count the number of quotes in the line.
158 * @param pLine the current line
159 * @return the count of quotes
160 */
161 private static int countQuotes(final String pLine) {
162 /* Handle no quote at all */
163 int myIndex = pLine.indexOf(ArduinoChar.QUOTE);
164 if (myIndex == -1) {
165 return 0;
166 }
167
168 /* Loop looking for more quotes */
169 for (int myCount = 1; ; myCount++) {
170 myIndex = pLine.indexOf(ArduinoChar.QUOTE, myIndex + 1);
171 if (myIndex == -1) {
172 return myCount;
173 }
174 }
175 }
176
177 /**
178 * process line.
179 * @param pSystem the system
180 * @param pMessage the current message
181 * @param pLine the line
182 * @return the current message
183 * @throws ArduinoException on error
184 */
185 public static ArduinoMessage parseLine(final ArduinoSystem pSystem,
186 final ArduinoMessage pMessage,
187 final String pLine) throws ArduinoException {
188 /* Access current message and token */
189 ArduinoMessage myMessage = pMessage;
190 switch (nextToken(pLine)) {
191 /* Message */
192 case ArduinoMessage.MARKER:
193 myMessage = ArduinoMessage.parseMessage(pSystem, pLine);
194 break;
195
196 /* Signal */
197 case ArduinoSignal.MARKER:
198 if (myMessage != null) {
199 ArduinoSignal.parseSignal(myMessage, pLine);
200 }
201 break;
202
203 /* Comment */
204 case ArduinoComments.MARKER:
205 ArduinoComments.parseComment(pSystem, pLine);
206 break;
207
208 /* attributeDef */
209 case ArduinoAttributes.MARKER_DEF:
210 ArduinoAttributes.parseAttributeDef(pSystem, ArduinoAttributes.MARKER_DEF, pLine);
211 break;
212
213 /* attributeDefault */
214 case ArduinoAttributes.MARKER_DEFAULT:
215 ArduinoAttributes.parseAttributeDefault(pSystem, ArduinoAttributes.MARKER_DEFAULT, pLine);
216 break;
217
218 /* attribute */
219 case ArduinoAttributes.MARKER:
220 ArduinoAttributes.parseAttribute(pSystem, ArduinoAttributes.MARKER, pLine);
221 break;
222
223 /* attributeRelDef */
224 case ArduinoAttributes.MARKER_REL_DEF:
225 ArduinoAttributes.parseAttributeDef(pSystem, ArduinoAttributes.MARKER_REL_DEF, pLine);
226 break;
227
228 /* attributeRelDefault */
229 case ArduinoAttributes.MARKER_REL_DEFAULT:
230 ArduinoAttributes.parseAttributeDefault(pSystem, ArduinoAttributes.MARKER_REL_DEFAULT, pLine);
231 break;
232
233 /* attributeRel */
234 case ArduinoAttributes.MARKER_REL:
235 ArduinoAttributes.parseAttribute(pSystem, ArduinoAttributes.MARKER_REL, pLine);
236 break;
237
238 /* xmitters */
239 case ArduinoNode.MARKER_TX:
240 ArduinoNode.parseXmitNodes(pSystem, pLine);
241 break;
242
243 /* values */
244 case ArduinoValues.MARKER:
245 ArduinoValues.parseValues(pSystem, pLine);
246 break;
247
248 /* Ignore if we do not recognise */
249 default:
250 break;
251 }
252
253 /* return the current message */
254 return myMessage;
255 }
256
257 /**
258 * Obtain the next token.
259 * @param pSource the current line
260 * @return the next token
261 */
262 static String nextToken(final String pSource) {
263 final int myLen = pSource.length();
264 for (int i = 0; i < myLen; i++) {
265 if (Character.isWhitespace(pSource.charAt(i))) {
266 return pSource.substring(0, i);
267 }
268 }
269 return pSource;
270 }
271
272 /**
273 * Obtain the next token which must be between quotes.
274 * @param pSource the current line
275 * @return the next quoted token
276 * @throws ArduinoParserException on error
277 */
278 static String nextQuotedToken(final String pSource) throws ArduinoParserException {
279 /* Token must begin with quote */
280 if (pSource.length() < 2 || pSource.charAt(0) != ArduinoChar.QUOTE) {
281 throw new ArduinoParserException("Missing start quote", pSource);
282 }
283
284 /* Must have another quote */
285 final int myIndex = pSource.indexOf(ArduinoChar.QUOTE, 1);
286 if (myIndex == -1) {
287 throw new ArduinoParserException("Missing end quote", pSource);
288 }
289
290 /* Return the token */
291 return pSource.substring(1, myIndex);
292 }
293
294 /**
295 * Strip the token from the line.
296 * @param pSource the current line
297 * @param pToken the starting token
298 * @return the stripped line
299 */
300 static String stripToken(final String pSource,
301 final String pToken) {
302 return pSource.startsWith(pToken)
303 ? pSource.substring(pToken.length()).trim()
304 : pSource;
305 }
306
307 /**
308 * Strip the quoted token from the line.
309 * @param pSource the current line
310 * @param pToken the starting token
311 * @return the stripped line
312 */
313 static String stripQuotedToken(final String pSource,
314 final String pToken) {
315 return stripToken(pSource.substring(1), pToken).substring(1).trim();
316 }
317
318 /**
319 * Parse number.
320 * @param pNumberDef the number representation
321 * @return the number
322 * @throws ArduinoParserException on error
323 */
324 static Number parseNumber(final String pNumberDef) throws ArduinoParserException {
325 /* Protect against exceptions */
326 try {
327 /* Obtain the numberDef as UpperCase and without + */
328 final String myNumber = pNumberDef.toUpperCase().replace('+', '0');
329
330 /* Parse the number */
331 return FORMAT.parse(myNumber);
332
333 /* Catch parsing errors */
334 } catch (ParseException e) {
335 throw new ArduinoParserException("Failed to parse number", pNumberDef);
336 }
337 }
338
339 /**
340 * Obtain the number of decimals in a number.
341 * @param pNumberDef the number representation (US format)
342 * @return the number of decimals
343 * @throws ArduinoParserException on error
344 */
345 static int determineNumDecimals(final String pNumberDef) throws ArduinoParserException {
346 /* Protect against exceptions */
347 try {
348 /* Access the definition */
349 String myDef = pNumberDef;
350 int numDecimals = 0;
351
352 /* Look for Exponential representation */
353 int myIndex = pNumberDef.indexOf('e');
354 if (myIndex == -1) {
355 myIndex = pNumberDef.indexOf('E');
356 }
357
358 /* If we found exponential representation */
359 if (myIndex != -1) {
360 numDecimals = -Integer.parseInt(myDef.substring(myIndex + 1));
361 myDef = myDef.substring(0, myIndex);
362 }
363
364 /* Look for Decimal representation */
365 myIndex = myDef.indexOf(ArduinoChar.DEC);
366 if (myIndex != -1) {
367 numDecimals += myDef.substring(myIndex + 1).length();
368 }
369
370 /* Return the number of decimals */
371 return numDecimals;
372
373 /* Catch parsing errors */
374 } catch (NumberFormatException e) {
375 throw new ArduinoParserException("Failed to parse number", pNumberDef);
376 }
377 }
378
379 /**
380 * Parser exception.
381 */
382 static final class ArduinoParserException extends Exception {
383 /**
384 * Serial versionID.
385 */
386 private static final long serialVersionUID = 8237660286012917142L;
387
388 /**
389 * The detail.
390 */
391 private final String theDetail;
392
393 /**
394 * Constructor.
395 * @param pMessage the message
396 * @param pDetail the detail
397 */
398 ArduinoParserException(final String pMessage,
399 final String pDetail) {
400 super(pMessage);
401 theDetail = pDetail;
402 }
403
404 /**
405 * Obtain the detail.
406 * @return the detail
407 */
408 public String getDetail() {
409 return theDetail;
410 }
411 }
412 }