4cffi bindings for the PhasorRT shared library.
6State is an opaque handle returned by :func:`new_state` and passed into
7execution and evaluation functions. This mirrors the C API directly
10from __future__
import annotations
14from pathlib
import Path
15from typing
import Optional, Sequence, Union
17from .Bytecode
import Bytecode
21def _to_bytes(bytecode:
"Union[bytes, bytearray, Bytecode]") -> bytes:
22 """Accept raw bytes/bytearray *or* a Bytecode object; always return bytes."""
23 if isinstance(bytecode, Bytecode):
24 return bytecode.to_bytes()
25 return bytes(bytecode)
30 int exec(void *state, const unsigned char *bytecode, size_t bytecodeSize,
31 const char *moduleName, int argc, const char **argv);
33 int evaluatePHS(void *state, const char *script, const char *moduleName,
34 const char *modulePath, bool verbose);
36 int evaluatePUL(void *state, const char *script, const char *moduleName);
38 bool compilePHS(const char *script, const char *moduleName,
39 const char *modulePath,
40 unsigned char *buffer, size_t bufferSize, size_t *outSize);
42 bool compilePUL(const char *script, const char *moduleName,
43 unsigned char *buffer, size_t bufferSize, size_t *outSize);
45 void *createState(void);
47 void initStdLib(void *state);
49 bool freeState(void *state);
51 bool resetState(void *state, bool resetFunctions, bool resetVariables);
55_CANDIDATES: dict[str, list[str]] = {
56 "linux": [
"libphasorrt.so",
"libphasorrt.so.1"],
57 "darwin": [
"libphasorrt.dylib",
"libphasorrt.1.dylib"],
58 "win32": [
"phasorrt.dll"],
61_LOAD_HINT: dict[str, str] = {
62 "linux":
"Set LD_LIBRARY_PATH or run ldconfig after installing Phasor.",
63 "darwin":
"Set DYLD_LIBRARY_PATH or install Phasor.",
64 "win32":
"Add phasorrt.dll to your system PATH",
75 key =
"linux" if sys.platform.startswith(
"linux")
else sys.platform
76 names = _CANDIDATES.get(key, _CANDIDATES[
"linux"])
77 hint = _LOAD_HINT.get(key,
"Ensure the library is on your system library search path.")
79 errors: list[str] = []
82 _lib = _ffi.dlopen(name)
84 except OSError
as exc:
85 errors.append(f
" {name}: {exc}")
88 f
"Could not load PhasorRT shared library on {sys.platform}.\n"
89 "Tried:\n" +
"\n".join(errors) + f
"\n{hint}"
92_DEFAULT_MODULE_NAME =
"Python"
98 return os.getcwd().encode(
"utf-8")
101def _ptr(state: Optional[StateHandle]):
102 """Return the raw cffi pointer for *state*, or NULL if *state* is None."""
103 return state
if state
is not None else _ffi.NULL
107 """Convert a sequence of strings to ``(argc, argv_p)`` for cffi."""
110 encoded = [_ffi.new(
"char[]", a.encode(
"utf-8"))
for a
in args]
111 return len(encoded), _ffi.new(
"const char *[]", encoded)
115 """Size-probe then compile as required by the C API.
118 A :class:`~phasor.Bytecode.Bytecode` object built from the compiled output.
120 out_size = _ffi.new(
"size_t *")
122 if not compile_fn(*leading_args, _ffi.NULL, 0, out_size):
123 raise RuntimeError(
"Compilation failed, check your source for errors.")
126 buf = _ffi.new(f
"unsigned char[{size}]")
128 if not compile_fn(*leading_args, buf, size, out_size):
129 raise RuntimeError(
"Compilation failed during buffer write; this is likely a PhasorRT bug.")
131 return Bytecode.from_bytes(bytes(_ffi.buffer(buf, size)))
134 """Allocate a new Phasor VM state and return its opaque handle.
136 Pass the handle to any execution or evaluation function via the
137 ``state`` parameter to reuse the same VM across multiple calls,
138 preserving globals and registered functions between them.
140 Always pair with :func:`free_state` when the state is no longer needed.
143 An opaque cffi handle representing the new VM state.
146 RuntimeError: If ``createState()`` returns NULL.
147 OSError: If the PhasorRT library cannot be loaded.
155 ptr = lib.createState()
157 raise RuntimeError(
"createState() returned NULL; PhasorRT may be uninitialised.")
161 """Register standard library functions into a given state.
164 state: The handle returned by :func:`new_state`.
167 RuntimeError: If ``initStdLib()`` reports failure.
170 raise RuntimeError(
"initStdLib() failed.")
173 """Release a VM state created by :func:`new_state`.
175 The handle must not be used after this call.
178 state: The handle returned by :func:`new_state`.
181 RuntimeError: If ``freeState()`` reports failure (double-free or corrupt pointer).
182 OSError: If the PhasorRT library cannot be loaded.
185 raise RuntimeError(
"freeState() failed; the state may already have been freed.")
191 reset_functions: bool =
False,
192 reset_variables: bool =
False,
194 """Reset a VM state (stack, PC, and bytecode are always cleared).
197 state: The handle returned by :func:`new_state`.
198 reset_functions: Also clear all registered functions when ``True``.
199 reset_variables: Also clear all global variables when ``True``.
202 RuntimeError: If ``resetState()`` reports failure.
203 OSError: If the PhasorRT library cannot be loaded.
207 # Soft reset: keep globals and functions, just reset execution state.
211 reset_state(vm, reset_functions=True, reset_variables=True)
214 raise RuntimeError(
"resetState() failed; state may be corrupt.")
219 module_name: str = _DEFAULT_MODULE_NAME,
220 module_path: Optional[str] =
None,
222 """Compile a Phasor (``.phs``) source string to ``.phsb`` bytecode.
225 script: Phasor source code to compile.
226 module_name: Name reported in error messages. Defaults to ``"Python"``.
227 module_path: Directory for resolving compile-time imports.
228 Defaults to the current working directory.
231 A :class:`~phasor.Bytecode.Bytecode` object, ready to inspect, modify,
232 pass to :func:`run`, or serialise with
233 :meth:`~phasor.Bytecode.Bytecode.save` / :meth:`~phasor.Bytecode.Bytecode.to_bytes`.
236 RuntimeError: If compilation fails.
237 OSError: If the PhasorRT library cannot be loaded.
239 mod_path = module_path.encode(
"utf-8")
if module_path
else _cwd_bytes()
242 script.encode(
"utf-8"),
243 module_name.encode(
"utf-8"),
251 module_name: Optional[str] =
None,
252 module_path: Optional[str] =
None,
254 """Read a ``.phs`` file and compile it to ``.phsb`` bytecode.
257 path: Path to the ``.phs`` source file.
258 module_name: Name reported in error messages.
259 Defaults to the file's stem (e.g. ``"hello"`` for ``hello.phs``).
260 module_path: Directory for resolving compile-time imports.
261 Defaults to the parent directory of *path*.
264 A :class:`~phasor.Bytecode.Bytecode` object.
267 FileNotFoundError: If *path* does not exist.
268 RuntimeError: If compilation fails.
269 OSError: If the PhasorRT library cannot be loaded.
273 raise FileNotFoundError(f
"Source file not found: {p}")
275 p.read_text(encoding=
"utf-8"),
276 module_name=module_name
or p.stem,
277 module_path=module_path
or str(p.resolve().parent),
284 module_name: str = _DEFAULT_MODULE_NAME,
286 """Compile a Pulsar (``.pul``) source string to ``.phsb`` bytecode.
289 script: Pulsar source code to compile.
290 module_name: Name reported in error messages. Defaults to ``"Python"``.
293 A :class:`~phasor.Bytecode.Bytecode` object.
296 RuntimeError: If compilation fails.
297 OSError: If the PhasorRT library cannot be loaded.
301 script.encode(
"utf-8"),
302 module_name.encode(
"utf-8"),
309 module_name: Optional[str] =
None,
311 """Read a ``.pul`` file and compile it to ``.phsb`` bytecode.
314 path: Path to the ``.pul`` source file.
315 module_name: Name reported in error messages.
316 Defaults to the file's stem.
319 A :class:`~phasor.Bytecode.Bytecode` object.
322 FileNotFoundError: If *path* does not exist.
323 RuntimeError: If compilation fails.
324 OSError: If the PhasorRT library cannot be loaded.
328 raise FileNotFoundError(f
"Source file not found: {p}")
330 p.read_text(encoding=
"utf-8"),
331 module_name=module_name
or p.stem,
335 bytecode: Union[bytes, bytearray, Bytecode],
337 state: Optional[StateHandle] =
None,
338 module_name: str = _DEFAULT_MODULE_NAME,
339 args: Sequence[str] = (),
341 """Execute pre-compiled ``.phsb`` bytecode.
344 bytecode: Raw ``.phsb`` bytes **or** a :class:`~phasor.Bytecode.Bytecode`
345 object (e.g. from :func:`compile_phs` or
346 :meth:`~phasor.Bytecode.Bytecode.load`).
347 state: An optional handle from :func:`new_state`. When ``None``,
348 PhasorRT creates and manages a transient state internally.
349 module_name: Name reported in error messages. Defaults to ``"Python"``.
350 args: Command-line arguments forwarded to the script as ``argv``.
353 The script's exit code (``-1`` may indicate an unhandled VM exception).
356 OSError: If the PhasorRT library cannot be loaded.
359 data = _ffi.from_buffer(
"unsigned char[]", raw)
362 _ptr(state), data, len(raw),
363 module_name.encode(
"utf-8"), argc, argv,
370 state: Optional[StateHandle] =
None,
371 module_name: Optional[str] =
None,
372 args: Sequence[str] = (),
374 """Load a ``.phsb`` file and execute it.
377 path: Path to the ``.phsb`` bytecode file.
378 state: An optional handle from :func:`new_state`.
379 module_name: Name reported in error messages.
380 Defaults to the file's stem.
381 args: Command-line arguments forwarded to the script.
384 The script's exit code.
387 FileNotFoundError: If *path* does not exist.
388 OSError: If the PhasorRT library cannot be loaded.
392 raise FileNotFoundError(f
"Bytecode file not found: {p}")
396 module_name=module_name
or p.stem,
404 state: Optional[StateHandle] =
None,
405 module_name: str = _DEFAULT_MODULE_NAME,
406 module_path: Optional[str] =
None,
407 verbose: bool =
False,
409 """Compile and execute a Phasor source string.
412 script: Phasor source code.
413 state: An optional handle from :func:`new_state`. When ``None``,
414 PhasorRT creates and manages a transient state internally.
415 module_name: Name reported in error messages. Defaults to ``"Python"``.
416 module_path: Directory for resolving compile-time imports.
417 Defaults to the current working directory.
418 verbose: Print the AST to stdout when ``True``.
421 The script's exit code.
424 OSError: If the PhasorRT library cannot be loaded.
426 mod_path = module_path.encode(
"utf-8")
if module_path
else _cwd_bytes()
429 script.encode(
"utf-8"),
430 module_name.encode(
"utf-8"),
439 state: Optional[StateHandle] =
None,
440 module_name: Optional[str] =
None,
441 verbose: bool =
False,
443 """Read and evaluate a ``.phs`` source file.
445 The file's parent directory is automatically used for resolving
446 compile-time imports.
449 path: Path to the ``.phs`` source file.
450 state: An optional handle from :func:`new_state`.
451 module_name: Name reported in error messages.
452 Defaults to the file's stem.
453 verbose: Print the AST to stdout when ``True``.
456 The script's exit code.
459 FileNotFoundError: If *path* does not exist.
460 OSError: If the PhasorRT library cannot be loaded.
464 raise FileNotFoundError(f
"Source file not found: {p}")
466 p.read_text(encoding=
"utf-8"),
468 module_name=module_name
or p.stem,
469 module_path=str(p.resolve().parent),
477 state: Optional[StateHandle] =
None,
478 module_name: str = _DEFAULT_MODULE_NAME,
480 """Compile and execute a Pulsar source string.
483 script: Pulsar source code.
484 state: An optional handle from :func:`new_state`. When ``None``,
485 PhasorRT creates and manages a transient state internally.
486 module_name: Name reported in error messages. Defaults to ``"Python"``.
489 The script's exit code.
492 OSError: If the PhasorRT library cannot be loaded.
496 script.encode(
"utf-8"),
497 module_name.encode(
"utf-8"),
504 state: Optional[StateHandle] =
None,
505 module_name: Optional[str] =
None,
507 """Read and evaluate a ``.pul`` source file.
510 path: Path to the ``.pul`` source file.
511 state: An optional handle from :func:`new_state`.
512 module_name: Name reported in error messages.
513 Defaults to the file's stem.
516 The script's exit code.
519 FileNotFoundError: If *path* does not exist.
520 OSError: If the PhasorRT library cannot be loaded.
524 raise FileNotFoundError(f
"Source file not found: {p}")
526 p.read_text(encoding=
"utf-8"),
528 module_name=module_name
or p.stem,
PHASOR_API int evaluatePUL(void *vmPtr, const char *script, const char *moduleName)
Executes a Pulsar Scripting Language script.
PHASOR_API int evaluatePHS(void *vmPtr, const char *script, const char *moduleName, const char *modulePath, bool verbose)
Executes a Phasor Programming Language script.
PHASOR_API bool freeState(void *vmPtr)
Frees an existing state instance.
PHASOR_API void initStdLib(void *vmPtr)
Register standard library to state instance.
PHASOR_API bool resetState(void *vmPtr, bool resetFunctions, bool resetVariables)
Resets the state.
bytes compile_phs_file(str|Path path, *, Optional[str] module_name=None, Optional[str] module_path=None)
None init_stdlib(StateHandle state)
int evaluate_pul(str script, *, Optional[StateHandle] state=None, str module_name=_DEFAULT_MODULE_NAME)
_ptr(Optional[StateHandle] state)
int evaluate_pul_file(str|Path path, *, Optional[StateHandle] state=None, Optional[str] module_name=None)
None reset_state(StateHandle state, *, bool reset_functions=False, bool reset_variables=False)
None free_state(StateHandle state)
Bytecode _two_pass_compile(compile_fn, *bytes leading_args)
bytes compile_phs(str script, *, str module_name=_DEFAULT_MODULE_NAME, Optional[str] module_path=None)
int evaluate_phs_file(str|Path path, *, Optional[StateHandle] state=None, Optional[str] module_name=None, bool verbose=False)
_build_argv(Sequence[str] args)
int run(Union[bytes, bytearray, Bytecode] bytecode, *, Optional[StateHandle] state=None, str module_name=_DEFAULT_MODULE_NAME, Sequence[str] args=())
bytes compile_pul_file(str|Path path, *, Optional[str] module_name=None)
bytes _to_bytes("Union[bytes, bytearray, Bytecode]" bytecode)
bytes compile_pul(str script, *, str module_name=_DEFAULT_MODULE_NAME)
int evaluate_phs(str script, *, Optional[StateHandle] state=None, str module_name=_DEFAULT_MODULE_NAME, Optional[str] module_path=None, bool verbose=False)
int run_file(str|Path path, *, Optional[StateHandle] state=None, Optional[str] module_name=None, Sequence[str] args=())
int exec(void *state, const unsigned char embeddedBytecode[], size_t embeddedBytecodeSize, const char *moduleName, int argc, const char *argv[])