├── .github └── workflows │ └── run-tests.yml ├── .gitignore ├── Idris2Python ├── Idris2Python.idr ├── Idris2Python │ ├── ModuleTemplate.idr │ └── PythonFFI.idr └── module_template │ ├── __init__.py │ ├── __main__.py │ └── idris2 │ ├── __init__.py │ ├── foreign_python.py │ └── refc_types.py ├── LICENSE ├── Makefile ├── PythonBindings ├── Python.idr └── Python │ ├── PythonBool.idr │ ├── PythonClass.idr │ ├── PythonDict.idr │ ├── PythonFunction.idr │ ├── PythonInteger.idr │ ├── PythonList.idr │ ├── PythonObject.idr │ ├── PythonString.idr │ ├── PythonTuple.idr │ └── PythonUnit.idr ├── README.md ├── idris2-python.ipkg ├── python-bindings.ipkg └── tests ├── Idris2Python ├── HelloWorld │ ├── expected │ ├── hello_world.idr │ └── run └── PythonFFI │ ├── expected │ ├── python_ffi.idr │ └── run ├── Idris2PythonTests.idr ├── Makefile ├── PythonBindings ├── Boolean │ ├── expected │ ├── python_bool.idr │ └── run ├── Integer │ ├── expected │ ├── python_integer.idr │ └── run ├── PythonClass │ ├── expected │ ├── python_class.idr │ └── run ├── PythonDict │ ├── expected │ ├── python_dict.idr │ └── run ├── PythonFunction │ ├── expected │ ├── python_function.idr │ └── run ├── PythonList │ ├── expected │ ├── python_list.idr │ └── run ├── PythonString │ ├── expected │ ├── python_string.idr │ └── run ├── PythonTuple │ ├── expected │ ├── python_tuple.idr │ └── run └── Unit │ ├── expected │ ├── python_unit.idr │ └── run └── idris2-python-tests.ipkg /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | run_tests: 11 | name: Run Tests 12 | runs-on: ubuntu-latest 13 | container: snazzybucket/idris2api 14 | steps: 15 | - name: Install additional prerequisites 16 | run: | 17 | apt-get update 18 | apt-get install --yes gcc libgmp3-dev python-is-python3 19 | - name: Check out Idris2-Python 20 | uses: actions/checkout@v2 21 | - name: Compile Idris2-Python 22 | run: | 23 | idris2 --build idris2-python.ipkg 24 | idris2 --install python-bindings.ipkg 25 | - name: Run Tests 26 | run: make test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /tests/build 3 | /tests/**/output 4 | /tests/failures 5 | -------------------------------------------------------------------------------- /Idris2Python/Idris2Python.idr: -------------------------------------------------------------------------------- 1 | module Idris2Python 2 | 3 | import Compiler.ANF 4 | import Compiler.Common 5 | import Compiler.RefC.CC 6 | import Compiler.RefC.RefC 7 | 8 | import Core.Context 9 | import Core.Core 10 | import Core.Directory 11 | 12 | import System 13 | import System.File 14 | 15 | import Idris.Driver 16 | import Idris.Syntax 17 | import Libraries.Utils.Path 18 | 19 | import Idris2Python.ModuleTemplate 20 | import Idris2Python.PythonFFI 21 | 22 | compile : Ref Ctxt Defs 23 | -> Ref Syn SyntaxInfo 24 | -> (tmpDir : String) 25 | -> (outputDir : String) 26 | -> ClosedTerm 27 | -> (outputModule : String) 28 | -> Core (Maybe String) 29 | compile defs syn tmpDir outputDir term outputModule = do 30 | let outputModulePath = outputDir outputModule 31 | 32 | coreLift_ $ mkdirAll outputModulePath 33 | cdata <- getCompileData False ANF term 34 | let defs = anf cdata 35 | 36 | let cSourceFile = outputModulePath "main.c" 37 | let cObjectFile = outputModulePath "main.o" 38 | let cSharedObjectFile = outputModulePath "main.so" 39 | 40 | _ <- generateCSourceFile {additionalFFILangs = ["python"]} defs cSourceFile 41 | _ <- compileCObjectFile {asLibrary = True} cSourceFile cObjectFile 42 | _ <- compileCFile {asShared = True} cObjectFile cSharedObjectFile 43 | 44 | templatePyInitFilePath <- findLibraryFile "Idris2Python/module_template/__init__.py" 45 | 46 | generatePyInitFile outputModulePath templatePyInitFilePath (pythonFFIs defs) 47 | _ <- coreCopyFile !(findLibraryFile "Idris2Python/module_template/__main__.py") outputModulePath 48 | ensureDirectoryExists $ outputModulePath "idris2" 49 | _ <- coreCopyFile !(findLibraryFile "Idris2Python/module_template/idris2/__init__.py") (outputModulePath "idris2") 50 | _ <- coreCopyFile !(findLibraryFile "Idris2Python/module_template/idris2/foreign_python.py") (outputModulePath "idris2") 51 | _ <- coreCopyFile !(findLibraryFile "Idris2Python/module_template/idris2/refc_types.py") (outputModulePath "idris2") 52 | 53 | pure $ Just outputModulePath 54 | 55 | executePython : Ref Ctxt Defs 56 | -> Ref Syn SyntaxInfo 57 | -> String 58 | -> ClosedTerm 59 | -> Core () 60 | executePython defs syn tmpDir term = coreLift_ $ do 61 | putStrLn "Execute expression not yet implemented for the Python backend" 62 | system "false" 63 | 64 | pythonCodegen : Codegen 65 | pythonCodegen = MkCG compile executePython Nothing Nothing 66 | 67 | export 68 | main : IO () 69 | main = mainWithCodegens [("python", pythonCodegen)] 70 | -------------------------------------------------------------------------------- /Idris2Python/Idris2Python/ModuleTemplate.idr: -------------------------------------------------------------------------------- 1 | module Idris2Python.ModuleTemplate 2 | 3 | import Data.List 4 | import Data.String 5 | 6 | import Core.Core 7 | import Core.Name 8 | 9 | import System.File 10 | 11 | import Libraries.Data.SortedSet 12 | import Libraries.Utils.Path 13 | 14 | import Idris2Python.PythonFFI 15 | 16 | unique : Ord a => List a -> List a 17 | unique = SortedSet.toList . fromList 18 | 19 | copyFile' : HasIO io 20 | => (sourcePath : String) 21 | -> (targetDirectory : String) 22 | -> io (Either FileError String) 23 | copyFile' sourcePath targetDirectory = do 24 | let Just targetName = fileName sourcePath 25 | | Nothing => pure $ Left FileNotFound 26 | let targetPath = targetDirectory targetName 27 | Right () <- copyFile sourcePath targetPath 28 | | Left (err, _) => pure $ Left err 29 | pure $ Right targetPath 30 | 31 | export 32 | coreCopyFile : (sourcePath : String) 33 | -> (targetDirectory : String) 34 | -> Core String 35 | coreCopyFile sourcePath targetDirectory = do 36 | Right targetPath <- coreLift $ copyFile' sourcePath targetDirectory 37 | | Left err => throw $ FileErr ("Cannot copy file " ++ sourcePath ++ " to directory " ++ targetDirectory) err 38 | pure targetPath 39 | 40 | export 41 | generatePyInitFile : (outputModulePath : String) 42 | -> (templatePyInitFilePath : String) 43 | -> (pyFFIs : List PythonFFI) 44 | -> Core () 45 | generatePyInitFile outputModulePath templatePyInitFilePath pyFFIs = do 46 | let pyFFIImports = map ("import " ++) $ unique $ map show $ catMaybes $ map pyModule pyFFIs 47 | let pyFFIStubInits = map initFFIStub pyFFIs 48 | 49 | Right pyInitFilePath <- coreLift $ copyFile' templatePyInitFilePath outputModulePath 50 | | Left err => throw $ FileErr "Cannot create module __init__.py file" err 51 | Right () <- coreLift $ appendFile pyInitFilePath $ unlines $ 52 | [""] ++ pyFFIImports ++ [""] ++ pyFFIStubInits 53 | | Left err => throw $ FileErr "Cannot reify __init__.py template file" err 54 | 55 | pure () 56 | -------------------------------------------------------------------------------- /Idris2Python/Idris2Python/PythonFFI.idr: -------------------------------------------------------------------------------- 1 | module Idris2Python.PythonFFI 2 | 3 | import Data.List 4 | 5 | import Compiler.ANF 6 | import Compiler.Common 7 | import Compiler.CompileExpr 8 | 9 | import Core.Name 10 | 11 | import Compiler.RefC.RefC 12 | 13 | public export 14 | record PythonFFI where 15 | constructor MkPythonFFI 16 | name : Name 17 | pyModule : Maybe Namespace 18 | pyDef : String 19 | argTypes : List CFType 20 | retType : CFType 21 | 22 | export 23 | pyFullDef : PythonFFI -> String 24 | pyFullDef (MkPythonFFI _ pyModule pyDef _ _) = show $ mkModuleIdent pyModule pyDef 25 | 26 | export 27 | pythonFFIs : List (Name, ANFDef) -> List PythonFFI 28 | pythonFFIs defs = do 29 | (name, MkAForeign ccs argTypes retType) <- defs 30 | | _ => [] 31 | let Just (_, pyDef :: opts) = parseCC ["python"] ccs 32 | | _ => [] 33 | let pyModule = case opts of 34 | [] => Nothing 35 | (m :: _) => Just $ mkNamespace m 36 | pure $ MkPythonFFI name pyModule pyDef argTypes retType 37 | 38 | ctypesTypeOfCFType : CFType -> String 39 | ctypesTypeOfCFType CFUnit = "ctypes.c_void_p" 40 | ctypesTypeOfCFType CFInt = "ctypes.c_int64" 41 | ctypesTypeOfCFType CFInt8 = "ctypes.c_int8" 42 | ctypesTypeOfCFType CFInt16 = "ctypes.c_int16" 43 | ctypesTypeOfCFType CFInt32 = "ctypes.c_int32" 44 | ctypesTypeOfCFType CFInt64 = "ctypes.c_int64" 45 | ctypesTypeOfCFType CFUnsigned8 = "ctypes.c_uint8" 46 | ctypesTypeOfCFType CFUnsigned16 = "ctypes.c_uint16" 47 | ctypesTypeOfCFType CFUnsigned32 = "ctypes.c_uint32" 48 | ctypesTypeOfCFType CFUnsigned64 = "ctypes.c_uint64" 49 | ctypesTypeOfCFType CFString = "ctypes.c_char_p" 50 | ctypesTypeOfCFType CFDouble = "ctypes.c_double" 51 | ctypesTypeOfCFType CFChar = "ctypes.c_char" 52 | ctypesTypeOfCFType CFWorld = "ctypes.POINTER(refc_types.Value_World)" 53 | ctypesTypeOfCFType (CFFun args rt) = concat [ 54 | "ctypes.CFUNCTYPE(", 55 | ctypesTypeOfCFType rt, 56 | ", ", 57 | ctypesTypeOfCFType args, 58 | ")" 59 | ] 60 | ctypesTypeOfCFType (CFIORes t) = ctypesTypeOfCFType t 61 | ctypesTypeOfCFType (CFUser n args) = "ctypes.py_object" 62 | ctypesTypeOfCFType n = assert_total $ idris_crash ("INTERNAL ERROR: Unknown FFI type in Python backend: " ++ show n) 63 | 64 | export 65 | initFFIStub : PythonFFI -> String 66 | initFFIStub pyFFI@(MkPythonFFI name pyModule pyDef argTypes (CFIORes retType)) = initFFIStub (MkPythonFFI name pyModule pyDef (concat $ init' argTypes) retType) 67 | initFFIStub pyFFI@(MkPythonFFI name pyModule pyDef argTypes retType) = concat [ 68 | "foreign_python.register_py_func(\"", 69 | cName $ NS (mkNamespace "python") name, 70 | "\", ", 71 | pyFullDef pyFFI, 72 | ", ", 73 | concat $ intersperse ", " $ map ctypesTypeOfCFType (retType :: argTypes), 74 | ")" 75 | ] 76 | -------------------------------------------------------------------------------- /Idris2Python/module_template/__init__.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import pathlib 3 | 4 | from .idris2 import foreign_python, refc_types 5 | 6 | cdll = ctypes.CDLL(pathlib.Path(__file__).parent / "main.so") 7 | 8 | # Python doesn't seem to like casting typed pointers to void pointers, while 9 | # the other way around is fine. 10 | # So expect to see lots of void pointers where you'd expect to see Idris types 11 | 12 | cdll.idris2_mkClosure.argtypes = (ctypes.c_void_p, ctypes.c_uint8, ctypes.c_uint8) 13 | cdll.idris2_mkClosure.restype = ctypes.c_void_p 14 | 15 | cdll.idris2_makeGCPointer.argtypes = (ctypes.py_object, ctypes.c_void_p) 16 | cdll.idris2_makeGCPointer.restype = ctypes.c_void_p 17 | 18 | cdll.idris2_newReference.argtypes = (ctypes.POINTER(refc_types.Value),) 19 | cdll.idris2_newReference.restype = ctypes.POINTER(refc_types.Value) 20 | 21 | cdll.idris2_removeReference.argtypes = (ctypes.POINTER(refc_types.Value),) 22 | cdll.idris2_removeReference.restype = ctypes.c_void_p 23 | 24 | cdll.idris2_apply_closure.argtypes = (ctypes.POINTER(refc_types.Value_Closure), ctypes.POINTER(refc_types.Value)) 25 | cdll.idris2_apply_closure.restype = ctypes.c_void_p 26 | 27 | cdll.main.argtypes = (ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)) 28 | 29 | foreign_python.cdll = cdll 30 | -------------------------------------------------------------------------------- /Idris2Python/module_template/__main__.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import sys 3 | 4 | from . import cdll 5 | 6 | argc = len(sys.argv) 7 | argv = (ctypes.c_char_p * argc)(*map(str.encode, sys.argv)) 8 | 9 | cdll.main(argc, argv) 10 | -------------------------------------------------------------------------------- /Idris2Python/module_template/idris2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madman-bob/idris2-python/26d3de4a5e54f91fb8553186fd09a72f327e5197/Idris2Python/module_template/idris2/__init__.py -------------------------------------------------------------------------------- /Idris2Python/module_template/idris2/foreign_python.py: -------------------------------------------------------------------------------- 1 | from ctypes import CFUNCTYPE, POINTER, c_char_p, c_void_p, cast, py_object, pythonapi 2 | from dataclasses import dataclass 3 | 4 | from .refc_types import GC_POINTER_TAG, Value, Value_Closure, Value_GCPointer, Value_Pointer, Value_World 5 | 6 | __all__ = ["to_idris_obj", "from_idris_obj", "to_idris_func", "register_py_func"] 7 | 8 | cdll = None # Backpatched to actual Idris cdll by the time this module is used 9 | 10 | pythonapi.Py_IncRef.argtypes = (py_object,) 11 | pythonapi.Py_IncRef.restype = c_void_p 12 | 13 | pythonapi.Py_DecRef.argtypes = (py_object,) 14 | pythonapi.Py_DecRef.restype = c_void_p 15 | 16 | 17 | def is_cfunc_type(obj_type): 18 | return hasattr(obj_type, "argtypes") 19 | 20 | 21 | @CFUNCTYPE(c_void_p, POINTER(Value_Pointer), c_void_p) 22 | def on_collect_idris_obj(idris_obj, _): 23 | py_obj = cast(idris_obj.contents.p, py_object).value 24 | pythonapi.Py_DecRef(py_obj) 25 | 26 | 27 | def to_idris_obj(py_obj, obj_type): 28 | if obj_type is py_object: 29 | pythonapi.Py_IncRef(py_obj) 30 | return cdll.idris2_makeGCPointer( 31 | py_object(py_obj), 32 | cdll.idris2_mkClosure(on_collect_idris_obj, 2, 0) 33 | ) 34 | 35 | if is_cfunc_type(obj_type): 36 | return to_idris_func(py_obj, obj_type._restype_, *obj_type._argtypes_) 37 | 38 | if obj_type is c_char_p: 39 | return cast(py_obj, c_void_p).value 40 | 41 | return py_obj 42 | 43 | 44 | def from_idris_obj(idris_obj, obj_type): 45 | if obj_type is py_object: 46 | if idris_obj is None: 47 | return None 48 | 49 | gc_pointer = cast(c_void_p(idris_obj), POINTER(Value_GCPointer)).contents 50 | 51 | if gc_pointer.header.tag != GC_POINTER_TAG: 52 | # RefC sometimes uses makeInt64(0) to represent (), instead of NULL 53 | # If we get here, we're in that case 54 | return None 55 | 56 | return cast(gc_pointer.p.contents.p, py_object).value 57 | 58 | if is_cfunc_type(obj_type): 59 | return from_idris_func(idris_obj, obj_type._restype_, *obj_type._argtypes_) 60 | 61 | return idris_obj 62 | 63 | 64 | def to_idris_args(args, ret_type, arg_type): 65 | args = iter(args) 66 | 67 | while True: 68 | if arg_type is POINTER(Value_World): 69 | yield cast(None, POINTER(Value)), ret_type 70 | else: 71 | yield cast(to_idris_obj(next(args), arg_type), POINTER(Value)), ret_type 72 | 73 | if not is_cfunc_type(ret_type): 74 | try: 75 | next(args) 76 | except StopIteration: 77 | return 78 | 79 | raise TypeError( 80 | f"Idris object is not a function: {ret_type}" 81 | ) 82 | 83 | ret_type, arg_type = ret_type._restype_, *ret_type._argtypes_ 84 | 85 | 86 | def to_idris_func(py_func, ret_type, *arg_types): 87 | idris_type = lambda c_type: ( 88 | c_void_p 89 | if c_type is py_object or is_cfunc_type(c_type) else 90 | c_type 91 | ) 92 | 93 | arg_types = tuple(filter(lambda c_type: c_type is not POINTER(Value_World), arg_types)) 94 | 95 | @CFUNCTYPE(idris_type(ret_type) if ret_type is not c_char_p else c_void_p, *map(idris_type, arg_types)) 96 | def idris_func(*args): 97 | return to_idris_obj(py_func(*( 98 | from_idris_obj(arg, arg_type) 99 | for arg, arg_type in zip(args, arg_types) 100 | )), ret_type) 101 | 102 | return idris_func 103 | 104 | 105 | @dataclass(frozen=True) 106 | class IdrisFunction: 107 | idris_func: POINTER(Value_Closure) 108 | ret_type: type 109 | arg_type: type 110 | 111 | def __post_init__(self): 112 | cdll.idris2_newReference(cast(self.idris_func, POINTER(Value))) 113 | 114 | def __call__(self, *args): 115 | cdll.idris2_newReference(cast(self.idris_func, POINTER(Value))) 116 | 117 | result = self.idris_func 118 | result_type = self.ret_type 119 | 120 | for arg, result_type in to_idris_args(args, self.ret_type, self.arg_type): 121 | result = cdll.idris2_apply_closure( 122 | cast(result, POINTER(Value_Closure)), 123 | arg 124 | ) 125 | 126 | return from_idris_obj(result, result_type) 127 | 128 | def __del__(self): 129 | cdll.idris2_removeReference(cast(self.idris_func, POINTER(Value))) 130 | 131 | 132 | def from_idris_func(idris_func, ret_type, arg_type): 133 | return IdrisFunction(cast(idris_func, POINTER(Value_Closure)), ret_type, arg_type) 134 | 135 | 136 | # Keep a list of Python-side references to the generated Idris functions, so 137 | # they don't get prematurely GCed 138 | idris_funcs = [] 139 | 140 | 141 | def register_py_func(c_name, py_func, ret_type, *arg_types): 142 | idris_func = to_idris_func(py_func, ret_type, *arg_types) 143 | idris_funcs.append(idris_func) 144 | c_void_p.in_dll(cdll, c_name).value = cast(idris_func, c_void_p).value 145 | -------------------------------------------------------------------------------- /Idris2Python/module_template/idris2/refc_types.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Python representation of the C types used in the Idris 2 RefC backend 3 | 4 | Python types representing C types representing Idris 2 types. 5 | 6 | This file will need updating should the Idris 2 file /support/refc/_datatypes.h change. 7 | """ 8 | 9 | import ctypes 10 | 11 | __all__ = [ 12 | "NO_TAG", 13 | "BITS32_TAG", 14 | "BITS64_TAG", 15 | "INT32_TAG", 16 | "INT64_TAG", 17 | "INTEGER_TAG", 18 | "DOUBLE_TAG", 19 | "STRING_TAG", 20 | 21 | "CLOSURE_TAG", 22 | "CONSTRUCTOR_TAG", 23 | 24 | "IOREF_TAG", 25 | "ARRAY_TAG", 26 | "POINTER_TAG", 27 | "GC_POINTER_TAG", 28 | "BUFFER_TAG", 29 | 30 | "MUTEX_TAG", 31 | "CONDITION_TAG", 32 | 33 | "Value_header", 34 | "Value", 35 | "Value_Bits32", 36 | "Value_Bits64", 37 | "Value_Int32", 38 | "Value_Int64", 39 | "Value_Integer", 40 | "Value_Double", 41 | "Value_String", 42 | "Value_Constructor", 43 | "Value_Closure", 44 | "Value_IORef", 45 | "Value_Pointer", 46 | "Value_GCPointer", 47 | "Value_Array", 48 | "Buffer", 49 | "Value_Buffer", 50 | "Value_Mutex", 51 | "Value_Condition", 52 | "Value_World", 53 | ] 54 | 55 | NO_TAG = 0 56 | BITS32_TAG = 3 57 | BITS64_TAG = 4 58 | INT32_TAG = 7 59 | INT64_TAG = 8 60 | INTEGER_TAG = 9 61 | DOUBLE_TAG = 10 62 | STRING_TAG = 12 63 | 64 | CLOSURE_TAG = 15 65 | CONSTRUCTOR_TAG = 17 66 | 67 | IOREF_TAG = 20 68 | ARRAY_TAG = 21 69 | POINTER_TAG = 22 70 | GC_POINTER_TAG = 23 71 | BUFFER_TAG = 24 72 | 73 | MUTEX_TAG = 30 74 | CONDITION_TAG = 31 75 | 76 | 77 | class Value_header(ctypes.Structure): 78 | _fields_ = [ 79 | ("refCounter", ctypes.c_int), 80 | ("tag", ctypes.c_int), 81 | ] 82 | 83 | 84 | class Value(ctypes.Structure): 85 | _fields_ = [ 86 | ("header", Value_header), 87 | ("payload", ctypes.c_char * 25), 88 | ] 89 | 90 | 91 | class Value_Bits32(ctypes.Structure): 92 | _fields_ = [ 93 | ("header", Value_header), 94 | ("ui32", ctypes.c_uint32), 95 | ] 96 | 97 | 98 | class Value_Bits64(ctypes.Structure): 99 | _fields_ = [ 100 | ("header", Value_header), 101 | ("ui64", ctypes.c_uint64), 102 | ] 103 | 104 | 105 | class Value_Int32(ctypes.Structure): 106 | _fields_ = [ 107 | ("header", Value_header), 108 | ("i32", ctypes.c_int32), 109 | ] 110 | 111 | 112 | class Value_Int64(ctypes.Structure): 113 | _fields_ = [ 114 | ("header", Value_header), 115 | ("i64", ctypes.c_int64), 116 | ] 117 | 118 | 119 | class Value_Integer(ctypes.Structure): 120 | _fields_ = [ 121 | ("header", Value_header), 122 | ("i", ctypes.c_void_p), # mpz_t 123 | ] 124 | 125 | 126 | class Value_Double(ctypes.Structure): 127 | _fields_ = [ 128 | ("header", Value_header), 129 | ("d", ctypes.c_double), 130 | ] 131 | 132 | 133 | class Value_String(ctypes.Structure): 134 | _fields_ = [ 135 | ("header", Value_header), 136 | ("str", ctypes.c_char_p), 137 | ] 138 | 139 | 140 | class Value_Constructor(ctypes.Structure): 141 | _fields_ = [ 142 | ("header", Value_header), 143 | ("total", ctypes.c_int32), 144 | ("tag", ctypes.c_int32), 145 | ("name", ctypes.c_char_p), 146 | ("args", ctypes.POINTER(ctypes.POINTER(Value))), 147 | ] 148 | 149 | 150 | class Value_Closure(ctypes.Structure): 151 | _fields_ = [ 152 | ("header", Value_header), 153 | ("f", ctypes.c_void_p), 154 | ("arity", ctypes.c_uint8), 155 | ("filled", ctypes.c_uint8), 156 | ("args", ctypes.POINTER(Value)), 157 | ] 158 | 159 | 160 | class Value_IORef(ctypes.Structure): 161 | _fields_ = [ 162 | ("header", Value_header), 163 | ("v", ctypes.POINTER(Value)), 164 | ] 165 | 166 | 167 | class Value_Pointer(ctypes.Structure): 168 | _fields_ = [ 169 | ("header", Value_header), 170 | ("p", ctypes.c_void_p), 171 | ] 172 | 173 | 174 | class Value_GCPointer(ctypes.Structure): 175 | _fields_ = [ 176 | ("header", Value_header), 177 | ("p", ctypes.POINTER(Value_Pointer)), 178 | ("onCollectFct", ctypes.POINTER(Value_Closure)), 179 | ] 180 | 181 | 182 | class Value_Array(ctypes.Structure): 183 | _fields_ = [ 184 | ("header", Value_header), 185 | ("capacity", ctypes.c_int), 186 | ("arr", ctypes.POINTER(ctypes.POINTER(Value))), 187 | ] 188 | 189 | 190 | class Buffer(ctypes.Structure): 191 | _fields_ = [ 192 | ("size", ctypes.c_int), 193 | ("data", ctypes.c_char_p) 194 | ] 195 | 196 | 197 | class Value_Buffer(ctypes.Structure): 198 | _fields_ = [ 199 | ("header", Value_header), 200 | ("buffer", Buffer), 201 | ] 202 | 203 | 204 | class Value_Mutex(ctypes.Structure): 205 | _fields_ = [ 206 | ("header", Value_header), 207 | ("mutex", ctypes.c_void_p), # pthread_mutex_t* 208 | ] 209 | 210 | 211 | class Value_Condition(ctypes.Structure): 212 | _fields_ = [ 213 | ("header", Value_header), 214 | ("cond", ctypes.c_void_p), # pthread_cond_t* 215 | ] 216 | 217 | 218 | # Not represented as a value in the C backend 219 | # Used here as a tag to recognize when closures require an additional application 220 | class Value_World(ctypes.Structure): 221 | _fields_ = [] 222 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Robert Wright 2 | School of Informatics, University of Edinburgh 3 | All rights reserved. 4 | 5 | This code is derived from software written by Robert Wright 6 | (robert.wright@ed.ac.uk). 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions 10 | are met: 11 | 1. Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | 3. None of the names of the copyright holders may be used to endorse 17 | or promote products derived from this software without specific 18 | prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 21 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 27 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 30 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | *** End of disclaimer. *** 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: idris2-python python-bindings install test retest clean 2 | 3 | idris2-python: build/exec/idris2-python 4 | 5 | build/exec/idris2-python: idris2-python.ipkg Idris2Python/* Idris2Python/*/* Idris2Python/*/*/* 6 | idris2 --build idris2-python.ipkg 7 | 8 | python-bindings: build/ttc/Python.ttc 9 | 10 | build/ttc/Python.ttc: python-bindings.ipkg PythonBindings/* PythonBindings/*/* 11 | idris2 --install python-bindings.ipkg 12 | 13 | install: idris2-python python-bindings 14 | 15 | test: 16 | make -C tests test 17 | 18 | retest: 19 | make -C tests retest 20 | 21 | clean: 22 | rm -rf build 23 | -------------------------------------------------------------------------------- /PythonBindings/Python.idr: -------------------------------------------------------------------------------- 1 | module Python 2 | 3 | import public Python.PythonBool 4 | import public Python.PythonClass 5 | import public Python.PythonDict 6 | import public Python.PythonFunction 7 | import public Python.PythonInteger 8 | import public Python.PythonList 9 | import public Python.PythonObject 10 | import public Python.PythonString 11 | import public Python.PythonTuple 12 | import public Python.PythonUnit 13 | 14 | %foreign "python: print" 15 | prim__py_print : PythonObject -> PrimIO () 16 | 17 | export 18 | print : PythonType io a => a -> io () 19 | print x = primIO $ prim__py_print !(toPy x) 20 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonBool.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonBool 2 | 3 | import Python.PythonObject 4 | 5 | export 6 | data PythonBool : Type where [external] 7 | 8 | export 9 | PrimPythonType PythonBool where 10 | 11 | %foreign "python: lambda: False" 12 | prim__py_false : PythonBool 13 | 14 | %foreign "python: lambda: True" 15 | prim__py_true : PythonBool 16 | 17 | export 18 | HasIO io => PythonType io Bool where 19 | toPy False = toPy prim__py_false 20 | toPy True = toPy prim__py_true 21 | 22 | %foreign "python: lambda *args: args[1] if args[0] else args[2]" 23 | prim__py_iif : PythonObject -> Int -> Int -> PrimIO Int 24 | 25 | export 26 | isTruthy : PythonType io a => a -> io Bool 27 | isTruthy x = map (== 1) $ primIO $ prim__py_iif !(toPy x) 1 0 28 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonClass.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonClass 2 | 3 | import Python.PythonObject 4 | import Python.PythonDict 5 | import Python.PythonList 6 | import Python.PythonString 7 | import Python.PythonTuple 8 | 9 | export 10 | data PythonClass : Type where [external] 11 | 12 | export 13 | PrimPythonType PythonClass where 14 | 15 | %foreign "python: type" 16 | prim__py_subclass : StringUTF8 -> PythonTuple -> PythonDict -> PrimIO PythonClass 17 | 18 | export 19 | subclass : HasIO io 20 | => StringUTF8 21 | -> List PythonClass 22 | -> PythonDict 23 | -> io PythonClass 24 | subclass name parents fields = primIO $ prim__py_subclass name !(pyToTuple !(toPyList parents)) fields 25 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonDict.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonDict 2 | 3 | import public Data.List.Quantifiers 4 | 5 | import Python.PythonList 6 | import Python.PythonObject 7 | import Python.PythonString 8 | 9 | export 10 | data PythonDict : Type where [external] 11 | 12 | export 13 | PrimPythonType PythonDict where 14 | 15 | %foreign "python: dict" 16 | prim__py_dict : PythonList -> PrimIO PythonDict 17 | 18 | export 19 | pyToDict : HasIO io => PythonList -> io PythonDict 20 | pyToDict = primIO . prim__py_dict 21 | 22 | export 23 | empty : HasIO io => io PythonDict 24 | empty = pyToDict !empty 25 | 26 | %foreign "python: dict.get" 27 | prim__py_dict_get_item : PythonDict -> StringUTF8 -> PythonObject -> PrimIO PythonObject 28 | 29 | export 30 | getItem : PythonType io a => PythonDict -> StringUTF8 -> a -> io PythonObject 31 | getItem d k fallback = primIO $ prim__py_dict_get_item d k !(toPy fallback) 32 | 33 | %foreign "python: dict.__setitem__" 34 | prim__py_dict_set_item : PythonDict -> StringUTF8 -> PythonObject -> PrimIO () 35 | 36 | export 37 | setItem : PythonType io a => PythonDict -> StringUTF8 -> a -> io () 38 | setItem d k v = primIO $ prim__py_dict_set_item d k !(toPy v) 39 | 40 | export 41 | extend : HasIO io 42 | => All (PythonType io) types 43 | => PythonDict 44 | -> All (Pair StringUTF8) types 45 | -> io () 46 | extend d [] = pure () 47 | extend @{io} @{pt :: pts} d ((k, v) :: rest) = do 48 | setItem d k v 49 | extend d rest 50 | 51 | export 52 | toPyDict : HasIO io 53 | => All (PythonType io) types 54 | => All (Pair StringUTF8) types 55 | -> io PythonDict 56 | toPyDict xs = do 57 | d <- empty 58 | extend d xs 59 | pure d 60 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonFunction.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonFunction 2 | 3 | import Python.PythonObject 4 | 5 | export 6 | data PythonFunction : Type -> Type -> Type where [external] 7 | 8 | export 9 | PrimPythonType a => PythonType io b => PythonType io (PythonFunction a b) where 10 | toPy = pure . believe_me 11 | 12 | %foreign "python: lambda f: lambda *args: f(args[0]) if len(args) == 1 else f(args[0])(*args[1:])" 13 | prim__py_func_cast : (PythonObject -> PrimIO PythonObject) -> PrimIO (PythonFunction PythonObject PythonObject) 14 | 15 | -- The Prelude function toPrim is linear, while we want unrestricted 16 | -- This can be removed when linear subtyping reintroduced, or unrestricted version introduced into Prelude 17 | export 18 | toPrimIO : IO a -> PrimIO a 19 | toPrimIO io world = toPrim io world 20 | 21 | export 22 | toPyFunc : PrimPythonType a => PythonType IO b => (a -> b) -> IO (PythonFunction a b) 23 | toPyFunc f = 24 | map believe_me {a = PythonFunction PythonObject PythonObject} {b = PythonFunction a b} $ 25 | primIO $ 26 | prim__py_func_cast $ 27 | toPrimIO . toPy . f . believe_me {a = PythonObject} {b = a} 28 | 29 | export 30 | PrimPythonType a => PythonType IO b => PythonType IO (a -> b) where 31 | toPy f = (toPyFunc $ toPrimIO . toPy . f) >>= toPy 32 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonInteger.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonInteger 2 | 3 | import Python.PythonObject 4 | 5 | export 6 | data PythonInteger : Type where [external] 7 | 8 | export 9 | PrimPythonType PythonInteger where 10 | 11 | %foreign "python: lambda i: i" 12 | prim__cast_to_py_int : Int -> PythonInteger 13 | 14 | export 15 | HasIO io => PythonType io Int where 16 | toPy = toPy . prim__cast_to_py_int 17 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonList.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonList 2 | 3 | import Python.PythonObject 4 | 5 | export 6 | data PythonList : Type where [external] 7 | 8 | export 9 | PrimPythonType PythonList where 10 | 11 | %foreign "python: list" 12 | prim__py_list : PrimIO PythonList 13 | 14 | export 15 | empty : HasIO io => io PythonList 16 | empty = primIO prim__py_list 17 | 18 | %foreign "python: list.append" 19 | prim__py_list_append : PythonList -> PythonObject -> PrimIO () 20 | 21 | export 22 | append : PythonType io a => PythonList -> a -> io () 23 | append l o = primIO $ prim__py_list_append l !(toPy o) 24 | 25 | %foreign "python: list.reverse" 26 | prim__py_list_reverse : PythonList -> PrimIO () 27 | 28 | export 29 | reverse : HasIO io => PythonList -> io () 30 | reverse = primIO . prim__py_list_reverse 31 | 32 | export 33 | toPyList : PythonType io a => List a -> io PythonList 34 | toPyList xs = do 35 | l <- toPyList' xs 36 | reverse l 37 | pure l 38 | where 39 | toPyList' : List a -> io PythonList 40 | toPyList' [] = empty 41 | toPyList' (x :: xs) = do 42 | l <- toPyList' xs 43 | append l x 44 | pure l 45 | 46 | export 47 | PythonType io a => PythonType io (List a) where 48 | toPy = toPyList >=> toPy 49 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonObject.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonObject 2 | 3 | public export 4 | interface PrimPythonType a where 5 | 6 | export 7 | data PythonObject : Type where [external] 8 | 9 | export 10 | PrimPythonType PythonObject where 11 | 12 | public export 13 | interface HasIO io => PythonType io a where 14 | toPy : a -> io PythonObject 15 | 16 | export 17 | HasIO io => PrimPythonType a => PythonType io a where 18 | toPy = pure . believe_me 19 | 20 | export 21 | PythonType io a => PythonType io (PrimIO a) where 22 | toPy = (liftIO . primIO) >=> toPy 23 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonString.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonString 2 | 3 | import Python.PythonObject 4 | 5 | export 6 | data StringUTF8 : Type where [external] 7 | 8 | export 9 | PrimPythonType StringUTF8 where 10 | 11 | %foreign "python: bytes.decode" 12 | prim__py_bytes_decode : String -> StringUTF8 13 | 14 | %foreign "python: str.encode" 15 | prim__py_str_encode : StringUTF8 -> String 16 | 17 | export 18 | toUTF8 : String -> StringUTF8 19 | toUTF8 = prim__py_bytes_decode 20 | 21 | export 22 | fromUTF8 : StringUTF8 -> String 23 | fromUTF8 = prim__py_str_encode 24 | 25 | export 26 | fromString : String -> StringUTF8 27 | fromString = toUTF8 28 | 29 | export 30 | HasIO io => PythonType io String where 31 | toPy = toPy . toUTF8 32 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonTuple.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonTuple 2 | 3 | import Python.PythonList 4 | import Python.PythonObject 5 | 6 | export 7 | data PythonTuple : Type where [external] 8 | 9 | export 10 | PrimPythonType PythonTuple where 11 | 12 | %foreign "python: tuple" 13 | prim__py_tuple : PythonList -> PrimIO PythonTuple 14 | 15 | export 16 | pyToTuple : HasIO io => PythonList -> io PythonTuple 17 | pyToTuple = primIO . prim__py_tuple 18 | -------------------------------------------------------------------------------- /PythonBindings/Python/PythonUnit.idr: -------------------------------------------------------------------------------- 1 | module Python.PythonUnit 2 | 3 | import Python.PythonObject 4 | 5 | -- Represented as None in Python 6 | export 7 | PrimPythonType () where 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Idris2-Python 2 | 3 | A Python backend for Idris 2. 4 | 5 | ## Prerequisites 6 | 7 | - Idris 2 and the Idris 2 API, as per the [Idris installation instructions](https://github.com/idris-lang/Idris2/blob/master/INSTALL.md). 8 | 9 | - A C compiler, and the `gmp` library. 10 | 11 | These can be installed by: 12 | 13 | ```bash 14 | sudo apt-get install gcc 15 | sudo apt-get install libgmp3-dev 16 | ``` 17 | 18 | - If using Python 3.6, you'll need to manually install `dataclasses`. 19 | 20 | ```bash 21 | pip3 install dataclasses 22 | ``` 23 | 24 | ## Installation 25 | 26 | To build the `Idris2-Python` backend, and install the `Python` Idris library, run: 27 | ```bash 28 | make install 29 | ``` 30 | 31 | This builds an executable `build/exec/idris2-python` that can be used to compile Idris 2 code into a Python module. 32 | 33 | ## Compile code 34 | 35 | To compile Idris 2 code to a Python module, use `idris2-python` as you would `idris2` when [compiling with the standard backend](https://idris2.readthedocs.io/en/latest/backends/index.html). 36 | 37 | eg. 38 | ```bash 39 | ./build/exec/idris2-python tests/Idris2Python/HelloWorld/hello_world.idr -o hello_world 40 | ``` 41 | 42 | This produces a Python module in `build/exec`. 43 | 44 | ## Run code 45 | 46 | A Python module is a folder containing an `__init__.py` file. 47 | Python modules are not referred to by path, instead by name. 48 | Python searches for modules in the current directory (non-recursively), then in `$PYTHONPATH`, then installed modules. 49 | 50 | The module created in the previous section can be run by temporarily adding your build directory to `$PYTHONPATH`. 51 | ```bash session 52 | $ PYTHONPATH=build/exec python -m hello_world 53 | Hello, world 54 | ``` 55 | 56 | ## Python FFIs 57 | 58 | You can use both C and Python FFIs in your Idris 2 code when compiling to Python. 59 | For convenience, a bindings library is provided for some Python builtins. 60 | 61 | Python FFIs may be declared with the `%foreign` directive, using the format `"python: func, module"` for a Python function `func` in module `module`. 62 | For builtins, omit the module. 63 | 64 | For example, 65 | ```idris 66 | %foreign "python: abs" 67 | abs : Int -> Int 68 | ``` 69 | and 70 | ```idris 71 | %foreign "python: floor, math" 72 | floor : Int -> Int 73 | ``` 74 | 75 | The Python builtins bindings may be accessed by importing the module `Python`. 76 | 77 | For example, 78 | ```idris 79 | import Python 80 | 81 | main : IO () 82 | main = do 83 | l <- PythonList.empty 84 | 85 | append l "Orange" 86 | append l "Apple" 87 | 88 | print l 89 | ``` 90 | -------------------------------------------------------------------------------- /idris2-python.ipkg: -------------------------------------------------------------------------------- 1 | package idris2-python 2 | 3 | sourcedir = "Idris2Python" 4 | 5 | depends = idris2, network 6 | 7 | modules = 8 | Idris2Python, 9 | Idris2Python.ModuleTemplate, 10 | Idris2Python.PythonFFI 11 | 12 | main = Idris2Python 13 | executable = idris2-python 14 | -------------------------------------------------------------------------------- /python-bindings.ipkg: -------------------------------------------------------------------------------- 1 | package python 2 | 3 | sourcedir = "PythonBindings" 4 | 5 | modules = 6 | Python, 7 | Python.PythonBool, 8 | Python.PythonClass, 9 | Python.PythonDict, 10 | Python.PythonFunction, 11 | Python.PythonInteger, 12 | Python.PythonList, 13 | Python.PythonObject, 14 | Python.PythonString, 15 | Python.PythonTuple, 16 | Python.PythonUnit 17 | -------------------------------------------------------------------------------- /tests/Idris2Python/HelloWorld/expected: -------------------------------------------------------------------------------- 1 | Hello, world 2 | -------------------------------------------------------------------------------- /tests/Idris2Python/HelloWorld/hello_world.idr: -------------------------------------------------------------------------------- 1 | main : IO () 2 | main = putStrLn "Hello, world" 3 | -------------------------------------------------------------------------------- /tests/Idris2Python/HelloWorld/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/Idris2Python/HelloWorld/hello_world.idr -o hello_world 4 | PYTHONPATH=build/exec/ python -m hello_world 5 | 6 | rm -rf build/exec/hello_world 7 | -------------------------------------------------------------------------------- /tests/Idris2Python/PythonFFI/expected: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | -------------------------------------------------------------------------------- /tests/Idris2Python/PythonFFI/python_ffi.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | %foreign "python: abs" 4 | abs : Int -> Int 5 | 6 | %foreign "python: gcd, math" 7 | gcd : Int -> Int -> Int 8 | 9 | main : IO () 10 | main = do 11 | putStrLn $ show $ abs $ -1 12 | putStrLn $ show $ gcd 4 6 13 | -------------------------------------------------------------------------------- /tests/Idris2Python/PythonFFI/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/Idris2Python/PythonFFI/python_ffi.idr -o python_ffi 4 | PYTHONPATH=build/exec/ python -m python_ffi 5 | 6 | rm -rf build/exec/python_ffi 7 | -------------------------------------------------------------------------------- /tests/Idris2PythonTests.idr: -------------------------------------------------------------------------------- 1 | module Idris2PythonTests 2 | 3 | import Test.Golden 4 | 5 | idris2PythonTests : TestPool 6 | idris2PythonTests = MkTestPool "Idris2Python" [] Nothing [ 7 | "HelloWorld", 8 | "PythonFFI" 9 | ] 10 | 11 | pythonBindingsTests : TestPool 12 | pythonBindingsTests = MkTestPool "PythonBindings" [] Nothing [ 13 | "Boolean", 14 | "Integer", 15 | "PythonClass", 16 | "PythonDict", 17 | "PythonFunction", 18 | "PythonList", 19 | "PythonString", 20 | "PythonTuple", 21 | "Unit" 22 | ] 23 | 24 | main : IO () 25 | main = runner [ 26 | testPaths "Idris2Python" idris2PythonTests, 27 | testPaths "PythonBindings" pythonBindingsTests 28 | ] 29 | where 30 | testPaths : String -> TestPool -> TestPool 31 | testPaths dir = { testCases $= map ((dir ++ "/") ++) } 32 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | INTERACTIVE ?= --interactive 2 | threads ?= $(shell (nproc || sysctl -n hw.ncpu) 2>/dev/null || echo 1) 3 | 4 | .PHONY: test retest clean 5 | 6 | test: build/exec/idris2-python-tests 7 | ./build/exec/idris2-python-tests $(INTERACTIVE) --timing --failure-file failures --threads $(threads) --only $(only) 8 | 9 | retest: build/exec/idris2-python-tests 10 | ./build/exec/idris2-python-tests $(INTERACTIVE) --timing --failure-file failures --threads $(threads) --only-file failures --only $(only) 11 | 12 | build/exec/idris2-python-tests: idris2-python-tests.ipkg Idris2PythonTests.idr 13 | idris2 --build idris2-python-tests.ipkg 14 | 15 | clean: 16 | $(RM) failures 17 | $(RM) -r build 18 | $(RM) -r **/**/build 19 | @find . -type f -name 'output' -exec rm -rf {} \; 20 | @find . -type f -name '*.ttc' -exec rm -f {} \; 21 | @find . -type f -name '*.ttm' -exec rm -f {} \; 22 | @find . -type f -name '*.ibc' -exec rm -f {} \; 23 | -------------------------------------------------------------------------------- /tests/PythonBindings/Boolean/expected: -------------------------------------------------------------------------------- 1 | False 2 | True 3 | False 4 | True 5 | -------------------------------------------------------------------------------- /tests/PythonBindings/Boolean/python_bool.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | main : IO () 4 | main = do 5 | Python.print False 6 | Python.print True 7 | 8 | Prelude.printLn !(isTruthy "") 9 | Prelude.printLn !(isTruthy "Hello, world") 10 | -------------------------------------------------------------------------------- /tests/PythonBindings/Boolean/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/Boolean/python_bool.idr -o python_bool 4 | PYTHONPATH=build/exec/ python -m python_bool 5 | 6 | rm -rf build/exec/python_bool 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/Integer/expected: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/PythonBindings/Integer/python_integer.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | main : IO () 4 | main = do 5 | Python.print $ the Int 1 6 | -------------------------------------------------------------------------------- /tests/PythonBindings/Integer/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/Integer/python_integer.idr -o python_integer 4 | PYTHONPATH=build/exec/ python -m python_integer 5 | 6 | rm -rf build/exec/python_integer 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonClass/expected: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonClass/python_class.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | %foreign "python: lambda: str" 4 | py_str_class : PythonClass 5 | 6 | main : IO () 7 | main = do 8 | Python.print !(subclass "MyString" [py_str_class] !PythonDict.empty) 9 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonClass/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/PythonClass/python_class.idr -o python_class 4 | PYTHONPATH=build/exec/ python -m python_class 5 | 6 | rm -rf build/exec/python_class 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonDict/expected: -------------------------------------------------------------------------------- 1 | {} 2 | {'name': 'Lancelot', 'quest': 'The Holy Grail'} 3 | The Holy Grail 4 | Blue 5 | {'name': 'Brian', 'quest': 'A peaceful life'} 6 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonDict/python_dict.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | main : IO () 4 | main = do 5 | d <- PythonDict.empty 6 | Python.print d 7 | 8 | setItem d "name" "Lancelot" 9 | setItem d "quest" "The Holy Grail" 10 | Python.print d 11 | 12 | Python.print !(getItem d "quest" "The Messiah") 13 | Python.print !(getItem d "favorite color" "Blue") 14 | 15 | Python.print !(toPyDict [ 16 | ("name", "Brian"), 17 | ("quest", "A peaceful life") 18 | ]) 19 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonDict/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/PythonDict/python_dict.idr -o python_dict 4 | PYTHONPATH=build/exec/ python -m python_dict 5 | 6 | rm -rf build/exec/python_dict 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonFunction/expected: -------------------------------------------------------------------------------- 1 | Called from within Python 2 | Multiple 3 | arg 4 | support 5 | CALLED AS PYTHON FUNCTION 6 | Called as Python function 7 | called as python object 8 | Called as Python object 9 | Some 10 | more 11 | args 12 | .. at 0x0> 13 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonFunction/python_function.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | %foreign "python: lambda f: f(\"Called from within Python\")" 4 | prim__py_call : (StringUTF8 -> PrimIO ()) -> PrimIO () 5 | 6 | python_call : (StringUTF8 -> IO ()) -> IO () 7 | python_call f = primIO $ prim__py_call $ toPrimIO . f 8 | 9 | %foreign "python: lambda f: f(*\"Multiple arg support\".split())" 10 | prim__py_call_curried : (StringUTF8 -> StringUTF8 -> StringUTF8 -> PrimIO ()) -> PrimIO () 11 | 12 | python_call_curried : (StringUTF8 -> StringUTF8 -> StringUTF8 -> IO ()) -> IO () 13 | python_call_curried f = primIO $ prim__py_call_curried $ \x, y, z => toPrimIO (f x y z) 14 | 15 | %foreign "python: lambda f: f(\"Called as Python function\")" 16 | call_py_func : PythonFunction StringUTF8 StringUTF8 -> StringUTF8 17 | 18 | %foreign "python: lambda: str.upper" 19 | py_func : PythonFunction StringUTF8 StringUTF8 20 | 21 | call_as_py_func : (StringUTF8 -> StringUTF8) -> IO StringUTF8 22 | call_as_py_func = toPyFunc >=> (pure . call_py_func) 23 | 24 | %foreign "python: lambda f: f(\"Called as Python object\")" 25 | call_py_obj : PythonObject -> StringUTF8 26 | 27 | %foreign "python: lambda: str.lower" 28 | py_obj : PythonObject 29 | 30 | call_as_py_obj : (StringUTF8 -> StringUTF8) -> IO StringUTF8 31 | call_as_py_obj = toPy >=> (pure . call_py_obj) 32 | 33 | %foreign "python: lambda f: f(*\"Some more args\".split())" 34 | prim__py_call_py_obj_curried : PythonObject -> PrimIO () 35 | 36 | call_as_py_obj_curried : (StringUTF8 -> StringUTF8 -> StringUTF8 -> IO ()) -> IO () 37 | call_as_py_obj_curried f = primIO $ prim__py_call_py_obj_curried $ !(toPy $ \x, y, z => toPrimIO (f x y z)) 38 | 39 | print_three : StringUTF8 -> StringUTF8 -> StringUTF8 -> IO () 40 | print_three x y z = do 41 | Python.print x 42 | Python.print y 43 | Python.print z 44 | 45 | main : IO () 46 | main = do 47 | python_call Python.print 48 | python_call_curried print_three 49 | 50 | Python.print $ call_py_func py_func 51 | Python.print !(call_as_py_func id) 52 | 53 | Python.print $ call_py_obj py_obj 54 | Python.print !(call_as_py_obj id) 55 | call_as_py_obj_curried print_three 56 | 57 | Python.print (id {a = PythonObject}) 58 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonFunction/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/PythonFunction/python_function.idr -o python_function 4 | PYTHONPATH=build/exec/ python -m python_function | sed 's/0x[0-9a-f]\{12\}/0x0/g' 5 | 6 | rm -rf build/exec/python_function 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonList/expected: -------------------------------------------------------------------------------- 1 | [] 2 | ['Orange', 'Apple'] 3 | ['Apple', 'Orange'] 4 | ['Pear', 'Banana'] 5 | ['Kiwi', 'Apple'] 6 | HELLO, WORLD 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonList/python_list.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | %foreign "python: str.title" 4 | title : StringUTF8 -> StringUTF8 5 | 6 | data PythonUTF8Iterable : Type where [external] 7 | 8 | PrimPythonType PythonUTF8Iterable where 9 | 10 | %foreign "python: (list)" 11 | prim__py_explode : StringUTF8 -> PrimIO PythonUTF8Iterable 12 | 13 | explode : HasIO io => StringUTF8 -> io PythonUTF8Iterable 14 | explode = primIO . prim__py_explode 15 | 16 | %foreign "python: map" 17 | prim__py_map : (StringUTF8 -> StringUTF8) -> PythonUTF8Iterable -> PrimIO PythonUTF8Iterable 18 | 19 | map : HasIO io => (StringUTF8 -> StringUTF8) -> PythonUTF8Iterable -> io PythonUTF8Iterable 20 | map f xs = primIO $ prim__py_map f xs 21 | 22 | %foreign "python: \"\".join" 23 | prim__py_concat : PythonUTF8Iterable -> PrimIO StringUTF8 24 | 25 | concat : HasIO io => PythonUTF8Iterable -> io StringUTF8 26 | concat = primIO . prim__py_concat 27 | 28 | main : IO () 29 | main = do 30 | l <- PythonList.empty 31 | Python.print l 32 | 33 | append l "Orange" 34 | append l "Apple" 35 | Python.print l 36 | 37 | reverse l 38 | Python.print l 39 | 40 | Python.print !(toPyList ["Pear", "Banana"]) 41 | Python.print $ the (List String) ["Kiwi", "Apple"] 42 | 43 | Python.print !(concat !(map title !(explode "hello, world"))) 44 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonList/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/PythonList/python_list.idr -o python_list 4 | PYTHONPATH=build/exec/ python -m python_list 5 | 6 | rm -rf build/exec/python_list 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonString/expected: -------------------------------------------------------------------------------- 1 | Hello, World 2 | Lorem, Ipsum 3 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonString/python_string.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | %foreign "python: str.title" 4 | title : StringUTF8 -> StringUTF8 5 | 6 | main : IO () 7 | main = do 8 | Python.print $ title "hello, world" 9 | Prelude.putStrLn $ fromUTF8 $ title "lorem, ipsum" 10 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonString/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/PythonString/python_string.idr -o python_string 4 | PYTHONPATH=build/exec/ python -m python_string 5 | 6 | rm -rf build/exec/python_string 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonTuple/expected: -------------------------------------------------------------------------------- 1 | ('Hello', 'world') 2 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonTuple/python_tuple.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | main : IO () 4 | main = do 5 | Python.print !(pyToTuple !(toPyList ["Hello", "world"])) 6 | -------------------------------------------------------------------------------- /tests/PythonBindings/PythonTuple/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/PythonTuple/python_tuple.idr -o python_tuple 4 | PYTHONPATH=build/exec/ python -m python_tuple 5 | 6 | rm -rf build/exec/python_tuple 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/Unit/expected: -------------------------------------------------------------------------------- 1 | None 2 | None 3 | -------------------------------------------------------------------------------- /tests/PythonBindings/Unit/python_unit.idr: -------------------------------------------------------------------------------- 1 | import Python 2 | 3 | main : IO () 4 | main = do 5 | pyNone <- Python.print () 6 | Python.print pyNone 7 | -------------------------------------------------------------------------------- /tests/PythonBindings/Unit/run: -------------------------------------------------------------------------------- 1 | cd ../../.. 2 | 3 | ./build/exec/idris2-python tests/PythonBindings/Unit/python_unit.idr -o python_unit 4 | PYTHONPATH=build/exec/ python -m python_unit 5 | 6 | rm -rf build/exec/python_unit 7 | -------------------------------------------------------------------------------- /tests/idris2-python-tests.ipkg: -------------------------------------------------------------------------------- 1 | package idris2-python-tests 2 | 3 | sourcedir = "." 4 | 5 | depends = contrib, python, test 6 | 7 | main = Idris2PythonTests 8 | executable = idris2-python-tests 9 | --------------------------------------------------------------------------------