Code View

fson / source / fson-1.1.0.0 / libs / internal / sdk / fson / IncludeResolver.hh
// SPDX-License-Identifier: MIT
#pragma once

#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include "cparse/Parser.hh"

namespace fedem
{
  namespace fson
  {
    class Document;
    class Object;

    // ─────────────────────────────────────────────────────────────────────
    // IncludeResolver
    //
    // Resolves the "%include" directives of a freshly parsed Document into a
    // query-visible view, following the Topic 10 decisions:
    //
    //   • merge  ("%include \"base.fson\"")    — the target's root-object
    //     members are shallow-merged into the including object as defaults;
    //     a key already present in the host wins (host defaults over base).
    //     Only top-level keys are compared — nested objects are not deep-
    //     merged (a host "logging" wholly shadows the base "logging").
    //   • alias  ("%include \"base.fson\" as v") — the target's root value is
    //     bound under key "v" (open-root: the target need not be an object),
    //     unless the host already has an active member "v" (host wins).
    //
    // Merged/aliased members are appended to the host object with their
    // origin set to the target path, so:
    //   • the query API sees them (merge view), and
    //   • the writer skips them (they never round-trip into the host file),
    //     and
    //   • setPath can route a write back to the origin file.
    //
    // A disabled directive ("--%include") is preserved structurally but not
    // resolved (its defaults are not applied).
    //
    // Resolution is recursive: a target's own %include directives are
    // resolved before its members are merged in, so transitive defaults
    // work. Relative target paths are resolved against the directory of the
    // including file (not the process CWD), so a file tree is relocatable.
    //
    // Cycles (a → b → a) are reported as errors and broken (the offending
    // directive contributes nothing); no exception is thrown, matching the
    // collect-and-continue contract. Every file opened during resolution is
    // recorded in dependencies() so a caller (Topic 11) can watch them.
    // ─────────────────────────────────────────────────────────────────────
    class IncludeResolver final
    {
      public:
        IncludeResolver( );

        // Resolves every directive reachable from the document's root value.
        // 'basePath' is the path of the file the document was parsed from;
        // its parent directory anchors relative include targets. Errors are
        // appended to errors(); dependencies are recorded in dependencies().
        void resolve( Document& document, std::string const& basePath );

        std::vector< parser::ParseError > const& errors( ) const noexcept;

        // Every included file opened during resolution, in first-seen order,
        // NOT counting the host file itself. This is what LoadResult exposes
        // as `dependencies` for backward compatibility.
        std::vector< std::string > const&        dependencies( ) const noexcept;

        // The complete set of files needed to resolve the document — the
        // host file PLUS every included file — de-duplicated and sorted.
        // This is the set a reload watcher should observe: a change to any
        // of them can alter the resolved document.
        std::set< std::string > const&           dependencySet( ) const noexcept;

      private:
        std::vector< parser::ParseError >  collectedErrors;
        std::vector< std::string >         dependencyPaths;  // includes only
        std::set< std::string >            allFiles;         // host + includes
        std::vector< std::string >         activeStack;  // cycle detection

        // Resolves the directives of one object, whose own file is 'owner
        // directory' (used to anchor relative targets) and whose canonical
        // path is 'ownerCanonical' (used for cycle detection).
        void resolveObject( Object& object,
                            std::string const& ownerDirectory );

        // Loads, parses and fully resolves the target file, returning its
        // document. Returns nullptr on open/parse failure or on a cycle
        // (an error is recorded in either case). 'canonical' receives the
        // canonical path actually used.
        std::unique_ptr< Document > loadTarget( std::string const& target,
                                                std::string const& ownerDirectory,
                                                std::string& canonical );

        void recordDependency( std::string const& canonical );
    };
  }  // end namespace fson
}  // end namespace fedem