views:

202

answers:

2

I want to emit dynamic error messages like all interpreters do nowadays, for example:

Name error: Undefined variable

would be constant, however what I want to reach is:

Name error: Undefined variable 'X', in line 1

Okay. The line number was really no problem: Every error message must have a line number, so I added it to the error emitter function:

Error( ErrType type, string msg, int line );

So where is my problem?

How do I get the 'X' into Undefined variable *?

  • I can't use sprintf as it doesn't support strings yet I use them everywhere
  • I can't simply use cout and connect everything as I want error messages to be supressable
  • I'd like to get everything into one function like above, Error()

How do I put together dynamic error messages?

For example: Error( Name, sprintf("Undefined variable %s", myVariableName ), lineNum );

(But myVariableName is a string and sprintf will mess things up)

+3  A: 

You can get a null-terminated C string out of myVariableName by calling c_str:

myVariableName.c_str()

Note that your use of sprintf is incorrect; the first parameter of sprintf takes the buffer into which to place the formatted string. If you are using std::string in your program, why use sprintf at all? If Error takes a std::string then you can just use string concatenation:

Error(Name, "Undefined variable " + myVariableName, lineNum);
James McNellis
Holy cow, I didn't know that you can simply use + to concenate some strings!
Rawr
Then vote him up :)
Michael Dorgan
+3  A: 

Clang solves that by having their diagnostics have operator<< overloaded, streaming the required arguments, which I adopted in my compiler too

DiagnosticBuilder Error( ErrType type, string msg, int line );

You can then call it like

Error(Serious, "Variable % is not known", lineNumber) << var;

When the diagnostic-builder's destructor is called, the error is emitted.

struct DiagnosticBuilder {
  DiagnosticBuilder(std::string const& format)
    :m_emit(true), m_format(format) 
  { }
  DiagnosticBuilder(DiagnosticBuilder const& other) 
    :m_emit(true), m_format(other.m_format), m_args(other.m_args) {
    other.m_emit = false;
  }
  ~DiagnosticBuilder() {
    if(m_emit) {
      /* iterate over m_format, and print the next arg 
         everytime you hit '%' */
    }
  }

  DiagnosticBuilder &operator<<(string const& s) {
    m_args.push_back(s);
    return *this;
  }
  DiagnosticBuilder &operator<<(int n) {
    std::ostringstream oss; oss << n;
    m_args.push_back(oss.str());
    return *this;
  }
  // ...
private:
  mutable bool m_emit;
  std::string m_format;
  std::vector<std::string> m_args;
};

I find that this way of doing it is quite convenient. You can extend it to support multiple languages by numbering the argument placeholders, like "Variable %2 isn't found in scope %1".

Johannes Schaub - litb
+1, That's a neat idea. I personally didn't like instantiating a diagnostic object, so I opted for a singleton logger that exposes its own `operator<<`. I may revisit that in the future.
greyfade
+1: I just implemented something very similar to this and it works very nicely. Thanks for the idea.
James McNellis