├── .gitignore ├── LICENSE ├── README.md ├── pytcc.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── pytcc ├── __init__.py ├── _libtcc.py ├── tcc.py └── version.py ├── setup.py └── test ├── factorial.c ├── libtcc1-32.a ├── program.c ├── test.c ├── testTCCclass.py ├── testargv.py ├── testcallcfunction.py ├── testccallspython.py ├── testcompilefile.py ├── testerror.py ├── testfactorial.py ├── testlib.py └── testrun.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2024 PyTCC contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PyTCC 2 | 3 | ### Introduction 4 | 5 | PyTCC is a Python wrapper for the tiny C compiler library. The project 6 | consists of two APIs: the pythonic API (pytcc.TCC) wich contains a 7 | more pythonic API for TCC and the native API (pytcc.libtcc) wich 8 | contains the raw ctypes prototypes that communicate with C. 9 | 10 | ### Installation 11 | 12 | Just do: 13 | 14 | ``` 15 | $ python setup.py install 16 | ``` 17 | 18 | ### Quick start 19 | 20 | import pytcc 21 | tcc = pytcc.TCC() 22 | tcc.add_library_path("./") 23 | tcc.compile_string(""" 24 | int main(int argc, char **argv) 25 | { 26 | printf("Hello world"); 27 | return 0; 28 | } 29 | """) 30 | tcc.run() 31 | 32 | 33 | ### Documentation 34 | 35 | At the present time, the only documentation are the tests. However, when I complete the pythonic API, 36 | 37 | I will document the module. 38 | 39 | ### Contributing 40 | 41 | Contributions are very appreciated, especially in the documentation 42 | section. To contribute, just create an issue or send a pull request on 43 | GitHub and I wil check it. 44 | -------------------------------------------------------------------------------- /pytcc.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: pytcc 3 | Version: 0.1 4 | Summary: Python wrapper for the Tiny C Compiler 5 | Home-page: https://github.com/thgcode/pytcc 6 | Author: Thiago Seus 7 | Author-email: thiago.seus@yahoo.com.br 8 | License: UNKNOWN 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /pytcc.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | pytcc/__init__.py 3 | pytcc/_libtcc.py 4 | pytcc/tcc.py 5 | pytcc/version.py 6 | pytcc.egg-info/PKG-INFO 7 | pytcc.egg-info/SOURCES.txt 8 | pytcc.egg-info/dependency_links.txt 9 | pytcc.egg-info/top_level.txt 10 | test/testTCCclass.py 11 | test/testargv.py 12 | test/testccallspython.py 13 | test/testfactorial.py 14 | test/testlib.py 15 | test/testrun.py -------------------------------------------------------------------------------- /pytcc.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pytcc.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | pytcc 2 | -------------------------------------------------------------------------------- /pytcc/__init__.py: -------------------------------------------------------------------------------- 1 | from .version import VERSION, BUNDLED_TCC_VERSION 2 | from ._libtcc import lib as libtcc 3 | from .tcc import TCC, MEMORY, FILE, OBJ, PREPROCESS 4 | __all__ = ["libtcc", "TCC", "VERSION", "BUNDLED_TCC_VERSION"] 5 | -------------------------------------------------------------------------------- /pytcc/_libtcc.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from ctypes.util import find_library 3 | import os 4 | import platform 5 | import sys 6 | 7 | # libtcc DLL handle 8 | if sys.platform == 'linux' or sys.platform == 'darwin': 9 | ext = 'so' 10 | elif sys.platform == 'win32': 11 | ext = 'dll' 12 | 13 | try: 14 | lib = cdll.LoadLibrary(f'libtcc.{ext}') 15 | except OSError: 16 | raise Exception( 17 | f'Could not find libtcc.{ext} in path. Either add ' 18 | f'TCC directory to path or CD into the folder containing libktcc.{ext}' 19 | ) 20 | except ImportError: 21 | raise Exception( 22 | 'LibTCC was not compiled with the same architecture as this running ' 23 | 'Python process. Either reinstall Python or TCC.' 24 | ) 25 | 26 | 27 | class TCCState(Structure): 28 | pass 29 | 30 | TCCState = POINTER(TCCState) 31 | lib.tcc_new.restype = TCCState 32 | lib.tcc_delete.restype = None 33 | lib.tcc_delete.argtypes = [TCCState] 34 | lib.tcc_set_lib_path.restype = None 35 | lib.tcc_set_lib_path.argtypes = [TCCState, c_char_p] 36 | error_func = CFUNCTYPE(None, c_void_p, c_char_p) 37 | lib.tcc_set_error_func.restype = None 38 | lib.tcc_set_error_func.argtypes = [TCCState, c_void_p, error_func] 39 | lib.tcc_set_options.restype = c_int 40 | lib.tcc_set_options.argtypes = [TCCState, c_char_p] 41 | lib.tcc_add_include_path.restype = c_int 42 | lib.tcc_add_include_path.argtypes = [TCCState, c_char_p] 43 | lib.tcc_add_sysinclude_path.restype = c_int 44 | lib.tcc_add_sysinclude_path.argtypes = [TCCState, c_char_p] 45 | lib.tcc_define_symbol.restype = c_int 46 | lib.tcc_define_symbol.argtypes = [TCCState, c_char_p, c_char_p] 47 | lib.tcc_undefine_symbol.argtypes = [TCCState, c_char_p] 48 | lib.tcc_undefine_symbol.restype = c_int 49 | lib.tcc_add_file.argtypes = [TCCState, c_char_p] 50 | lib.tcc_add_file.restype = c_int 51 | lib.tcc_compile_string.argtypes = [TCCState, c_char_p] 52 | lib.tcc_compile_string.restype = c_int 53 | lib.tcc_set_output_type.argtypes = [TCCState, c_int] 54 | lib.tcc_set_output_type.restype = c_int 55 | 56 | TCC_OUTPUT_MEMORY = 1 57 | TCC_OUTPUT_FILE = 2 58 | TCC_OUTPUT_DLL = 3 59 | TCC_OUTPUT_OBJ = 4 60 | TCC_OUTPUT_PREPROCESS = 5 61 | 62 | lib.tcc_add_library_path.argtypes = [TCCState, c_char_p] 63 | lib.tcc_add_library_path.restype = c_int 64 | lib.tcc_add_library.argtypes = [TCCState, c_char_p] 65 | lib.tcc_add_library.restype = c_int 66 | lib.tcc_add_symbol.argtypes = [TCCState, c_char_p, c_void_p] 67 | lib.tcc_add_symbol.restype = c_int 68 | 69 | lib.tcc_output_file.argtypes = [TCCState, c_char_p] 70 | lib.tcc_output_file.restype = c_int 71 | lib.tcc_run.argtypes = [TCCState, c_int, POINTER(c_char_p)] 72 | lib.tcc_run.restype = c_int 73 | lib.tcc_relocate.argtypes = [TCCState, c_void_p] 74 | lib.tcc_relocate.restype = c_int 75 | 76 | TCC_RELOCATE_AUTO = c_void_p(1) 77 | lib.tcc_get_symbol.argtypes = [TCCState, c_char_p] 78 | lib.tcc_get_symbol.restype = c_void_p 79 | -------------------------------------------------------------------------------- /pytcc/tcc.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_char_p 2 | from functools import wraps 3 | from ._libtcc import (lib, TCC_OUTPUT_MEMORY as MEMORY, TCC_OUTPUT_FILE as FILE, 4 | TCC_OUTPUT_OBJ as OBJ, TCC_OUTPUT_PREPROCESS as PREPROCESS, TCC_RELOCATE_AUTO as AUTO, 5 | error_func) 6 | import sys 7 | 8 | message = "" 9 | 10 | @error_func 11 | def on_error(userdata, tmessage): 12 | global message 13 | message = tmessage.decode(sys.getdefaultencoding()) 14 | 15 | def _bytes(string, encoding=sys.getdefaultencoding()): 16 | """Always returns a byte string.""" 17 | if isinstance(string, bytes): 18 | return string 19 | else: 20 | return bytes(string, encoding) 21 | 22 | class Error(Exception): 23 | pass 24 | 25 | def ok_or_exception(func, *args, **kw): 26 | """A decorator that raizes an exception if the result of the call is 27 | nonzero.""" 28 | @wraps(func) 29 | def f(*args, **kw): 30 | result = func(*args, **kw) 31 | if result != 0: 32 | raise Error("Error in %s: %d: %s" % (func.__name__, result, message)) 33 | return result 34 | return f 35 | 36 | class _DefiningSymbolsDict(dict): 37 | """A dictionary that defines symbols in the TCC compiler.""" 38 | def __init__(self, state): 39 | super(_DefiningSymbolsDict, self).__init__() 40 | self.state = state 41 | 42 | def __setitem__(self, item, value): 43 | super(_DefiningSymbolsDict, self).__setitem__(item, value) 44 | lib.tcc_define_symbol(self.state, _bytes(item), _bytes(value)) 45 | 46 | def __delitem__(self, item): 47 | super(_DefiningSymbolsDict, self).__delitem__(item) 48 | lib.tcc_undefine_symbol(self.state, _bytes(item)) 49 | 50 | class TCC(object): 51 | """Represents a TCCState structure. 52 | The output type parameter, if present, controls if TCC will generate 53 | executable code in a file or the code will be directly executed in 54 | memory.""" 55 | def __init__(self, output_type=MEMORY): 56 | self.state = lib.tcc_new() 57 | self._library_path = None 58 | self.output_type = output_type 59 | self.preprocessor_symbols = _DefiningSymbolsDict(self.state) 60 | lib.tcc_set_error_func(self.state, None, on_error) 61 | 62 | def __del__(self): 63 | return lib.tcc_delete(self.state) 64 | 65 | @property 66 | def library_path(self): 67 | return self._library_path 68 | 69 | @library_path.setter 70 | def library_path(self, path): 71 | self._library_path = path 72 | return lib.tcc_set_lib_path(self.state, _bytes(path)) 73 | 74 | @ok_or_exception 75 | def add_include_path(self, path): 76 | """Adds the specified path to the TCC search path list.""" 77 | return lib.tcc_add_include_path(self.state, _bytes(path)) 78 | 79 | @ok_or_exception 80 | def add_sysinclude_path(self, path): 81 | """Adds the specified path to the TCC system include search path 82 | list, so that #include if foo.h is in this path will work for 83 | example.""" 84 | return lib.tcc_add_sysinclude_path(self.state, _bytes(path)) 85 | 86 | @ok_or_exception 87 | def add_file(self, path): 88 | """Adds the specified file for compilation.""" 89 | return lib.tcc_add_file(self.state, _bytes(path)) 90 | 91 | @ok_or_exception 92 | def compile_string(self, string): 93 | """Compiles the C source code in the specified string or bytes 94 | object.""" 95 | return lib.tcc_compile_string(self.state, _bytes(string)) 96 | 97 | def compile_file(self, file): 98 | """Compiles the C source code in the file specified. 99 | The file parameter can be a file name as string or a file-like 100 | object for reading.""" 101 | if hasattr(file, "read"): 102 | return self.compile_string(file.read()) 103 | else: 104 | with open(file, "r") as f: 105 | return self.compile_file(f) 106 | 107 | @property 108 | def output_type(self): 109 | """Controls if TCC will write the compilled program to a file or 110 | executed in-memory.""" 111 | return self.output_type 112 | 113 | @output_type.setter 114 | @ok_or_exception 115 | def output_type(self, type): 116 | self._output_type = type 117 | return lib.tcc_set_output_type(self.state, type) 118 | 119 | @ok_or_exception 120 | def add_library_path(self, path): 121 | """Adds the specified path to the TCC library search path 122 | list.""" 123 | return lib.tcc_add_library_path(self.state, _bytes(path)) 124 | 125 | @ok_or_exception 126 | def add_library(self, library): 127 | """Links the specified library in the code generated by TCC.""" 128 | return lib.tcc_add_library(self.state, _bytes(library)) 129 | 130 | @ok_or_exception 131 | def add_symbol(self, symbol, value): 132 | """Adds the specified ctypes function as a symbol to the 133 | compilled program, so it can be referenced and 134 | used to callback Python in the C code.""" 135 | return lib.tcc_add_symbol(self.state, _bytes(symbol), value) 136 | 137 | @ok_or_exception 138 | def output_file(self, path): 139 | """Writes the compilled program to the filename specified.""" 140 | return lib.tcc_output_file(self.state, _bytes(path)) 141 | 142 | @ok_or_exception 143 | def _run(self, argc, argv): 144 | """Internal method. Runs the compilled program passing the 145 | specified argc and argv.""" 146 | return lib.tcc_run(self.state, argc, argv) 147 | 148 | def run(self, argv=None): 149 | """Runs the compilled program. If argv is specified, it will be 150 | exposed to the C code as the program's command line.""" 151 | if argv is None: 152 | argv = [] 153 | c_argc = len(argv) 154 | if argv: 155 | c_argv = (c_char_p * c_argc)(*[c_char_p(_bytes(x)) for x in argv]) 156 | else: 157 | c_argv = None 158 | return self._run(c_argc, c_argv) 159 | 160 | @ok_or_exception 161 | def relocate(self, type=AUTO): 162 | """Exposes the compilled program's symbols for Python to use.""" 163 | return lib.tcc_relocate(self.state, type) 164 | 165 | def get_symbol(self, symbol): 166 | """Gets the specified symbol from the compilled program 167 | (normally a function).""" 168 | return lib.tcc_get_symbol(self.state, _bytes(symbol)) 169 | -------------------------------------------------------------------------------- /pytcc/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.1" 2 | BUNDLED_TCC_VERSION = "0.9.26" 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from pytcc.version import VERSION 3 | setup( 4 | name="pytcc", 5 | version=VERSION, 6 | url="https://github.com/thgcode/pytcc", 7 | description="Python wrapper for the Tiny C Compiler", 8 | author="Thiago Seus", 9 | author_email="thiago.seus@yahoo.com.br", 10 | packages = ["pytcc"]) 11 | -------------------------------------------------------------------------------- /test/factorial.c: -------------------------------------------------------------------------------- 1 | int factorial(int n) 2 | { 3 | int i, result; 4 | result = 1; 5 | if (n == 0) 6 | return 0; 7 | for (i = 2; i <= n; i++) 8 | { 9 | result *= i; 10 | } 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /test/libtcc1-32.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thgcode/pytcc/bf16b424de60a0033daaead4783712cdeaaf316c/test/libtcc1-32.a -------------------------------------------------------------------------------- /test/program.c: -------------------------------------------------------------------------------- 1 | int main() 2 | { 3 | printf("Hello world\n"); 4 | } 5 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | int sum(int a, int b) 2 | { 3 | #ifdef DEBUG 4 | printf("Sum %d %d\n", a, b); 5 | #endif 6 | return a + b; 7 | } 8 | -------------------------------------------------------------------------------- /test/testTCCclass.py: -------------------------------------------------------------------------------- 1 | from pytcc import TCC, FILE 2 | comp = TCC(output_type=FILE) 3 | comp.preprocessor_symbols["DEBUG"] = "1" 4 | comp.add_library_path("./") 5 | comp.add_file("test.c") 6 | comp.compile_string(''' 7 | int main() 8 | { 9 | printf("%d", sum(2, 2)); 10 | return 0; 11 | } 12 | ''') 13 | comp.output_file("b.exe") 14 | -------------------------------------------------------------------------------- /test/testargv.py: -------------------------------------------------------------------------------- 1 | from pytcc import TCC 2 | import sys 3 | comp = TCC() 4 | comp.add_library_path("./") 5 | comp.compile_string(''' 6 | int main(int argc, char **argv) 7 | { 8 | int i; 9 | for (i = 0; i < argc; i++) 10 | printf("%s", argv[i]); 11 | return 0; 12 | } 13 | ''') 14 | comp.run(sys.argv) 15 | -------------------------------------------------------------------------------- /test/testcallcfunction.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pathlib import Path 3 | from ctypes import * 4 | from pytcc import TCC 5 | 6 | source = ''' 7 | #include "Python.h" 8 | 9 | PyObject * pop(PyObject * self, PyObject * args, PyObject * kwargs) 10 | { 11 | return PyLong_FromLong(sum(2, square(2))); 12 | } 13 | 14 | int sum(int a, int b) 15 | { 16 | return a + b; 17 | } 18 | 19 | int main(int argc, char **argv) 20 | { 21 | printf("%s %d\n", argv[0], sum(2, 2)); 22 | return 0; 23 | } 24 | ''' 25 | 26 | def square(n): 27 | return n ** 2 28 | 29 | c_square = CFUNCTYPE(c_int, c_int)(square) 30 | 31 | python_dir = Path(sys.executable).parent 32 | comp = TCC() 33 | comp.add_library_path(f'{python_dir}') 34 | comp.add_include_path(f'{python_dir / "include"}') 35 | comp.add_library('python37') 36 | comp.add_symbol('square', c_square) 37 | comp.compile_string(source) 38 | comp.relocate() 39 | pop = CFUNCTYPE(py_object)(comp.get_symbol('pop')) 40 | 41 | print(pop()) 42 | -------------------------------------------------------------------------------- /test/testccallspython.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_int, CFUNCTYPE, POINTER 2 | from pytcc import TCC 3 | comp = TCC() 4 | comp.preprocessor_symbols["DEBUG"] = "1" 5 | comp.add_library_path("./") 6 | 7 | def square(n): 8 | return n ** 2 9 | 10 | squaresignature = CFUNCTYPE(c_int, c_int) 11 | func = squaresignature(square) 12 | 13 | comp.add_symbol("square", func) 14 | comp.compile_string(''' 15 | int main(int argc, char **argv) 16 | { 17 | printf("%d", square(6)); 18 | return 0; 19 | } 20 | ''') 21 | comp.run() 22 | -------------------------------------------------------------------------------- /test/testcompilefile.py: -------------------------------------------------------------------------------- 1 | from pytcc import TCC, FILE 2 | comp = TCC(output_type=FILE) 3 | comp.add_library_path("./") 4 | comp.compile_file("program.c") 5 | comp.output_file("program.exe") 6 | -------------------------------------------------------------------------------- /test/testerror.py: -------------------------------------------------------------------------------- 1 | from pytcc import TCC 2 | comp = TCC() 3 | comp.preprocessor_symbols["DEBUG"] = "1" 4 | comp.add_library_path("./") 5 | comp.add_file("test.c") 6 | comp.compile_string(''' 7 | int main(int argc, char **argv) 8 | { 9 | printf("%s %d", x, sum(2, 2)); 10 | return 0; 11 | } 12 | ''') 13 | comp.run(["test"]) 14 | -------------------------------------------------------------------------------- /test/testfactorial.py: -------------------------------------------------------------------------------- 1 | from ctypes import CFUNCTYPE, POINTER, c_int 2 | from pytcc import TCC 3 | comp = TCC() 4 | comp.add_library_path("./") 5 | comp.add_file("factorial.c") 6 | comp.relocate() 7 | factorial = comp.get_symbol("factorial") 8 | factorialsignature = CFUNCTYPE(c_int, c_int) 9 | func = factorialsignature(factorial) 10 | print(func(6)) # Returns the factorial of 6 11 | -------------------------------------------------------------------------------- /test/testlib.py: -------------------------------------------------------------------------------- 1 | from pytcc import libtcc, FILE 2 | state = libtcc.tcc_new() 3 | libtcc.tcc_set_output_type(state, FILE) 4 | libtcc.tcc_add_library_path(state, b".") 5 | libtcc.tcc_compile_string(state, b''' 6 | int main() 7 | { 8 | printf("Hello!"); 9 | return 0; 10 | } 11 | ''') 12 | libtcc.tcc_output_file(state, b"a.exe") 13 | libtcc.tcc_delete(state) 14 | -------------------------------------------------------------------------------- /test/testrun.py: -------------------------------------------------------------------------------- 1 | from pytcc import TCC 2 | comp = TCC() 3 | comp.preprocessor_symbols["DEBUG"] = "1" 4 | comp.add_library_path("./") 5 | comp.add_file("test.c") 6 | comp.compile_string(''' 7 | int main(int argc, char **argv) 8 | { 9 | printf("%s %d", argv[0], sum(2, 2)); 10 | return 0; 11 | } 12 | ''') 13 | comp.run(["test"]) 14 | --------------------------------------------------------------------------------