From 4029bf7a179c8eb76aade3470c77c1dc3f25600d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Lindqvist?= Date: Fri, 24 Oct 2014 01:01:15 +0200 Subject: [PATCH] python: feature to create python callbacks/functions, now you can call hofs like map and reduce --- extra/python/ffi/ffi.factor | 34 ++++++++++++++++- extra/python/objects/objects.factor | 10 ++++- extra/python/python-docs.factor | 23 ++++++++++-- extra/python/python.factor | 12 +++++- extra/python/syntax/syntax-tests.factor | 50 +++++++++++++++++++++++-- 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/extra/python/ffi/ffi.factor b/extra/python/ffi/ffi.factor index d73fea8f0b..aa9283474f 100644 --- a/extra/python/ffi/ffi.factor +++ b/extra/python/ffi/ffi.factor @@ -1,5 +1,6 @@ USING: alien alien.c-types alien.destructors alien.libraries -alien.libraries.finder alien.syntax assocs kernel sequences system ; +alien.libraries.finder alien.syntax assocs classes.struct kernel sequences +system ; IN: python.ffi ! << "python" { "3.0" "3" "2.7" "2.6" } ! Python 3 has a different api, enable someday @@ -14,6 +15,32 @@ LIBRARY: python C-TYPE: PyObject +! Methods +CONSTANT: METH_OLDARGS 0x0000 +CONSTANT: METH_VARARGS 0x0001 +CONSTANT: METH_KEYWORDS 0x0002 +CONSTANT: METH_NOARGS 0x0004 +CONSTANT: METH_O 0x0008 +CONSTANT: METH_CLASS 0x0010 +CONSTANT: METH_STATIC 0x0020 +CONSTANT: METH_COEXIST 0x0040 + +C-TYPE: PyCFunction + +STRUCT: PyMethodDef + { ml_name void* } + { ml_meth PyCFunction* } + { ml_flags int } + { ml_doc c-string } ; + +FUNCTION: PyObject* PyCFunction_NewEx ( PyMethodDef* ml, + PyObject* self, + PyObject* module ) ; + +CALLBACK: PyObject* PyCallback ( PyObject* self, + PyObject* args, + PyObject* kw ) ; + ! Top-level FUNCTION: c-string Py_GetVersion ( ) ; FUNCTION: void Py_Initialize ( ) ; @@ -61,6 +88,8 @@ FUNCTION: int PyList_Size ( PyObject* l ) ; ! Steals the reference FUNCTION: int PyList_SetItem ( PyObject* l, int pos, PyObject* o ) ; +! Sequences +FUNCTION: int PySequence_Check ( PyObject* o ) ; ! Modules FUNCTION: c-string PyModule_GetName ( PyObject* module ) ; @@ -108,6 +137,9 @@ FUNCTION: long PyLong_AsLong ( PyObject* o ) ; ! Floats FUNCTION: PyObject* PyFloat_FromDouble ( double d ) ; +! Types +FUNCTION: int PyType_Check ( PyObject* obj ) ; + ! Reference counting FUNCTION: void Py_IncRef ( PyObject* o ) ; FUNCTION: void Py_DecRef ( PyObject* o ) ; diff --git a/extra/python/objects/objects.factor b/extra/python/objects/objects.factor index 9172493030..e605975d0d 100644 --- a/extra/python/objects/objects.factor +++ b/extra/python/objects/objects.factor @@ -1,4 +1,5 @@ -USING: alien.c-types alien.data kernel python.errors python.ffi ; +USING: alien.c-types alien.data classes.struct kernel python.errors +python.ffi ; IN: python.objects ! Objects @@ -58,3 +59,10 @@ IN: python.objects : py-list-set-item ( obj pos val -- ) unsteal-ref PyList_SetItem check-zero ; + +! Functions +: ( alien -- cfunction ) + f swap METH_VARARGS f PyMethodDef f f + ! It's not clear from the docs whether &Py_DecRef is right for + ! PyCFunction_NewEx, but I'm betting on it. + PyCFunction_NewEx check-new-ref ; diff --git a/extra/python/python-docs.factor b/extra/python/python-docs.factor index 2d78f8bd70..346f175214 100644 --- a/extra/python/python-docs.factor +++ b/extra/python/python-docs.factor @@ -1,5 +1,6 @@ +USING: alien destructors help.markup help.syntax python python.throwing +quotations ; IN: python -USING: python python.throwing help.markup help.syntax ; HELP: py-initialize { $description "Initializes the python binding. This word must be called before any other words in the api can be used" } ; @@ -19,6 +20,21 @@ HELP: >py } { $see-also py> } ; +HELP: quot>py-callback +{ $values { "quot" { $quotation ( args kw -- ret ) } } { "alien" alien } } +{ $description "Creates a python-compatible alien callback from a quotation." } +{ $examples + "This is how you create a callback which returns the double of its first positional parameter:" + { $unchecked-example + "USING: python ;" + ": double-fun ( -- alien ) [ drop first 2 * ] quot>py-callback ;" + } +} ; + +HELP: with-quot>py-cfunction +{ $values { "alien" alien } { "quot" quotation } } +{ $description "Wrapper for " { $link with-callback } " to be used when passing functions as arguments to Python functions. It should be used in conjunction with " { $link quot>py-callback } " which creates the callbacks this word consumes." } ; + HELP: python-error { $error-description "When Python throws an exception, it is translated to this Factor error. " { $slot "type" } " is the class name of the python exception object, " { $slot "message" } " its string and " { $slot "traceback" } " a sequence of traceback lines, if the error has one, or " { $link f } " otherwise." } ; @@ -26,7 +42,7 @@ ARTICLE: "python" "Python binding" "The " { $vocab-link "python" } " vocab and its subvocabs implements a simple binding for libpython, allowing factor code to call native python." $nl "Converting to and from Python:" -{ $subsections >py py> } +{ $subsections >py py> quot>py-callback } "Error handling:" { $subsections python-error } "Initialization and finalization:" @@ -35,4 +51,5 @@ $nl { $subsections py-import } "The vocab " { $vocab-link "python.syntax" } " implements a higher level factorific interface on top of the lower-level constructs in this vocab. Prefer to use that vocab most of the time." { $notes "Sometimes the embedded python interpreter can't find or finds the wrong load path to it's module library. To counteract that problem it is recommended that the " { $snippet "PYTHONHOME" } " environment variable is set before " { $link py-initialize } " is called. E.g:" } -{ $code "\"C:/python27-64bit/\" \"PYTHONHOME\" set-os-env" } ; +{ $code "\"C:/python27-64bit/\" \"PYTHONHOME\" set-os-env" } +{ $warning "All code that calls Python words should always be wrapped in a " { $link with-destructors } " context. The reason is that the words add references to Pythons internal memory heap which are removed when the destructors trigger." } ; diff --git a/extra/python/python.factor b/extra/python/python.factor index a298fd196c..016749ebb5 100644 --- a/extra/python/python.factor +++ b/extra/python/python.factor @@ -1,4 +1,4 @@ -USING: alien.c-types alien.data arrays assocs command-line fry +USING: alien alien.c-types alien.data arrays assocs command-line fry hashtables init io.encodings.utf8 kernel namespaces python.errors python.ffi python.objects sequences specialized-arrays strings vectors ; @@ -100,5 +100,15 @@ ERROR: missing-type type ; dup "__class__" getattr "__name__" getattr PyString_AsString py-type-dispatch get ?at [ call( x -- x ) ] [ missing-type ] if ; +! Callbacks +: quot>py-callback ( quot: ( args kw -- ret ) -- alien ) + '[ + [ nip ] dip + [ [ py> ] [ { } ] if* ] bi@ @ >py + ] PyCallback ; inline + +: with-quot>py-cfunction ( alien quot -- ) + '[ @ ] with-callback ; inline + [ py-initialize ] "py-initialize" add-startup-hook [ py-finalize ] "py-finalize" add-shutdown-hook diff --git a/extra/python/syntax/syntax-tests.factor b/extra/python/syntax/syntax-tests.factor index c107dce225..eeaa91484e 100644 --- a/extra/python/syntax/syntax-tests.factor +++ b/extra/python/syntax/syntax-tests.factor @@ -1,6 +1,6 @@ -USING: accessors arrays assocs continuations destructors fry io.files.temp -kernel math namespaces python python.ffi python.objects python.syntax -sequences sets splitting tools.test unicode.categories ; +USING: accessors arrays assocs continuations destructors destructors.private +fry io.files.temp kernel math namespaces python python.ffi python.objects +python.syntax sequences sets splitting tools.test unicode.categories ; IN: python.syntax.tests : py-test ( result quot -- ) @@ -77,6 +77,12 @@ PY-FROM: sys => getrefcount ( obj -- n ) ; [ 0 py-tuple-get-item getrefcount py> ] tri - ] py-test +{ t } [ + 6 + [ getrefcount py> 1 - ] + [ always-destructors get [ alien>> = ] with count ] bi = +] py-test + PY-METHODS: file => close ( self -- ) fileno ( self -- n ) @@ -166,3 +172,41 @@ PY-FROM: wsgiref.simple_server => make_server ( iface port callback -- httpd ) ; [ [ 987 >py basename drop ] ignore-errors ] with-destructors ] times ] unit-test + + +! Working with types +PY-METHODS: obj => + __name__ ( self -- n ) ; + +PY-QUALIFIED-FROM: types => UnicodeType ( -- ) ; + +{ "unicode" } [ + types:$UnicodeType $__name__ py> +] py-test + +! Make callbacks + +PY-QUALIFIED-FROM: __builtin__ => + None ( -- ) + map ( func seq -- seq' ) + reduce ( func seq -- seq' ) ; + +{ V{ 1 2 3 } } [ + __builtin__:$None { 1 2 3 } >py __builtin__:map py> +] py-test + +: double-fun ( -- alien ) + [ drop first 2 * ] quot>py-callback ; + +{ V{ 2 4 16 2 4 68 } } [ + double-fun [ { 1 2 8 1 2 34 } >py __builtin__:map py> ] with-quot>py-cfunction +] py-test + +: reduce-func ( -- alien ) + [ drop first2 + ] quot>py-callback ; + +{ 48 } [ + reduce-func [ + { 1 2 8 1 2 34 } >py __builtin__:reduce py> + ] with-quot>py-cfunction +] py-test