diff --git a/extra/python/ffi/ffi.factor b/extra/python/ffi/ffi.factor new file mode 100644 index 0000000000..a7cd55d6b7 --- /dev/null +++ b/extra/python/ffi/ffi.factor @@ -0,0 +1,101 @@ +USING: + alien + alien.c-types + alien.destructors + alien.libraries alien.libraries.finder + alien.syntax + kernel + sequences ; +IN: python.ffi + +<< "python" { "3.0" "2.6" "2.7" } [ + "python" prepend find-library +] map-find drop cdecl add-library >> + +LIBRARY: python + +C-TYPE: PyObject + +! Top-level +FUNCTION: c-string Py_GetVersion ( ) ; +FUNCTION: void Py_Initialize ( ) ; +FUNCTION: bool Py_IsInitialized ( ) ; +FUNCTION: void Py_Finalize ( ) ; + +! Misc +FUNCTION: int PyRun_SimpleString ( c-string command ) ; + +! Importing +FUNCTION: PyObject* PyImport_AddModule ( c-string name ) ; +FUNCTION: long PyImport_GetMagicNumber ( ) ; +FUNCTION: PyObject* PyImport_ImportModule ( c-string name ) ; + +! Dicts +FUNCTION: PyObject* PyDict_GetItemString ( PyObject* d, c-string key ) ; +FUNCTION: PyObject* PyDict_New ( ) ; +FUNCTION: int PyDict_Size ( PyObject* d ) ; +FUNCTION: int PyDict_SetItemString ( PyObject* d, + c-string key, + PyObject* val ) ; +FUNCTION: int PyDict_SetItem ( PyObject* d, PyObject* k, PyObject* o ) ; +FUNCTION: PyObject* PyDict_Items ( PyObject *d ) ; + +! Tuples +FUNCTION: PyObject* PyTuple_GetItem ( PyObject* t, int pos ) ; +FUNCTION: PyObject* PyTuple_New ( int len ) ; +FUNCTION: int PyTuple_SetItem ( PyObject* t, int pos, PyObject* o ) ; +FUNCTION: int PyTuple_Size ( PyObject* t ) ; + +! Lists (sequences) +FUNCTION: PyObject* PyList_GetItem ( PyObject* l, int pos ) ; +FUNCTION: int PyList_Size ( PyObject* t ) ; + + +! Modules +FUNCTION: c-string PyModule_GetName ( PyObject* module ) ; +FUNCTION: PyObject* PyModule_GetDict ( PyObject* module ) ; + +! Objects +FUNCTION: PyObject* PyObject_CallObject ( PyObject* callable, + PyObject* args ) ; +FUNCTION: PyObject* PyObject_Call ( PyObject* callable, + PyObject* args, + PyObject* kw ) ; +FUNCTION: PyObject* PyObject_GetAttrString ( PyObject* callable, + c-string attr_name ) ; +FUNCTION: PyObject* PyObject_Str ( PyObject* o ) ; + +! Strings +FUNCTION: c-string PyString_AsString ( PyObject* string ) ; +FUNCTION: PyObject* PyString_FromString ( c-string v ) ; + +! Unicode +FUNCTION: PyObject* PyUnicode_DecodeUTF8 ( c-string s, + int size, + void* errors ) ; +FUNCTION: PyObject* PyUnicodeUCS4_FromString ( c-string s ) ; +FUNCTION: PyObject* PyUnicodeUCS4_AsUTF8String ( PyObject* unicode ) ; + +! Ints +FUNCTION: long PyInt_AsLong ( PyObject* io ) ; + +! Longs +FUNCTION: PyObject* PyLong_FromLong ( long v ) ; +FUNCTION: long PyLong_AsLong ( PyObject* o ) ; + +! Floats +FUNCTION: PyObject* PyFloat_FromDouble ( double d ) ; + +! Reference counting +FUNCTION: void Py_IncRef ( PyObject* o ) ; +FUNCTION: void Py_DecRef ( PyObject* o ) ; +DESTRUCTOR: Py_DecRef + +! Reflection +FUNCTION: c-string PyEval_GetFuncName ( PyObject* func ) ; + +! Errors +FUNCTION: void PyErr_Print ( ) ; +FUNCTION: void PyErr_Fetch ( PyObject** ptype, + PyObject** pvalue, + PyObject** *ptraceback ) ; diff --git a/extra/python/python-tests.factor b/extra/python/python-tests.factor new file mode 100644 index 0000000000..973679d76c --- /dev/null +++ b/extra/python/python-tests.factor @@ -0,0 +1,113 @@ +USING: + accessors arrays assocs + calendar + continuations + fry kernel + math + namespaces + python python.ffi + sequences + strings tools.test ; +IN: python.tests + +: py-test ( result quot -- ) + '[ _ with-py ] unit-test ; inline + +[ t ] [ Py_GetVersion string? ] unit-test + +[ "os" ] [ "os" PyImport_ImportModule PyModule_GetName ] py-test + +[ t ] [ "os" import "getpid" getattr { } py-call 0 > ] py-test + +[ t ] [ Py_IsInitialized ] py-test + +! Importing +[ { "ImportError" "No module named kolobi" } ] [ + [ "kolobi" import ] [ [ type>> ] [ message>> ] bi 2array ] recover +] py-test + +! Tuples +[ 2 ] [ 2 py-tuple-size ] py-test + +: py-date>factor ( py-obj -- timestamp ) + { "year" "month" "day" } [ getattr >factor ] with map + first3 0 0 0 instant ; + +! Datetimes +[ t ] [ + [ py-date>factor ] "date" py-type-dispatch get set-at + "datetime" import + "date" getattr "today" getattr + { } py-call + today instant >>gmt-offset = +] py-test + +! Unicode +[ "غثههح" ] [ + "os.path" import "basename" getattr { "غثههح" } py-call +] py-test + +! Instance variables +[ 7 ] [ + "datetime" import "timedelta" getattr + { 7 } >py call-object "days" getattr >factor +] py-test + +! Create a dictonary +[ 0 ] [ py-dict-size ] py-test + +! Dictionary with object keys +[ 1 ] [ + dup 0 >py 33 >py py-dict-set-item py-dict-size +] py-test + +! Dictionary with string keys +[ 1 ] [ + [ "foo" 33 >py py-dict-set-item-string ] [ py-dict-size ] bi +] py-test + +! Get dictionary items +[ 33 ] [ + "tjaba" + [ 33 >py py-dict-set-item-string ] + [ py-dict-get-item-string >factor ] 2bi +] py-test + +! Nest dicts +[ 0 ] [ + "foo" + [ py-dict-set-item-string ] + [ py-dict-get-item-string ] 2bi + py-dict-size +] py-test + +! Nested tuples +[ 3 ] [ + 1 dup 0 3 py-tuple-set-item + 0 py-tuple-get-item py-tuple-size +] py-test + +! Round tripping! +[ { "foo" { 99 77 } } ] [ { "foo" { 99 77 } } >py >factor ] py-test + +[ H{ { "foo" "bar" } { 3 4 } } ] [ + H{ { "foo" "bar" } { 3 4 } } >py >factor +] py-test + +! Kwargs +[ 2014 10 22 ] [ + "datetime" import "date" getattr + { } { "year" 2014 "month" 10 "day" 22 } py-call2 + [ year>> ] [ month>> ] [ day>> ] tri +] py-test + +SYMBOLS: year month day ; + +[ 2014 10 22 ] [ + "datetime" import "date" getattr + { } { year 2014 month 10 day 22 } py-call2 + [ year>> ] [ month>> ] [ day>> ] tri +] py-test + +! Modules +[ t ] [ "os" import PyModule_GetDict py-dict-size 200 > ] py-test diff --git a/extra/python/python.factor b/extra/python/python.factor new file mode 100644 index 0000000000..25c9d56a47 --- /dev/null +++ b/extra/python/python.factor @@ -0,0 +1,146 @@ +USING: + accessors + alien alien.c-types alien.data + arrays + assocs + destructors + fry + grouping + hashtables + kernel + namespaces + python.ffi + sequences + strings + words ; +IN: python +QUALIFIED: math + +! Error handling +ERROR: python-error type message ; + +: get-error ( -- ptype pvalue ) + { void* void* void* } [ PyErr_Fetch ] with-out-parameters drop ; + +: throw-error ( ptype pvalue -- ) + [ "__name__" PyObject_GetAttrString ] [ PyObject_Str ] bi* [ &Py_DecRef ] bi@ + [ PyString_AsString ] bi@ python-error ; + +: (check-return) ( value/f -- value' ) + [ get-error throw-error f ] unless* ; + +: check-return ( value/f -- value' ) + (check-return) ; ! &Py_DecRef ; + +: check-return-code ( return -- ) + 0 = [ get-error throw-error ] unless ; + +! Importing +: import ( str -- module ) + PyImport_ImportModule check-return ; + +! Objects +: getattr ( obj str -- value ) + PyObject_GetAttrString check-return ; + +: call-object ( obj args -- value ) + PyObject_CallObject check-return ; + +! Context +: with-py ( quot -- ) + '[ Py_Initialize _ call Py_Finalize ] with-destructors ; inline + +! Types and their methods +: ( length -- tuple ) + PyTuple_New check-return ; + +: py-tuple-set-item ( obj pos val -- ) + PyTuple_SetItem check-return-code ; + +: py-tuple-get-item ( obj pos -- val ) + PyTuple_GetItem check-return ; + +: py-tuple-size ( obj -- len ) + PyTuple_Size ; + +: ( -- dict ) + PyDict_New check-return ; + +: py-dict-set-item ( obj key val -- ) + PyDict_SetItem check-return-code ; + +: py-dict-set-item-string ( dict key val -- ) + PyDict_SetItemString check-return-code ; + +: py-dict-get-item-string ( obj key -- val ) + PyDict_GetItemString check-return ; + +: py-dict-size ( obj -- len ) + PyDict_Size ; + +: py-list-size ( list -- len ) + PyList_Size ; + +: py-list-get-item ( obj pos -- val ) + PyList_GetItem check-return ; + +! Data marshalling to Python +GENERIC: (>py) ( obj -- obj' ) +M: string (>py) PyUnicodeUCS4_FromString ; +M: math:fixnum (>py) PyLong_FromLong ; +M: math:float (>py) PyFloat_FromDouble ; + +M: array (>py) + [ length dup ] [ [ (>py) ] map ] bi + [ rot py-tuple-set-item ] with each-index ; + +M: hashtable (>py) + swap dupd [ + swapd [ (>py) ] [ (>py) ] bi* py-dict-set-item + ] with assoc-each ; + +! I'll make a fast-path for this +M: word (>py) name>> (>py) ; + +: >py ( obj -- py-obj ) + (>py) ; ! &Py_DecRef ; + +! Data marshalling to Factor +SYMBOL: py-type-dispatch + +DEFER: >factor + +: init-py-type-dispatch ( -- table ) + H{ + { "NoneType" [ drop f ] } + { "dict" [ PyDict_Items (check-return) >factor >hashtable ] } + { "int" [ PyInt_AsLong ] } + + { "list" [ + dup py-list-size iota [ py-list-get-item >factor ] with map + ] } + { "long" [ PyLong_AsLong ] } + { "str" [ PyString_AsString (check-return) ] } + { "tuple" [ + dup py-tuple-size iota [ py-tuple-get-item >factor ] with map + ] } + { "unicode" [ + PyUnicodeUCS4_AsUTF8String (check-return) + PyString_AsString (check-return) + ] } + } clone ; + +py-type-dispatch [ init-py-type-dispatch ] initialize + +ERROR: missing-type type ; + +: >factor ( py-obj -- obj ) + dup "__class__" getattr "__name__" getattr PyString_AsString + py-type-dispatch get ?at [ call( x -- x ) ] [ missing-type ] if ; + +! Utility +: py-call ( obj args -- value ) + >py call-object >factor ; + +: py-call2 ( obj args kwargs -- value ) + [ >py ] [ 2 group >hashtable >py ] bi* PyObject_Call >factor ;