From cf37c79f3132285a7d02f54573b1cfa85cb6b919 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 15:29:59 -0700 Subject: lib/clippy: dynamically wrap graph nodes The number of nodes in a graph will change as soon as cmd_graph_merge is supported as an operation, therefore size this dynamically. Signed-off-by: David Lamparter --- lib/command_py.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index f8abcf8ef4..ccdbfc5f8e 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -68,6 +68,7 @@ struct wrap_graph { char *definition; struct graph *graph; + size_t n_nodewrappers; struct wrap_graph_node **nodewrappers; }; @@ -138,6 +139,8 @@ static PyMethodDef methods_graph_node[] = { static void graph_node_wrap_free(void *arg) { struct wrap_graph_node *wrap = arg; + + assert(wrap->idx < wrap->wgraph->n_nodewrappers); wrap->wgraph->nodewrappers[wrap->idx] = NULL; Py_DECREF(wrap->wgraph); } @@ -166,6 +169,15 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, PyErr_SetString(PyExc_ValueError, "cannot find node in graph"); return NULL; } + if (i >= wgraph->n_nodewrappers) { + wgraph->nodewrappers = + realloc(wgraph->nodewrappers, + (i + 1) * sizeof(wgraph->nodewrappers[0])); + memset(wgraph->nodewrappers + wgraph->n_nodewrappers, 0, + sizeof(wgraph->nodewrappers[0]) * + (i + 1 - wgraph->n_nodewrappers)); + wgraph->n_nodewrappers = i + 1; + } if (wgraph->nodewrappers[i]) { PyObject *obj = (PyObject *)wgraph->nodewrappers[i]; Py_INCREF(obj); @@ -298,8 +310,6 @@ static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) gwrap->graph = graph; gwrap->definition = strdup(def); - gwrap->nodewrappers = calloc(vector_active(graph->nodes), - sizeof(gwrap->nodewrappers[0])); return (PyObject *)gwrap; } -- cgit v1.2.3 From 7fb8729a32e53a11f5cb0c5c741a35c59f33a273 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 15:30:09 -0700 Subject: lib/clippy: allow creating empty graph When merging graphs, it makes sense to allow starting with an empty one. Signed-off-by: David Lamparter --- lib/command_py.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index ccdbfc5f8e..ada0f0d386 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -296,7 +296,7 @@ static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) if (!gwrap) return NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "z|s", (char **)kwnames, &def, &doc)) return NULL; @@ -304,12 +304,18 @@ static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); - struct cmd_element cmd = {.string = def, .doc = doc}; - cmd_graph_parse(graph, &cmd); - cmd_graph_names(graph); + if (def) { + struct cmd_element cmd = { .string = def, .doc = doc }; + + cmd_graph_parse(graph, &cmd); + cmd_graph_names(graph); + + gwrap->definition = strdup(def); + } else { + gwrap->definition = strdup("NULL"); + } gwrap->graph = graph; - gwrap->definition = strdup(def); return (PyObject *)gwrap; } -- cgit v1.2.3 From 34a4f858b7823c07fd1d7faa385f6a692136fc2f Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 15:36:20 -0700 Subject: lib/clippy: wrap cmd_graph_merge via Graph.merge() Export cmd_graph_merge() to python code via graph1.merge(graph2). Signed-off-by: David Lamparter --- lib/command_py.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index ada0f0d386..82ca5a8ffc 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -258,9 +258,13 @@ static PyObject *graph_first(PyObject *self, PyObject *args) return graph_to_pyobj(gwrap, gn); }; +static PyObject *graph_merge(PyObject *self, PyObject *args); + static PyMethodDef methods_graph[] = { - {"first", graph_first, METH_NOARGS, "first graph node"}, - {}}; + { "first", graph_first, METH_NOARGS, "first graph node" }, + { "merge", graph_merge, METH_VARARGS, "merge graphs" }, + {} +}; static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds); @@ -285,6 +289,20 @@ static PyTypeObject typeobj_graph = { .tp_methods = methods_graph, }; +static PyObject *graph_merge(PyObject *self, PyObject *args) +{ + PyObject *py_other; + struct wrap_graph *gwrap = (struct wrap_graph *)self; + struct wrap_graph *gother; + + if (!PyArg_ParseTuple(args, "O!", &typeobj_graph, &py_other)) + return NULL; + + gother = (struct wrap_graph *)py_other; + cmd_graph_merge(gwrap->graph, gother->graph, +1); + Py_RETURN_NONE; +} + /* top call / entrypoint for python code */ static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) { -- cgit v1.2.3 From f85e58af12ae3ebff84262af6b7d15a137875881 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 15:55:43 -0700 Subject: lib/clippy: add a __repr__ for graph nodes Make it a little easier to work on python code using this wrapper. Signed-off-by: David Lamparter --- lib/command_py.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index 82ca5a8ffc..a80925d2d9 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -145,6 +145,14 @@ static void graph_node_wrap_free(void *arg) Py_DECREF(wrap->wgraph); } +static PyObject *repr_graph_node(PyObject *arg) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)arg; + + return PyUnicode_FromFormat("<_clippy.GraphNode %p [%zu] %s>", + wrap->node, wrap->idx, wrap->type); +} + static PyTypeObject typeobj_graph_node = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.GraphNode", .tp_basicsize = sizeof(struct wrap_graph_node), @@ -154,6 +162,7 @@ static PyTypeObject typeobj_graph_node = { .tp_free = graph_node_wrap_free, .tp_members = members_graph_node, .tp_methods = methods_graph_node, + .tp_repr = repr_graph_node, }; static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, -- cgit v1.2.3 From 8511f39987e1ee3438d7ff836e7edf7f619667d8 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 16:08:04 -0700 Subject: lib/clippy: allow accessing graph nodes by index Add len(graph) and graph[i] wrappers to access arbitrary nodes in a graph. Signed-off-by: David Lamparter --- lib/command_py.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index a80925d2d9..6c051f55cd 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -29,6 +29,7 @@ struct wrap_graph; static PyObject *graph_to_pyobj(struct wrap_graph *graph, struct graph_node *gn); +static PyObject *graph_to_pyobj_idx(struct wrap_graph *wgraph, size_t i); /* * nodes are wrapped as follows: @@ -168,7 +169,6 @@ static PyTypeObject typeobj_graph_node = { static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, struct graph_node *gn) { - struct wrap_graph_node *wrap; size_t i; for (i = 0; i < vector_active(wgraph->graph->nodes); i++) @@ -178,6 +178,15 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, PyErr_SetString(PyExc_ValueError, "cannot find node in graph"); return NULL; } + + return graph_to_pyobj_idx(wgraph, i); +} + +static PyObject *graph_to_pyobj_idx(struct wrap_graph *wgraph, size_t i) +{ + struct wrap_graph_node *wrap; + struct graph_node *gn = vector_slot(wgraph->graph->nodes, i); + if (i >= wgraph->n_nodewrappers) { wgraph->nodewrappers = realloc(wgraph->nodewrappers, @@ -287,6 +296,30 @@ static void graph_wrap_free(void *arg) free(wgraph->definition); } +static Py_ssize_t graph_length(PyObject *self) +{ + struct wrap_graph *gwrap = (struct wrap_graph *)self; + + return vector_active(gwrap->graph->nodes); +} + +static PyObject *graph_item(PyObject *self, Py_ssize_t idx) +{ + struct wrap_graph *gwrap = (struct wrap_graph *)self; + + if (idx >= vector_active(gwrap->graph->nodes)) + return PyErr_Format(PyExc_IndexError, + "index %zd past graph size %u", idx, + vector_active(gwrap->graph->nodes)); + + return graph_to_pyobj_idx(gwrap, idx); +} + +static PySequenceMethods seq_graph = { + .sq_length = graph_length, + .sq_item = graph_item, +}; + static PyTypeObject typeobj_graph = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.Graph", .tp_basicsize = sizeof(struct wrap_graph), @@ -296,6 +329,7 @@ static PyTypeObject typeobj_graph = { .tp_free = graph_wrap_free, .tp_members = members_graph, .tp_methods = methods_graph, + .tp_as_sequence = &seq_graph, }; static PyObject *graph_merge(PyObject *self, PyObject *args) -- cgit v1.2.3 From 3c1556f386a6c5866e1b5352571e44ed3e22da76 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 16:19:29 -0700 Subject: lib/clippy: expose graph nodes' back pointers There's a wrapper for nodes' outgoing pointers, but not incoming yet. Signed-off-by: David Lamparter --- lib/command_py.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index 6c051f55cd..411caa52d6 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -109,6 +109,24 @@ static PyObject *graph_node_next(PyObject *self, PyObject *args) pylist = PyList_New(vector_active(wrap->node->to)); for (size_t i = 0; i < vector_active(wrap->node->to); i++) { struct graph_node *gn = vector_slot(wrap->node->to, i); + + PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn)); + } + return pylist; +}; + +static PyObject *graph_node_prev(PyObject *self, PyObject *args) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + PyObject *pylist; + + if (wrap->node->data && + ((struct cmd_token *)wrap->node->data)->type == START_TKN) + return PyList_New(0); + pylist = PyList_New(vector_active(wrap->node->from)); + for (size_t i = 0; i < vector_active(wrap->node->from); i++) { + struct graph_node *gn = vector_slot(wrap->node->from, i); + PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn)); } return pylist; @@ -133,9 +151,11 @@ static PyObject *graph_node_join(PyObject *self, PyObject *args) }; static PyMethodDef methods_graph_node[] = { - {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"}, - {"join", graph_node_join, METH_NOARGS, "outbound join node"}, - {}}; + { "next", graph_node_next, METH_NOARGS, "outbound graph edge list" }, + { "prev", graph_node_prev, METH_NOARGS, "inbound graph edge list" }, + { "join", graph_node_join, METH_NOARGS, "outbound join node" }, + {} +}; static void graph_node_wrap_free(void *arg) { -- cgit v1.2.3 From e2344206ca26169bbc64b16a838856253013c1df Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 18:29:11 -0700 Subject: lib/clippy: expose JOIN_TKN's fork node FORK_TKN's join node is already exposed, mirror to expose JOIN_TKN's fork node. (contains minor cleanup to make checkpatch.pl shut up) Signed-off-by: David Lamparter --- lib/command_py.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index 411caa52d6..99438d4f5a 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -138,22 +138,40 @@ static PyObject *graph_node_prev(PyObject *self, PyObject *args) static PyObject *graph_node_join(PyObject *self, PyObject *args) { struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + struct cmd_token *tok; if (!wrap->node->data || ((struct cmd_token *)wrap->node->data)->type == END_TKN) Py_RETURN_NONE; - struct cmd_token *tok = wrap->node->data; + tok = wrap->node->data; if (tok->type != FORK_TKN) Py_RETURN_NONE; return graph_to_pyobj(wrap->wgraph, tok->forkjoin); }; +static PyObject *graph_node_fork(PyObject *self, PyObject *args) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + struct cmd_token *tok; + + if (!wrap->node->data || + ((struct cmd_token *)wrap->node->data)->type == END_TKN) + Py_RETURN_NONE; + + tok = wrap->node->data; + if (tok->type != JOIN_TKN) + Py_RETURN_NONE; + + return graph_to_pyobj(wrap->wgraph, tok->forkjoin); +}; + static PyMethodDef methods_graph_node[] = { { "next", graph_node_next, METH_NOARGS, "outbound graph edge list" }, { "prev", graph_node_prev, METH_NOARGS, "inbound graph edge list" }, { "join", graph_node_join, METH_NOARGS, "outbound join node" }, + { "fork", graph_node_fork, METH_NOARGS, "inbound fork node" }, {} }; -- cgit v1.2.3 From cb9d20b712c6b09ee6516f134e44e2a4a7181694 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 18:29:51 -0700 Subject: lib/clippy: improve graph node member access Expose all of the struct members of cmd_token, and retrieve them dynamically rather than copying them around. The problem with copying them is that they can change as a result of merge(), and if there is an existing wrapper object around it will not have its copy updated to match. Signed-off-by: David Lamparter --- lib/command_py.c | 91 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 21 deletions(-) (limited to 'lib/command_py.c') diff --git a/lib/command_py.c b/lib/command_py.c index 99438d4f5a..e459071426 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -45,13 +45,6 @@ struct wrap_graph_node { bool allowrepeat; const char *type; - bool deprecated; - bool hidden; - const char *text; - const char *desc; - const char *varname; - long long min, max; - struct graph_node *node; struct wrap_graph *wgraph; size_t idx; @@ -86,11 +79,75 @@ static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds) READONLY, (char *)#name " (" #type ")" \ } static PyMemberDef members_graph_node[] = { - member(allowrepeat, T_BOOL), member(type, T_STRING), - member(deprecated, T_BOOL), member(hidden, T_BOOL), - member(text, T_STRING), member(desc, T_STRING), - member(min, T_LONGLONG), member(max, T_LONGLONG), - member(varname, T_STRING), {}, + /* clang-format off */ + member(type, T_STRING), + member(idx, T_ULONG), + {}, + /* clang-format on */ +}; +#undef member + +static PyObject *graph_node_get_str(PyObject *self, void *poffset) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + void *offset = (char *)wrap->node->data + (ptrdiff_t)poffset; + const char *val = *(const char **)offset; + + if (!val) + Py_RETURN_NONE; + return PyUnicode_FromString(val); +} + +static PyObject *graph_node_get_bool(PyObject *self, void *poffset) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + void *offset = (char *)wrap->node->data + (ptrdiff_t)poffset; + bool val = *(bool *)offset; + + return PyBool_FromLong(val); +} + +static PyObject *graph_node_get_ll(PyObject *self, void *poffset) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + void *offset = (char *)wrap->node->data + (ptrdiff_t)poffset; + long long val = *(long long *)offset; + + return PyLong_FromLongLong(val); +} + +static PyObject *graph_node_get_u8(PyObject *self, void *poffset) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + void *offset = (char *)wrap->node->data + (ptrdiff_t)poffset; + uint8_t val = *(uint8_t *)offset; + + return PyLong_FromUnsignedLong(val); +} + +/* clang-format off */ +#define member(name, variant) \ + { \ + (char *)#name, \ + graph_node_get_##variant, \ + NULL, \ + (char *)#name " (" #variant ")", \ + (void *)offsetof(struct cmd_token, name), \ + } +/* clang-format on */ + +static PyGetSetDef getset_graph_node[] = { + /* clang-format off */ + member(attr, u8), + member(allowrepeat, bool), + member(varname_src, u8), + member(text, str), + member(desc, str), + member(min, ll), + member(max, ll), + member(varname, str), + {}, + /* clang-format on */ }; #undef member @@ -200,6 +257,7 @@ static PyTypeObject typeobj_graph_node = { .tp_new = refuse_new, .tp_free = graph_node_wrap_free, .tp_members = members_graph_node, + .tp_getset = getset_graph_node, .tp_methods = methods_graph_node, .tp_repr = repr_graph_node, }; @@ -281,15 +339,6 @@ static PyObject *graph_to_pyobj_idx(struct wrap_graph *wgraph, size_t i) default: wrap->type = "???"; } - - wrap->deprecated = !!(tok->attr & CMD_ATTR_DEPRECATED); - wrap->hidden = !!(tok->attr & CMD_ATTR_HIDDEN); - wrap->text = tok->text; - wrap->desc = tok->desc; - wrap->varname = tok->varname; - wrap->min = tok->min; - wrap->max = tok->max; - wrap->allowrepeat = tok->allowrepeat; } return (PyObject *)wrap; -- cgit v1.2.3 From ece3132471a1f361b82dde47bbf8cbf9675a12a8 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Sat, 20 Jul 2024 18:28:35 -0700 Subject: lib/clippy: add `CMD_ELEMENT_TKN` The command graph has its tail end nodes pointing at the `struct cmd_element` rather than a `struct cmd_token`. This is a bit weird to begin with, but becomes very annoying for the python bindings where there is just no `struct cmd_element`. Create a `CMD_ELEMENT_TKN` type for `cmd_token` instead, and replace the tail end token in the python bindings with an instance of that. Signed-off-by: David Lamparter --- lib/command_graph.c | 3 +++ lib/command_graph.h | 3 +++ lib/command_py.c | 21 +++++++++++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) (limited to 'lib/command_py.c') diff --git a/lib/command_graph.c b/lib/command_graph.c index ff3c11db69..20ab6b321b 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -267,6 +267,9 @@ static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb) case NEG_ONLY_TKN: case WORD_TKN: case ASNUM_TKN: +#ifdef BUILDING_CLIPPY + case CMD_ELEMENT_TKN: +#endif return true; } diff --git a/lib/command_graph.h b/lib/command_graph.h index 25aa47db7b..313c97fe87 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -54,6 +54,9 @@ enum cmd_token_type { END_TKN, // last token in line NEG_ONLY_TKN, // filter token, match if "no ..." command +#ifdef BUILDING_CLIPPY + CMD_ELEMENT_TKN, // python bindings only +#endif SPECIAL_TKN = FORK_TKN, }; /* clang-format on */ diff --git a/lib/command_py.c b/lib/command_py.c index e459071426..a77adcd7dc 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -160,8 +160,8 @@ static PyObject *graph_node_next(PyObject *self, PyObject *args) struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; PyObject *pylist; - if (wrap->node->data - && ((struct cmd_token *)wrap->node->data)->type == END_TKN) + if (wrap->node->data && + ((struct cmd_token *)wrap->node->data)->type == CMD_ELEMENT_TKN) return PyList_New(0); pylist = PyList_New(vector_active(wrap->node->to)); for (size_t i = 0; i < vector_active(wrap->node->to); i++) { @@ -335,6 +335,7 @@ static PyObject *graph_to_pyobj_idx(struct wrap_graph *wgraph, size_t i) item(START_TKN); item(END_TKN); item(NEG_ONLY_TKN); + item(CMD_ELEMENT_TKN); #undef item default: wrap->type = "???"; @@ -436,16 +437,16 @@ static PyObject *graph_merge(PyObject *self, PyObject *args) /* top call / entrypoint for python code */ static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) { - const char *def, *doc = NULL; + const char *def, *doc = NULL, *name = NULL; struct wrap_graph *gwrap; - static const char *kwnames[] = {"cmddef", "doc", NULL}; + static const char *const kwnames[] = { "cmddef", "doc", "name", NULL }; gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0); if (!gwrap) return NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "z|s", (char **)kwnames, - &def, &doc)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "z|ss", (char **)kwnames, + &def, &doc, &name)) return NULL; struct graph *graph = graph_new(); @@ -454,10 +455,18 @@ static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) if (def) { struct cmd_element cmd = { .string = def, .doc = doc }; + struct graph_node *last; cmd_graph_parse(graph, &cmd); cmd_graph_names(graph); + last = vector_slot(graph->nodes, + vector_active(graph->nodes) - 1); + assert(last->data == &cmd); + + last->data = cmd_token_new(CMD_ELEMENT_TKN, 0, name, def); + last->del = (void (*)(void *))cmd_token_del; + gwrap->definition = strdup(def); } else { gwrap->definition = strdup("NULL"); -- cgit v1.2.3