Code View

mstring / mstring-1.3.1.2 / src / MStringParser.hh
// 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 &currentLocale( );
    Message &currentMessage( );

    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;
};