nk 1.0.0
C++ Numerical Computing Runtime
Loading...
Searching...
No Matches
vfs.hpp
1// include/vfs.hpp
2//
3// Minimal filesystem abstraction that lets built-ins like csvread / csvwrite
4// route through either the real disk (native/CLI) or an IDE-supplied virtual
5// filesystem (IndexedDB in the browser, a mounted folder in Electron).
6//
7// Design notes:
8//
9// * Two implementations ship here: NativeFS (std::filesystem) and
10// CallbackFS (delegates to std::function hooks). The IDE installs a
11// CallbackFS on the Engine at startup and points its hooks at tempFS
12// or the Local Folder backend in JS.
13//
14// * The Engine owns a small registry ("native", "temporary", "local"),
15// plus a path resolver that reads NK_FS / NK_CWD (via env) and the
16// current script's origin.
17//
18// * A backend that isn't registered is simply absent — asking for
19// "temporary" on a CLI build throws. No silent fallback.
20//
21// * The interface is deliberately small. Phase 8 added listDir / stat /
22// mkdir / rmdir / unlink / tempArea — needed by the m-file resolver
23// and the dir / mkdir / rmdir / delete / tempdir / tempname / which /
24// exist('foo','file') builtins. All have safe default implementations
25// so older CallbackFS hosts that omit the new hooks still work for
26// legacy read/write traffic.
27
28#pragma once
29
30#include <cstdint>
31#include <functional>
32#include <memory>
33#include <optional>
34#include <stdexcept>
35#include <string>
36#include <vector>
37
38namespace nk {
39
40// ── DirEntry / FileStat — types returned by VFS introspection methods ──
41
43{
44 std::string name; // basename only (no path prefix)
45 bool isDirectory = false;
46};
47
49{
50 int64_t size = 0; // bytes (0 for directories)
51 int64_t mtime = 0; // last-modified time, seconds since epoch
52 // (0 if backend can't supply one — caller falls
53 // back to content hash for cache invalidation)
54 enum class Kind { File, Directory, Other };
55 Kind kind = Kind::Other;
56};
57
59{
60public:
61 virtual ~VirtualFS() = default;
62
63 virtual std::string readFile(const std::string &path) = 0;
64 virtual void writeFile(const std::string &path, const std::string &content) = 0;
65 virtual bool exists(const std::string &path) = 0;
66
67 // Binary-safe read — returns the raw file bytes with no text/UTF-8
68 // reinterpretation (the std::string is just a byte container). Needed
69 // by imread/audioread/etc. The default delegates to readFile, which is
70 // byte-accurate for native (ifstream) backends; the IDE's CallbackFS
71 // overrides it with a real binary channel (the text readFile path
72 // would UTF-8-mangle bytes ≥ 0x80 crossing JS↔WASM).
73 virtual std::string readFileBytes(const std::string &path) { return readFile(path); }
74
75 // Binary-safe write — the std::string is a raw byte container, written
76 // verbatim with no text/encoding translation. Needed by imwrite/
77 // audiowrite/etc. The default delegates to writeFile, which is
78 // byte-accurate for native (binary ofstream) backends; the IDE's
79 // CallbackFS overrides it with a real binary channel (the text
80 // writeFile path would UTF-8-mangle bytes ≥ 0x80 crossing JS↔WASM).
81 virtual void writeFileBytes(const std::string &path, const std::string &bytes)
82 {
83 writeFile(path, bytes);
84 }
85
86 virtual std::string name() const = 0;
87
88 // ── Phase 8 extension — directory ops + introspection ───────────
89 // Default implementations throw — backends opt in by overriding.
90 // Callers that want a graceful "unsupported" code-path catch
91 // std::runtime_error.
92
93 virtual std::vector<DirEntry> listDir(const std::string &path)
94 {
95 throw std::runtime_error(name() + ": listDir not supported");
96 }
97
98 // Returns std::nullopt when the path doesn't exist. Throws on
99 // backend errors (perm denied etc.).
100 virtual std::optional<FileStat> stat(const std::string &path)
101 {
102 return std::nullopt;
103 }
104
105 virtual void mkdir(const std::string &path)
106 {
107 throw std::runtime_error(name() + ": mkdir not supported");
108 }
109
110 virtual void rmdir(const std::string &path)
111 {
112 throw std::runtime_error(name() + ": rmdir not supported");
113 }
114
115 virtual void unlink(const std::string &path)
116 {
117 throw std::runtime_error(name() + ": unlink not supported");
118 }
119
120 // Returns the backend's temp area prefix (an absolute, possibly
121 // VFS-prefixed path). NativeFS returns std::filesystem::temp_directory_path().
122 // CallbackFS returns whatever the host provided, or "" if none.
123 virtual std::string tempArea()
124 {
125 return {};
126 }
127
128 // Backend's "current directory" — used by pwd/cd before the engine's
129 // own cwd is set. NativeFS returns std::filesystem::current_path().
130 // Hosted backends (CallbackFS) return whatever the host hooked in,
131 // or "" if none.
132 virtual std::string cwd()
133 {
134 return {};
135 }
136};
137
138// ── NativeFS — std::filesystem / std::ifstream / std::ofstream ──
139class NativeFS final : public VirtualFS
140{
141public:
142 std::string readFile(const std::string &path) override;
143 void writeFile(const std::string &path, const std::string &content) override;
144 bool exists(const std::string &path) override;
145 std::string name() const override { return "native"; }
146
147 // Phase 8 — directory + introspection
148 std::vector<DirEntry> listDir(const std::string &path) override;
149 std::optional<FileStat> stat(const std::string &path) override;
150 void mkdir(const std::string &path) override;
151 void rmdir(const std::string &path) override;
152 void unlink(const std::string &path) override;
153 std::string tempArea() override;
154 std::string cwd() override;
155};
156
157// ── CallbackFS — delegates to std::function hooks (WASM bridge) ──
158class CallbackFS final : public VirtualFS
159{
160public:
161 using ReadFunc = std::function<std::string(const std::string &)>;
162 using WriteFunc = std::function<void(const std::string &, const std::string &)>;
163 using ExistsFunc = std::function<bool(const std::string &)>;
164 using ReadBytesFunc = std::function<std::string(const std::string &)>;
165 using WriteBytesFunc = std::function<void(const std::string &, const std::string &)>;
166
167 // Phase 8 hooks. All optional — methods fall back to the
168 // VirtualFS-level defaults (throw or empty) when not supplied.
169 using ListDirFunc = std::function<std::vector<DirEntry>(const std::string &)>;
170 using StatFunc = std::function<std::optional<FileStat>(const std::string &)>;
171 using MkdirFunc = std::function<void(const std::string &)>;
172 using RmdirFunc = std::function<void(const std::string &)>;
173 using UnlinkFunc = std::function<void(const std::string &)>;
174 using TempAreaFunc = std::function<std::string()>;
175 using CwdFunc = std::function<std::string()>;
176
177 CallbackFS(std::string n, ReadFunc r, WriteFunc w, ExistsFunc e)
178 : name_(std::move(n)), read_(std::move(r)), write_(std::move(w)), exists_(std::move(e))
179 {}
180
181 std::string readFile(const std::string &path) override
182 {
183 if (!read_)
184 throw std::runtime_error(name_ + ": read hook not installed");
185 return read_(path);
186 }
187 void writeFile(const std::string &path, const std::string &content) override
188 {
189 if (!write_)
190 throw std::runtime_error(name_ + ": write hook not installed");
191 write_(path, content);
192 }
193 bool exists(const std::string &path) override { return exists_ ? exists_(path) : false; }
194 std::string name() const override { return name_; }
195
196 // Binary read hook — opt-in; falls back to the text readFile when not
197 // installed (so existing callers keep working).
198 void setReadBytes(ReadBytesFunc f) { readBytes_ = std::move(f); }
199 std::string readFileBytes(const std::string &path) override
200 {
201 if (readBytes_) return readBytes_(path);
202 return readFile(path);
203 }
204
205 // Binary write hook — opt-in; falls back to the text writeFile when not
206 // installed (so existing callers keep working).
207 void setWriteBytes(WriteBytesFunc f) { writeBytes_ = std::move(f); }
208 void writeFileBytes(const std::string &path, const std::string &bytes) override
209 {
210 if (writeBytes_) { writeBytes_(path, bytes); return; }
211 writeFile(path, bytes);
212 }
213
214 // Phase 8 — opt-in setters; absent hook → fall through to default behaviour.
215 void setListDir(ListDirFunc f) { listDir_ = std::move(f); }
216 void setStat(StatFunc f) { stat_ = std::move(f); }
217 void setMkdir(MkdirFunc f) { mkdir_ = std::move(f); }
218 void setRmdir(RmdirFunc f) { rmdir_ = std::move(f); }
219 void setUnlink(UnlinkFunc f) { unlink_ = std::move(f); }
220 void setTempArea(TempAreaFunc f) { tempArea_ = std::move(f); }
221 void setCwd(CwdFunc f) { cwd_ = std::move(f); }
222
223 std::vector<DirEntry> listDir(const std::string &path) override
224 {
225 if (listDir_) return listDir_(path);
226 return VirtualFS::listDir(path);
227 }
228 std::optional<FileStat> stat(const std::string &path) override
229 {
230 if (stat_) return stat_(path);
231 return std::nullopt;
232 }
233 void mkdir(const std::string &path) override
234 {
235 if (mkdir_) { mkdir_(path); return; }
236 VirtualFS::mkdir(path);
237 }
238 void rmdir(const std::string &path) override
239 {
240 if (rmdir_) { rmdir_(path); return; }
241 VirtualFS::rmdir(path);
242 }
243 void unlink(const std::string &path) override
244 {
245 if (unlink_) { unlink_(path); return; }
246 VirtualFS::unlink(path);
247 }
248 std::string tempArea() override
249 {
250 return tempArea_ ? tempArea_() : std::string{};
251 }
252 std::string cwd() override
253 {
254 return cwd_ ? cwd_() : std::string{};
255 }
256
257private:
258 std::string name_;
259 ReadFunc read_;
260 WriteFunc write_;
261 ExistsFunc exists_;
262 ReadBytesFunc readBytes_;
263 WriteBytesFunc writeBytes_;
264 ListDirFunc listDir_;
265 StatFunc stat_;
266 MkdirFunc mkdir_;
267 RmdirFunc rmdir_;
268 UnlinkFunc unlink_;
269 TempAreaFunc tempArea_;
270 CwdFunc cwd_;
271};
272
273} // namespace nk
Definition vfs.hpp:43