diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05919ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +foo.c +example_builtin diff --git a/bash.h b/bash.h new file mode 100644 index 0000000..f1056d2 --- /dev/null +++ b/bash.h @@ -0,0 +1,65 @@ +#include + +#include +#include +#include +#include + +extern char **make_builtin_argv(WORD_LIST*, int*); + +#define DEFINE_BUILTIN(name) \ + struct builtin name##_struct = { \ + #name, \ + name##_builtin_func, \ + BUILTIN_ENABLED, \ + NULL, \ + NULL, \ + 0 \ + } + +#define WRAP_FUNC_WITH_BUILTIN(name)\ + int name##_builtin_func(WORD_LIST *list) { \ + int argc, ret; \ + char **argv; \ + argv = make_builtin_argv(list, &argc); \ + ret = name(argc, argv); \ + free(argv); \ + return ret;\ + } + +#define PY_FUNC(bash_name, modname, function) \ + int bash_name(int argc, char **argv) { \ + dlopen("libpython3.13.so", RTLD_NOW | RTLD_GLOBAL);\ + if (!Py_IsInitialized()) { \ + PyImport_AppendInittab(#modname, PyInit_##modname); \ + Py_Initialize(); \ + } \ + int _##bash_name(int argc, char **argv) { \ + int ret = 1; \ + PyObject *mod = NULL, *func = NULL, *result; \ + \ + mod = PyImport_ImportModule(#modname); \ + func = PyObject_GetAttrString(mod, #function); \ + \ + PyObject *py_argv = PyTuple_New(argc - 1); \ + \ + for (int i = 1; i < argc; i++) { \ + PyObject *arg_as_str = PyUnicode_FromString(argv[i]); \ + PyTuple_SetItem(py_argv, i-1, arg_as_str); \ + } \ + \ + result = PyObject_CallObject(func, py_argv); \ + if (!result) { PyErr_Print(); goto finally; } \ + ret = (int) PyFloat_AsDouble(result); \ + \ + finally: \ + Py_XDECREF(result); \ + Py_XDECREF(py_argv); \ + Py_XDECREF(func); \ + Py_XDECREF(mod); \ + return ret; \ + } \ + return _##bash_name(argc, argv); \ + } \ + WRAP_FUNC_WITH_BUILTIN(bash_name); \ + DEFINE_BUILTIN(bash_name); diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6414abd --- /dev/null +++ b/build.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# maybe uses non-posix regex and thus may be non-portable. oops. +# the bash regex implementation is actually platform dependent, lol. + +shopt -s extglob + +PY_VERSION=3.13 +PYFLAGS="$(/usr/bin/python${PY_VERSION}-config --cflags --ldflags)" +BASH_INCLUDE_DIR="/usr/include/bash" + +function translate_function_names() { + # cython generates garbled names like __pyx_pf_7py_test_2foo for a python function named foo + # this function generates a list of #defines that translate these names to what they are in python + + file="${1}" + + grep 'static PyObject \*__pyx_pf_' "${file}" | grep -v 'proto' | while read -r line; do + desired_name=$"${line#*$"${file/.c/}"_}" + desired_name="${desired_name/#+([0-9])/}" + real_name="${line#*\*}" + echo "#define ${desired_name%(*} ${real_name%(*}" +done +} + +py2c() { + rm "${1/.py/.c}" + cython3 -3 "${1}" -o "${1/.py/.c}" + translate_function_names $(basename ${1/.py/.c}) >> "${1/.py/.c}" +} + +compile() { + gcc -I$BASH_INCLUDE_DIR{/include,/builtins,}\ + $PYFLAGS $1 -o ${1/.c/} -Wall\ + -lpython3.13 \ + -fPIC -shared -O3 +} + +py2c foo.py +compile example_builtin.c diff --git a/example_builtin.c b/example_builtin.c new file mode 100644 index 0000000..68172c1 --- /dev/null +++ b/example_builtin.c @@ -0,0 +1,6 @@ +#include "bash.h" +#include "foo.c" + +PY_FUNC(foo, foo, _foo); +PY_FUNC(bar, foo, _bar); +PY_FUNC(graph, foo, _complicated_function); diff --git a/foo.py b/foo.py new file mode 100644 index 0000000..c322f9f --- /dev/null +++ b/foo.py @@ -0,0 +1,22 @@ +def _foo(*_): + print("foo.py") + return 0 + +def _bar(*args): + for arg in args: + print(arg) + + return 0 + +def _complicated_function(*args): + mean, stdv, *_ = args + mean, stdv = int(mean), int(stdv) + import numpy as np + import matplotlib.pyplot as plt + from scipy.stats import norm + x_min = norm.ppf(0.005, loc=mean, scale=stdv) + x_max = norm.ppf(0.995, loc=mean, scale=stdv) + x = np.linspace(x_min, x_max, 1000) + plt.plot(x, norm.pdf(x, mean, stdv)) + plt.show() + return 0