// SPDX-License-Identifier: MIT
#pragma once
#include <iosfwd>
#include <string>
#include <vector>
#include <cparse/Parser.hh>
#include "Module.hh"
#include "Settings.hh"
/// \file MStringParser.hh
/// \brief The message-definition grammar, a hand-written recursive-descent
/// parser on top of cparse (replaces the former flex/bison front end).
///
/// The input is line oriented. Outside a message only `$DIRECTIVE` lines,
/// blank lines and `#` comments may appear. After `$MESSAGE name` come the
/// `@ name type` parameter lines, then the texts: `[language charset]` set
/// headers, `'single quoted'`, `"double quoted"` and `|bar` lines. The next
/// directive ends the message. See docs/syntax.md for the full grammar.
/// \brief State shared by the parser of the main file and the parsers of
/// the files it `$IMPORT`s — an imported file changes the same
/// settings and adds to the same module, exactly as if it were inline.
struct ParseContext
{
explicit ParseContext( Module &module );
Module &module;
MessageSettings settings; ///< Settings the next `$MESSAGE` copies.
std::string language { "C" }; ///< `$LANGUAGE`.
std::string charset { "UTF-8" }; ///< `$CHARSET`.
std::vector<std::string> importFolders; ///< `-I` folders.
std::vector<std::string> importStack; ///< Canonical paths being parsed (recursion guard).
std::vector<fedem::parser::ParseError> diagnostics;
bool trace { false };
/// \brief Whether a diagnostic other than a warning was recorded.
bool failed( ) const;
};
/// \brief Whole-module warnings, after parsing: a message that lacks a text in
/// a language other messages have (it falls back to its default text),
/// and a parameter one language's text uses and another's does not.
void CheckTranslations( ParseContext &context );
class MStringParser
: public fedem::parser::Parser
{
public:
explicit MStringParser( ParseContext &context );
std::string getGrammarName( ) const noexcept override;
/// \brief Parse the message file \p path (as the user or `$IMPORT` named it).
/// \return `false` on error; the reason is in `ParseContext::diagnostics`.
bool parseFile( const std::string &path );
/// \brief Parse \p input (standard input), reported as \p name.
bool parseStream( const std::string &name, std::istream &input );
protected:
bool start( ) override;
bool skipComments( ) override;
private:
struct Location
{
unsigned long int line;
unsigned long int col;
};
/// Thrown by `error()` after it recorded the diagnostic; caught in `start()`.
struct Abort
{
};
// lexical helpers
char peek( );
char next( );
bool atEnd( );
bool atEndOfLine( );
Location here( ) const;
void skipBlanks( );
void skipEmptyLines( );
void expectEndOfLine( );
std::string readWhile( bool ( *accept )( char ) );
std::string readIdentifier( );
std::string requireIdentifier( const std::string &what );
std::string requireWord( bool ( *accept )( char ), const std::string &what );
std::string readQualifiedName( const std::string &what );
std::string readRestOfLine( );
std::string readKeyword( );
[[noreturn]] void error( const std::string &message, Location location );
[[noreturn]] void error( const std::string &message );
void warning( const std::string &message, Location location );
// directives
void directive( );
void languageDirective( );
void charsetDirective( );
void classDirective( ClassConcept &concept, const std::string &directive, bool exception );
void functionDirective( FunctionConcept &concept, const std::string &directive );
void errorDirective( );
void syslogDirective( );
void includeDirective( );
void importDirective( );
void usingDirective( );
void namespaceDirective( );
void moduleDirective( );
void messageDirective( );
// directive options
bool commonOption( Concept &concept, const std::string &option );
bool classOption( ClassConcept &concept, const std::string &option );
bool functionOption( FunctionConcept &concept, const std::string &option, Location location );
void unknownOption( const std::string &option, const std::string &directive, Location location );
// message body
void messageLine( );
void parameterLine( );
void setHeaderLine( );
void quotedTextLine( char quote );
void barTextLine( );
void escapeSequence( std::string &text );
void macro( LocaleMessage &locale, Location open );
LocaleMessage ¤tLocale( );
Message ¤tMessage( );
std::string resolveImport( const std::string &filename ) const;
void closeMessage( );
ParseContext &_Context;
std::string _DisplayName; ///< The file name diagnostics use.
char _Last { '\n' }; ///< The last consumed character.
bool _InMessage { false };
std::size_t _MessageIndex { 0 };
Location _MessageLocation { 0, 0 };
bool _TextStarted { false }; ///< A text or set header was seen: no more parameters.
std::string _MessageLanguage;
std::string _MessageCharset;
};