@sema-lang/sema 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -0
- package/dist/backends/indexed-db.d.ts +62 -0
- package/dist/backends/indexed-db.js +151 -0
- package/dist/backends/local-storage.d.ts +20 -0
- package/dist/backends/local-storage.js +19 -0
- package/dist/backends/memory.d.ts +21 -0
- package/dist/backends/memory.js +20 -0
- package/dist/backends/session-storage.d.ts +24 -0
- package/dist/backends/session-storage.js +23 -0
- package/dist/backends/web-storage.d.ts +32 -0
- package/dist/backends/web-storage.js +103 -0
- package/dist/index.d.ts +299 -0
- package/dist/index.js +324 -0
- package/dist/vfs.d.ts +53 -0
- package/dist/vfs.js +1 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sema-lang/sema — Sema Lisp interpreter for JavaScript
|
|
3
|
+
*
|
|
4
|
+
* A client-side scripting engine powered by WebAssembly.
|
|
5
|
+
* Embed Sema as a scripting language in your web applications.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```js
|
|
9
|
+
* import { SemaInterpreter } from "@sema-lang/sema";
|
|
10
|
+
*
|
|
11
|
+
* const sema = await SemaInterpreter.create();
|
|
12
|
+
* const result = sema.evalStr("(+ 1 2 3)");
|
|
13
|
+
* console.log(result.value); // "6"
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type { VFSBackend } from "./vfs.js";
|
|
17
|
+
/** Result of evaluating Sema code. */
|
|
18
|
+
export interface EvalResult {
|
|
19
|
+
/** The string representation of the result value, or null if the expression returned nil. */
|
|
20
|
+
value: string | null;
|
|
21
|
+
/** Lines printed to stdout during evaluation (via `print`, `println`, `display`). */
|
|
22
|
+
output: string[];
|
|
23
|
+
/** Error message if evaluation failed, or null on success. */
|
|
24
|
+
error: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** Options for creating a SemaInterpreter. */
|
|
27
|
+
export interface InterpreterOptions {
|
|
28
|
+
/**
|
|
29
|
+
* URL to the `.wasm` binary. Required when loading from a CDN or
|
|
30
|
+
* when the default resolution doesn't work.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```js
|
|
34
|
+
* const sema = await SemaInterpreter.create({
|
|
35
|
+
* wasmUrl: "https://cdn.jsdelivr.net/npm/@sema-lang/sema-wasm@1.9.0/sema_wasm_bg.wasm"
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
wasmUrl?: string | URL;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to include the standard library. Default: `true`.
|
|
42
|
+
* Set to `false` for a minimal interpreter with only special forms.
|
|
43
|
+
*/
|
|
44
|
+
stdlib?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Array of capabilities to deny. Available capabilities:
|
|
47
|
+
* - `"network"` — deny HTTP functions (http/get, http/post, etc.)
|
|
48
|
+
* - `"fs-read"` — deny VFS read operations
|
|
49
|
+
* - `"fs-write"` — deny VFS write operations
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```js
|
|
53
|
+
* const sema = await SemaInterpreter.create({ deny: ["network"] });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
deny?: Array<"network" | "fs-read" | "fs-write">;
|
|
57
|
+
/**
|
|
58
|
+
* Optional VFS backend for persisting files across page reloads.
|
|
59
|
+
*
|
|
60
|
+
* Built-in backends:
|
|
61
|
+
* - {@link MemoryBackend} — ephemeral, no persistence (default behavior)
|
|
62
|
+
* - {@link LocalStorageBackend} — persist to localStorage (~5 MB limit)
|
|
63
|
+
* - {@link SessionStorageBackend} — persist within the tab session
|
|
64
|
+
* - {@link IndexedDBBackend} — persist to IndexedDB (recommended for production)
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```js
|
|
68
|
+
* import { SemaInterpreter, IndexedDBBackend } from "@sema-lang/sema";
|
|
69
|
+
* const sema = await SemaInterpreter.create({
|
|
70
|
+
* vfs: new IndexedDBBackend({ namespace: "my-project" }),
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
vfs?: VFSBackend;
|
|
75
|
+
}
|
|
76
|
+
/** Virtual filesystem usage statistics. */
|
|
77
|
+
export interface VFSStats {
|
|
78
|
+
/** Number of files currently in the VFS. */
|
|
79
|
+
files: number;
|
|
80
|
+
/** Total bytes used. */
|
|
81
|
+
bytes: number;
|
|
82
|
+
/** Maximum number of files allowed. */
|
|
83
|
+
maxFiles: number;
|
|
84
|
+
/** Maximum total bytes allowed. */
|
|
85
|
+
maxBytes: number;
|
|
86
|
+
/** Maximum bytes per individual file. */
|
|
87
|
+
maxFileBytes: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A Sema Lisp interpreter instance.
|
|
91
|
+
*
|
|
92
|
+
* Each interpreter has its own isolated environment — variables defined in one
|
|
93
|
+
* interpreter are not visible in another.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```js
|
|
97
|
+
* const sema = await SemaInterpreter.create();
|
|
98
|
+
*
|
|
99
|
+
* // Evaluate expressions
|
|
100
|
+
* sema.evalStr("(define x 42)");
|
|
101
|
+
* const r = sema.evalStr("(* x x)");
|
|
102
|
+
* console.log(r.value); // "1764"
|
|
103
|
+
*
|
|
104
|
+
* // Register JS functions
|
|
105
|
+
* sema.registerFunction("greet", (name) => `Hello, ${name}!`);
|
|
106
|
+
* sema.evalStr('(greet "world")'); // => "Hello, world!"
|
|
107
|
+
*
|
|
108
|
+
* // Preload modules
|
|
109
|
+
* sema.preloadModule("utils", "(define (double x) (* x 2))");
|
|
110
|
+
* sema.evalStr('(import "utils")');
|
|
111
|
+
* sema.evalStr("(double 21)"); // => "42"
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare class SemaInterpreter {
|
|
115
|
+
private _inner;
|
|
116
|
+
private _vfsBackend;
|
|
117
|
+
private constructor();
|
|
118
|
+
/**
|
|
119
|
+
* Create a new SemaInterpreter instance.
|
|
120
|
+
*
|
|
121
|
+
* This initializes the WASM module (once, globally) and creates an interpreter.
|
|
122
|
+
* The WASM module is cached — subsequent calls are fast.
|
|
123
|
+
*
|
|
124
|
+
* @param opts - Configuration options
|
|
125
|
+
* @returns A ready-to-use SemaInterpreter
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```js
|
|
129
|
+
* // Default: full standard library
|
|
130
|
+
* const sema = await SemaInterpreter.create();
|
|
131
|
+
*
|
|
132
|
+
* // Minimal: only special forms
|
|
133
|
+
* const minimal = await SemaInterpreter.create({ stdlib: false });
|
|
134
|
+
*
|
|
135
|
+
* // From CDN
|
|
136
|
+
* const cdn = await SemaInterpreter.create({
|
|
137
|
+
* wasmUrl: "https://cdn.jsdelivr.net/npm/@sema-lang/sema-wasm@1.9.0/sema_wasm_bg.wasm"
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
static create(opts?: InterpreterOptions): Promise<SemaInterpreter>;
|
|
142
|
+
private _vfsHost;
|
|
143
|
+
/**
|
|
144
|
+
* Evaluate a string of Sema code.
|
|
145
|
+
*
|
|
146
|
+
* Definitions persist across calls — you can `define` a function in one call
|
|
147
|
+
* and use it in the next.
|
|
148
|
+
*
|
|
149
|
+
* @param code - Sema source code to evaluate
|
|
150
|
+
* @returns The evaluation result
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```js
|
|
154
|
+
* const r = sema.evalStr("(map (lambda (x) (* x x)) '(1 2 3 4 5))");
|
|
155
|
+
* console.log(r.value); // "(1 4 9 16 25)"
|
|
156
|
+
* console.log(r.output); // [] (no print statements)
|
|
157
|
+
* console.log(r.error); // null (no error)
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
evalStr(code: string): EvalResult;
|
|
161
|
+
/**
|
|
162
|
+
* Evaluate code with async HTTP support.
|
|
163
|
+
*
|
|
164
|
+
* Use this when your Sema code uses `http/get` or other network functions.
|
|
165
|
+
* The interpreter will automatically handle the async fetch operations.
|
|
166
|
+
*
|
|
167
|
+
* @param code - Sema source code to evaluate
|
|
168
|
+
* @returns The evaluation result
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```js
|
|
172
|
+
* const r = await sema.evalStrAsync('(http/get "https://api.example.com/data")');
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
evalStrAsync(code: string): Promise<EvalResult>;
|
|
176
|
+
/**
|
|
177
|
+
* Register a JavaScript function that can be called from Sema code.
|
|
178
|
+
*
|
|
179
|
+
* Arguments are passed as native JavaScript values (numbers, strings,
|
|
180
|
+
* booleans, null, arrays, objects). The return value is converted back
|
|
181
|
+
* to a Sema value.
|
|
182
|
+
*
|
|
183
|
+
* @param name - The function name in Sema (e.g., "my-fn")
|
|
184
|
+
* @param fn - The JavaScript function to call
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```js
|
|
188
|
+
* // Simple function — args are native JS values
|
|
189
|
+
* sema.registerFunction("add1", (n) => n + 1);
|
|
190
|
+
*
|
|
191
|
+
* // Multiple args
|
|
192
|
+
* sema.registerFunction("greet", (greeting, name) => `${greeting}, ${name}!`);
|
|
193
|
+
*
|
|
194
|
+
* // Returning structured data (objects become Sema maps)
|
|
195
|
+
* sema.registerFunction("get-user", (id) => ({ name: "Alice", age: 30 }));
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
registerFunction(name: string, fn: (...args: any[]) => any): void;
|
|
199
|
+
/**
|
|
200
|
+
* Preload a virtual module so that `(import "name")` works without a file.
|
|
201
|
+
*
|
|
202
|
+
* @param name - The module name (used in `(import "name")`)
|
|
203
|
+
* @param source - Sema source code defining the module's exports
|
|
204
|
+
* @throws If the module source has syntax or evaluation errors
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```js
|
|
208
|
+
* sema.preloadModule("utils", `
|
|
209
|
+
* (define (double x) (* x 2))
|
|
210
|
+
* (define pi 3.14159)
|
|
211
|
+
* `);
|
|
212
|
+
*
|
|
213
|
+
* sema.evalStr('(import "utils")');
|
|
214
|
+
* sema.evalStr("(double pi)"); // => "6.28318"
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
preloadModule(name: string, source: string): void;
|
|
218
|
+
/**
|
|
219
|
+
* Read a file from the virtual filesystem.
|
|
220
|
+
* @returns The file contents, or null if the file doesn't exist.
|
|
221
|
+
*/
|
|
222
|
+
readFile(path: string): string | null;
|
|
223
|
+
/**
|
|
224
|
+
* Write a file to the virtual filesystem.
|
|
225
|
+
* Quotas: 1 MB per file, 16 MB total, 256 files max.
|
|
226
|
+
* @throws If a VFS quota is exceeded.
|
|
227
|
+
*/
|
|
228
|
+
writeFile(path: string, content: string): void;
|
|
229
|
+
/**
|
|
230
|
+
* Delete a file from the virtual filesystem.
|
|
231
|
+
* @returns true if the file existed and was deleted.
|
|
232
|
+
*/
|
|
233
|
+
deleteFile(path: string): boolean;
|
|
234
|
+
/**
|
|
235
|
+
* List entries in a VFS directory.
|
|
236
|
+
* @returns Array of file and directory names (not full paths).
|
|
237
|
+
*/
|
|
238
|
+
listFiles(dir?: string): string[];
|
|
239
|
+
/**
|
|
240
|
+
* Check if a path exists in the VFS (file or directory).
|
|
241
|
+
*/
|
|
242
|
+
fileExists(path: string): boolean;
|
|
243
|
+
/**
|
|
244
|
+
* Create a directory (and parent directories) in the VFS.
|
|
245
|
+
*/
|
|
246
|
+
mkdir(path: string): void;
|
|
247
|
+
/**
|
|
248
|
+
* Check if a path is a directory in the VFS.
|
|
249
|
+
*/
|
|
250
|
+
isDirectory(path: string): boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Get VFS usage statistics (file count, bytes used, quotas).
|
|
253
|
+
*/
|
|
254
|
+
vfsStats(): VFSStats;
|
|
255
|
+
/**
|
|
256
|
+
* Clear all files and directories from the VFS.
|
|
257
|
+
*/
|
|
258
|
+
resetVFS(): void;
|
|
259
|
+
/**
|
|
260
|
+
* Persist VFS changes to the configured backend.
|
|
261
|
+
*
|
|
262
|
+
* No-op if no VFS backend was provided during creation.
|
|
263
|
+
* Call this after eval to save files.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```js
|
|
267
|
+
* await sema.evalStrAsync(code);
|
|
268
|
+
* await sema.flushVFS(); // persist to localStorage/IndexedDB/etc.
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
flushVFS(): Promise<void>;
|
|
272
|
+
/**
|
|
273
|
+
* Clear the VFS and the persistent backend (if configured).
|
|
274
|
+
*/
|
|
275
|
+
resetVFSAndBackend(): Promise<void>;
|
|
276
|
+
/**
|
|
277
|
+
* Get the Sema interpreter version.
|
|
278
|
+
*
|
|
279
|
+
* @returns Version string (e.g., "1.9.0")
|
|
280
|
+
*/
|
|
281
|
+
version(): string;
|
|
282
|
+
/**
|
|
283
|
+
* Free the interpreter's WASM memory.
|
|
284
|
+
*
|
|
285
|
+
* Call this when you're done with the interpreter to release resources.
|
|
286
|
+
* The interpreter cannot be used after calling this method.
|
|
287
|
+
*/
|
|
288
|
+
dispose(): void;
|
|
289
|
+
}
|
|
290
|
+
export type { VFSBackend, VFSHost } from "./vfs.js";
|
|
291
|
+
export { MemoryBackend } from "./backends/memory.js";
|
|
292
|
+
export { LocalStorageBackend } from "./backends/local-storage.js";
|
|
293
|
+
export type { LocalStorageBackendOptions } from "./backends/local-storage.js";
|
|
294
|
+
export { SessionStorageBackend } from "./backends/session-storage.js";
|
|
295
|
+
export type { SessionStorageBackendOptions } from "./backends/session-storage.js";
|
|
296
|
+
export { IndexedDBBackend } from "./backends/indexed-db.js";
|
|
297
|
+
export type { IndexedDBBackendOptions } from "./backends/indexed-db.js";
|
|
298
|
+
/** @deprecated Use `SemaInterpreter` instead. */
|
|
299
|
+
export { SemaInterpreter as Interpreter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sema-lang/sema — Sema Lisp interpreter for JavaScript
|
|
3
|
+
*
|
|
4
|
+
* A client-side scripting engine powered by WebAssembly.
|
|
5
|
+
* Embed Sema as a scripting language in your web applications.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```js
|
|
9
|
+
* import { SemaInterpreter } from "@sema-lang/sema";
|
|
10
|
+
*
|
|
11
|
+
* const sema = await SemaInterpreter.create();
|
|
12
|
+
* const result = sema.evalStr("(+ 1 2 3)");
|
|
13
|
+
* console.log(result.value); // "6"
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
// Internal state
|
|
17
|
+
let _init = null;
|
|
18
|
+
let _SemaInterpreter = null;
|
|
19
|
+
let _initialized = false;
|
|
20
|
+
let _initPromise = null;
|
|
21
|
+
async function ensureInit(wasmUrl) {
|
|
22
|
+
if (_initialized)
|
|
23
|
+
return;
|
|
24
|
+
if (_initPromise) {
|
|
25
|
+
await _initPromise;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
_initPromise = (async () => {
|
|
29
|
+
// Dynamic import so bundlers can tree-shake when not used
|
|
30
|
+
const mod = await import("@sema-lang/sema-wasm");
|
|
31
|
+
_init = mod.default;
|
|
32
|
+
_SemaInterpreter = mod.SemaInterpreter;
|
|
33
|
+
if (wasmUrl) {
|
|
34
|
+
await _init(wasmUrl);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
await _init();
|
|
38
|
+
}
|
|
39
|
+
_initialized = true;
|
|
40
|
+
})();
|
|
41
|
+
await _initPromise;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A Sema Lisp interpreter instance.
|
|
45
|
+
*
|
|
46
|
+
* Each interpreter has its own isolated environment — variables defined in one
|
|
47
|
+
* interpreter are not visible in another.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```js
|
|
51
|
+
* const sema = await SemaInterpreter.create();
|
|
52
|
+
*
|
|
53
|
+
* // Evaluate expressions
|
|
54
|
+
* sema.evalStr("(define x 42)");
|
|
55
|
+
* const r = sema.evalStr("(* x x)");
|
|
56
|
+
* console.log(r.value); // "1764"
|
|
57
|
+
*
|
|
58
|
+
* // Register JS functions
|
|
59
|
+
* sema.registerFunction("greet", (name) => `Hello, ${name}!`);
|
|
60
|
+
* sema.evalStr('(greet "world")'); // => "Hello, world!"
|
|
61
|
+
*
|
|
62
|
+
* // Preload modules
|
|
63
|
+
* sema.preloadModule("utils", "(define (double x) (* x 2))");
|
|
64
|
+
* sema.evalStr('(import "utils")');
|
|
65
|
+
* sema.evalStr("(double 21)"); // => "42"
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export class SemaInterpreter {
|
|
69
|
+
constructor(inner, vfsBackend = null) {
|
|
70
|
+
this._inner = inner;
|
|
71
|
+
this._vfsBackend = vfsBackend;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a new SemaInterpreter instance.
|
|
75
|
+
*
|
|
76
|
+
* This initializes the WASM module (once, globally) and creates an interpreter.
|
|
77
|
+
* The WASM module is cached — subsequent calls are fast.
|
|
78
|
+
*
|
|
79
|
+
* @param opts - Configuration options
|
|
80
|
+
* @returns A ready-to-use SemaInterpreter
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```js
|
|
84
|
+
* // Default: full standard library
|
|
85
|
+
* const sema = await SemaInterpreter.create();
|
|
86
|
+
*
|
|
87
|
+
* // Minimal: only special forms
|
|
88
|
+
* const minimal = await SemaInterpreter.create({ stdlib: false });
|
|
89
|
+
*
|
|
90
|
+
* // From CDN
|
|
91
|
+
* const cdn = await SemaInterpreter.create({
|
|
92
|
+
* wasmUrl: "https://cdn.jsdelivr.net/npm/@sema-lang/sema-wasm@1.9.0/sema_wasm_bg.wasm"
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
static async create(opts) {
|
|
97
|
+
await ensureInit(opts?.wasmUrl);
|
|
98
|
+
const needsOptions = opts?.stdlib === false || (opts?.deny && opts.deny.length > 0);
|
|
99
|
+
let inner;
|
|
100
|
+
if (needsOptions) {
|
|
101
|
+
inner = _SemaInterpreter.createWithOptions({
|
|
102
|
+
stdlib: opts?.stdlib ?? true,
|
|
103
|
+
deny: opts?.deny,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
inner = new _SemaInterpreter();
|
|
108
|
+
}
|
|
109
|
+
const interp = new SemaInterpreter(inner, opts?.vfs ?? null);
|
|
110
|
+
if (interp._vfsBackend) {
|
|
111
|
+
await interp._vfsBackend.init?.();
|
|
112
|
+
await interp._vfsBackend.hydrate(interp._vfsHost());
|
|
113
|
+
}
|
|
114
|
+
return interp;
|
|
115
|
+
}
|
|
116
|
+
_vfsHost() {
|
|
117
|
+
return {
|
|
118
|
+
readFile: (p) => this.readFile(p),
|
|
119
|
+
writeFile: (p, c) => this.writeFile(p, c),
|
|
120
|
+
deleteFile: (p) => this.deleteFile(p),
|
|
121
|
+
mkdir: (p) => this.mkdir(p),
|
|
122
|
+
listFiles: (d) => this.listFiles(d),
|
|
123
|
+
fileExists: (p) => this.fileExists(p),
|
|
124
|
+
isDirectory: (p) => this.isDirectory(p),
|
|
125
|
+
resetVFS: () => this.resetVFS(),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Evaluate a string of Sema code.
|
|
130
|
+
*
|
|
131
|
+
* Definitions persist across calls — you can `define` a function in one call
|
|
132
|
+
* and use it in the next.
|
|
133
|
+
*
|
|
134
|
+
* @param code - Sema source code to evaluate
|
|
135
|
+
* @returns The evaluation result
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```js
|
|
139
|
+
* const r = sema.evalStr("(map (lambda (x) (* x x)) '(1 2 3 4 5))");
|
|
140
|
+
* console.log(r.value); // "(1 4 9 16 25)"
|
|
141
|
+
* console.log(r.output); // [] (no print statements)
|
|
142
|
+
* console.log(r.error); // null (no error)
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
evalStr(code) {
|
|
146
|
+
return this._inner.evalGlobal(code);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Evaluate code with async HTTP support.
|
|
150
|
+
*
|
|
151
|
+
* Use this when your Sema code uses `http/get` or other network functions.
|
|
152
|
+
* The interpreter will automatically handle the async fetch operations.
|
|
153
|
+
*
|
|
154
|
+
* @param code - Sema source code to evaluate
|
|
155
|
+
* @returns The evaluation result
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```js
|
|
159
|
+
* const r = await sema.evalStrAsync('(http/get "https://api.example.com/data")');
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async evalStrAsync(code) {
|
|
163
|
+
return (await this._inner.evalAsync(code));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Register a JavaScript function that can be called from Sema code.
|
|
167
|
+
*
|
|
168
|
+
* Arguments are passed as native JavaScript values (numbers, strings,
|
|
169
|
+
* booleans, null, arrays, objects). The return value is converted back
|
|
170
|
+
* to a Sema value.
|
|
171
|
+
*
|
|
172
|
+
* @param name - The function name in Sema (e.g., "my-fn")
|
|
173
|
+
* @param fn - The JavaScript function to call
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```js
|
|
177
|
+
* // Simple function — args are native JS values
|
|
178
|
+
* sema.registerFunction("add1", (n) => n + 1);
|
|
179
|
+
*
|
|
180
|
+
* // Multiple args
|
|
181
|
+
* sema.registerFunction("greet", (greeting, name) => `${greeting}, ${name}!`);
|
|
182
|
+
*
|
|
183
|
+
* // Returning structured data (objects become Sema maps)
|
|
184
|
+
* sema.registerFunction("get-user", (id) => ({ name: "Alice", age: 30 }));
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
registerFunction(name, fn) {
|
|
188
|
+
this._inner.registerFunction(name, fn);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Preload a virtual module so that `(import "name")` works without a file.
|
|
192
|
+
*
|
|
193
|
+
* @param name - The module name (used in `(import "name")`)
|
|
194
|
+
* @param source - Sema source code defining the module's exports
|
|
195
|
+
* @throws If the module source has syntax or evaluation errors
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```js
|
|
199
|
+
* sema.preloadModule("utils", `
|
|
200
|
+
* (define (double x) (* x 2))
|
|
201
|
+
* (define pi 3.14159)
|
|
202
|
+
* `);
|
|
203
|
+
*
|
|
204
|
+
* sema.evalStr('(import "utils")');
|
|
205
|
+
* sema.evalStr("(double pi)"); // => "6.28318"
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
preloadModule(name, source) {
|
|
209
|
+
const result = this._inner.preloadModule(name, source);
|
|
210
|
+
if (!result.ok) {
|
|
211
|
+
throw new Error(`Failed to preload module "${name}": ${result.error}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Read a file from the virtual filesystem.
|
|
216
|
+
* @returns The file contents, or null if the file doesn't exist.
|
|
217
|
+
*/
|
|
218
|
+
readFile(path) {
|
|
219
|
+
const result = this._inner.readFile(path);
|
|
220
|
+
return result === null || result === undefined ? null : result;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Write a file to the virtual filesystem.
|
|
224
|
+
* Quotas: 1 MB per file, 16 MB total, 256 files max.
|
|
225
|
+
* @throws If a VFS quota is exceeded.
|
|
226
|
+
*/
|
|
227
|
+
writeFile(path, content) {
|
|
228
|
+
const error = this._inner.writeFile(path, content);
|
|
229
|
+
if (typeof error === "string") {
|
|
230
|
+
throw new Error(error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Delete a file from the virtual filesystem.
|
|
235
|
+
* @returns true if the file existed and was deleted.
|
|
236
|
+
*/
|
|
237
|
+
deleteFile(path) {
|
|
238
|
+
return this._inner.deleteFile(path);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* List entries in a VFS directory.
|
|
242
|
+
* @returns Array of file and directory names (not full paths).
|
|
243
|
+
*/
|
|
244
|
+
listFiles(dir) {
|
|
245
|
+
return Array.from(this._inner.listFiles(dir ?? "/"));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if a path exists in the VFS (file or directory).
|
|
249
|
+
*/
|
|
250
|
+
fileExists(path) {
|
|
251
|
+
return this._inner.fileExists(path);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Create a directory (and parent directories) in the VFS.
|
|
255
|
+
*/
|
|
256
|
+
mkdir(path) {
|
|
257
|
+
this._inner.mkdir(path);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if a path is a directory in the VFS.
|
|
261
|
+
*/
|
|
262
|
+
isDirectory(path) {
|
|
263
|
+
return this._inner.isDirectory(path);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get VFS usage statistics (file count, bytes used, quotas).
|
|
267
|
+
*/
|
|
268
|
+
vfsStats() {
|
|
269
|
+
return this._inner.vfsStats();
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Clear all files and directories from the VFS.
|
|
273
|
+
*/
|
|
274
|
+
resetVFS() {
|
|
275
|
+
this._inner.resetVFS();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Persist VFS changes to the configured backend.
|
|
279
|
+
*
|
|
280
|
+
* No-op if no VFS backend was provided during creation.
|
|
281
|
+
* Call this after eval to save files.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```js
|
|
285
|
+
* await sema.evalStrAsync(code);
|
|
286
|
+
* await sema.flushVFS(); // persist to localStorage/IndexedDB/etc.
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
async flushVFS() {
|
|
290
|
+
if (this._vfsBackend) {
|
|
291
|
+
await this._vfsBackend.flush(this._vfsHost());
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Clear the VFS and the persistent backend (if configured).
|
|
296
|
+
*/
|
|
297
|
+
async resetVFSAndBackend() {
|
|
298
|
+
this._inner.resetVFS();
|
|
299
|
+
await this._vfsBackend?.reset?.();
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get the Sema interpreter version.
|
|
303
|
+
*
|
|
304
|
+
* @returns Version string (e.g., "1.9.0")
|
|
305
|
+
*/
|
|
306
|
+
version() {
|
|
307
|
+
return this._inner.version();
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Free the interpreter's WASM memory.
|
|
311
|
+
*
|
|
312
|
+
* Call this when you're done with the interpreter to release resources.
|
|
313
|
+
* The interpreter cannot be used after calling this method.
|
|
314
|
+
*/
|
|
315
|
+
dispose() {
|
|
316
|
+
this._inner.free();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
export { MemoryBackend } from "./backends/memory.js";
|
|
320
|
+
export { LocalStorageBackend } from "./backends/local-storage.js";
|
|
321
|
+
export { SessionStorageBackend } from "./backends/session-storage.js";
|
|
322
|
+
export { IndexedDBBackend } from "./backends/indexed-db.js";
|
|
323
|
+
/** @deprecated Use `SemaInterpreter` instead. */
|
|
324
|
+
export { SemaInterpreter as Interpreter };
|
package/dist/vfs.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host bridge — methods the backend calls to read/write the in-memory WASM VFS.
|
|
3
|
+
* Provided by SemaInterpreter, not implemented by users.
|
|
4
|
+
*/
|
|
5
|
+
export interface VFSHost {
|
|
6
|
+
readFile(path: string): string | null;
|
|
7
|
+
writeFile(path: string, content: string): void;
|
|
8
|
+
deleteFile(path: string): boolean;
|
|
9
|
+
mkdir(path: string): void;
|
|
10
|
+
listFiles(dir: string): string[];
|
|
11
|
+
fileExists(path: string): boolean;
|
|
12
|
+
isDirectory(path: string): boolean;
|
|
13
|
+
resetVFS(): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Pluggable VFS storage backend.
|
|
17
|
+
*
|
|
18
|
+
* Implement this interface to persist VFS state across page reloads.
|
|
19
|
+
* The backend runs outside the eval loop, so async is allowed.
|
|
20
|
+
*
|
|
21
|
+
* Built-in implementations:
|
|
22
|
+
* - {@link MemoryBackend} — ephemeral, no persistence
|
|
23
|
+
* - {@link LocalStorageBackend} — persist to localStorage (~5 MB limit)
|
|
24
|
+
* - {@link SessionStorageBackend} — persist within the tab session
|
|
25
|
+
* - {@link IndexedDBBackend} — persist to IndexedDB (recommended for production)
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { SemaInterpreter, IndexedDBBackend } from "@sema-lang/sema";
|
|
30
|
+
*
|
|
31
|
+
* const sema = await SemaInterpreter.create({
|
|
32
|
+
* vfs: new IndexedDBBackend({ namespace: "my-app" }),
|
|
33
|
+
* });
|
|
34
|
+
* await sema.evalStrAsync(code);
|
|
35
|
+
* await sema.flushVFS(); // persist changes
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface VFSBackend {
|
|
39
|
+
/** Optional: open DB connections, request permissions, etc. */
|
|
40
|
+
init?(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Populate the in-memory WASM VFS from persistent storage.
|
|
43
|
+
* Called once during `SemaInterpreter.create()`.
|
|
44
|
+
*/
|
|
45
|
+
hydrate(host: VFSHost): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Persist current in-memory VFS state to storage.
|
|
48
|
+
* Called explicitly via `sema.flushVFS()`.
|
|
49
|
+
*/
|
|
50
|
+
flush(host: VFSHost): Promise<void>;
|
|
51
|
+
/** Optional: clear all persistent data. */
|
|
52
|
+
reset?(): Promise<void>;
|
|
53
|
+
}
|
package/dist/vfs.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|