// SPDX-License-Identifier: MIT
#pragma once
#include <memory>
#include <optional>
#include <stdexcept>
#include <vector>
#include "Comment.hh"
#include "Object.hh"
namespace fedem
{
namespace fson
{
// ─────────────────────────────────────────────────────────────────────
// Document (docs/notation/00005-fson_file.rrd)
//
// A complete FSON file: a root value surrounded by optional trivia.
// header : comments before the root value
// footer : comments after the root value
// The root is never null. Since v0.9.0.0 (JSON5 superset, decision
// A.2a) the root may be ANY value, not just an object — a bare array,
// string, number, boolean, or null is a valid FSON file. getRoot()
// is kept for source compatibility with object-rooted documents: it
// throws std::out_of_range when the root is not an Object. Callers
// that need to handle non-object roots should use getRootObject(),
// which returns nullptr instead of throwing.
// ─────────────────────────────────────────────────────────────────────
class Document final
{
public:
Document( );
// Throws std::out_of_range when the root is not an Object.
Object& getRoot( );
Object const& getRoot( ) const;
// Returns nullptr when the root is not an Object.
Object* getRootObject( ) noexcept;
Object const* getRootObject( ) const noexcept;
Value& getRootValue( ) noexcept;
Value const& getRootValue( ) const noexcept;
// Throws std::invalid_argument when root is null.
void setRoot( std::unique_ptr< Value > root );
CommentList& getHeaderComments( ) noexcept;
CommentList const& getHeaderComments( ) const noexcept;
CommentList& getFooterComments( ) noexcept;
CommentList const& getFooterComments( ) const noexcept;
// ── path API forwarders (see Object for path syntax) ────────────
// All of these operate on getRootObject(): when the root is not
// an Object, findPath()/setPath() return nullptr, containsPath()/
// removePath() return false, the typed getters return fallback,
// and atPath() throws std::out_of_range (same as a not-found path).
Value* findPath( std::string const& path ) noexcept;
Value const* findPath( std::string const& path ) const noexcept;
bool containsPath( std::string const& path ) const noexcept;
Value& atPath( std::string const& path );
Value const& atPath( std::string const& path ) const;
Value* setPath( std::string const& path, std::unique_ptr< Value > value );
bool removePath( std::string const& path ) noexcept;
// ── provenance forwarders (see Object) ──────────────────────────
// All operate on getRootObject(); when the root is not an object,
// isLocal() returns false, provenanceOf() returns nullopt, and
// provenanceMap() returns an empty vector.
bool isLocal( std::string const& path ) const noexcept;
std::optional< Object::Provenance >
provenanceOf( std::string const& path ) const;
std::vector< Object::Provenance > provenanceMap( ) const;
std::string getString ( std::string const& path, std::string const& fallback = std::string( ) ) const;
long long getInteger( std::string const& path, long long fallback = 0LL ) const noexcept;
double getDouble ( std::string const& path, double fallback = 0.0 ) const noexcept;
bool getBoolean( std::string const& path, bool fallback = false ) const noexcept;
private:
CommentList header;
std::unique_ptr< Value > root;
CommentList footer;
Document( Document const& ) = delete;
Document( Document&& ) = delete;
Document& operator=( Document const& ) = delete;
Document& operator=( Document&& ) = delete;
};
inline Document::Document( )
: root( std::make_unique< Object >( ) )
{
}
inline Object& Document::getRoot( )
{
Object* object = root->asObject( );
if( !object )
throw std::out_of_range( "fson::Document: root is not an object" );
return *object;
}
inline Object const& Document::getRoot( ) const
{
Object const* object = root->asObject( );
if( !object )
throw std::out_of_range( "fson::Document: root is not an object" );
return *object;
}
inline Object* Document::getRootObject( ) noexcept
{
return root->asObject( );
}
inline Object const* Document::getRootObject( ) const noexcept
{
return root->asObject( );
}
inline Value& Document::getRootValue( ) noexcept
{
return *root;
}
inline Value const& Document::getRootValue( ) const noexcept
{
return *root;
}
inline void Document::setRoot( std::unique_ptr< Value > root )
{
if( !root )
throw std::invalid_argument( "fson::Document: root must not be null" );
this->root = std::move( root );
}
inline CommentList& Document::getHeaderComments( ) noexcept
{
return header;
}
inline CommentList const& Document::getHeaderComments( ) const noexcept
{
return header;
}
inline CommentList& Document::getFooterComments( ) noexcept
{
return footer;
}
inline CommentList const& Document::getFooterComments( ) const noexcept
{
return footer;
}
inline Value* Document::findPath( std::string const& path ) noexcept
{
Object* object = getRootObject( );
return object ? object->findPath( path ) : nullptr;
}
inline Value const* Document::findPath( std::string const& path ) const noexcept
{
Object const* object = getRootObject( );
return object ? object->findPath( path ) : nullptr;
}
inline bool Document::containsPath( std::string const& path ) const noexcept
{
Object const* object = getRootObject( );
return object && object->containsPath( path );
}
inline Value& Document::atPath( std::string const& path )
{
Object* object = getRootObject( );
if( !object )
throw std::out_of_range( "fson::Document: root is not an object" );
return object->atPath( path );
}
inline Value const& Document::atPath( std::string const& path ) const
{
Object const* object = getRootObject( );
if( !object )
throw std::out_of_range( "fson::Document: root is not an object" );
return object->atPath( path );
}
inline Value* Document::setPath( std::string const& path,
std::unique_ptr< Value > value )
{
Object* object = getRootObject( );
return object ? object->setPath( path, std::move( value ) ) : nullptr;
}
inline bool Document::removePath( std::string const& path ) noexcept
{
Object* object = getRootObject( );
return object && object->removePath( path );
}
inline bool Document::isLocal( std::string const& path ) const noexcept
{
Object const* object = getRootObject( );
return object && object->isLocal( path );
}
inline std::optional< Object::Provenance >
Document::provenanceOf( std::string const& path ) const
{
Object const* object = getRootObject( );
return object ? object->provenanceOf( path ) : std::nullopt;
}
inline std::vector< Object::Provenance > Document::provenanceMap( ) const
{
Object const* object = getRootObject( );
return object ? object->provenanceMap( ) : std::vector< Object::Provenance >( );
}
inline std::string Document::getString( std::string const& path,
std::string const& fallback ) const
{
Object const* object = getRootObject( );
return object ? object->getString( path, fallback ) : fallback;
}
inline long long Document::getInteger( std::string const& path,
long long fallback ) const noexcept
{
Object const* object = getRootObject( );
return object ? object->getInteger( path, fallback ) : fallback;
}
inline double Document::getDouble( std::string const& path,
double fallback ) const noexcept
{
Object const* object = getRootObject( );
return object ? object->getDouble( path, fallback ) : fallback;
}
inline bool Document::getBoolean( std::string const& path,
bool fallback ) const noexcept
{
Object const* object = getRootObject( );
return object ? object->getBoolean( path, fallback ) : fallback;
}
} // end namespace fson
} // end namespace fedem