3

I have a piece of code parsing a text file line by line. I have to goals: Testing the syntax of the text and extracting information of it. It is quite likely that syntax errors occur so I want to provice helpful information about where and what.

To give an idea of the problem, I have a text file like the following (simplified example):

1=\tSTRING\tDevice name
15=\tFLOAT\tSpeed
17=\tINTEGER\tMax Speed
18=INTEGER\tMax Speed

As you can guess, the syntax of each line is: <Parameter ID>=\t<Data Type>\t<Description>

My goal is to

  • Return a vector of structs for every parameter.
  • If there is an error, give an error message
    • for example: "Error in Line 2: data type of INTEGER is not allowed"
    • for example: "Error in Line 3: missing tab"

My general structure is:

  • A "function": std::vector<ParameterDAO> ParseText (std::string text)
  • A "sub function" ParameterDAO ParseTextLine (std::string text)
  • As you can guess, ParseTextLine is called by ParseText for each line.
  • Some "subsub functions" used by ParseTextLine (checking spaces in the text, checking elements for validity/range/...

FYI: The strings/substrings itself I parse with regular expressions and some standard string operations (compare, ...). But this is not the main point in my question.

OK, now some more details of my implementation:

  1. Any of my functions (ParseText, ParseTextLine, ...) can throw an exception.
  2. I always throw the standard exception std::invalid_argument("my error message")
  3. The function "ParseText" always checks for exceptions thrown in one of the sub functions to add the "Error in Line x" message. This is done by getting the what-message of the exception thrown, creating a new string with this message and the line info an rethrow the message:
  4. The code calling "ParseText" also checks for exceptions. If an exceptions has occured it will show the error message (for example "Error in Line 3: missing tab" to the user

Code snippet for 3:

try
{
    Parse_HPA_CATEGORY_SingleLine_THROWS;
}
catch ( std::exception e )
{
    std::string l_ErrorMessage = "Error in Line x: ";
    l_ErrorMessage.append ( e.what () );
    throw std::invalid_argument ( l_ErrorMessage.c_str() );
}

This structure works and has the following benefit:

  • The error message is close to the location where the error occurs (for example close to a string compare or the regular expression).

But there may be some drawbacks / things I am not sure about, too:

  • In the unit test, I have to repeat the string literally ( I don't know if this is actually bad).
  • I read (unfortunately I can't remember where) that the "what message" is usually not used to directly create error messages. Do I misuse the "what message"? Should I maybe deviate an special exception error class from std::exception for every exception case?
  • The ParseText function makes a kind of rethrow. Is there a way to avoid this?
4

Using exception::what() for text that is show to the end-user is indeed a problem. Suppose your project becomes a real success and you get a request to make a translation of it into some other language. That will be next to impossible if the text you show your users is directly copied from exception::what().

The correct way is indeed to create an exception (hierarchy) for the problems that your parser can encounter. For example:

class FormatError : public std::runtime_error
{
public:
  //...
  void set_line_number(int);
  int get_line_number() const;
private:
  int line_number;
};

class InvalidDataType : public FormatError
{
  // ...
private:
  String dataType;
};
// etc.

Using a common base class makes it easy to add the line number information at a later point (in ParseText) and re-throw the same exception (with throw;)

  • Worked very well. I used rethrow mechanism. Additionally, now I completely banned the using of "what" and used an additional string member instead. When using any functionality of this code, I always catch FormatError (not e.g. InvalidDataType). So I added a string member and a "GetErrorMessage" to FormatError, which creates an error string (good idea, or should the error class not create a string?). All derived classes e.g. InvalidDataType only contain a constructor call to the FormatError with the error text as an argument. – Semjon Mössinger Jan 2 '17 at 15:15
  • @Ergodicity: Letting the error class generate the string has still the problem that you can't really easily translate it to a different language. However, the use of different classes does enable you to move the string creation to the UI when you find that that is the better place to have it. At that point, the UI code would start catching the specific error classes and create the appropriate user message. – Bart van Ingen Schenau Jan 2 '17 at 15:22
  • OK, thank you for this hint. In the next years (maybe forever) we will not add any other language. BTW, one other point: I now realize that a big advantage of exceptions is that you have unique classes (like an ID). But you can give them a hierarchy via inheritance (and use it for catching). And you can group a set of exceptions via namespaces. – Semjon Mössinger Jan 2 '17 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.