View Javadoc
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 }