@pydantic/monty 0.0.7 → 0.0.8

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 CHANGED
@@ -30,7 +30,7 @@ const result = m.run({ inputs: { x: 10, y: 20 } }) // returns 30
30
30
  For synchronous external functions, pass them directly to `run()`:
31
31
 
32
32
  ```ts
33
- const m = new Monty('add(2, 3)', { externalFunctions: ['add'] })
33
+ const m = new Monty('add(2, 3)')
34
34
 
35
35
  const result = m.run({
36
36
  externalFunctions: {
@@ -46,7 +46,6 @@ import { Monty, runMontyAsync } from '@pydantic/monty'
46
46
 
47
47
  const m = new Monty('fetch_data(url)', {
48
48
  inputs: ['url'],
49
- externalFunctions: ['fetch_data'],
50
49
  })
51
50
 
52
51
  const result = await runMontyAsync(m, {
@@ -65,7 +64,7 @@ const result = await runMontyAsync(m, {
65
64
  For fine-grained control over external function calls, use `start()` and `resume()`:
66
65
 
67
66
  ```ts
68
- const m = new Monty('a() + b()', { externalFunctions: ['a', 'b'] })
67
+ const m = new Monty('a() + b()')
69
68
 
70
69
  let progress = m.start()
71
70
  while (progress instanceof MontySnapshot) {
@@ -161,13 +160,11 @@ if (snapshot instanceof MontySnapshot) {
161
160
  - `Monty.load(data)` - Deserialize from binary format
162
161
  - `scriptName` - The script name (default: `'main.py'`)
163
162
  - `inputs` - Declared input variable names
164
- - `externalFunctions` - Declared external function names
165
163
 
166
164
  ### `MontyOptions`
167
165
 
168
166
  - `scriptName?: string` - Name used in tracebacks (default: `'main.py'`)
169
167
  - `inputs?: string[]` - Input variable names
170
- - `externalFunctions?: string[]` - External function names
171
168
  - `typeCheck?: boolean` - Enable type checking on construction
172
169
  - `typeCheckPrefixCode?: string` - Code to prepend for type checking
173
170
 
package/index.d.ts CHANGED
@@ -32,20 +32,26 @@ export declare class Monty {
32
32
  /**
33
33
  * Executes the code and returns the result, or an exception object if execution fails.
34
34
  *
35
+ * If runtime `externalFunctions` are provided, the start/resume loop is used
36
+ * to dispatch external function calls and name lookups. Otherwise, code is
37
+ * executed directly.
38
+ *
35
39
  * @param options - Execution options (inputs, limits, externalFunctions)
36
40
  * @returns The result of the last expression, or a MontyException if execution fails
37
41
  */
38
42
  run(options?: RunOptions | undefined | null): JsMontyObject | MontyException
39
43
  /**
40
- * Starts execution and returns either a snapshot (paused at external call), completion, or error.
44
+ * Starts execution and returns a snapshot (paused at external call or name lookup),
45
+ * completion, or error.
41
46
  *
42
47
  * This method enables iterative execution where code pauses at external function
43
- * calls, allowing the host to provide return values or exceptions before resuming.
48
+ * calls or name lookups, allowing the host to provide return values before resuming.
44
49
  *
45
50
  * @param options - Execution options (inputs, limits)
46
- * @returns MontySnapshot if paused, MontyComplete if done, or MontyException if failed
51
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
52
+ * name lookup, MontyComplete if done, or MontyException if failed
47
53
  */
48
- start(options?: StartOptions | undefined | null): MontySnapshot | MontyComplete | MontyException
54
+ start(options?: StartOptions | undefined | null): MontySnapshot | MontyNameLookup | MontyComplete | MontyException
49
55
  /**
50
56
  * Serializes the Monty instance to a binary format.
51
57
  *
@@ -66,8 +72,6 @@ export declare class Monty {
66
72
  get scriptName(): string
67
73
  /** Returns the input variable names. */
68
74
  get inputs(): Array<string>
69
- /** Returns the external function names. */
70
- get externalFunctions(): Array<string>
71
75
  /** Returns a string representation of the Monty instance. */
72
76
  repr(): string
73
77
  }
@@ -122,25 +126,65 @@ export declare class MontyException {
122
126
  }
123
127
  export type JsMontyException = MontyException
124
128
 
129
+ /**
130
+ * Represents paused execution waiting for a name to be resolved.
131
+ *
132
+ * The host should check if the variable name corresponds to a known value
133
+ * (e.g., an external function). Call `resume()` with the value to continue
134
+ * execution, or call `resume()` with no value to raise `NameError`.
135
+ */
136
+ export declare class MontyNameLookup {
137
+ /** Returns the name of the script being executed. */
138
+ get scriptName(): string
139
+ /** Returns the name of the variable being looked up. */
140
+ get variableName(): string
141
+ /**
142
+ * Resumes execution after resolving the name lookup.
143
+ *
144
+ * If `value` is provided, the name resolves to that value and execution continues.
145
+ * If `value` is omitted or undefined, the VM raises a `NameError`.
146
+ *
147
+ * @param options - Optional object with `value` to resolve the name to
148
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
149
+ * another name lookup, MontyComplete if done, or MontyException if failed
150
+ */
151
+ resume(options?: NameLookupResumeOptions | undefined | null): MontySnapshot | Self | MontyComplete | MontyException
152
+ /**
153
+ * Serializes the MontyNameLookup to a binary format.
154
+ *
155
+ * The serialized data can be stored and later restored with `MontyNameLookup.load()`.
156
+ *
157
+ * @returns Buffer containing the serialized name lookup snapshot
158
+ */
159
+ dump(): Buffer
160
+ /**
161
+ * Deserializes a MontyNameLookup from binary format.
162
+ *
163
+ * @param data - The serialized data from `dump()`
164
+ * @param options - Optional load options
165
+ * @returns A new MontyNameLookup instance
166
+ */
167
+ static load(data: Buffer, options?: NameLookupLoadOptions | undefined | null): MontyNameLookup
168
+ /** Returns a string representation of the MontyNameLookup. */
169
+ repr(): string
170
+ }
171
+
125
172
  /**
126
173
  * Stateful no-replay REPL session.
127
174
  *
128
- * Each call to `feed()` compiles and executes only the provided snippet against
129
- * existing session state.
175
+ * Create with `new MontyRepl()` then call `feed()` to execute snippets
176
+ * incrementally against persistent heap and namespace state.
130
177
  */
131
178
  export declare class MontyRepl {
132
179
  /**
133
- * Creates a REPL session directly from source code.
180
+ * Creates an empty REPL session ready to receive snippets via `feed()`.
134
181
  *
135
- * This mirrors `Monty.create(...)` for parsing/type-checking options, then
136
- * initializes a stateful REPL that executes the initial module once.
182
+ * No code is parsed or executed at construction time — all execution
183
+ * is driven through `feed()`.
137
184
  *
138
- * @param code - Python code to execute for REPL initialization
139
- * @param options - Parser/type-checking configuration
140
- * @param startOptions - Initial inputs and optional resource limits
141
- * @returns MontyRepl on success, or error object on failure
185
+ * @param options - Optional configuration (scriptName, limits)
142
186
  */
143
- static create(code: string, options?: MontyOptions | undefined | null, startOptions?: StartOptions | undefined | null): Self | MontyException | MontyTypingError
187
+ constructor(options?: MontyReplOptions | undefined | null)
144
188
  /** Returns the script name for this REPL session. */
145
189
  get scriptName(): string
146
190
  /** Executes one incremental snippet against persistent REPL state. */
@@ -174,9 +218,10 @@ export declare class MontySnapshot {
174
218
  * Exactly one of `returnValue` or `exception` must be provided.
175
219
  *
176
220
  * @param options - Object with either `returnValue` or `exception`
177
- * @returns MontySnapshot if paused, MontyComplete if done, or MontyException if failed
221
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
222
+ * name lookup, MontyComplete if done, or MontyException if failed
178
223
  */
179
- resume(options: ResumeOptions): Self | MontyComplete | MontyException
224
+ resume(options: ResumeOptions): Self | MontyNameLookup | MontyComplete | MontyException
180
225
  /**
181
226
  * Serializes the MontySnapshot to a binary format.
182
227
  *
@@ -279,14 +324,42 @@ export interface MontyOptions {
279
324
  scriptName?: string
280
325
  /** List of input variable names available in the code. */
281
326
  inputs?: Array<string>
282
- /** List of external function names the code can call. */
283
- externalFunctions?: Array<string>
284
327
  /** Whether to perform type checking on the code. Default: false */
285
328
  typeCheck?: boolean
286
329
  /** Optional code to prepend before type checking. */
287
330
  typeCheckPrefixCode?: string
288
331
  }
289
332
 
333
+ /**
334
+ * Options for creating a new `MontyRepl` instance.
335
+ *
336
+ * Controls the script name shown in tracebacks and optional resource limits
337
+ * that apply to all subsequent `feed()` calls.
338
+ */
339
+ export interface MontyReplOptions {
340
+ /** Name used in tracebacks and error messages. Default: 'main.py' */
341
+ scriptName?: string
342
+ /** Resource limits configuration applied to all snippet executions. */
343
+ limits?: ResourceLimits
344
+ }
345
+
346
+ /** Options for loading a serialized name lookup snapshot. */
347
+ export interface NameLookupLoadOptions {
348
+ /** Optional print callback function. */
349
+ printCallback?: JsPrintCallback
350
+ }
351
+
352
+ /**
353
+ * Options for resuming execution from a name lookup.
354
+ *
355
+ * If `value` is provided, the name resolves to that value and execution continues.
356
+ * If `value` is omitted or undefined, the VM raises a `NameError`.
357
+ */
358
+ export interface NameLookupResumeOptions {
359
+ /** The value to provide for the name. */
360
+ value?: unknown
361
+ }
362
+
290
363
  /**
291
364
  * Resource limits configuration from JavaScript.
292
365
  *
package/index.js CHANGED
@@ -81,8 +81,8 @@ function requireNative() {
81
81
  try {
82
82
  const binding = require('@pydantic/monty-android-arm64')
83
83
  const bindingPackageVersion = require('@pydantic/monty-android-arm64/package.json').version
84
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
85
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
84
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
85
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
86
86
  }
87
87
  return binding
88
88
  } catch (e) {
@@ -97,8 +97,8 @@ function requireNative() {
97
97
  try {
98
98
  const binding = require('@pydantic/monty-android-arm-eabi')
99
99
  const bindingPackageVersion = require('@pydantic/monty-android-arm-eabi/package.json').version
100
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
101
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
100
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
101
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
102
102
  }
103
103
  return binding
104
104
  } catch (e) {
@@ -118,8 +118,8 @@ function requireNative() {
118
118
  try {
119
119
  const binding = require('@pydantic/monty-win32-x64-gnu')
120
120
  const bindingPackageVersion = require('@pydantic/monty-win32-x64-gnu/package.json').version
121
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
122
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
121
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
122
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
123
123
  }
124
124
  return binding
125
125
  } catch (e) {
@@ -134,8 +134,8 @@ function requireNative() {
134
134
  try {
135
135
  const binding = require('@pydantic/monty-win32-x64-msvc')
136
136
  const bindingPackageVersion = require('@pydantic/monty-win32-x64-msvc/package.json').version
137
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
138
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
137
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
138
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
139
139
  }
140
140
  return binding
141
141
  } catch (e) {
@@ -151,8 +151,8 @@ function requireNative() {
151
151
  try {
152
152
  const binding = require('@pydantic/monty-win32-ia32-msvc')
153
153
  const bindingPackageVersion = require('@pydantic/monty-win32-ia32-msvc/package.json').version
154
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
155
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
154
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
155
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
156
156
  }
157
157
  return binding
158
158
  } catch (e) {
@@ -167,8 +167,8 @@ function requireNative() {
167
167
  try {
168
168
  const binding = require('@pydantic/monty-win32-arm64-msvc')
169
169
  const bindingPackageVersion = require('@pydantic/monty-win32-arm64-msvc/package.json').version
170
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
171
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
170
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
171
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
172
172
  }
173
173
  return binding
174
174
  } catch (e) {
@@ -186,8 +186,8 @@ function requireNative() {
186
186
  try {
187
187
  const binding = require('@pydantic/monty-darwin-universal')
188
188
  const bindingPackageVersion = require('@pydantic/monty-darwin-universal/package.json').version
189
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
190
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
189
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
190
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
191
191
  }
192
192
  return binding
193
193
  } catch (e) {
@@ -202,8 +202,8 @@ function requireNative() {
202
202
  try {
203
203
  const binding = require('@pydantic/monty-darwin-x64')
204
204
  const bindingPackageVersion = require('@pydantic/monty-darwin-x64/package.json').version
205
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
206
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
205
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
206
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
207
207
  }
208
208
  return binding
209
209
  } catch (e) {
@@ -218,8 +218,8 @@ function requireNative() {
218
218
  try {
219
219
  const binding = require('@pydantic/monty-darwin-arm64')
220
220
  const bindingPackageVersion = require('@pydantic/monty-darwin-arm64/package.json').version
221
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
222
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
221
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
222
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
223
223
  }
224
224
  return binding
225
225
  } catch (e) {
@@ -238,8 +238,8 @@ function requireNative() {
238
238
  try {
239
239
  const binding = require('@pydantic/monty-freebsd-x64')
240
240
  const bindingPackageVersion = require('@pydantic/monty-freebsd-x64/package.json').version
241
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
242
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
241
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
242
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
243
243
  }
244
244
  return binding
245
245
  } catch (e) {
@@ -254,8 +254,8 @@ function requireNative() {
254
254
  try {
255
255
  const binding = require('@pydantic/monty-freebsd-arm64')
256
256
  const bindingPackageVersion = require('@pydantic/monty-freebsd-arm64/package.json').version
257
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
258
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
257
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
258
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
259
259
  }
260
260
  return binding
261
261
  } catch (e) {
@@ -275,8 +275,8 @@ function requireNative() {
275
275
  try {
276
276
  const binding = require('@pydantic/monty-linux-x64-musl')
277
277
  const bindingPackageVersion = require('@pydantic/monty-linux-x64-musl/package.json').version
278
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
279
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
278
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
279
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
280
280
  }
281
281
  return binding
282
282
  } catch (e) {
@@ -291,8 +291,8 @@ function requireNative() {
291
291
  try {
292
292
  const binding = require('@pydantic/monty-linux-x64-gnu')
293
293
  const bindingPackageVersion = require('@pydantic/monty-linux-x64-gnu/package.json').version
294
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
295
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
294
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
295
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
296
296
  }
297
297
  return binding
298
298
  } catch (e) {
@@ -309,8 +309,8 @@ function requireNative() {
309
309
  try {
310
310
  const binding = require('@pydantic/monty-linux-arm64-musl')
311
311
  const bindingPackageVersion = require('@pydantic/monty-linux-arm64-musl/package.json').version
312
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
313
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
312
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
313
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
314
314
  }
315
315
  return binding
316
316
  } catch (e) {
@@ -325,8 +325,8 @@ function requireNative() {
325
325
  try {
326
326
  const binding = require('@pydantic/monty-linux-arm64-gnu')
327
327
  const bindingPackageVersion = require('@pydantic/monty-linux-arm64-gnu/package.json').version
328
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
329
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
328
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
329
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
330
330
  }
331
331
  return binding
332
332
  } catch (e) {
@@ -343,8 +343,8 @@ function requireNative() {
343
343
  try {
344
344
  const binding = require('@pydantic/monty-linux-arm-musleabihf')
345
345
  const bindingPackageVersion = require('@pydantic/monty-linux-arm-musleabihf/package.json').version
346
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
347
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
346
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
347
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
348
348
  }
349
349
  return binding
350
350
  } catch (e) {
@@ -359,8 +359,8 @@ function requireNative() {
359
359
  try {
360
360
  const binding = require('@pydantic/monty-linux-arm-gnueabihf')
361
361
  const bindingPackageVersion = require('@pydantic/monty-linux-arm-gnueabihf/package.json').version
362
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
363
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
362
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
363
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
364
364
  }
365
365
  return binding
366
366
  } catch (e) {
@@ -377,8 +377,8 @@ function requireNative() {
377
377
  try {
378
378
  const binding = require('@pydantic/monty-linux-loong64-musl')
379
379
  const bindingPackageVersion = require('@pydantic/monty-linux-loong64-musl/package.json').version
380
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
381
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
380
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
381
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
382
382
  }
383
383
  return binding
384
384
  } catch (e) {
@@ -393,8 +393,8 @@ function requireNative() {
393
393
  try {
394
394
  const binding = require('@pydantic/monty-linux-loong64-gnu')
395
395
  const bindingPackageVersion = require('@pydantic/monty-linux-loong64-gnu/package.json').version
396
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
397
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
396
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
397
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
398
398
  }
399
399
  return binding
400
400
  } catch (e) {
@@ -411,8 +411,8 @@ function requireNative() {
411
411
  try {
412
412
  const binding = require('@pydantic/monty-linux-riscv64-musl')
413
413
  const bindingPackageVersion = require('@pydantic/monty-linux-riscv64-musl/package.json').version
414
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
415
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
414
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
415
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
416
416
  }
417
417
  return binding
418
418
  } catch (e) {
@@ -427,8 +427,8 @@ function requireNative() {
427
427
  try {
428
428
  const binding = require('@pydantic/monty-linux-riscv64-gnu')
429
429
  const bindingPackageVersion = require('@pydantic/monty-linux-riscv64-gnu/package.json').version
430
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
431
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
430
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
431
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
432
432
  }
433
433
  return binding
434
434
  } catch (e) {
@@ -444,8 +444,8 @@ function requireNative() {
444
444
  try {
445
445
  const binding = require('@pydantic/monty-linux-ppc64-gnu')
446
446
  const bindingPackageVersion = require('@pydantic/monty-linux-ppc64-gnu/package.json').version
447
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
448
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
447
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
448
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
449
449
  }
450
450
  return binding
451
451
  } catch (e) {
@@ -460,8 +460,8 @@ function requireNative() {
460
460
  try {
461
461
  const binding = require('@pydantic/monty-linux-s390x-gnu')
462
462
  const bindingPackageVersion = require('@pydantic/monty-linux-s390x-gnu/package.json').version
463
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
464
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
463
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
464
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
465
465
  }
466
466
  return binding
467
467
  } catch (e) {
@@ -480,8 +480,8 @@ function requireNative() {
480
480
  try {
481
481
  const binding = require('@pydantic/monty-openharmony-arm64')
482
482
  const bindingPackageVersion = require('@pydantic/monty-openharmony-arm64/package.json').version
483
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
484
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
483
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
484
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
485
485
  }
486
486
  return binding
487
487
  } catch (e) {
@@ -496,8 +496,8 @@ function requireNative() {
496
496
  try {
497
497
  const binding = require('@pydantic/monty-openharmony-x64')
498
498
  const bindingPackageVersion = require('@pydantic/monty-openharmony-x64/package.json').version
499
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
500
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
499
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
500
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
501
501
  }
502
502
  return binding
503
503
  } catch (e) {
@@ -512,8 +512,8 @@ function requireNative() {
512
512
  try {
513
513
  const binding = require('@pydantic/monty-openharmony-arm')
514
514
  const bindingPackageVersion = require('@pydantic/monty-openharmony-arm/package.json').version
515
- if (bindingPackageVersion !== '0.0.7' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
516
- throw new Error(`Native binding package version mismatch, expected 0.0.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
515
+ if (bindingPackageVersion !== '0.0.8' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
516
+ throw new Error(`Native binding package version mismatch, expected 0.0.8 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
517
517
  }
518
518
  return binding
519
519
  } catch (e) {
@@ -579,11 +579,12 @@ if (!nativeBinding) {
579
579
  throw new Error(`Failed to load native binding`)
580
580
  }
581
581
 
582
- const { Monty, MontyComplete, MontyException, JsMontyException, MontyRepl, MontySnapshot, MontyTypingError } = nativeBinding
582
+ const { Monty, MontyComplete, MontyException, JsMontyException, MontyNameLookup, MontyRepl, MontySnapshot, MontyTypingError } = nativeBinding
583
583
  export { Monty }
584
584
  export { MontyComplete }
585
585
  export { MontyException }
586
586
  export { JsMontyException }
587
+ export { MontyNameLookup }
587
588
  export { MontyRepl }
588
589
  export { MontySnapshot }
589
590
  export { MontyTypingError }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pydantic/monty",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "description": "Sandboxed Python interpreter for JavaScript/TypeScript",
6
6
  "main": "wrapper.js",
@@ -98,11 +98,11 @@
98
98
  "arrowParens": "always"
99
99
  },
100
100
  "optionalDependencies": {
101
- "@pydantic/monty-win32-x64-msvc": "0.0.7",
102
- "@pydantic/monty-darwin-x64": "0.0.7",
103
- "@pydantic/monty-linux-x64-gnu": "0.0.7",
104
- "@pydantic/monty-darwin-arm64": "0.0.7",
105
- "@pydantic/monty-linux-arm64-gnu": "0.0.7",
106
- "@pydantic/monty-wasm32-wasi": "0.0.7"
101
+ "@pydantic/monty-win32-x64-msvc": "0.0.8",
102
+ "@pydantic/monty-darwin-x64": "0.0.8",
103
+ "@pydantic/monty-linux-x64-gnu": "0.0.8",
104
+ "@pydantic/monty-darwin-arm64": "0.0.8",
105
+ "@pydantic/monty-linux-arm64-gnu": "0.0.8",
106
+ "@pydantic/monty-wasm32-wasi": "0.0.8"
107
107
  }
108
108
  }
package/wrapper.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { ExceptionInfo, ExceptionInput, Frame, JsMontyObject, MontyOptions, ResourceLimits, ResumeOptions, RunOptions, SnapshotLoadOptions, StartOptions } from './index.js';
2
- import { MontyRepl as NativeMontyRepl, MontySnapshot as NativeMontySnapshot, MontyComplete as NativeMontyComplete, MontyException as NativeMontyException, MontyTypingError as NativeMontyTypingError } from './index.js';
3
- export type { MontyOptions, RunOptions, ResourceLimits, Frame, ExceptionInfo, StartOptions, ResumeOptions, ExceptionInput, SnapshotLoadOptions, JsMontyObject, };
1
+ import type { ExceptionInfo, ExceptionInput, Frame, JsMontyObject, MontyOptions, NameLookupLoadOptions, NameLookupResumeOptions, ResourceLimits, ResumeOptions, RunOptions, SnapshotLoadOptions, StartOptions } from './index.js';
2
+ import { MontySnapshot as NativeMontySnapshot, MontyNameLookup as NativeMontyNameLookup, MontyComplete as NativeMontyComplete, MontyException as NativeMontyException, MontyTypingError as NativeMontyTypingError } from './index.js';
3
+ export type { MontyOptions, RunOptions, ResourceLimits, Frame, ExceptionInfo, StartOptions, ResumeOptions, ExceptionInput, SnapshotLoadOptions, NameLookupResumeOptions, NameLookupLoadOptions, JsMontyObject, };
4
4
  /**
5
5
  * Alias for ResourceLimits (deprecated name).
6
6
  */
@@ -110,13 +110,14 @@ export declare class Monty {
110
110
  */
111
111
  run(options?: RunOptions): JsMontyObject;
112
112
  /**
113
- * Starts execution and returns either a snapshot (paused at external call) or completion.
113
+ * Starts execution and returns a snapshot (paused at external call or name lookup) or completion.
114
114
  *
115
115
  * @param options - Execution options (inputs, limits)
116
- * @returns MontySnapshot if an external function call is pending, MontyComplete if done
116
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
117
+ * name lookup, MontyComplete if done
117
118
  * @throws {MontyRuntimeError} If the code raises an exception
118
119
  */
119
- start(options?: StartOptions): MontySnapshot | MontyComplete;
120
+ start(options?: StartOptions): MontySnapshot | MontyNameLookup | MontyComplete;
120
121
  /**
121
122
  * Serializes the Monty instance to a binary format.
122
123
  */
@@ -129,21 +130,30 @@ export declare class Monty {
129
130
  get scriptName(): string;
130
131
  /** Returns the input variable names. */
131
132
  get inputs(): string[];
132
- /** Returns the external function names. */
133
- get externalFunctions(): string[];
134
133
  /** Returns a string representation of the Monty instance. */
135
134
  repr(): string;
136
135
  }
136
+ /** Options for creating a new MontyRepl instance. */
137
+ export interface MontyReplOptions {
138
+ /** Name used in tracebacks and error messages. Default: 'main.py' */
139
+ scriptName?: string;
140
+ /** Resource limits applied to all snippet executions. */
141
+ limits?: ResourceLimits;
142
+ }
137
143
  /**
138
144
  * Incremental no-replay REPL session.
145
+ *
146
+ * Create with `new MontyRepl()` then call `feed()` to execute snippets
147
+ * incrementally against persistent state.
139
148
  */
140
149
  export declare class MontyRepl {
141
150
  private _native;
142
151
  /**
143
- * Creates a REPL session directly from source code.
152
+ * Creates an empty REPL session ready to receive snippets via `feed()`.
153
+ *
154
+ * @param options - Optional configuration (scriptName, limits)
144
155
  */
145
- static create(code: string, options?: MontyOptions, startOptions?: StartOptions): MontyRepl;
146
- constructor(nativeRepl: NativeMontyRepl);
156
+ constructor(options?: MontyReplOptions);
147
157
  /** Returns the script name for this REPL session. */
148
158
  get scriptName(): string;
149
159
  /**
@@ -182,10 +192,11 @@ export declare class MontySnapshot {
182
192
  * Resumes execution with either a return value or an exception.
183
193
  *
184
194
  * @param options - Object with either `returnValue` or `exception`
185
- * @returns MontySnapshot if another external call is pending, MontyComplete if done
195
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
196
+ * name lookup, MontyComplete if done
186
197
  * @throws {MontyRuntimeError} If the code raises an exception
187
198
  */
188
- resume(options: ResumeOptions): MontySnapshot | MontyComplete;
199
+ resume(options: ResumeOptions): MontySnapshot | MontyNameLookup | MontyComplete;
189
200
  /**
190
201
  * Serializes the MontySnapshot to a binary format.
191
202
  */
@@ -197,6 +208,43 @@ export declare class MontySnapshot {
197
208
  /** Returns a string representation of the MontySnapshot. */
198
209
  repr(): string;
199
210
  }
211
+ /**
212
+ * Represents paused execution waiting for a name to be resolved.
213
+ *
214
+ * The host should check if the variable name corresponds to a known value
215
+ * (e.g., an external function). Call `resume()` with the value to continue
216
+ * execution, or call `resume()` with no value to raise `NameError`.
217
+ */
218
+ export declare class MontyNameLookup {
219
+ private _native;
220
+ constructor(nativeNameLookup: NativeMontyNameLookup);
221
+ /** Returns the name of the script being executed. */
222
+ get scriptName(): string;
223
+ /** Returns the name of the variable being looked up. */
224
+ get variableName(): string;
225
+ /**
226
+ * Resumes execution after resolving the name lookup.
227
+ *
228
+ * If `value` is provided, the name resolves to that value and execution continues.
229
+ * If `value` is omitted/undefined, the VM raises a `NameError`.
230
+ *
231
+ * @param options - Optional object with `value` to resolve the name to
232
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
233
+ * another name lookup, MontyComplete if done
234
+ * @throws {MontyRuntimeError} If the code raises an exception
235
+ */
236
+ resume(options?: NameLookupResumeOptions): MontySnapshot | MontyNameLookup | MontyComplete;
237
+ /**
238
+ * Serializes the MontyNameLookup to a binary format.
239
+ */
240
+ dump(): Buffer;
241
+ /**
242
+ * Deserializes a MontyNameLookup from binary format.
243
+ */
244
+ static load(data: Buffer, options?: NameLookupLoadOptions): MontyNameLookup;
245
+ /** Returns a string representation of the MontyNameLookup. */
246
+ repr(): string;
247
+ }
200
248
  /**
201
249
  * Represents completed execution with a final output value.
202
250
  */
@@ -218,6 +266,8 @@ export interface RunMontyAsyncOptions {
218
266
  externalFunctions?: Record<string, (...args: unknown[]) => unknown>;
219
267
  /** Resource limits. */
220
268
  limits?: ResourceLimits;
269
+ /** Callback invoked on each print() call. The first argument is the stream name (always "stdout"), the second is the printed text. */
270
+ printCallback?: (stream: string, text: string) => void;
221
271
  }
222
272
  /**
223
273
  * Runs a Monty script with async external function support.
@@ -235,7 +285,6 @@ export interface RunMontyAsyncOptions {
235
285
  * @example
236
286
  * const m = new Monty('result = await fetch_data(url)', {
237
287
  * inputs: ['url'],
238
- * externalFunctions: ['fetch_data']
239
288
  * });
240
289
  *
241
290
  * const result = await runMontyAsync(m, {
package/wrapper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Custom error classes that extend Error for proper JavaScript error handling.
2
2
  // These wrap the native Rust classes to provide instanceof support.
3
- import { Monty as NativeMonty, MontyRepl as NativeMontyRepl, MontySnapshot as NativeMontySnapshot, MontyComplete as NativeMontyComplete, MontyException as NativeMontyException, MontyTypingError as NativeMontyTypingError, } from './index.js';
3
+ import { Monty as NativeMonty, MontyRepl as NativeMontyRepl, MontySnapshot as NativeMontySnapshot, MontyNameLookup as NativeMontyNameLookup, MontyComplete as NativeMontyComplete, MontyException as NativeMontyException, MontyTypingError as NativeMontyTypingError, } from './index.js';
4
4
  /**
5
5
  * Base class for all Monty interpreter errors.
6
6
  *
@@ -222,10 +222,11 @@ export class Monty {
222
222
  return result;
223
223
  }
224
224
  /**
225
- * Starts execution and returns either a snapshot (paused at external call) or completion.
225
+ * Starts execution and returns a snapshot (paused at external call or name lookup) or completion.
226
226
  *
227
227
  * @param options - Execution options (inputs, limits)
228
- * @returns MontySnapshot if an external function call is pending, MontyComplete if done
228
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
229
+ * name lookup, MontyComplete if done
229
230
  * @throws {MontyRuntimeError} If the code raises an exception
230
231
  */
231
232
  start(options) {
@@ -254,10 +255,6 @@ export class Monty {
254
255
  get inputs() {
255
256
  return this._native.inputs;
256
257
  }
257
- /** Returns the external function names. */
258
- get externalFunctions() {
259
- return this._native.externalFunctions;
260
- }
261
258
  /** Returns a string representation of the Monty instance. */
262
259
  repr() {
263
260
  return this._native.repr();
@@ -265,26 +262,18 @@ export class Monty {
265
262
  }
266
263
  /**
267
264
  * Incremental no-replay REPL session.
265
+ *
266
+ * Create with `new MontyRepl()` then call `feed()` to execute snippets
267
+ * incrementally against persistent state.
268
268
  */
269
269
  export class MontyRepl {
270
270
  /**
271
- * Creates a REPL session directly from source code.
271
+ * Creates an empty REPL session ready to receive snippets via `feed()`.
272
+ *
273
+ * @param options - Optional configuration (scriptName, limits)
272
274
  */
273
- static create(code, options, startOptions) {
274
- const result = NativeMontyRepl.create(code, options, startOptions);
275
- if (result instanceof NativeMontyException) {
276
- if (result.exception.typeName === 'SyntaxError') {
277
- throw new MontySyntaxError(result);
278
- }
279
- throw new MontyRuntimeError(result);
280
- }
281
- if (result instanceof NativeMontyTypingError) {
282
- throw new MontyTypingError(result);
283
- }
284
- return new MontyRepl(result);
285
- }
286
- constructor(nativeRepl) {
287
- this._native = nativeRepl;
275
+ constructor(options) {
276
+ this._native = new NativeMontyRepl(options);
288
277
  }
289
278
  /** Returns the script name for this REPL session. */
290
279
  get scriptName() {
@@ -310,7 +299,10 @@ export class MontyRepl {
310
299
  }
311
300
  /** Restores a REPL session from bytes. */
312
301
  static load(data) {
313
- return new MontyRepl(NativeMontyRepl.load(data));
302
+ const native = NativeMontyRepl.load(data);
303
+ const repl = Object.create(MontyRepl.prototype);
304
+ repl._native = native;
305
+ return repl;
314
306
  }
315
307
  /** Returns a string representation of the REPL session. */
316
308
  repr() {
@@ -324,6 +316,11 @@ function wrapStartResult(result) {
324
316
  if (result instanceof NativeMontyException) {
325
317
  throw new MontyRuntimeError(result);
326
318
  }
319
+ // Check MontyNameLookup before MontySnapshot — napi `Either4` may cause
320
+ // false positives with `instanceof` if checked in the wrong order.
321
+ if (result instanceof NativeMontyNameLookup) {
322
+ return new MontyNameLookup(result);
323
+ }
327
324
  if (result instanceof NativeMontySnapshot) {
328
325
  return new MontySnapshot(result);
329
326
  }
@@ -362,7 +359,8 @@ export class MontySnapshot {
362
359
  * Resumes execution with either a return value or an exception.
363
360
  *
364
361
  * @param options - Object with either `returnValue` or `exception`
365
- * @returns MontySnapshot if another external call is pending, MontyComplete if done
362
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
363
+ * name lookup, MontyComplete if done
366
364
  * @throws {MontyRuntimeError} If the code raises an exception
367
365
  */
368
366
  resume(options) {
@@ -387,6 +385,58 @@ export class MontySnapshot {
387
385
  return this._native.repr();
388
386
  }
389
387
  }
388
+ /**
389
+ * Represents paused execution waiting for a name to be resolved.
390
+ *
391
+ * The host should check if the variable name corresponds to a known value
392
+ * (e.g., an external function). Call `resume()` with the value to continue
393
+ * execution, or call `resume()` with no value to raise `NameError`.
394
+ */
395
+ export class MontyNameLookup {
396
+ constructor(nativeNameLookup) {
397
+ this._native = nativeNameLookup;
398
+ }
399
+ /** Returns the name of the script being executed. */
400
+ get scriptName() {
401
+ return this._native.scriptName;
402
+ }
403
+ /** Returns the name of the variable being looked up. */
404
+ get variableName() {
405
+ return this._native.variableName;
406
+ }
407
+ /**
408
+ * Resumes execution after resolving the name lookup.
409
+ *
410
+ * If `value` is provided, the name resolves to that value and execution continues.
411
+ * If `value` is omitted/undefined, the VM raises a `NameError`.
412
+ *
413
+ * @param options - Optional object with `value` to resolve the name to
414
+ * @returns MontySnapshot if paused at function call, MontyNameLookup if paused at
415
+ * another name lookup, MontyComplete if done
416
+ * @throws {MontyRuntimeError} If the code raises an exception
417
+ */
418
+ resume(options) {
419
+ const result = this._native.resume(options);
420
+ return wrapStartResult(result);
421
+ }
422
+ /**
423
+ * Serializes the MontyNameLookup to a binary format.
424
+ */
425
+ dump() {
426
+ return this._native.dump();
427
+ }
428
+ /**
429
+ * Deserializes a MontyNameLookup from binary format.
430
+ */
431
+ static load(data, options) {
432
+ const nativeLookup = NativeMontyNameLookup.load(data, options);
433
+ return new MontyNameLookup(nativeLookup);
434
+ }
435
+ /** Returns a string representation of the MontyNameLookup. */
436
+ repr() {
437
+ return this._native.repr();
438
+ }
439
+ }
390
440
  /**
391
441
  * Represents completed execution with a final output value.
392
442
  */
@@ -419,7 +469,6 @@ export class MontyComplete {
419
469
  * @example
420
470
  * const m = new Monty('result = await fetch_data(url)', {
421
471
  * inputs: ['url'],
422
- * externalFunctions: ['fetch_data']
423
472
  * });
424
473
  *
425
474
  * const result = await runMontyAsync(m, {
@@ -433,21 +482,38 @@ export class MontyComplete {
433
482
  * });
434
483
  */
435
484
  export async function runMontyAsync(montyRunner, options = {}) {
436
- const { inputs, externalFunctions = {}, limits } = options;
485
+ const { inputs, externalFunctions = {}, limits, printCallback } = options;
437
486
  let progress = montyRunner.start({
438
487
  inputs,
439
488
  limits,
489
+ printCallback,
440
490
  });
441
- while (progress instanceof MontySnapshot) {
491
+ while (!(progress instanceof MontyComplete)) {
492
+ if (progress instanceof MontyNameLookup) {
493
+ // Name lookup — check if the name is a known external function
494
+ const name = progress.variableName;
495
+ const extFunction = externalFunctions[name];
496
+ if (extFunction) {
497
+ // Resolve the name as a function value
498
+ progress = progress.resume({ value: extFunction });
499
+ }
500
+ else {
501
+ // Unknown name — resume with no value to raise NameError
502
+ progress = progress.resume();
503
+ }
504
+ continue;
505
+ }
506
+ // MontySnapshot — external function call
442
507
  const snapshot = progress;
443
508
  const funcName = snapshot.functionName;
444
509
  const extFunction = externalFunctions[funcName];
445
510
  if (!extFunction) {
446
- // Function not found - resume with a KeyError exception
511
+ // Function not found this shouldn't normally happen since NameLookup
512
+ // would have raised NameError, but handle it defensively
447
513
  progress = snapshot.resume({
448
514
  exception: {
449
- type: 'KeyError',
450
- message: `"External function '${funcName}' not found"`,
515
+ type: 'NameError',
516
+ message: `name '${funcName}' is not defined`,
451
517
  },
452
518
  });
453
519
  continue;