// SPDX-License-Identifier: MIT
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "cparse/Parser.hh"
#include "Comment.hh"
#include "Document.hh"
namespace fedem
{
namespace fson
{
class Array;
class Include;
class Object;
class Value;
// ─────────────────────────────────────────────────────────────────────
// FsonParser
//
// Recursive-descent FSON parser built on fedem::parser::Parser,
// implementing the grammar in docs/notation (00005-fson_file et al).
//
// Error strategy: collect-and-continue. Errors are recorded in the
// inherited error collector (errors()/hasErrors()) and parsing resumes
// at the next ',' / '}' / ']' synchronisation point. A document is
// produced even for faulty input; callers MUST check hasErrors()
// before trusting or re-writing it.
//
// Semantics implemented per project decisions:
// • duplicate ACTIVE member names are an error (ParseError::Kind::
// Faulty); the duplicate member is still stored structurally so no
// source information is lost
// • \uXXXX escapes are decoded to UTF-8 (surrogate pairs combined;
// invalid sequences are reported and replaced with U+FFFD)
// • "--" disables a member (quoted or bare key alike)
//
// JSON5-superset grammar (v0.9.0.0, decision A.2a and the JSON5
// acceptance list): the document root may be any value, not just an
// object; strings may be single- or double-quoted, with line
// continuations and \xNN/\v/\0 escapes (any other character after
// '\' is itself, a deliberate simplification vs. strict JSON5); a
// trailing comma is allowed in object and array bodies; numbers admit
// a leading '+', "0x"/"0X" hex literals, a leading or trailing '.',
// and the literal tokens Infinity/-Infinity/NaN; and whitespace
// additionally recognises NBSP, BOM, U+2028/2029, and the other
// Unicode Zs space separators. Nested block comments remain supported
// (the one deliberate exception to the JSON5-superset claim — JSON5
// itself forbids nesting).
//
// Comment attachment: leading comments before a member/element,
// trailing end-of-line comment on the same line as the value end,
// dangling comments before '}' / ']', document header/footer.
// ─────────────────────────────────────────────────────────────────────
class FsonParser final : public parser::Parser
{
public:
FsonParser( );
~FsonParser( ) override;
std::string getGrammarName( ) const noexcept override;
// Parses the given file. Returns nullptr only when the file could
// not be opened; otherwise returns the (possibly partial) document.
// Check hasErrors() afterwards.
std::unique_ptr< Document > parseFile( std::string const& filename );
protected:
bool start( ) override;
bool skipComments( ) override;
bool skipWhiteSpaces( ) override;
private:
struct PendingComment
{
Comment comment;
unsigned long int line;
};
std::unique_ptr< Document > document;
std::vector< PendingComment > pending;
// ── trivia management ───────────────────────────────────────────
CommentList takeAllPending( );
std::optional< Comment > takePendingOnLine( unsigned long int line );
void restorePending( CommentList comments );
// ── grammar rules ───────────────────────────────────────────────
bool parseObjectBody( Object& object );
bool parseArrayBody ( Array& array );
bool parseValue ( std::unique_ptr< Value >& result );
bool parseString ( std::string& result );
// Parses a `|`-block multi-line string (00040-string.rrd option 3).
// Precondition: the next character is '|'. Decodes into result.
bool parseMultilineString( std::string& result );
// Continuation lookahead for `|`-blocks: skips spaces/tabs and any
// /* */ block comments on the current physical line only.
void skipInlineBlanksAndBlockComments( );
bool parseName ( std::string& result );
bool parseNumber ( std::unique_ptr< Value >& result );
bool parseLiteralTail( std::string const& literal );
// Returns the appended member, or nullptr when the member could
// not be formed; memberLine receives the line of the value end.
Member* parseMember( Object& object, unsigned long int& memberLine );
// Parses a "%include" directive (the caller has confirmed a '%'
// lookahead). Returns the appended Include, or nullptr on error;
// entryLine receives the line of the directive end for trailing-
// comment attachment. Consumes any leading "--" disabled prefix.
Include* parseInclude( Object& object, unsigned long int& entryLine );
// Non-consuming lookahead: true when the next entry is a "%include"
// directive, i.e. a '%' optionally preceded by a "--" disabled prefix
// (and interleaving trivia). Restores the cursor before returning.
bool looksLikeInclude( );
// ── string helpers ──────────────────────────────────────────────
bool readHexQuad( unsigned long int& codeUnit );
void decodeUnicodeEscape( std::string& result );
bool readHexByte( unsigned long int& byteValue );
void decodeHexByteEscape( std::string& result );
static void appendUtf8( std::string& result, unsigned long int codePoint );
// Panic-mode recovery: consumes input until the next ',' / '}' /
// ']' at the current nesting depth (or EOF), skipping strings and
// comments. The synchronisation character itself is not consumed.
void recover( );
};
} // end namespace fson
} // end namespace fedem