diff --git a/MEOS.NET.Tests/CallerAllocatedArrayTests.cs b/MEOS.NET.Tests/CallerAllocatedArrayTests.cs new file mode 100644 index 0000000..f1ea7a5 --- /dev/null +++ b/MEOS.NET.Tests/CallerAllocatedArrayTests.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +using MEOS.NET.Functions; +using MEOS.NET.Types; + +namespace MEOS.NET.Tests +{ + /// + /// The `each` family fills an array the CALLER allocates, so the wrapper + /// takes that array rather than making one: MEOS writes one pointer per + /// member of the object, and storage for fewer of them is written past. + /// + [TestClass] + public class CallerAllocatedArrayTests : MeosTest + { + [TestMethod] + public void EachFillsTheArrayTheCallerGivesIt() + { + Jsonb jb = Jsonb.In("{\"a\": 1, \"b\": \"two\"}")!; + IntPtr values = Marshal.AllocHGlobal(IntPtr.Size * 8); + try + { + IntPtr[] keys = Meos.JsonbEach(jb.Ptr, values); + + Assert.AreEqual(2, keys.Length); + Assert.AreEqual("a", Meos.TextOut(keys[0])); + Assert.AreEqual("b", Meos.TextOut(keys[1])); + Assert.AreEqual("1", + Meos.JsonbOut(Marshal.ReadIntPtr(values, 0))); + Assert.AreEqual("\"two\"", + Meos.JsonbOut(Marshal.ReadIntPtr(values, IntPtr.Size))); + } + finally + { + Marshal.FreeHGlobal(values); + } + } + } +} diff --git a/MEOS.NET.Tests/OutParameterTests.cs b/MEOS.NET.Tests/OutParameterTests.cs index c80285d..a96d16d 100644 --- a/MEOS.NET.Tests/OutParameterTests.cs +++ b/MEOS.NET.Tests/OutParameterTests.cs @@ -49,5 +49,34 @@ public void ATemporalFloatAnswersItsValueAtAMoment() Assert.IsNull( temp.ValueAtTimestamptz(new DateTime(2024, 12, 9, 0, 0, 0, DateTimeKind.Utc), true)); } + + [TestMethod] + public void ANearestNeighbourStepAnswersTheEntryAndItsDistance() + { + RTree tree = RTree.CreateStbox()!; + STBox near = STBox.In("STBOX X((1,1),(2,2))")!; + STBox far = STBox.In("STBOX X((9,9),(10,10))")!; + Assert.IsTrue(tree.Insert(near.Ptr, 1)); + Assert.IsTrue(tree.Insert(far.Ptr, 2)); + + STBox query = STBox.In("STBOX X((0,0),(0,0))")!; + RTreeNNCursor walk = RTreeNNCursor.Open(tree, query.Ptr)!; + try + { + (long Id, double Distance)? nearest = walk.Next(); + (long Id, double Distance)? next = walk.Next(); + + Assert.IsNotNull(nearest); + Assert.IsNotNull(next); + Assert.AreEqual(1L, nearest!.Value.Id); + Assert.AreEqual(2L, next!.Value.Id); + Assert.IsTrue(next.Value.Distance > nearest.Value.Distance); + Assert.IsNull(walk.Next()); + } + finally + { + walk.Close(); + } + } } } diff --git a/MEOS.NET/Functions/Meos.meos_json.g.cs b/MEOS.NET/Functions/Meos.meos_json.g.cs index 27542e7..81fd440 100644 --- a/MEOS.NET/Functions/Meos.meos_json.g.cs +++ b/MEOS.NET/Functions/Meos.meos_json.g.cs @@ -105,52 +105,34 @@ public static IntPtr[] JsonArrayElementsText(IntPtr js) public static int JsonArrayLength(IntPtr js) => SafeExecution(() => Native.JsonArrayLength(js)); - public static (IntPtr[], IntPtr[]) JsonEach(IntPtr js) + public static IntPtr[] JsonEach(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonEach(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonEach(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) JsonEachText(IntPtr js) + public static IntPtr[] JsonEachText(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonEachText(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonEachText(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static IntPtr JsonExtractPath(IntPtr js, IntPtr path_elems, int path_len) @@ -228,52 +210,34 @@ public static bool JsonbContained(IntPtr jb1, IntPtr jb2) public static bool JsonbContains(IntPtr jb1, IntPtr jb2) => SafeExecution(() => Native.JsonbContains(jb1, jb2)); - public static (IntPtr[], IntPtr[]) JsonbEach(IntPtr jb) + public static IntPtr[] JsonbEach(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonbEach(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonbEach(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) JsonbEachText(IntPtr jb) + public static IntPtr[] JsonbEachText(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonbEachText(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonbEachText(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static bool JsonbExists(IntPtr jb, IntPtr key) diff --git a/MEOS.NET/Functions/Meos.pgtypes.g.cs b/MEOS.NET/Functions/Meos.pgtypes.g.cs index 5dd3553..5992523 100644 --- a/MEOS.NET/Functions/Meos.pgtypes.g.cs +++ b/MEOS.NET/Functions/Meos.pgtypes.g.cs @@ -534,52 +534,34 @@ public static IntPtr[] PgJsonArrayElementsText(IntPtr js) public static int PgJsonArrayLength(IntPtr js) => SafeExecution(() => Native.PgJsonArrayLength(js)); - public static (IntPtr[], IntPtr[]) PgJsonEach(IntPtr js) + public static IntPtr[] PgJsonEach(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonEach(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonEach(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) PgJsonEachText(IntPtr js) + public static IntPtr[] PgJsonEachText(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonEachText(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonEachText(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static IntPtr[] PgJsonObjectKeys(IntPtr js) @@ -642,52 +624,34 @@ public static bool PgJsonbContained(IntPtr jb1, IntPtr jb2) public static bool PgJsonbContains(IntPtr jb1, IntPtr jb2) => SafeExecution(() => Native.PgJsonbContains(jb1, jb2)); - public static (IntPtr[], IntPtr[]) PgJsonbEach(IntPtr jb) + public static IntPtr[] PgJsonbEach(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonbEach(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonbEach(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) PgJsonbEachText(IntPtr jb) + public static IntPtr[] PgJsonbEachText(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonbEachText(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonbEachText(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static bool PgJsonbEq(IntPtr jb1, IntPtr jb2) diff --git a/MEOS.NET/Types/Geometry.g.cs b/MEOS.NET/Types/Geometry.g.cs index 41b26c9..66ec8f2 100644 --- a/MEOS.NET/Types/Geometry.g.cs +++ b/MEOS.NET/Types/Geometry.g.cs @@ -89,6 +89,21 @@ public double Length() public double MaxDistance2d(Geo gs2) => Meos.GeomMaxDistance2d(this.Ptr, gs2.Ptr); + public (Geo?, double) MinBoundingRadius() + { + IntPtr _radius = Marshal.AllocHGlobal(8); + try + { + var _answered = Meos.GeomMinBoundingRadius(this.Ptr, _radius); + + return (MEOSFactory.WrapGeo(_answered), Marshal.PtrToStructure(_radius)); + } + finally + { + Marshal.FreeHGlobal(_radius); + } + } + public Geo? OrientedEnvelope() => MEOSFactory.WrapGeo(Meos.GeomOrientedEnvelope(this.Ptr)); diff --git a/MEOS.NET/Types/Jsonb.g.cs b/MEOS.NET/Types/Jsonb.g.cs index d08e44c..210a745 100644 --- a/MEOS.NET/Types/Jsonb.g.cs +++ b/MEOS.NET/Types/Jsonb.g.cs @@ -86,20 +86,6 @@ public bool Contained(Jsonb jb2) } } - public (Text?[], Jsonb?[]) Each() - { - var _answered = Meos.JsonbEach(this.Ptr); - - return (MEOSFactory.WrapTextArray(_answered.Item1), MEOSFactory.WrapJsonbArray(_answered.Item2)); - } - - public (Text?[], Text?[]) EachText() - { - var _answered = Meos.JsonbEachText(this.Ptr); - - return (MEOSFactory.WrapTextArray(_answered.Item1), MEOSFactory.WrapTextArray(_answered.Item2)); - } - public bool Exists(Text key) => Meos.JsonbExists(this.Ptr, key.Ptr); diff --git a/MEOS.NET/Types/RTreeNNCursor.g.cs b/MEOS.NET/Types/RTreeNNCursor.g.cs index 0c638b4..06e88f1 100644 --- a/MEOS.NET/Types/RTreeNNCursor.g.cs +++ b/MEOS.NET/Types/RTreeNNCursor.g.cs @@ -17,6 +17,26 @@ internal RTreeNNCursor(IntPtr ptr) : base(ptr) { } public void Close() => Meos.RtreeNnCursorClose(this.Ptr); + public (long, double)? Next() + { + IntPtr _id_out = Marshal.AllocHGlobal(8); + IntPtr _dist_out = Marshal.AllocHGlobal(8); + try + { + if (!Meos.RtreeNnCursorNext(this.Ptr, _id_out, _dist_out)) + { + return null; + } + + return (Marshal.ReadInt64(_id_out), Marshal.PtrToStructure(_dist_out)); + } + finally + { + Marshal.FreeHGlobal(_id_out); + Marshal.FreeHGlobal(_dist_out); + } + } + public static RTreeNNCursor? Open(RTree rtree, IntPtr query) => MEOSFactory.WrapRTreeNNCursor(Meos.RtreeNnCursorOpen(rtree.Ptr, query)); diff --git a/MEOS.NET/Types/SPNNCursor.g.cs b/MEOS.NET/Types/SPNNCursor.g.cs index 6ad4c3a..48859a7 100644 --- a/MEOS.NET/Types/SPNNCursor.g.cs +++ b/MEOS.NET/Types/SPNNCursor.g.cs @@ -17,6 +17,26 @@ internal SPNNCursor(IntPtr ptr) : base(ptr) { } public void Close() => Meos.SptreeNnCursorClose(this.Ptr); + public (long, double)? Next() + { + IntPtr _id_out = Marshal.AllocHGlobal(8); + IntPtr _dist_out = Marshal.AllocHGlobal(8); + try + { + if (!Meos.SptreeNnCursorNext(this.Ptr, _id_out, _dist_out)) + { + return null; + } + + return (Marshal.ReadInt64(_id_out), Marshal.PtrToStructure(_dist_out)); + } + finally + { + Marshal.FreeHGlobal(_id_out); + Marshal.FreeHGlobal(_dist_out); + } + } + public static SPNNCursor? Open(SPTree sptree, IntPtr query) => MEOSFactory.WrapSPNNCursor(Meos.SptreeNnCursorOpen(sptree.Ptr, query)); diff --git a/tools/objectgen.py b/tools/objectgen.py index 5763f01..7bcd57d 100644 --- a/tools/objectgen.py +++ b/tools/objectgen.py @@ -275,7 +275,7 @@ class Method: def __init__(self, name: str, ret: str, params: list[tuple[str, str]], body: str, static: bool, arrays: list[tuple[str, str]] | None = None, - out_param: tuple[str, int, str] | None = None, + out_params: list[tuple[str, int, str]] | None = None, structs: list[tuple[str, str]] | None = None, scalar_arrays: list[str] | None = None, length_out: str | None = None, byte_buffer: bool = False): @@ -286,8 +286,9 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]], self.static = static # (parameter name, element class) for each counted array the method takes. self.arrays = arrays or [] - # (parameter name, bytes, reader expression) for a value out-parameter. - self.out_param = out_param + # (parameter name, bytes, reader expression) for each value MEOS states + # through an out-parameter. + self.out_params = out_params or [] # (parameter name, struct type) for each struct argument passed by value. self.structs = structs or [] # The caller's own arrays of scalars, pinned across the call. @@ -300,7 +301,7 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]], def needs_a_body(self) -> bool: """Whether the call needs anything allocated, pinned or read around it.""" return bool(self.structs or self.arrays or self.scalar_arrays - or self.out_param or self.length_out + or self.out_params or self.length_out or (self.ret.startswith("(") and self.ret.endswith(")"))) @@ -508,21 +509,40 @@ def method_for(self, cls: str, entry: dict) -> Method | None: out_params = [o for o in out_params if codegen.csharp_param_name(o) != length_out] - # A `bool` return with one value out-parameter is MEOS saying whether the - # value exists: the method answers the value, or nothing. - result_out = None - if clean(f["returnType"]["c"]) == "bool" and len(out_params) == 1: + # An out-parameter is a value MEOS answers, so every one of them is read + # back and none appears in the C# signature. A `bool` return alongside + # them is MEOS saying whether the values exist rather than an answer of + # its own, so the method answers them or nothing; any other return is an + # answer and stands beside them. + # An out-parameter the flat wrapper already answers — the array it fills + # and that array's count — is gone from the wrapper's own signature, and + # what the wrapper answers is not the object layer's to read again. + taken = {pname for _t, pname in params} + out_params = [o for o in out_params + if codegen.csharp_param_name(o) in taken] + # A by-pointer count says the call answers a NUMBER of things, so an + # out-parameter beside one holds as many as it states — `jsonb_each` + # fills its `Jsonb **values` with one pointer per member of the object. + # Reading such a parameter as a single value allocates room for one and + # takes what MEOS wrote first for the whole answer. + if any(p["name"] == "count" and clean(p["cType"]) == "int *" + for p in f.get("params", [])): + out_params = [] + + # And a pointer to a MEOS value is the value itself, handed over for the + # call to fill — `rtree_search(..., MeosArray *result)` takes the array + # it appends to. What MEOS WRITES THROUGH the pointer is a scalar, or a + # value whose address it states (`T **`); those are its answers, and the + # rest stay ordinary arguments. + result_outs = [] + for name in out_params: pointee = clean(next( (p["cType"] for p in f.get("params", []) - if p["name"] == out_params[0]), "")) + if p["name"] == name), "")) reader = OUT_PARAM_READERS.get(pointee) or self.wrapped_out_reader(pointee) if reader: - result_out = (codegen.csharp_param_name(out_params[0]), reader) - else: - self.deferred[cls].append( - f"{oo}: the value out-parameter {out_params[0]} is a " - f"{pointee}, which has no reader") - return None + result_outs.append((codegen.csharp_param_name(name), reader)) + answers_existence = clean(f["returnType"]["c"]) == "bool" and result_outs if length_out and clean(f["returnType"]["c"]) == "uint8_t *": ret = ("byte[]?", "$") @@ -539,16 +559,24 @@ def method_for(self, cls: str, entry: dict) -> Method | None: codegen.csharp_param_name(a["param"]) for a in input_arrays} - if result_out is not None: - ret_type = f"{result_out[1][0]}?" + if answers_existence: + # The `bool` is not an answer: what MEOS wrote is. + answered = ", ".join(r[0] for _n, r in result_outs) + ret_type = (f"{answered}?" if len(result_outs) == 1 + else f"({answered})?") ret_expr = "$" + elif result_outs: + # The return is an answer of its own and the out-parameters stand + # beside it, in the order MEOS declares them. + ret_type = ", ".join([ret_type] + [r[0] for _n, r in result_outs]) + ret_type = f"({ret_type})" sig: list[tuple[str, str]] = [] arrays: list[tuple[str, str]] = [] scalar_arrays: list[str] = [] structs: list[tuple[str, str]] = [] for cs_type, pname in params: - if result_out is not None and pname == result_out[0]: + if any(pname == n for n, _r in result_outs): args.append(scratch(pname)) continue if pname == length_out: @@ -590,12 +618,17 @@ def method_for(self, cls: str, entry: dict) -> Method | None: structs.append((pname, mapped[0])) call = f"Meos.{codegen.public_name(fname)}({', '.join(args)})" - out = None - if result_out is not None: - name, (_, size, reader) = result_out - out = (name, size, reader.format(scratch(name))) - return Method(pascal(oo), ret_type, sig, ret_expr.replace("$", call), static, - arrays, out, structs, scalar_arrays, length_out, + outs = [(name, size, reader.format(scratch(name))) + for name, (_t, size, reader) in result_outs] + body = ret_expr.replace("$", call) + if result_outs and not answers_existence: + # The call is made once and its own answer read beside the values it + # wrote, so the template carries the two halves apart. + read = ", ".join([ret_expr.replace("$", "_answered")] + + [reader for _n, _s, reader in outs]) + body = f"{call}|>({read})" + return Method(pascal(oo), ret_type, sig, body, static, + arrays, outs, structs, scalar_arrays, length_out, ret_type == "byte[]?") def inherited_names(self, cls: str) -> set[tuple]: @@ -693,8 +726,7 @@ def call_body(self, method: Method) -> list[str]: name = scratch(method.length_out) setup.append(f" IntPtr {name} = Marshal.AllocHGlobal(sizeof(long));") teardown.append(f" Marshal.FreeHGlobal({name});") - if method.out_param: - name, size, _reader = method.out_param + for name, size, _reader in method.out_params: setup.append( f" IntPtr {scratch(name)} = Marshal.AllocHGlobal({size});") teardown.append(f" Marshal.FreeHGlobal({scratch(name)});") @@ -712,17 +744,18 @@ def call_body(self, method: Method) -> list[str]: def answer(self, method: Method) -> list[str]: """What the body does with the call, once everything is in place.""" - if method.out_param: - # MEOS answers `false` where the value does not exist and leaves the - # out-parameter untouched, so the method answers null there. - _name, _size, reader = method.out_param + if method.out_params and "|>" not in method.body: + # MEOS answers `false` where the values do not exist and leaves the + # out-parameters untouched, so the method answers null there. + read = ", ".join(reader for _n, _s, reader in method.out_params) + answered = read if len(method.out_params) == 1 else f"({read})" return [ f" if (!{method.body})", " {", " return null;", " }", "", - f" return {reader};", + f" return {answered};", ] if method.byte_buffer: # A byte array does not carry its own length, so what MEOS wrote