mstring  1.3.1
Generates C++ message functions and classes from localized message definitions
mstring message files

A message file defines messages: texts with parameters, in one or more languages. mstring turns it into a C++ header and source with, per message, the functions and classes the settings ask for.

The grammar, one railroad diagram per rule, is in [docs/grammar/](grammar) (NNNNN-<rule>.rrd sources, .svg drawings, render.py to redraw them) and on mstring.fedem.eu under Language.

The file is line oriented. Outside a message only $DIRECTIVE lines, blank lines and # comments may appear. Directive and option names are case-insensitive ($MESSAGE, $message, $Message).

# comment
$DIRECTIVE option ... # a comment may follow

Messages

$MESSAGE name [: ARTEFACT...]
@ parameter type # zero or more parameters, before any text
...
text line # the texts: see below
...
[language] # texts in another language
text line
...

name is a C++ identifier and must be unique in the module. A message ends at the next $DIRECTIVE (or the end of the file). A message without any text gets a warning.

The settings directives below are sticky: they apply to every message that follows. To choose the artefacts of one message only, list them after a colon — exactly these are generated, named by the current settings:

$MESSAGE Timeout : STRING THROW # no streamable, error, syslog here

The artefacts are STREAMABLE, STRING, ERROR, EXCEPTION, THROW and SYSLOG.

Parameters

@ user std::string const &
@ count int

The type is the rest of the line. The names stream, loc, option_parameter, streamLocale, originalFormat, internal_locale_name and names starting with member_ or internal_ are reserved for the generated code.

A generated class (streamable, exception) stores a parameter as std::decay_t< type >, so an object built from a std::string const & never refers to a temporary.

Text lines

  • 'single quoted' — no macros ({$x} is literal), no newline at the end;
  • "double quoted" — macros, no newline at the end;
  • |bar text (to the end of the line) — macros, and a newline at the end, unless the line ends with a backslash.

The lines of one language are concatenated. Blanks before the opening quote or bar are ignored; inside the text they are kept.

Escapes, in every form:

  • \n \t \r \a \b \f \v — control characters;
  • a doubled backslash — one backslash; a backslash before a single or a double quote — that quote;
  • \{ — a literal {: \{$x} is not a macro;
  • a backslash and 1–3 octal digits (\303), or \x and 1–2 hex digits (\xC3) — that byte;
  • a backslash before anything else is the backslash itself: C:\temp stays C:\temp.

A |bar line that ends with \ is joined to the next line without a newline. A quoted string must close on its line.

Macros

"{$expression}"
"{$expression;format;format...}"

expression is any C++ expression, usually a parameter; it is written to the stream as stream << ( expression ). Braces inside it may nest ({$ std::string{ "x" } }); a ; or an unbalanced } can be escaped as \; / \}.

A format applies to this value only — the stream's formatting (flags, precision, width, fill, locale) is restored afterwards:

  • std::setw(4), std::hex, std::fixed, … — a manipulator: stream << std::setw(4);
  • width(4), precision(2), fill('0'), setf(...), unsetf(...), flags(...), imbue(...), or anything starting with . — a member call: stream.precision(2);

Manipulators with arguments need $INCLUDE <iomanip>.

Languages

$LANGUAGE en # the language of texts without a header (default C)
$MESSAGE Welcome
"Welcome" # en
[tr]
"Hoş geldin"
[de]
"Willkommen"

A [language] header switches the language of the text lines that follow, until the next header. The generated code picks the text by the locale name of the stream it writes to:

  • a language matches a locale name exactly, or when followed by _, . or @: [tr] and [tr_TR] both match tr_TR.UTF-8, [C] matches C.UTF-8;
  • the more specific language is tried first (tr_TR before tr);
  • no match: the text in the message's $LANGUAGE, or else its first text.

The text bytes are written byte for byte (as octal escapes), so the source file's charset is the charset of the texts.

$CHARSET name and a charset in the header ([de ISO-8859-1]) are deprecated: they only label the text with a comment in the generated code, and give a warning.

Translation checks

After parsing, mstring warns about

  • a message that has no text in a language some other message has — it falls back to its default text at run time;
  • a parameter that one language's text uses and another's does not — almost always a translation mistake. (A parameter no language uses is fine: it is data the streamable / exception class carries.)

What is generated

Per message the source gets up to two internal text functions: one that writes the text to a std::ostream (used by the streamable class and the error function) and one that appends it to a std::string without a stream (used by the string and syslog functions and the exception's What()). The latter appends literals directly, string values as they are and integers with std::to_chars when the locale is "C"; everything else — and every macro with formats — goes through a std::ostringstream imbued with the locale, so the text is always exactly what streaming would produce, 4–8 times faster (see bench/). Each artefact is switched on and named by its directive; the settings in effect at $MESSAGE apply to that message. With LOCALE EXTRA ENABLE (the default) every function has a second overload taking a trailing std::locale const &loc, which selects the language; without it the stream's (for a string: the global) locale does.

Directive Default name Generates
$STRING <name>Str std::string <name>Str( parameters )
$STREAMABLE <name>Streamable a class holding the parameters, with getters, operator<< and a virtual PrintOn( std::ostream & )
$ERROR <name>Error void <name>Error( parameters ): writes the text on std::cerr, then runs the EXIT statement (default std::exit( EXIT_FAILURE ))
$EXCEPTION <name>Exception an exception class holding the parameters: What() (a std::string), what() (as std::exception::what()), getters
$THROW Throw<name> [[noreturn]] void Throw<name>( parameters ): throws the exception class (generated for $THROW even when $EXCEPTION is disabled)
$SYSLOG Log<name> void Log<name>( parameters, int option_parameter = FACILITY \| LEVEL ): sends the text to syslog()

Directives

Naming and switching — all artefacts

$STRING ENABLE | DISABLE
$STRING PREFIX word | POSTFIX word
$STRING NOPREFIX | NOPOSTFIX
$STRING LOCALE EXTRA ENABLE | DISABLE

(the same for $STREAMABLE, $ERROR, $EXCEPTION, $THROW, $SYSLOG). The first PREFIX / POSTFIX / NO… of an artefact replaces both defaults: after $STRING PREFIX get, message Hello gives getHello, not getHelloStr. Several options may share a line: $STRING ENABLE PREFIX get.

Classes — <tt>$STREAMABLE</tt>, <tt>$EXCEPTION</tt>

$EXCEPTION INHERITED [PUBLIC|PROTECTED|PRIVATE] name[::name]...
$EXCEPTION OVERRIDE name # or OVERRIDE NONE

By default an exception class gets What() (the text as a std::string) and what(). When its base already implements what() on top of a pure virtual text method — virtual std::string name( ) const noexcept — OVERRIDE name implements that method instead (override, noexcept) and generates neither What() nor what(). It needs an INHERITED base.

(PARENT is a synonym of INHERITED.) The access defaults to PUBLIC, so catch ( std::exception const & ) catches a class derived from std::exception. The base is default-constructed. A streamable class overrides the base's virtual PrintOn( std::ostream & ) const, if it has one.

Functions — <tt>$STRING</tt>, <tt>$ERROR</tt>, <tt>$THROW</tt>, <tt>$SYSLOG</tt>

$STRING NONMEMBER # a free function (default)
$STRING [CONST|NONCONST] MEMBER OF class[::class] # a member of your class
$STRING [CONST|NONCONST] MEMBER AS name # a member of the streamable class
  • MEMBER OF defines class::<function>( parameters... ); declare it in your class (and $INCLUDE its header). C++ requires the class to be in the $NAMESPACE or in a namespace nested in it.
  • MEMBER AS name declares and defines name() in the message's streamable class — it uses the stored parameters, so it takes none. Needs $STREAMABLE ENABLE.
  • CONST (the default) makes it a const member function.

<tt>$ERROR EXIT</tt> and <tt>$SYSLOG</tt>

$ERROR EXIT statement # the rest of the line, e.g. EXIT throw Fatal( );
$SYSLOG FACILITY AUTH|AUTHPRIV|CRON|DAEMON|FTP|KERN|LOCAL0…LOCAL7|LPR|MAIL|NEWS|USER|UUCP
$SYSLOG LEVEL EMERG|ALERT|CRIT|ERR|WARNING|NOTICE|INFO|DEBUG

EXIT takes the rest of the line, so it is the last option on its line. The defaults are std::exit( EXIT_FAILURE ), USER and INFO.

The module

$MODULE name [HEADER ext | NOHEADER] [SOURCE ext | INLINE]
$NAMESPACE [name[::name]...]
$EXPORT [MACRO]
$INCLUDE "file" | <file> | MACRO
$USING name[::name]...
$IMPORT filename
  • $MODULE names the output files: name.hh and name.cpp by default (mstring -m overrides the name). NOHEADER writes the declarations into the source, which then compiles on its own. INLINE writes a header-only module: every definition is inline in the header, no source file (several INLINE modules can be included into one translation unit). NOSOURCE — the header without any definitions — is deprecated (a warning); use INLINE.
  • $NAMESPACE a::b puts everything into namespace a { namespace b {; an empty $NAMESPACE goes back to the global namespace. The last one wins.
  • $EXPORT MACRO puts a visibility macro before every generated class (class MACRO Name) and free-function declaration — for code built into a shared library with hidden visibility, or a DLL. $INCLUDE the header that defines the macro. $EXPORT alone removes it. The last one wins.
  • $INCLUDE adds an #include to the header.
  • $USING adds using name; inside the namespace of the header (the parameter types may rely on it).
  • $IMPORT parses another message file as if its lines were here — its settings and messages count. It is searched next to the importing file, then in the working folder, then in the -I folders. The name may be quoted.

Command line

mstring [-h] [-v] [-t] [-w] [-m <module>] [-O <folder>] [-I <folders>]... [<message file>]
Option
-m <module> the module name of the output files (overrides $MODULE)
-O <folder> write the output files into <folder> (it must exist)
-I <folders> $IMPORT search folders, :-separated; may be repeated
-t trace: print every $MESSAGE on stderr
-w do not print warnings
-v, -h version, help

Without a file (or with - ) the input is standard input. An output file is only rewritten when its content changes, so an unchanged message file does not trigger a rebuild. Errors are reported as file:line:column: error: ... (warnings as ... warning: ...); the exit code is 1 on any error, warnings do not change it.