# 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 — `$STREAMABLE`, `$EXCEPTION`
```
$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 — `$STRING`, `$ERROR`, `$THROW`, `$SYSLOG`
```
$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.
### `$ERROR EXIT` and `$SYSLOG`
```
$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.