/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ │ above copyright notice and this permission notice appear in all copies. │ │ │ │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/conv.h" #include "libc/fmt/libgen.h" #include "libc/serialize.h" #include "libc/log/libfatal.internal.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/stdio/append.h" #include "libc/str/str.h" #include "libc/x/xasprintf.h" #include "third_party/chibicc/chibicc.h" static void AppendStringLiteral(char **b, const char *s, const char *indent) { int c, w, l, o; for (o = l = 0;; l = c) { switch ((c = *s++ & 255)) { case 0: return; case '\r': continue; case '\t': w = READ16LE("\\t"); break; case '\n': w = READ32LE("\\n\\\n"); break; case '"': w = READ16LE("\\\""); break; case '\\': w = READ16LE("\\\\"); break; case '`': /* convert markdown to restructured text */ if (o) { o = 0; w = READ16LE("''"); } else if (*s == '`') { w = '`'; ++s; } else { o = 1; w = READ16LE("``"); } break; default: if ((0x00 <= c && c <= 0x1F) || c == 0x7F || (c == '?' && l == '?')) { w = '\\'; w |= ('0' + ((c & 0300) >> 6)) << 010; w |= ('0' + ((c & 0070) >> 3)) << 020; w |= ('0' + ((c & 0007) >> 0)) << 030; } else { w = c; } break; } appendw(b, w); if (c == '\n' && indent) { appends(b, indent); } } } static void AppendJavadown(char **b, const struct Javadown *j) { size_t i; const char *s, *s2; if (j->title && *j->title) { AppendStringLiteral(b, j->title, 0); if (j->text && *j->text) { appendw(b, READ32LE("\\n\\\n")); appendw(b, READ32LE("\\n\\\n")); } } if (j->text && *j->text) { AppendStringLiteral(b, j->text, 0); } if (j->tags.n) { appendw(b, READ32LE("\\n\\\n")); for (i = 0; i < j->tags.n; ++i) { appendw(b, READ64LE("\\n\\\n:\0\0")); AppendStringLiteral(b, j->tags.p[i].tag, 0); s = j->tags.p[i].text; if (!strcmp(j->tags.p[i].tag, "param") && s && (s2 = strchr(s, ' '))) { appendw(b, ' '); appendd(b, s, s2 - s); s = s2 + 1; } appendw(b, ':'); if (s && *s) { appendw(b, ' '); AppendStringLiteral(b, s, " "); } } } } static void AppendScalar(char **b, struct Type *ty, char *name, int i) { if (ty->is_atomic) appendw(b, READ64LE("_Atomic ")); if (i && ty->is_const) appendw(b, READ64LE("const \0")); if (ty->is_unsigned) appends(b, "unsigned "); appends(b, name); } static void AppendType(char **b, struct Type *ty, int i) { switch (ty->kind) { case TY_VOID: appends(b, "void"); break; case TY_BOOL: appends(b, "_Bool"); break; case TY_CHAR: AppendScalar(b, ty, "char", i); break; case TY_SHORT: AppendScalar(b, ty, "short", i); break; case TY_INT: case TY_ENUM: AppendScalar(b, ty, "int", i); break; case TY_LONG: AppendScalar(b, ty, "long", i); break; case TY_INT128: AppendScalar(b, ty, "__int128", i); break; case TY_FLOAT: AppendScalar(b, ty, "float", i); break; case TY_DOUBLE: AppendScalar(b, ty, "double", i); break; case TY_LDOUBLE: AppendScalar(b, ty, "long double", i); break; case TY_FUNC: AppendType(b, ty->return_ty, i); appends(b, " (*)()"); break; case TY_PTR: if (!ty->array_len) { AppendType(b, ty->base, i + 1); if (ty->base->kind != TY_FUNC) { appendw(b, '*'); if (i && ty->is_const) appendw(b, READ64LE(" const\0")); if (ty->is_restrict) appends(b, " restrict"); } break; } /* undecay */ case TY_ARRAY: AppendType(b, ty->base, i + 1); appendw(b, '['); if (!i && ty->is_static) appendw(b, READ64LE("static ")); if (!i && ty->is_restrict) appends(b, "restrict "); appendf(b, "%lu", ty->array_len); appendw(b, ']'); break; default: assert(0); } } static bool IsSupportedReturnType(struct Type *ty) { switch (ty->kind) { case TY_VOID: case TY_BOOL: case TY_CHAR: case TY_SHORT: case TY_INT: case TY_LONG: case TY_FLOAT: case TY_DOUBLE: return true; case TY_PTR: if (ty->base->kind == TY_CHAR) { return !ty->base->is_unsigned; } else { return false; } default: return false; } } static bool IsSupportedParameterType(struct Type *ty) { switch (ty->kind) { case TY_BOOL: case TY_CHAR: case TY_SHORT: case TY_INT: case TY_LONG: case TY_FLOAT: case TY_DOUBLE: return true; case TY_PTR: if (ty->base->kind == TY_CHAR) { return true; } else { return false; } default: return false; } } static bool Reject(char **b, struct Obj *obj, struct Type *ty, const char *reason) { appendf(b, "\n/* %s: %s: ", obj->name, reason); AppendType(b, ty, 0); appendw(b, READ32LE(" */\n")); return false; } static bool IsFunctionSupported(char **b, struct Obj *func) { Obj *param; if (!IsSupportedReturnType(func->ty->return_ty)) { return Reject(b, func, func->ty->return_ty, "unsupported return type"); } for (param = func->params; param; param = param->next) { if (!IsSupportedParameterType(param->ty)) { return Reject(b, func, param->ty, "unsupported parameter type"); } } return true; } static int GetParamDirective(struct Obj **param) { bool is_unsigned; is_unsigned = (*param)->ty->is_unsigned; switch ((*param)->ty->kind) { case TY_BOOL: return 'p'; case TY_CHAR: return is_unsigned ? 'B' : 'b'; case TY_SHORT: return is_unsigned ? 'H' : 'h'; case TY_INT: return is_unsigned ? 'I' : 'i'; case TY_LONG: return is_unsigned ? 'L' : 'l'; case TY_FLOAT: return 'f'; case TY_DOUBLE: return 'd'; case TY_PTR: if ((*param)->ty->base->kind == TY_CHAR) { if ((*param)->ty->base->is_unsigned && ((*param)->next && ((*param)->next->ty->kind == TY_LONG && (*param)->next->ty->is_unsigned))) { *param = (*param)->next; return READ16LE("y*"); } else { return READ16LE("s*"); } } else { UNREACHABLE(); } default: UNREACHABLE(); } } static char *GetParamIntermediate(struct Obj **param) { bool is_unsigned; is_unsigned = (*param)->ty->is_unsigned; switch ((*param)->ty->kind) { case TY_BOOL: return "int"; case TY_CHAR: return is_unsigned ? "unsigned char" : "signed char"; case TY_SHORT: return is_unsigned ? "unsigned short" : "short"; case TY_INT: return is_unsigned ? "unsigned" : "int"; case TY_LONG: return is_unsigned ? "unsigned long" : "long"; case TY_FLOAT: return "float"; case TY_DOUBLE: return "float"; case TY_PTR: if ((*param)->ty->base->kind == TY_CHAR) { *param = (*param)->next; return "Py_buffer"; } else { UNREACHABLE(); } default: UNREACHABLE(); } } static const char *GetReturnIntermediate(struct Type *ty) { bool is_unsigned; is_unsigned = ty->is_unsigned; switch (ty->kind) { case TY_BOOL: return "int"; case TY_CHAR: return is_unsigned ? "unsigned char" : "signed char"; case TY_SHORT: return is_unsigned ? "unsigned short" : "short"; case TY_INT: return is_unsigned ? "unsigned" : "int"; case TY_LONG: return is_unsigned ? "unsigned long" : "long"; case TY_FLOAT: return "float"; case TY_DOUBLE: return "float"; case TY_PTR: if (ty->base->kind == TY_CHAR) { if (ty->base->is_const) { return "const char*"; } else { return "char*"; } } else { UNREACHABLE(); } default: UNREACHABLE(); } } static void AppendFunction(char **b, Obj *func, const char *module) { Obj *param; const char *name; appendf(b, "\nPyDoc_STRVAR(pb_%s_%s_doc,\n\"%s($module", module, func->name, func->name); for (param = func->params; param; param = param->next) { appendw(b, READ16LE(", ")); appends(b, param->name); } appends(b, ")"); if (func->javadown) { appends(b, "\\n\\\n--\\n\\n\\\n"); AppendJavadown(b, func->javadown->javadown); } appendw(b, READ32LE("\");\n\n")); AppendType(b, func->ty->return_ty, 0); appendw(b, ' '); appends(b, func->name); appendw(b, '('); if (func->params) { AppendType(b, func->params->ty, 0); for (param = func->params->next; param; param = param->next) { appendw(b, READ16LE(", ")); AppendType(b, param->ty, 0); } } else { appendw(b, READ32LE("void")); } appendw(b, READ32LE(");\n\n")); appends(b, "static PyObject*\n"); appendf(b, "pb_%s_%s(PyObject* self_, PyObject* args_)\n", module, func->name); appendw(b, READ16LE("{\n")); appendw(b, READ32LE(" ")); appends(b, "PyObject* res_;\n"); if (func->ty->return_ty->kind != TY_VOID) { appendw(b, READ32LE(" ")); appends(b, GetReturnIntermediate(func->ty->return_ty)); appendw(b, READ64LE(" ret_;\n")); } if (func->params) { for (param = func->params; param; param = param->next) { name = param->name; appendw(b, READ32LE(" ")); appends(b, GetParamIntermediate(¶m)); appendw(b, ' '); appends(b, name); appendw(b, READ16LE(";\n")); if (!param) break; } appends(b, " if (!PyArg_ParseTuple(args_, \""); for (param = func->params; param; param = param->next) { appendw(b, GetParamDirective(¶m)); if (!param) break; } appendf(b, ":%s\"", func->name); for (param = func->params; param; param = param->next) { appendf(b, ", &%s", param->name); } appends(b, ")) return 0;\n"); } appendw(b, READ32LE(" ")); if (func->ty->return_ty->kind != TY_VOID) { appendw(b, READ64LE("ret_ = ")); } appends(b, func->name); appendw(b, '('); for (param = func->params; param; param = param->next) { if (param != func->params) { appendw(b, READ16LE(", ")); } appends(b, param->name); if (param->ty->kind == TY_PTR && param->ty->base->kind == TY_CHAR) { appendw(b, READ32LE(".buf")); if (param->ty->base->is_unsigned && (param->next && (param->next->ty->kind == TY_LONG && param->next->ty->is_unsigned))) { appendf(b, ", %s.len", param->name); param = param->next; } } } appends(b, ");\n"); switch (func->ty->return_ty->kind) { case TY_VOID: appends(b, " res_ = Py_None;\n"); appends(b, " Py_INCREF(res_);\n"); break; case TY_BOOL: appends(b, " res_ = ret_ ? Py_True : Py_False;\n"); appends(b, " Py_INCREF(res_);\n"); break; case TY_CHAR: case TY_SHORT: case TY_INT: appends(b, " res_ = PyLong_FromLong(ret_);\n"); break; case TY_LONG: if (func->ty->return_ty->is_unsigned) { appends(b, " res_ = PyLong_FromUnsignedLong(ret_);\n"); } else { appends(b, " res_ = PyLong_FromLong(ret_);\n"); } break; case TY_FLOAT: case TY_DOUBLE: appends(b, " res_ = PyFloat_FromDouble(ret_);\n"); break; case TY_PTR: appends(b, "\ if (ret_) {\n\ res_ = PyUnicode_DecodeUTF8(ret_, strlen(ret_), 0);\n\ } else {\n\ res_ = Py_None;\n\ Py_INCREF(res_);\n\ }\n"); if (!func->ty->return_ty->base->is_const) { appends(b, " free(res_);\n"); } break; default: assert(0); } for (param = func->params; param; param = param->next) { if (param->ty->kind == TY_PTR && param->ty->base->kind == TY_CHAR) { appendf(b, " PyBuffer_Release(&%s);\n", param->name); } } appends(b, " return res_;\n"); appendw(b, READ16LE("}\n")); } void output_bindings_python(const char *path, Obj *prog, Token *tok) { int fd; Obj *obj; char *b = 0; char *bm = 0; const char *module; module = basename(stripexts(strdup(tok->file->name))); appends(&b, "\ #define PY_SSIZE_T_CLEAN\n\ #include \"third_party/python/Include/abstract.h\"\n\ #include \"third_party/python/Include/boolobject.h\"\n\ #include \"third_party/python/Include/floatobject.h\"\n\ #include \"third_party/python/Include/import.h\"\n\ #include \"third_party/python/Include/longobject.h\"\n\ #include \"third_party/python/Include/methodobject.h\"\n\ #include \"third_party/python/Include/modsupport.h\"\n\ #include \"third_party/python/Include/moduleobject.h\"\n\ #include \"third_party/python/Include/pymacro.h\"\n\ #include \"third_party/python/Include/pyport.h\"\n\ "); if (tok->file->javadown) { appendf(&b, "\nPyDoc_STRVAR(pb_%s_doc,\n\"", module); AppendJavadown(&b, tok->file->javadown); appendw(&b, READ32LE("\");\n")); } for (obj = prog; obj; obj = obj->next) { if (obj->is_function) { if (obj->is_static) continue; if (!obj->is_definition) continue; if (*obj->name == '_') continue; if (strchr(obj->name, '$')) continue; if (!IsFunctionSupported(&b, obj)) continue; AppendFunction(&b, obj, module); appendf(&bm, " {\"%s\", pb_%s_%s, %s, pb_%s_%s_doc},\n", obj->name, module, obj->name, obj->params ? "METH_VARARGS" : "METH_NOARGS", module, obj->name); } } appends(&bm, " {0},\n"); appendf(&b, "\nstatic PyMethodDef pb_%s_methods[] = {\n", module); appendd(&b, bm, appendz(bm).i); appends(&b, "};\n"); appendf(&b, "\n\ static struct PyModuleDef pb_%s_module = {\n\ PyModuleDef_HEAD_INIT,\n\ \"%s\",\n\ %s,\n\ -1,\n\ pb_%s_methods,\n\ };\n\ \n\ PyMODINIT_FUNC\n\ PyInit_%s(void)\n\ {\n\ return PyModule_Create(&pb_%s_module);\n\ }\n\ \n\ __attribute__((__section__(\".rodata.pytab.1\")))\n\ const struct _inittab _PyImport_Inittab_%s = {\n\ \"%s\",\n\ PyInit_%s,\n\ };\n\ ", module, module, tok->file->javadown ? gc(xasprintf("pb_%s_doc", module)) : "0", module, module, module, module, module, module); CHECK_NE(-1, (fd = creat(path, 0644))); CHECK_NE(-1, xwrite(fd, b, appendz(b).i)); CHECK_NE(-1, close(fd)); free(bm); free(b); }