diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 00000000..190bf3c6 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.12) + +project(libsbcl_librarian C) + +include(GNUInstallDirs) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_library(sbcl_librarian SHARED libsbcl_librarian.c libsbcl_librarian.h libsbcl_librarian_err.h entry_point.c) +target_link_directories(sbcl_librarian PRIVATE $ENV{BUILD_PREFIX}/lib) +target_link_libraries(sbcl_librarian sbcl) + +add_custom_command( + OUTPUT libsbcl_librarian.c libsbcl_librarian.h + COMMAND ${CMAKE_COMMAND} -E env CL_SOURCE_REGISTRY=${CMAKE_CURRENT_SOURCE_DIR}/..// sbcl --script ${CMAKE_CURRENT_SOURCE_DIR}/generate-bindings.lisp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +install(TARGETS sbcl_librarian + LIBRARY + RUNTIME +) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsbcl_librarian.core TYPE LIB) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsbcl_librarian.h TYPE INCLUDE) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libsbcl_librarian_err.h TYPE INCLUDE) diff --git a/lib/VERSION.txt b/lib/VERSION.txt new file mode 100644 index 00000000..6e8bf73a --- /dev/null +++ b/lib/VERSION.txt @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/entry_point.c b/lib/entry_point.c new file mode 100644 index 00000000..c9cbd8b9 --- /dev/null +++ b/lib/entry_point.c @@ -0,0 +1,70 @@ +#define LIBSBCL_API_BUILD +#ifdef __linux__ +#define _GNU_SOURCE +#endif + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#endif + +#include +#include +#include + +#include "libsbcl_librarian.h" + +#define BUF_SIZE 1024 + +extern char *sbcl_runtime_home; +extern char *sbcl_runtime; +extern char *dir_name(char *path); +extern int initialize_lisp(int argc, const char *argv[], char *envp[]); + +static void do_initialize_lisp(const char *libsbcl_path) +{ + char *libsbcl_dir = dir_name(libsbcl_path); + int libsbcl_dir_len = strlen(libsbcl_dir); + int core_path_size = libsbcl_dir_len + sizeof("libsbcl_librarian.core") + 1; + char *core_path = malloc(core_path_size); + + snprintf(core_path, core_path_size, "%slibsbcl_librarian.core", libsbcl_dir); + + const char *init_args[] = {"", "--dynamic-space-size", "8192", "--core", core_path, "--noinform", "--no-userinit"}; + + initialize_lisp(sizeof(init_args) / sizeof(init_args[0]), init_args, NULL); + + int sbcl_home_path_size = libsbcl_dir_len + sizeof("sbcl") + 1; + int libsbcl_path_size = strlen(libsbcl_path) + 1; + sbcl_runtime = malloc(libsbcl_path_size); + strncpy(sbcl_runtime, libsbcl_path, libsbcl_path_size); + sbcl_runtime_home = malloc(sbcl_home_path_size); + snprintf(sbcl_runtime_home, sbcl_home_path_size, "%ssbcl", libsbcl_dir); + lisp_funcall0_by_name("os-cold-init-or-reinit", "sb-sys"); +} + +#ifdef _WIN32 +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) { + char libsbcl_path[BUF_SIZE]; + + GetModuleFileNameA(hinstDLL, libsbcl_path, BUF_SIZE); + do_initialize_lisp(libsbcl_path); + } + + return TRUE; +} +#else +__attribute__((constructor)) +static void init(void) +{ + Dl_info info; + + dladdr(do_initialize_lisp, &info); + do_initialize_lisp(info.dli_fname); +} +#endif diff --git a/lib/generate-bindings.lisp b/lib/generate-bindings.lisp new file mode 100644 index 00000000..b226d0a2 --- /dev/null +++ b/lib/generate-bindings.lisp @@ -0,0 +1,17 @@ +(require "asdf") + +(asdf:load-system :sbcl-librarian) +(handler-bind ((deprecation-condition #'continue)) + (asdf:load-system :swank)) +(in-package #:sbcl-librarian) + +(define-aggregate-library libsbcl-librarian (:function-linkage "LIBSBCL_LIBRARIAN_API") + diagnostics + environment + errors + handles + loader) + +(build-bindings libsbcl-librarian "." :omit-init-function t) +(build-python-bindings libsbcl-librarian "." :omit-init-call t) +(build-core-and-die libsbcl-librarian ".") diff --git a/lib/libsbcl_librarian_err.h b/lib/libsbcl_librarian_err.h new file mode 100644 index 00000000..910bf463 --- /dev/null +++ b/lib/libsbcl_librarian_err.h @@ -0,0 +1,6 @@ +typedef enum { + LISP_ERR_SUCCESS = 0, + LISP_ERR_FAILURE = 1, + LISP_ERR_BUG = 2, + LISP_ERR_FATAL = 3 +} lisp_err_t; diff --git a/lib/python/pyproject.toml b/lib/python/pyproject.toml new file mode 100644 index 00000000..2b6f0b16 --- /dev/null +++ b/lib/python/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "sbcl_librarian" +dynamic = ["version"] + +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.package-data] +"*" = ["py.typed"] + +[tool.setuptools.dynamic] +version = {attr = "sbcl_librarian.version.__version__"} diff --git a/lib/python/src/sbcl_librarian/__init__.py b/lib/python/src/sbcl_librarian/__init__.py new file mode 100644 index 00000000..03e1498a --- /dev/null +++ b/lib/python/src/sbcl_librarian/__init__.py @@ -0,0 +1,4 @@ +from . import errors, raw, wrapper +from .version import __version__ + +__all__ = ["errors", "raw", "wrapper", "__version__"] diff --git a/lib/python/src/sbcl_librarian/debug.py b/lib/python/src/sbcl_librarian/debug.py new file mode 100644 index 00000000..d3ac24f4 --- /dev/null +++ b/lib/python/src/sbcl_librarian/debug.py @@ -0,0 +1,140 @@ +"""Functions for debugging Lisp""" + +import ctypes +import platform +from enum import Enum + +import sbcl_librarian.raw + + +def enable_backtrace() -> None: + """ + Enable printing backtraces when Lisp errors are + signaled. + """ + sbcl_librarian.raw.enable_backtrace(1) + + +def disable_backtrace() -> None: + """ + Disable printing backtraces when Lisp errors are + signaled. + """ + sbcl_librarian.raw.enable_backtrace(0) + + +def disable_debugger() -> None: + """Disable the Lisp debugger""" + sbcl_librarian.raw.lisp_disable_debugger() + + +def enable_debugger() -> None: + """Enable the lisp debugger""" + sbcl_librarian.raw.lisp_enable_debugger() + + +def gc() -> None: + """Explicitly run the Lisp garbage collector.""" + sbcl_librarian.raw.lisp_gc() + + +def memory_report() -> None: + """Print a report about memory use to standard out.""" + sbcl_librarian.raw.lisp_memory_report() + + +def lisp_memory() -> int: + """Return the memory currently used by the Lisp process""" + result = ctypes.c_ulonglong() + sbcl_librarian.raw.lisp_dynamic_usage(result) + return result.value + + +def start_swank_server(port: int) -> None: + """ + Start a swank server so that Lisp devs can interact with the Lisp + process directly. + """ + if port <= 1024: + raise ValueError("Port should be greater than 1024") + + sbcl_librarian.raw.lisp_start_swank_server(port) + + +def crash() -> None: + """Signal crash in the Lisp process.""" + sbcl_librarian.raw.crash() + + +ProfilingMode = Enum("ProfilingMode", [":CPU", ":ALLOC", ":TIME"]) + + +def start_profiling( + max_samples: int = 500, + mode: ProfilingMode = ProfilingMode[":CPU"], + sample_interval: float = 0.01, +) -> None: + """Start profiling the Lisp image. + + `max_samples`: The meximum number of stack traces to collect. + + `mode`: :CPU profiles cpu time, :TIME profiles wall clock time, + :ALLOC traces thread-local allocation region overflow handlers. + + `sample_interval`: the number of seconds between samples. + """ + + if platform.system() == "Windows": + raise Exception("Profiling is not supported on windows") + + if not max_samples > 0: + raise ValueError(f"max_samples must be greater than zero, not {max_samples}") + + if not sample_interval > 0.0: + raise ValueError(f"sample_interval must be greater than zero, not {sample_interval}") + + args = ( + f"(:max-samples {max_samples} :mode {mode.name} :sample-interval {sample_interval})".encode( + "utf-8" + ) + ) + sbcl_librarian.raw.lisp_start_profiling(args) + + +def stop_profiling() -> None: + """Stop the profiler.""" + if platform.system() == "Windows": + raise Exception("Profiling is not supported on windows") + + sbcl_librarian.raw.lisp_stop_profiling() + + +def profiler_report() -> None: + """Print a report of the results of profiling.""" + if platform.system() == "Windows": + raise Exception("Profiling is not supported on windows") + + sbcl_librarian.raw.lisp_profiler_report("(:type :FLAT)".encode("utf-8")) + + +def profiler_reset() -> None: + """Reset the proflier.""" + if platform.system() == "Windows": + raise Exception("Profiling is not supported on windows") + + sbcl_librarian.raw.lisp_reset_profiler() + + +def handle_count() -> int: + """Get the number of (apparently living) handles to Lisp objects.""" + result = ctypes.c_int() + sbcl_librarian.raw.lisp_handle_count(result) + return result.value + + +def print_error() -> None: + """Print the most recent Lisp error messages.""" + result = ctypes.c_char_p() + sbcl_librarian.raw.get_error_message(result) + if result.value is not None: + print(result.value.decode("utf-8")) # noqa: T201 diff --git a/lib/python/src/sbcl_librarian/errors.py b/lib/python/src/sbcl_librarian/errors.py new file mode 100644 index 00000000..7151baa4 --- /dev/null +++ b/lib/python/src/sbcl_librarian/errors.py @@ -0,0 +1,7 @@ +class lisp_err_t(int): + _map = { + 0: "LISP_ERR_SUCCESS", + 1: "LISP_ERR_FAILURE", + 2: "LISP_ERR_BUG", + 3: "LISP_ERR_FATAL", + } diff --git a/lib/python/src/sbcl_librarian/fixtures.py b/lib/python/src/sbcl_librarian/fixtures.py new file mode 100644 index 00000000..c710a501 --- /dev/null +++ b/lib/python/src/sbcl_librarian/fixtures.py @@ -0,0 +1,32 @@ +import gc +from typing import Generator + +import pytest + +import sbcl_librarian.debug as debug + + +@pytest.fixture +def check_handle_leaks() -> Generator[None, None, None]: + """ + Fixture to check that all lisp handles are freed by the end of a test. + + Useful for ensuring that memory leaks within lisp do not occur. + """ + + # Perform a GC to ensure any previously used handles are cleaned up + gc.collect() + + # Check that there are no active handles before the test + pre_count = debug.handle_count() + + yield + + # Perform a GC to ensure any handles are cleaned up + gc.collect() + + # Check that no new handles were created + post_count = debug.handle_count() + assert post_count == pre_count, f"{post_count - pre_count} lisp handles were leaked." + + return None diff --git a/lib/python/src/sbcl_librarian/version.py b/lib/python/src/sbcl_librarian/version.py new file mode 100644 index 00000000..6831c847 --- /dev/null +++ b/lib/python/src/sbcl_librarian/version.py @@ -0,0 +1,9 @@ +import importlib.metadata +from pathlib import Path + +try: + path = Path(__file__).parents[3] / "VERSION.txt" + with Path.open(path, "r") as f: + __version__ = f.readline().strip() +except FileNotFoundError: + __version__ = importlib.metadata.version(__name__.split(".")[0]) diff --git a/lib/python/src/sbcl_librarian/wrapper.py b/lib/python/src/sbcl_librarian/wrapper.py new file mode 100644 index 00000000..518a8e24 --- /dev/null +++ b/lib/python/src/sbcl_librarian/wrapper.py @@ -0,0 +1,132 @@ +import ctypes +import functools +import logging +import pickle +import platform +import signal +import sys +from typing import Any, Callable + +import _signal # type: ignore + +import sbcl_librarian.raw +import sbcl_librarian.errors + + +logger = logging.getLogger(__name__) + + +class LispError(Exception): + pass + + +class LispBug(Exception): + pass + + +class LispFatal(Exception): + pass + + +LispHandle = ctypes.c_void_p + + +class LispObject: + """A base class for wrapped Lisp objects. + + Each such Python object maintains a handle to a Lisp object. + This handle is, by default, released on Python garbage collection. + """ + + def __init__(self, handle: LispHandle, release: bool = True): + """Construct a new LispObject. + + Args: + handle: the handle to wrap + release: if True, release the handle when this object is deleted + """ + self._handle = handle + self._release = release + + @property + def handle(self) -> LispHandle: + return self._handle + + def __eq__(self, other: Any) -> bool: + return isinstance(other, LispObject) and sbcl_librarian.raw.lisp_handle_eq( + self.handle, other.handle + ) + + def __del__(self) -> None: + if self._release: + # https://stackoverflow.com/questions/8590238/unable-to-reference-an-imported-module-in-del + try: # noqa: SIM105 + sbcl_librarian.raw.lisp_release_handle(self.handle) + except AttributeError: + pass + + def __getstate__(self) -> None: + raise pickle.PicklingError("Unable to pickle Lisp object.") + + +SIGINT = int(signal.SIGINT) +SIG_UNBLOCK = int(signal.SIG_UNBLOCK) if platform.system() != "Windows" else None +MASK_1 = [int(signal.SIGSEGV), int(signal.SIGTRAP)] if platform.system() != "Windows" else None +MASK_2 = [int(signal.SIGINT)] if platform.system() != "Windows" else None + + +def lift_fn(name: str, fn: Callable[..., Any]) -> Callable[..., Any]: + macos = platform.system() == "Darwin" + linux = platform.system() == "Linux" + + def safe_call(args: Any, kwargs: Any) -> Any: + handler = _signal.getsignal( # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType] + SIGINT + ) + args = tuple(a.handle if isinstance(a, LispObject) else a for a in args) + retval = None + logger.debug("%s%s", name, args) + try: + # The SBCL runtime uses some signals on Linux: + # https://github.com/sbcl/sbcl/blob/master/src/runtime/interrupt.c#L27-L39 + if linux: + _signal.pthread_sigmask( # pyright: ignore[reportUnknownMemberType] + SIG_UNBLOCK, MASK_1 + ) + retval = fn(*args, **kwargs) + finally: + _signal.signal(SIGINT, handler) # pyright: ignore[reportUnknownMemberType] + # SBCL runtime may have blocked SIGINT, so unblock + if macos: + _signal.pthread_sigmask( # pyright: ignore[reportUnknownMemberType] + SIG_UNBLOCK, MASK_2 + ) + + return retval + + @functools.wraps(fn) + def with_exceptions(*args: Any, **kwargs: Any) -> Any: + result = safe_call(args, kwargs) + if result != 0: + if result == 3: + raise LispFatal( + "SBCL crashed with a fatal, non-recoverable error. All subsequent calls into Lisp will raise the same exception." + ) + msg = ctypes.c_char_p() + sbcl_librarian.raw.get_error_message(ctypes.byref(msg)) + if result == 1: + raise LispError(msg.value and msg.value.decode("utf-8")) + else: + raise LispBug(msg.value and msg.value.decode("utf-8")) + return result + + return with_exceptions + + +__all__ = [ + "LispError", + "LispBug", + "LispHandle", + "LispObject", + "lift_fn" +] diff --git a/recipe/bld.bat b/recipe/bld.bat new file mode 100644 index 00000000..32403a29 --- /dev/null +++ b/recipe/bld.bat @@ -0,0 +1 @@ +pwsh -file build.ps1 diff --git a/recipe/build.ps1 b/recipe/build.ps1 new file mode 100644 index 00000000..bfd57cde --- /dev/null +++ b/recipe/build.ps1 @@ -0,0 +1,24 @@ +# Build sbcl +$env:CHERE_INVOKING="yes" +$env:MSYS2_PATH_TYPE="inherit" +$env:MSYSTEM="MINGW64" +C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo .." + +# Install SBCL into Conda prefix +pushd ../sbcl +robocopy .\output $env:LIBRARY_BIN sbcl.core +robocopy .\src\runtime $env:LIBRARY_BIN sbcl.exe +robocopy .\obj\sbcl-home\contrib $env:LIBRARY_BIN\contrib /e +robocopy .\src\runtime $env:LIBRARY_LIB libsbcl.a +popd + +# Build libsbcl +C:\msys64\usr\bin\bash.exe -lc 'cmake --build .' +C:\msys64\usr\bin\bash.exe -lc "cmake --install . --prefix=$env:PREFIX" +popd + +# Un/set SBCL_HOME on environment de/activation +mkdir -p $env:CONDA_PREFIX\etc\conda\activate.d +mkdir -p $env:CONDA_PREFIX\etc\conda\deactivate.d +"set SBCL_HOME=%CONDA_PREFIX%\Library\bin" | Out-File $env:CONDA_PREFIX\etc\conda\activate.d\env_vars.bat +"set SBCL_HOME=" | Out-File $env:CONDA_PREFIX\etc\conda\deactivate.d\env_vars.bat diff --git a/recipe/build.sh b/recipe/build.sh new file mode 100644 index 00000000..f51900ab --- /dev/null +++ b/recipe/build.sh @@ -0,0 +1,26 @@ +conda install --offline /Users/kssingh/sbcl-2.2.4-10__sbcl_2.2.4.0_gb0f6cecc4.tar.bz2 +SBCL_HOME= + +if [[ "$OSTYPE" == "darwin"* && $(uname -m) == "arm64" ]]; then + : +else + export LD_LIBRARY_PATH=$CONDA_PREFIX/lib/sbcl # required because conda activation scripts are not run on install + + mkdir -p $PREFIX/etc/conda/activate.d + mkdir -p $PREFIX/etc/conda/deactivate.d + printf '#/bin/sh\n\nexport LD_LIBRARY_PATH=$CONDA_PREFIX/lib/sbcl\n' > $PREFIX/etc/conda/activate.d/env_vars.sh + printf '#/bin/sh\n\nunset LD_LIBRARY_PATH' > $PREFIX/etc/conda/deactivate.d/env_vars.sh +fi + +pushd lib +mkdir build +cd build +# Build libsbcl +cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. +cmake --build . +cmake --install . --prefix=$PREFIX +popd + +# Package Python library +cp lib/build/libsbcl_librarian.py lib/python/src/sbcl_librarian/raw.py +$PYTHON -m pip install lib/python/ --no-deps --ignore-installed -vv diff --git a/recipe/conda_build_config.yaml b/recipe/conda_build_config.yaml new file mode 100644 index 00000000..d0384de4 --- /dev/null +++ b/recipe/conda_build_config.yaml @@ -0,0 +1,9 @@ +python: + - 3.10 + - 3.11 + - 3.12 + +c_compiler: # [win] +- vs2022 # [win] +cxx_compiler: # [win] +- vs2022 # [win] diff --git a/recipe/meta.yaml b/recipe/meta.yaml new file mode 100644 index 00000000..5bec8231 --- /dev/null +++ b/recipe/meta.yaml @@ -0,0 +1,27 @@ +{% set version = load_file_regex(load_file="../lib/VERSION.txt", regex_pattern="(.*)\n", from_recipe_dir=True).group(1) %} + +package: + name: sbcl-librarian + version: {{ version }} + +source: + - path: ../sbcl-librarian.asd + - path: ../src + folder: src + - path: ../lib + folder: lib + - git_url: https://github.com/slime/slime.git + folder: slime + - path: build.ps1 # [win] + +requirements: + build: +# - {{ compiler('c') }} +# - {{ compiler('cxx') }} + - python + - cmake + host: + - sbcl + - python {{ python }} + run: + - python {{ python }} diff --git a/sbcl-librarian.asd b/sbcl-librarian.asd index 597145ba..cdb43994 100644 --- a/sbcl-librarian.asd +++ b/sbcl-librarian.asd @@ -11,9 +11,12 @@ :pathname "src/" :components ((:file "package") (:file "asdf-utils") + (:file "util") (:file "types") (:file "function") (:file "api") + (:file "conditions") + (:file "errors") (:file "library") (:file "bindings") (:file "python-bindings") @@ -21,7 +24,8 @@ (:file "loader") (:file "environment") (:file "fasl-lib") - (:file "diagnostics"))) + (:file "diagnostics") + )) (asdf:defsystem #:sbcl-librarian/project :description "Project skeleton builder for SBCL-LIBRARIAN" diff --git a/src/api.lisp b/src/api.lisp index 6bdccd84..22337cfc 100644 --- a/src/api.lisp +++ b/src/api.lisp @@ -99,17 +99,17 @@ Prepends FUNCTION-PREFIX to generated function names, and wraps error handling a :documentation "A list of specifications.")) (:documentation "A specification of functions and types for export to a shared library.")) -(defmacro define-api (name (&key error-map (function-prefix "")) +(defmacro define-api (name (&key (function-prefix "")) &body specs) "Define an API. In addition to constructing a suitable API object, this also ensures that alien callable definitions are defined." `(progn - ,@(callable-definitions-from-spec function-prefix error-map specs) + ,@(callable-definitions-from-spec function-prefix 'default-error-map specs) (defvar ,name (make-instance 'api :name ',name - :error-map ',error-map + :error-map 'default-error-map :function-prefix ,function-prefix :specs ',specs)))) diff --git a/src/bindings.lisp b/src/bindings.lisp index 4ed1641d..d1c53c57 100644 --- a/src/bindings.lisp +++ b/src/bindings.lisp @@ -106,6 +106,7 @@ (let ((guard (format nil "_~A_h" c-name))) (format stream "#ifndef ~A~%" guard) (format stream "#define ~A~%~%" guard)) + (format stream "#include ~%~%") (when linkage (write-linkage-macro linkage build-flag stream)) (dolist (api (library-apis library)) diff --git a/src/conditions.lisp b/src/conditions.lisp index 8e90e5b5..1add7e70 100644 --- a/src/conditions.lisp +++ b/src/conditions.lisp @@ -42,14 +42,14 @@ :accessor lisp-bug-args) (backtrace :initarg :backtrace :accessor lisp-bug-backtrace - :initform (error "backtrace is required")) - (context :initform (list) + :initform (required 'backtrace)) + (context :initform nil :accessor lisp-bug-context)) (:report (lambda (c s) (with-slots (reason args backtrace context) c (let ((*print-circle* nil)) (format s "Internal lisp bug: ~?~% -If you are seeing this, please file an issue on Gitlab and include this error message in the description. +If you are seeing this, please file an issue and include this error message in the description. ~A Context: @@ -57,7 +57,7 @@ Context: ~A" reason args - *gitlab-issue-url* + "TODO" context (if *print-backtrace-in-bug* backtrace diff --git a/src/diagnostics.lisp b/src/diagnostics.lisp index 75f047fa..da7812cf 100644 --- a/src/diagnostics.lisp +++ b/src/diagnostics.lisp @@ -36,7 +36,7 @@ (defun profiler-report (args) (apply #'sb-sprof:report (read-from-string args))) -(sbcl-librarian:define-api diagnostics (:function-prefix "") +(define-api diagnostics (:function-prefix "") (:function (("lisp_memory_report" memory-report) :void ()) (("lisp_dynamic_usage" sb-kernel:dynamic-usage) :uint64 ()) diff --git a/src/errors.lisp b/src/errors.lisp index 1ca0c196..d05e1ffa 100644 --- a/src/errors.lisp +++ b/src/errors.lisp @@ -26,47 +26,46 @@ (loop (push 1 test))))) (define-error-map default-error-map error-type (:no-error 0 :fatal-error 3) - ((warning #'continue) - - (sbcl-librarian:lisp-error - (lambda (c) - (when *show-backtrace* - (sb-debug:print-backtrace - :stream *error-output* - :emergency-best-effort t)) - (setf *error-message* (format nil "~A" c)) - (return-from default-error-map 1))) - - (sbcl-librarian:lisp-bug - (lambda (c) - (when *show-backtrace* - (sb-debug:print-backtrace - :stream *error-output* - :emergency-best-effort t)) - - (let ((sbcl-librarian:*print-backtrace-in-bug* t)) - (setf *error-message* (format nil "~A" c))) - - (return-from default-error-map 2))) - - (error - (lambda (c) - (let ((bug (make-instance 'sbcl-librarian:lisp-bug - :reason (format nil "~A" c) - :args nil - :backtrace (with-output-to-string (s) - (sb-debug:print-backtrace - :stream s - :emergency-best-effort t))))) - - (let ((sbcl-librarian:*print-backtrace-in-bug* t)) - (setf *error-message* (format nil "~A" bug))) - - (return-from default-error-map 2)))))) - -(sbcl-librarian:define-api errors (:error-map default-error-map) - (:literal "/* lisp */") - (:type error-type) + ((cl:warning #'continue) + + (lisp-error + (lambda (c) + (when *show-backtrace* + (sb-debug:print-backtrace + :stream *error-output* + :emergency-best-effort t)) + (setf *error-message* (format nil "~A" c)) + (return-from default-error-map 1))) + + (lisp-bug + (lambda (c) + (when *show-backtrace* + (sb-debug:print-backtrace + :stream *error-output* + :emergency-best-effort t)) + + (let ((*print-backtrace-in-bug* t)) + (setf *error-message* (format nil "~A" c))) + + (return-from default-error-map 2))) + + (cl:error + (lambda (c) + (let ((bug (make-instance 'lisp-bug + :reason (format nil "~A" c) + :args nil + :backtrace (with-output-to-string (s) + (sb-debug:print-backtrace + :stream s + :emergency-best-effort t))))) + + (let ((*print-backtrace-in-bug* t)) + (setf *error-message* (format nil "~A" bug))) + + (return-from default-error-map 2)))))) + +(define-api errors () + (:literal "/* errors */") (:function (get-error-message :string ()) (enable-backtrace :void ((on :int))) diff --git a/src/package.lisp b/src/package.lisp index 29880f7f..38beea09 100644 --- a/src/package.lisp +++ b/src/package.lisp @@ -1,8 +1,13 @@ ;;;; package.lisp +(require "sb-sprof") (require :sb-sprof) (defpackage #:sbcl-librarian + (:shadow + #:error + #:warning + #:assert) (:use #:cl) (:export #:define-handle-type #:define-enum-type @@ -20,5 +25,10 @@ #:loader #:handles - #:environment)) + #:environment + + #:error + #:warning + #:bug + #:unreachable)) diff --git a/src/python-bindings.lisp b/src/python-bindings.lisp index 42473a3d..42fdd1e6 100644 --- a/src/python-bindings.lisp +++ b/src/python-bindings.lisp @@ -12,11 +12,12 @@ (format nil "~a = ~a.~a ~a.restype = ~a -~a.argtypes = [~{~a~^, ~}]" +~a.argtypes = [~{~a~^, ~}] +~:[~;~a = sbcl_librarian.wrapper.lift_fn(\"~:*~a\", ~:*~a)~%~]" ;; First line (coerce-to-c-name callable-name) - library-name - (coerce-to-c-name callable-name) + library-name + (coerce-to-c-name callable-name) ;; Second line (coerce-to-c-name callable-name) (python-type return-type) @@ -26,7 +27,10 @@ (loop :for (name type) :in typed-lambda-list :collect (python-type type)) (and result-type - (list (format nil "POINTER(~a)" (python-type result-type)))))))) + (list (format nil "POINTER(~a)" (python-type result-type))))) + ;; Fourth line (optional) + (eql 'error-type return-type) + (coerce-to-c-name callable-name)))) (defun write-default-python-header (library stream &optional (omit-init-call nil) (library-path nil)) @@ -35,12 +39,14 @@ (format stream "from ctypes import *~%") (format stream "from ctypes.util import find_library~%") (format stream "from pathlib import Path~%~%") + (format stream "import sbcl_librarian.wrapper~%") + (format stream "from sbcl_librarian.errors import lisp_err_t~%~%") (if library-path (format stream "libpath = Path(\"~a\")~%~%" library-path) (progn (format stream "try:~%") - (format stream " libpath = Path(find_library('~a')).resolve()~%" name) + (format stream " libpath = Path(find_library('~a')).resolve()~%" (subseq name 3)) (format stream "except TypeError as e:~%") (format stream " raise Exception('Unable to locate ~a') from e~%~%" name))) diff --git a/src/util.lisp b/src/util.lisp new file mode 100644 index 00000000..97c053bd --- /dev/null +++ b/src/util.lisp @@ -0,0 +1,6 @@ +(in-package #:sbcl-librarian) + +(defun required (name) + "A function to call as a slot initializer when it's required." + (declare (type symbol name)) + (error "A slot ~S (of package ~S) is required but not supplied" name (symbol-package name)))