Skip to content

Commit a698030

Browse files
authored
neuronapi: add top-level objref/strdef get and set accessors (#3830)
* neuronapi: add top-level objref/strdef get and set accessors Adds nrn_symbol_object_get/set and nrn_symbol_str_get/set. A top-level objref or strdef stores its Object*/char* in the top-level object-data array (hoc_top_level_data[sym->u.oboff].pobj/.ppstr), not at sym->u.pval, so nrn_symbol_dataptr cannot return it. These read and write that storage: object_set follows HOC's assignment refcounting (unref old, ref new; NULL clears), and str_set copies via hoc_assign_str. Non-matching symbol types return NULL / nonzero rather than crashing.
1 parent f5ccbdb commit a698030

5 files changed

Lines changed: 179 additions & 0 deletions

File tree

docs/capi.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,57 @@ Functions, objects, and the stack
752752
Provides direct access to variable data for efficient reading/writing.
753753
e.g., use this for getting/setting the value of ``t`` (time).
754754
755+
.. c:function:: Object* nrn_symbol_object_get(const Symbol* sym)
756+
757+
Get the object bound to a top-level ``objref``.
758+
759+
:param sym: Symbol for a top-level ``objref``.
760+
:returns: The bound object, or ``NULL`` if the objref is nil or ``sym`` is
761+
not an objref.
762+
763+
The object is returned *borrowed* -- its reference count is not
764+
incremented. Call :c:func:`nrn_object_ref` to retain it beyond the next
765+
assignment to the objref. Complements :c:func:`nrn_symbol_dataptr`, which
766+
returns ``NULL`` for an objref because it is not a ``double*``.
767+
768+
.. c:function:: bool nrn_symbol_object_set(Symbol* sym, Object* obj)
769+
770+
Bind an object to a top-level ``objref``.
771+
772+
:param sym: Symbol for a top-level ``objref``.
773+
:param obj: The object to bind, or ``NULL`` to make the objref nil.
774+
:returns: ``true`` on success, ``false`` if ``sym`` is not an objref.
775+
776+
Follows HOC's assignment reference-counting: the previously bound object is
777+
released and the new one retained.
778+
779+
.. c:function:: const char* nrn_symbol_str_get(const Symbol* sym)
780+
781+
Get the string held by a top-level ``strdef``.
782+
783+
:param sym: Symbol for a top-level ``strdef``.
784+
:returns: The string, or ``NULL`` if ``sym`` is not a strdef.
785+
786+
.. c:function:: bool nrn_symbol_str_set(Symbol* sym, const char* value)
787+
788+
Set the string held by a top-level ``strdef``.
789+
790+
:param sym: Symbol for a top-level ``strdef``.
791+
:param value: The string to copy in.
792+
:returns: ``true`` on success, ``false`` if ``sym`` is not a strdef.
793+
794+
The value is copied into the strdef's storage (the previous string is
795+
freed).
796+
797+
**Python Equivalent:**
798+
799+
.. code-block:: python
800+
801+
n.s = "cell" # nrn_symbol_str_set
802+
name = n.s # nrn_symbol_str_get
803+
n.obj = vec # nrn_symbol_object_set
804+
bound = n.obj # nrn_symbol_object_get
805+
755806
.. c:function:: bool nrn_symbol_is_array(const Symbol* sym)
756807
757808
Check if a symbol represents an array.

src/nrniv/neuronapi.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,53 @@ double* nrn_symbol_dataptr(const Symbol* sym) {
286286
}
287287
}
288288

289+
Object* nrn_symbol_object_get(const Symbol* sym) {
290+
// A top-level objref (`objref o`) stores its Object* in the top-level
291+
// object-data array, not at sym->u.pval. Returns the bound object, or NULL
292+
// if the objref is nil or `sym` is not an objref. The object is returned
293+
// borrowed (its reference count is not incremented); call nrn_object_ref to
294+
// retain it past the next assignment to this objref.
295+
if (!sym || sym->type != OBJECTVAR) {
296+
return nullptr;
297+
}
298+
return hoc_top_level_data[sym->u.oboff].pobj[0];
299+
}
300+
301+
bool nrn_symbol_object_set(Symbol* sym, Object* obj) {
302+
// Bind `obj` to a top-level objref, following HOC's assignment refcount
303+
// rules: release the previously bound object and retain the new one. A NULL
304+
// obj clears the objref (makes it nil). Returns true on success, false if
305+
// `sym` is not an objref.
306+
if (!sym || sym->type != OBJECTVAR) {
307+
return false;
308+
}
309+
Object** cell = hoc_top_level_data[sym->u.oboff].pobj;
310+
hoc_dec_refcount(cell); // unref the old content and NULL the cell
311+
*cell = obj;
312+
hoc_obj_ref(obj); // NULL-safe
313+
return true;
314+
}
315+
316+
const char* nrn_symbol_str_get(const Symbol* sym) {
317+
// A top-level strdef (`strdef s`) stores its char* in the top-level
318+
// object-data array. Returns the string, or NULL if `sym` is not a strdef.
319+
if (!sym || sym->type != STRING) {
320+
return nullptr;
321+
}
322+
return hoc_top_level_data[sym->u.oboff].ppstr[0];
323+
}
324+
325+
bool nrn_symbol_str_set(Symbol* sym, const char* value) {
326+
// Copy `value` into a top-level strdef's storage (freeing the previous
327+
// string), via the same helper HOC string assignment uses. Returns true on
328+
// success, false if `sym` is not a strdef.
329+
if (!sym || sym->type != STRING) {
330+
return false;
331+
}
332+
hoc_assign_str(hoc_top_level_data[sym->u.oboff].ppstr, value);
333+
return true;
334+
}
335+
289336
bool nrn_symbol_is_array(const Symbol* sym) {
290337
return sym->arayinfo != nullptr;
291338
}

src/nrniv/neuronapi.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ void nrn_symbol_push(Symbol* sym);
8181
int nrn_symbol_type(const Symbol* sym);
8282
int nrn_symbol_subtype(const Symbol* sym);
8383
double* nrn_symbol_dataptr(const Symbol* sym);
84+
Object* nrn_symbol_object_get(const Symbol* sym);
85+
bool nrn_symbol_object_set(Symbol* sym, Object* obj);
86+
const char* nrn_symbol_str_get(const Symbol* sym);
87+
bool nrn_symbol_str_set(Symbol* sym, const char* value);
8488
bool nrn_symbol_is_array(const Symbol* sym);
8589
void nrn_double_push(double val);
8690
double nrn_double_pop(void);

test/api/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ foreach(
88
object_new_wrap.cpp
99
segment_diam.cpp
1010
sections.cpp
11+
symbol_object_string.cpp
1112
vclamp.cpp)
1213
string(REPLACE "." "_" api_test_name "${api_test_file}")
1314
add_executable(${api_test_name} ${api_test_file})

test/api/symbol_object_string.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH
2+
// Exercises the top-level objref and strdef accessors: nrn_symbol_object_get/set
3+
// and nrn_symbol_str_get/set. These read and write the Object* / char* that a
4+
// top-level `objref`/`strdef` stores in the top-level object-data array, which
5+
// nrn_symbol_dataptr cannot return (it is not a double*).
6+
#include <array>
7+
#include <cstring>
8+
#include <iostream>
9+
#include "neuronapi.h"
10+
11+
using std::cerr;
12+
using std::endl;
13+
14+
extern "C" void modl_reg(){/* No modl_reg */};
15+
16+
static bool check(bool cond, const char* msg) {
17+
if (!cond) {
18+
cerr << "FAIL: " << msg << endl;
19+
}
20+
return cond;
21+
}
22+
23+
int main(void) {
24+
static std::array<const char*, 4> argv = {"symbol_object_string",
25+
"-nogui",
26+
"-nopython",
27+
nullptr};
28+
nrn_init(3, argv.data());
29+
30+
bool ok = true;
31+
32+
// --- strdef get/set ---
33+
nrn_hoc_call("strdef s");
34+
nrn_hoc_call("s = \"hello\"");
35+
Symbol* s = nrn_symbol("s");
36+
ok &= check(s != nullptr, "strdef symbol resolves");
37+
const char* sval = nrn_symbol_str_get(s);
38+
ok &= check(sval != nullptr && std::strcmp(sval, "hello") == 0,
39+
"str_get reads the strdef value");
40+
41+
ok &= check(nrn_symbol_str_set(s, "world") == true, "str_set succeeds on a strdef");
42+
ok &= check(std::strcmp(nrn_symbol_str_get(s), "world") == 0, "str_set updated the value");
43+
// Confirm HOC sees the written value too (aliases the same storage).
44+
nrn_hoc_call("hoc_ac_ = strcmp(s, \"world\")");
45+
ok &= check(*nrn_symbol_dataptr(nrn_symbol("hoc_ac_")) == 0.0, "HOC reads the str_set value");
46+
47+
// --- objref get/set ---
48+
nrn_hoc_call("objref o");
49+
nrn_hoc_call("o = new Vector(3)");
50+
Symbol* o = nrn_symbol("o");
51+
ok &= check(o != nullptr, "objref symbol resolves");
52+
Object* vec = nrn_symbol_object_get(o);
53+
ok &= check(vec != nullptr, "object_get reads the bound object");
54+
ok &= check(nrn_vector_capacity(vec) == 3, "the bound object is the Vector(3)");
55+
56+
// Rebind the objref to a different Vector via object_set.
57+
Object* vec5 = nrn_object_new(nrn_symbol("Vector"), 0); // empty Vector
58+
nrn_object_ref(vec5);
59+
ok &= check(nrn_symbol_object_set(o, vec5) == true, "object_set succeeds on an objref");
60+
ok &= check(nrn_symbol_object_get(o) == vec5, "object_set rebound the objref");
61+
// HOC sees the new binding.
62+
nrn_hoc_call("hoc_ac_ = o.size()");
63+
ok &= check(*nrn_symbol_dataptr(nrn_symbol("hoc_ac_")) == 0.0, "HOC sees the rebound object");
64+
65+
// Clearing to nil.
66+
ok &= check(nrn_symbol_object_set(o, nullptr) == true, "object_set(NULL) clears the objref");
67+
ok &= check(nrn_symbol_object_get(o) == nullptr, "objref is nil after clear");
68+
69+
// --- type mismatches return NULL / false, not a crash ---
70+
ok &= check(nrn_symbol_object_get(s) == nullptr, "object_get on a strdef returns NULL");
71+
ok &= check(nrn_symbol_str_get(o) == nullptr, "str_get on an objref returns NULL");
72+
ok &= check(nrn_symbol_object_set(s, vec5) == false, "object_set on a strdef is rejected");
73+
ok &= check(nrn_symbol_str_set(o, "x") == false, "str_set on an objref is rejected");
74+
75+
return ok ? 0 : 1;
76+
}

0 commit comments

Comments
 (0)