nk 1.0.0
C++ Numerical Computing Runtime
Loading...
Searching...
No Matches
fs_context.hpp
1// fs/include/nk/fs/fs_context.hpp
2//
3// FsContext — the engine-agnostic filesystem session: the VirtualFS registry,
4// the script-origin stack, the current working directory, and the path
5// resolver that ties them together.
6//
7// Lives in fs/ (L0) so path resolution is reusable WITHOUT the Engine — the
8// core-free C++ API and the toolboxes resolve user paths through an FsContext
9// reference rather than through Engine. The layering guard pins fs:{fs}, so
10// this is STL-only: resolvePath() therefore throws std::runtime_error rather
11// than nk::Error (which lives in value/). The Engine owns an FsContext,
12// forwards its filesystem API to it, and rethrows resolvePath's
13// std::runtime_error as nk::Error so the MATLAB-visible error type is
14// unchanged for in-engine callers.
15#pragma once
16
17#include <nk/fs/branding.hpp> // envGet / envVarName (NK_FS / NK_CWD)
18#include <nk/fs/vfs.hpp> // VirtualFS
19
20#include <cstddef>
21#include <memory>
22#include <stdexcept>
23#include <string>
24#include <unordered_map>
25#include <vector>
26
27namespace nk {
28
30{
31public:
32 // ── Virtual filesystem registry ───────────────────────────
33 //
34 // Registry of named filesystems ("native", "temporary", "local", …).
35 // A native FS is pre-registered by the Engine constructor; the IDE
36 // installs "temporary" / "local" via CallbackFS hooks at startup.
37 void registerVirtualFS(std::unique_ptr<VirtualFS> fs);
38 VirtualFS *findVirtualFS(const std::string &name) const;
39
40 // ── Script-origin stack ───────────────────────────────────
41 //
42 // Each frame carries (fsName, scriptDir):
43 // * fsName — VFS that the script came from. Used by resolvePath
44 // as the implicit FS for relative paths inside the
45 // script.
46 // * scriptDir — directory containing the script. Used by the m-file
47 // resolver as the implicit search dir, so sibling .m
48 // files resolve without addpath.
49 // The 1-arg overload pushes an empty scriptDir — kept for callers that
50 // only know the FS (IDE running an unsaved buffer, tests).
51 void pushScriptOrigin(const std::string &fsName);
52 void pushScriptOrigin(const std::string &fsName, const std::string &scriptDir);
53 void popScriptOrigin();
54 const std::string *currentScriptOrigin() const; // fsName, may be null
55 const std::string *currentScriptDir() const; // dir, may be null/empty
56
57 // ── Current working directory ─────────────────────────────
58 //
59 // * `cd` / setCwd write here — canonical when non-empty.
60 // * pwd reports this (with backend-cwd fallback when empty).
61 // * resolvePath consults this first, then NK_CWD env var.
62 // The two-tier model lets hosts seed cwd via `setenv NK_CWD`
63 // without having to call setCwd, while still letting in-engine `cd`
64 // calls take precedence once they happen.
65 const std::string &cwd() const { return cwd_; }
66 void setCwd(const std::string &p) { cwd_ = p; }
67
68 // ── Path resolution ───────────────────────────────────────
69 //
70 // resolvePath() picks the right backend by this order of precedence:
71 // 1. explicit prefix in the path ("temporary:/foo", "local:/foo",
72 // "native:/foo") — wins over everything
73 // 2. env var NK_FS, if it names a registered backend
74 // 3. the current script's origin (set by the IDE before eval)
75 // 4. "native" if registered
76 // Relative paths are joined with `cwd_` when set; otherwise with
77 // NK_CWD as a host-runtime fallback. cwd_ takes precedence — once
78 // `cd` / setCwd writes to it, the env var is ignored.
80 {
81 VirtualFS *fs;
82 std::string path;
83 };
84 // Throws std::runtime_error when the requested filesystem is unknown or
85 // unavailable. (The Engine rethrows these as nk::Error.)
86 ResolvedPath resolvePath(const std::string &userPath) const;
87
88 // ── MATLAB-style file descriptor table ────────────────────
89 //
90 // fopen / fclose / fprintf(fid, …) machinery. File IDs 0, 1, 2 are
91 // reserved for stdin/stdout/stderr (the fprintf builtin routes 1/2 to the
92 // engine output sink); user files get 3, 4, … from nextFid_. For 'r' the
93 // file is read into `buffer` on open and `cursor` advances as reading
94 // builtins consume it; for 'w' `buffer` accumulates fprintf output and is
95 // flushed via VirtualFS::writeFile on fclose; 'a' is 'w' seeded with the
96 // existing content. All writes are buffered in memory until close — the
97 // sync-mirror VirtualFS can't do partial writes efficiently, and MATLAB
98 // only guarantees visibility on close. Lives here (fs/, L0) so the
99 // stateful fopen-family is Engine-free; the Engine forwards to it.
100 struct OpenFile
101 {
102 std::string path;
103 std::string mode;
104 VirtualFS *fs = nullptr;
105 std::string buffer;
106 std::size_t cursor = 0;
107 // forRead and forWrite can BOTH be true ('r+'/'w+'/'a+'). appendOnly
108 // snaps the cursor to end-of-buffer before each write ('a'/'a+').
109 bool forRead = false;
110 bool forWrite = false;
111 bool appendOnly = false;
112 // Last soft-failure text for ferror(fid); cleared by ferror(fid,'clear').
113 std::string lastError;
114 };
115
116 int openFile(const std::string &userPath, const std::string &mode);
117 bool closeFile(int fid);
118 void closeAllFiles();
119 OpenFile *findFile(int fid);
120 // Sorted list of user-opened fids (>= 3). Powers `fopen('all')`.
121 std::vector<int> openFileIds() const;
122 // Error text from the most recent openFile() call ([fid, errmsg] = fopen).
123 const std::string &lastFopenError() const { return lastFopenError_; }
124
125private:
126 std::unordered_map<std::string, std::unique_ptr<VirtualFS>> virtualFs_;
127 struct ScriptOriginEntry
128 {
129 std::string fsName;
130 std::string scriptDir;
131 };
132 std::vector<ScriptOriginEntry> scriptOriginStack_;
133 std::string cwd_;
134
135 // open-file table (see OpenFile above) — the fopen-family state, moved
136 // out of the Engine so the stateful file builtins are Engine-free.
137 std::unordered_map<int, OpenFile> openFiles_;
138 int nextFid_ = 3;
139 std::string lastFopenError_;
140};
141
142} // namespace nk