diff --git a/README.md b/README.md index 0ae757c..793f67a 100644 --- a/README.md +++ b/README.md @@ -30,25 +30,34 @@ CPython](https://andykeep.com/SchemeWorkshop2022/scheme2022-final22.pdf). ### Requirements This module has a few mandatory requirements: -- You must have a recent version of Gambit compiled with the - `--enable-multiple-threaded-vms` option. +- You must have a recent version of Gambit compiled with thread support + either using the configure option `--enable-thread-system`, which is + enabled by default in recent versions of Gambit, or with the configure + option `--enable-multiple-threaded-vms`. - You must have a dynamically linked version of CPython >= 3.7 installed. This module will link against the CPython shared library. - A Windows, Linux or macOS operating system (other OSes have not been tested but might work). ### Installation -You can install the module either by importing it `(import -(github.com/gambit/python))` or directly through gsi: +You can install and compile the program at the command-line as such: ``` sh gsi -install github.com/gambit/python +gsc github.com/gambit/python ``` -Gambit will download and compile the code. During compilation, the module will -perform an automatic discovery of the installed CPython executable. Various C -compiler options will be determined by introspection. This module will create a -`virtualenv` to manage its packages. +or you can download and compile the module lazily by importing it from within +`gsi` or any program compiled with the C backend: + +``` scheme +(import (github.com/gambit/python)) +``` + +In both cases, Gambit will download and compile the code. During compilation, +the module will perform an automatic discovery of the installed CPython +executable. Various C compiler options will be determined by introspection. This +module will create a `virtualenv` to manage its packages. Users can configure the compilation with environment variables: @@ -122,6 +131,6 @@ gsi github.com/gambit/python/test/leaks This software is distributed under the same terms as [Gambit](https://github.com/gambit/gambit). -© Marc Feeley 2021-2022 +© Marc Feeley 2021-2025 -© Marc-André Bélanger 2021-2022 +© Marc-André Bélanger 2021-2025 diff --git a/demo/demo.scm b/demo/demo.scm new file mode 100644 index 0000000..78227ad --- /dev/null +++ b/demo/demo.scm @@ -0,0 +1,19 @@ +#!/usr/bin/env gsi-script + +(define-library (github.com/gambit/python demo) + + (import (..)) ;; relative import of python (preserves the version) + (import (except (gambit) six.infix)) ;; for lambda, this-source-file, etc + (import (_six python)) ;; for \... syntax + + (begin + + (define year 2025) + + (println "Calendar for " year ":") + + \import calendar + + (print \calendar.calendar(`year)) + + (println "Demo source code: " (this-source-file)))) diff --git a/examples/csv.scm b/examples/csv.scm new file mode 100644 index 0000000..f7439f2 --- /dev/null +++ b/examples/csv.scm @@ -0,0 +1,22 @@ +(import (_six python) + (github.com/gambit/python)) + +\import csv + +(define (read-csv path) + \f=open(`path) + \reader=csv.reader(f) + (let loop ((acc '())) + (with-exception-catcher + (lambda (e) + ;; The exception will be a pair (PyObject* . repr(PyObject*)) + \f.close() + (if \isinstance(`(car e), StopIteration) + (reverse acc) ;; Return the result + (write e))) ;; Propagate the exception + ;; Iterate using __next__() until StopIteration is raised + (lambda () (loop (cons \reader.__next__() acc)))))) + +(pretty-print (read-csv (path-expand "~~userlib/github.com/gambit/python/@/examples/data.csv"))) + +;; (("A" "B" "C") ("1" "2" "3")) diff --git a/examples/data.csv b/examples/data.csv new file mode 100644 index 0000000..12ad13e --- /dev/null +++ b/examples/data.csv @@ -0,0 +1,2 @@ +A,B,C +1,2,3 diff --git a/python#.scm b/python#.scm index eb31f91..617705a 100644 --- a/python#.scm +++ b/python#.scm @@ -2,7 +2,7 @@ ;;; File: "python#.scm" -;;; Copyright (c) 2020-2022 by Marc Feeley, All Rights Reserved. +;;; Copyright (c) 2020-2025 by Marc Feeley, All Rights Reserved. ;;; Copyright (c) 2020-2022 by Marc-André Bélanger, All Rights Reserved. ;;;============================================================================ @@ -17,13 +17,13 @@ Py_single_input ;; Initialization, Finalization, and Threads Py_Initialize Py_Finalize -Py_SetPath -Py_SetProgramName -Py_SetPythonHome -;; PySys -PySys_SetArgv -PySys_SetArgvEx +;; These are no longer available: +;; Py_SetPath +;; Py_SetProgramName +;; Py_SetPythonHome +;; PySys_SetArgv +;; PySys_SetArgvEx ;; PyRun_* PyRun_SimpleString diff --git a/python.scm b/python.scm index 5590fea..02a93e8 100644 --- a/python.scm +++ b/python.scm @@ -2,7 +2,7 @@ ;;; File: "python.scm" -;;; Copyright (c) 2020-2022 by Marc Feeley, All Rights Reserved. +;;; Copyright (c) 2020-2025 by Marc Feeley, All Rights Reserved. ;;; Copyright (c) 2020-2022 by Marc-André Bélanger, All Rights Reserved. ;;;============================================================================ @@ -23,7 +23,7 @@ (declare (not safe)) ;; claim code has no type errors (declare (block)) ;; claim no global is assigned -(##namespace ("" +(##namespace ("" pp current-thread make-thread thread-start! thread-sleep! make-mutex mutex-lock! mutex-unlock!)) @@ -260,14 +260,23 @@ ;;;---------------------------------------------------------------------------- -;; Check that Gambit has been compiled with thread support. - (c-declare #<= 13 + , 0 +#endif + )) { dst = ___FAL; } } @@ -1277,7 +1290,8 @@ if (___FIXNUMP(src)) { #endif #ifdef ___BIG_ENDIAN - /* TODO: use _PyLong_FromByteArray(...) after copying bignum */ + printf(\"conversion from big-endian bignum to Python int is not yet supported\\n\"); + exit(1); /* TODO: better error handling! */ #endif } @@ -1405,8 +1419,8 @@ if (PYOBJECTPTR_to_SCMOBJ(num, &num_scmobj, ___RETURN_POS) dst = ___FAL; else { - ___FIELD(dst, 0) = num_scmobj; - ___FIELD(dst, 1) = den_scmobj; + ___VECTORELEM(dst, 0) = num_scmobj; + ___VECTORELEM(dst, 1) = den_scmobj; ___EXT(___release_scmobj) (dst); } ___EXT(___release_scmobj) (den_scmobj); @@ -1421,12 +1435,8 @@ ___return(dst); ") src))) (if dst - (begin - (vector-set! dst 0 (PyObject*->object (vector-ref dst 0))) - (vector-set! dst 1 (PyObject*->object (vector-ref dst 1))) - (if (eqv? (vector-ref dst 1) 1) - (vector-ref dst 0) - (##subtype-set! dst 2))) ;; ratnum subtype = 2 + (##/2 (PyObject*->object (vector-ref dst 0)) + (PyObject*->object (vector-ref dst 1))) (error "PyObject*/Fraction->ratnum conversion error")))) (define ints->PyObject*/Fraction @@ -1927,33 +1937,28 @@ ___return(dst); (define (PyObject*->object src) (define (conv src) -;; (let ((converter (table-ref PyObject*-converters (PyObject*-type-name src) #f))) -;; (if converter -;; (converter src) - (case (car (##foreign-tags src)) - ((PyObject*/None) (PyObject*/None->void src)) - ((PyObject*/bool) (PyObject*/bool->boolean src)) - ((PyObject*/int) (PyObject*/int->exact-integer src)) - ((PyObject*/float) (PyObject*/float->flonum src)) - ((PyObject*/complex) (PyObject*/complex->cpxnum src)) - ((PyObject*/Fraction) (PyObject*/Fraction->ratnum src)) - ((PyObject*/str) (PyObject*/str->string src)) - ((PyObject*/bytes) (PyObject*/bytes->u8vector src)) - ((PyObject*/bytearray) (PyObject*/bytearray->u8vector src)) - ((PyObject*/list) (list-conv src)) - ((PyObject*/tuple) (vector-conv src)) - ((PyObject*/dict) (table-conv src)) - ((PyObject*/function - PyObject*/builtin_function_or_method - PyObject*/method - PyObject*/method_descriptor) (procedure-conv src)) - ((PyObject*/cell) (PyCell_Get src)) - (else - (cond ((= 1 (PyCallable_Check src)) (procedure-conv src)) - ((SchemeObject? src) (SchemeObject->object src)) - (else src)))) -;;)) -) + (case (car (##foreign-tags src)) + ((PyObject*/None) (PyObject*/None->void src)) + ((PyObject*/bool) (PyObject*/bool->boolean src)) + ((PyObject*/int) (PyObject*/int->exact-integer src)) + ((PyObject*/float) (PyObject*/float->flonum src)) + ((PyObject*/complex) (PyObject*/complex->cpxnum src)) + ((PyObject*/Fraction) (PyObject*/Fraction->ratnum src)) + ((PyObject*/str) (PyObject*/str->string src)) + ((PyObject*/bytes) (PyObject*/bytes->u8vector src)) + ((PyObject*/bytearray) (PyObject*/bytearray->u8vector src)) + ((PyObject*/list) (list-conv src)) + ((PyObject*/tuple) (vector-conv src)) + ((PyObject*/dict) (table-conv src)) + ((PyObject*/function + PyObject*/builtin_function_or_method + PyObject*/method + PyObject*/method_descriptor) (procedure-conv src)) + ((PyObject*/cell) (PyCell_Get src)) + (else + (cond ((= 1 (PyCallable_Check src)) (procedure-conv src)) + ((SchemeObject? src) (SchemeObject->object src)) + (else src))))) (define (list-conv src) (let* ((vect (PyObject*/list->vector src)) @@ -2251,7 +2256,6 @@ return_with_check_PyObjectPtr(PyObject_CallFunctionObjArgs(___arg1, ___arg2, ___ #include -#include /* converters */ @@ -2270,7 +2274,7 @@ ___SCMOBJ convert_from_python(PyObject *val) { if (err != ___FIX(___NO_ERR)) { printf("could not convert PyObject* to SCMOBJ\n"); - exit(1); + exit(1); /* TODO: better error handling! */ } #ifdef DEBUG_LOWLEVEL @@ -2297,7 +2301,7 @@ PyObject *convert_to_python(___SCMOBJ val) { if (err != ___FIX(___NO_ERR)) { printf("could not convert SCMOBJ to PyObject*\n"); - exit(1); + exit(1); /* TODO: better error handling! */ } result = ptr; @@ -2385,6 +2389,7 @@ def _pfpc_loop(fpc_state):\n\ else:\n\ message = (_op_error, op)\n\ _pfpc_send(fpc_state, message)\n\ + message = None\n\ \n\ def _pfpc_call(fn, args, kw_keys, kw_vals):\n\ fpc_state = _pfpc_get_fpc_state()\n\ @@ -2427,7 +2432,7 @@ void python_thread_main(___thread *self) { PyObject *m = PyImport_AddModule("__main__"); PyObject *v = PyObject_GetAttrString(m, "_pfpc_start"); - PyObject_CallOneArg(v, python_fpc_state->capsule); /* call _pfpc_start */ + PyObject_CallFunctionObjArgs(v, python_fpc_state->capsule, NULL); /* call _pfpc_start */ GIL_RELEASE(); } @@ -2455,14 +2460,14 @@ ___SCMOBJ procedural_interrupt_execute_fn(void *self, ___SCMOBJ op) { if (___FIXNUMP(scheme_fpc_state)) { printf("heap overflow\n"); - exit(1); + exit(1); /* TODO: better error handling! */ } ___EXT(___set_data_rc)(python_fpc_state, scheme_fpc_state); ___EXT(___register_rc)(___PSP python_fpc_state); - ___FIELD(scheme_fpc_state, 1) = ___GLO__23__23_start_2d_buddy; + ___VECTORELEM(scheme_fpc_state, 1) = ___GLO__23__23_start_2d_buddy; if (___EXT(___POINTER_to_SCMOBJ)(___ps, ___CAST(void*,python_fpc_state), @@ -2472,10 +2477,10 @@ ___SCMOBJ procedural_interrupt_execute_fn(void *self, ___SCMOBJ op) { ___RETURN_POS) != ___FIX(___NO_ERR)) { printf("could not convert python_fpc_state to foreign\n"); - exit(1); + exit(1); /* TODO: better error handling! */ } - ___FIELD(scheme_fpc_state, 3) = python_fpc_state_scmobj; + ___VECTORELEM(scheme_fpc_state, 3) = python_fpc_state_scmobj; #if 0 ___EXT(___release_scmobj)(python_fpc_state_scmobj); @@ -2514,8 +2519,12 @@ fpc_state *alloc_python_fpc_state(___processor_state ___ps) { python_fpc_state->pstate = ___ps; + GIL_ACQUIRE(); + capsule = PyCapsule_New(python_fpc_state, NULL, NULL); + GIL_RELEASE(); + if (capsule == NULL) { printf("could not allocate capsule\n"); ___EXT(___release_rc)(python_fpc_state); @@ -2549,7 +2558,7 @@ void setup_python_fpc_state(___SCMOBJ scheme_fpc_state) { python_fpc_state = alloc_python_fpc_state(___PSTATE); - ___FIELD(___FIELD(scheme_fpc_state, 3),___FOREIGN_PTR) = ___CAST(___WORD, python_fpc_state); + ___FOREIGN_PTR_FIELD(___VECTORELEM(scheme_fpc_state, 3)) = ___CAST(___WORD, python_fpc_state); ___EXT(___set_data_rc)(python_fpc_state, scheme_fpc_state); @@ -2561,7 +2570,7 @@ void setup_python_fpc_state(___SCMOBJ scheme_fpc_state) { if (___EXT(___thread_create)(&python_fpc_state->python_thread) != ___FIX(___NO_ERR)) { - printf("can't create Python thread (was Gambit configured with --enable-multiple-threaded-vms?)\n"); + printf("can't create Python thread\n"); exit(1); /* TODO: better error handling! */ } @@ -2574,7 +2583,7 @@ void setup_python_fpc_state(___SCMOBJ scheme_fpc_state) { void cleanup_python_fpc_state(___SCMOBJ scheme_fpc_state) { fpc_state *python_fpc_state = - ___CAST(fpc_state*,___FIELD(___FIELD(scheme_fpc_state, 3),___FOREIGN_PTR)); + ___CAST(fpc_state*,___FOREIGN_PTR_FIELD(___VECTORELEM(scheme_fpc_state, 3))); #ifdef DEBUG_LOWLEVEL printf("cleanup_python_fpc_state() enter\n"); @@ -2597,7 +2606,7 @@ void sfpc_send(___SCMOBJ scheme_fpc_state, PyObject *message) { PYOBJECTPTR_INCREF(message, "sfpc_send"); fpc_state *python_fpc_state = - ___CAST(fpc_state*,___FIELD(___FIELD(scheme_fpc_state, 3),___FOREIGN_PTR)); + ___CAST(fpc_state*,___FOREIGN_PTR_FIELD(___VECTORELEM(scheme_fpc_state, 3))); #ifdef DEBUG_LOWLEVEL printf("sfpc_send() setting python_fpc_state->message\n"); @@ -2616,10 +2625,10 @@ void sfpc_send(___SCMOBJ scheme_fpc_state, PyObject *message) { PyObject *sfpc_recv(___SCMOBJ scheme_fpc_state) { fpc_state *python_fpc_state = - ___CAST(fpc_state*,___FIELD(___FIELD(scheme_fpc_state, 3),___FOREIGN_PTR)); + ___CAST(fpc_state*,___FOREIGN_PTR_FIELD(___VECTORELEM(scheme_fpc_state, 3))); #ifdef DEBUG_LOWLEVEL - printf("sfpc_recv() returning python_fpc_state->message\n"); + PYOBJECTPTR_REFCNT_SHOW(python_fpc_state->message, "sfpc_recv() returning python_fpc_state->message"); #endif return python_fpc_state->message; @@ -2688,7 +2697,7 @@ static PyObject *pfpc_recv(PyObject *self, PyObject *args) { Py_END_ALLOW_THREADS #ifdef DEBUG_LOWLEVEL - printf("pfpc_recv() returning python_fpc_state->message\n"); + PYOBJECTPTR_REFCNT_SHOW(python_fpc_state->message, "pfpc_recv() returning python_fpc_state->message"); #endif return python_fpc_state->message; @@ -2830,7 +2839,7 @@ end-of-c-declare (define (make-null-fpc_state*) (let ((x ((c-lambda () fpc_state* "___return(___CAST(void*,1));")))) - ((c-lambda (scheme-object) void "___FIELD(___ARG1,___FOREIGN_PTR) = ___CAST(___WORD,NULL);") x) + ((c-lambda (scheme-object) void "___FOREIGN_PTR_FIELD(___ARG1) = ___CAST(___WORD,NULL);") x) x)) (define scheme-fpc-state-table #f) @@ -2903,7 +2912,7 @@ end-of-c-declare (define (sfpc-recv scheme-fpc-state) ;; (pp (list 'sfpc-recv scheme-fpc-state)) (mutex-lock! (vector-ref scheme-fpc-state 2)) - ((c-lambda (scheme-object) PyObject*!own "sfpc_recv") + ((c-lambda (scheme-object) PyObject* "sfpc_recv") scheme-fpc-state)) #; @@ -3056,10 +3065,12 @@ end-of-c-declare (define python-exec (sfpc-send-recv (get-scheme-fpc-state!) (vector op-get-exec))) +;; Create a _SchemeProcedure instance. +;; obj is a SchemeObject that contains a pointer to a Scheme function. (define python-SchemeProcedure (let ((_SchemeProcedure (python-eval "foreign(_SchemeProcedure)"))) (lambda (obj) - (PyObject_CallFunctionObjArgs1 _SchemeProcedure (object->SchemeObject obj))))) + (PyObject_CallFunctionObjArgs1 _SchemeProcedure obj)))) ((python-eval "__import__('sys').path.append") (path-expand "site-packages" python-venv-lib-dir)) diff --git a/python.sld b/python.sld index d4b8c88..09b61b2 100644 --- a/python.sld +++ b/python.sld @@ -2,7 +2,7 @@ ;;; File: "python.sld" -;;; Copyright (c) 2020-2022 by Marc Feeley, All Rights Reserved. +;;; Copyright (c) 2020-2025 by Marc Feeley, All Rights Reserved. ;;; Copyright (c) 2020-2022 by Marc-André Bélanger, All Rights Reserved. ;;;============================================================================ @@ -21,13 +21,13 @@ ;; Initialization, Finalization, and Threads Py_Initialize Py_Finalize - Py_SetPath - Py_SetProgramName - Py_SetPythonHome - ;; PySys - PySys_SetArgv - PySys_SetArgvEx +;; These are no longer available: +;; Py_SetPath +;; Py_SetProgramName +;; Py_SetPythonHome +;; PySys_SetArgv +;; PySys_SetArgvEx ;; PyRun_* PyRun_SimpleString diff --git a/test/test.scm b/test/test.scm index 79fe673..dea53b6 100644 --- a/test/test.scm +++ b/test/test.scm @@ -66,6 +66,10 @@ (let ((p (python-eval "foreign(lambda x: x)"))) \s=`p (test-assert \s==`p)) +(let ((p (lambda (x) x))) + \s=`(scheme p) + (test-assert (eq? \s p))) +(test-equal (cos 0) \(`cos)(0)) ;; *args and **kwargs (let ((p (python-eval "lambda x, *args, **kwargs: args if x else kwargs")))