diff --git a/MEOS.NET.Tests/MultipleAnswerTests.cs b/MEOS.NET.Tests/MultipleAnswerTests.cs
new file mode 100644
index 0000000..ded1b29
--- /dev/null
+++ b/MEOS.NET.Tests/MultipleAnswerTests.cs
@@ -0,0 +1,30 @@
+using MEOS.NET.Structures;
+using MEOS.NET.Types;
+
+namespace MEOS.NET.Tests
+{
+ ///
+ /// A call that answers several values answers them together, so the method
+ /// answers a tuple and the call is made once — the halves of one answer
+ /// come from one call.
+ ///
+ [TestClass]
+ public class MultipleAnswerTests : MeosTest
+ {
+ [TestMethod]
+ public void SplittingByTimeAnswersThePiecesAndTheirBins()
+ {
+ Temporal trip = TFloat.In(
+ "[1@2024-12-06, 2@2024-12-07, 3@2024-12-08]")!;
+
+ (Temporal?[] pieces, DateTime[] bins) = trip.TimeSplit(
+ Interval.In("1 day")!.Value,
+ new DateTime(2024, 12, 6, 0, 0, 0, DateTimeKind.Utc));
+
+ Assert.AreEqual(pieces.Length, bins.Length);
+ Assert.IsTrue(pieces.Length > 1);
+ Assert.AreEqual(new DateTime(2024, 12, 6, 0, 0, 0, DateTimeKind.Utc),
+ bins[0]);
+ }
+ }
+}
diff --git a/MEOS.NET/Functions/Meos.meos.g.cs b/MEOS.NET/Functions/Meos.meos.g.cs
index 757a524..dd03274 100644
--- a/MEOS.NET/Functions/Meos.meos.g.cs
+++ b/MEOS.NET/Functions/Meos.meos.g.cs
@@ -3136,8 +3136,19 @@ public static int[] TintValues(IntPtr temp)
finally { Marshal.FreeHGlobal(_cnt); }
}
- public static IntPtr TbigintValues(IntPtr temp, IntPtr count)
- => SafeExecution(() => Native.TbigintValues(temp, count));
+ public static long[] TbigintValues(IntPtr temp)
+ {
+ IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));
+ try
+ {
+ IntPtr _p = SafeExecution(() => Native.TbigintValues(temp, _cnt));
+ int _n = Marshal.ReadInt32(_cnt);
+ long[] _out = new long[_n];
+ Marshal.Copy(_p, _out, 0, _n);
+ return _out;
+ }
+ finally { Marshal.FreeHGlobal(_cnt); }
+ }
public static double TnumberAvgValue(IntPtr temp)
=> SafeExecution(() => Native.TnumberAvgValue(temp));
diff --git a/MEOS.NET/Types/Jsonb.g.cs b/MEOS.NET/Types/Jsonb.g.cs
index 210a745..d08e44c 100644
--- a/MEOS.NET/Types/Jsonb.g.cs
+++ b/MEOS.NET/Types/Jsonb.g.cs
@@ -86,6 +86,20 @@ 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/MEOSConvert.g.cs b/MEOS.NET/Types/MEOSConvert.g.cs
index ec372f1..b65354c 100644
--- a/MEOS.NET/Types/MEOSConvert.g.cs
+++ b/MEOS.NET/Types/MEOSConvert.g.cs
@@ -33,6 +33,30 @@ internal static int ToDateADT(DateOnly day)
=> Meos.DateIn(
day.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
+ /// Each MEOS TimestampTz of an array, as a DateTime.
+ internal static DateTime[] ToDateTimeArray(long[] moments)
+ {
+ DateTime[] values = new DateTime[moments.Length];
+ for (int i = 0; i < moments.Length; i++)
+ {
+ values[i] = ToDateTime(moments[i]);
+ }
+
+ return values;
+ }
+
+ /// Each MEOS DateADT of an array, as a DateOnly.
+ internal static DateOnly[] ToDateOnlyArray(int[] days)
+ {
+ DateOnly[] values = new DateOnly[days.Length];
+ for (int i = 0; i < days.Length; i++)
+ {
+ values[i] = ToDateOnly(days[i]);
+ }
+
+ return values;
+ }
+
/// The struct MEOS answers through a pointer, as a value. The
/// memory behind the pointer stays MEOS's, as it does for every other
/// value the layer reads back.
diff --git a/MEOS.NET/Types/MEOSFactory.g.cs b/MEOS.NET/Types/MEOSFactory.g.cs
index bf0c29c..2768a23 100644
--- a/MEOS.NET/Types/MEOSFactory.g.cs
+++ b/MEOS.NET/Types/MEOSFactory.g.cs
@@ -329,6 +329,30 @@ public static class MEOSFactory
return wrapped;
}
+ /// The value at as the RTreeNNCursor
+ /// class the model gives its runtime type.
+ public static RTreeNNCursor? WrapRTreeNNCursor(IntPtr ptr)
+ {
+ if (ptr == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return new RTreeNNCursor(ptr);
+ }
+
+ /// Every element of a C array of RTreeNNCursor pointers, wrapped.
+ public static RTreeNNCursor?[] WrapRTreeNNCursorArray(IntPtr[] ptrs)
+ {
+ RTreeNNCursor?[] wrapped = new RTreeNNCursor?[ptrs.Length];
+ for (int i = 0; i < ptrs.Length; i++)
+ {
+ wrapped[i] = WrapRTreeNNCursor(ptrs[i]);
+ }
+
+ return wrapped;
+ }
+
/// The value at as the Raquet
/// class the model gives its runtime type.
public static Raquet? WrapRaquet(IntPtr ptr)
@@ -353,6 +377,30 @@ public static class MEOSFactory
return wrapped;
}
+ /// The value at as the SPNNCursor
+ /// class the model gives its runtime type.
+ public static SPNNCursor? WrapSPNNCursor(IntPtr ptr)
+ {
+ if (ptr == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return new SPNNCursor(ptr);
+ }
+
+ /// Every element of a C array of SPNNCursor pointers, wrapped.
+ public static SPNNCursor?[] WrapSPNNCursorArray(IntPtr[] ptrs)
+ {
+ SPNNCursor?[] wrapped = new SPNNCursor?[ptrs.Length];
+ for (int i = 0; i < ptrs.Length; i++)
+ {
+ wrapped[i] = WrapSPNNCursor(ptrs[i]);
+ }
+
+ return wrapped;
+ }
+
/// The value at as the SPTree
/// class the model gives its runtime type.
public static SPTree? WrapSPTree(IntPtr ptr)
diff --git a/MEOS.NET/Types/RTreeNNCursor.g.cs b/MEOS.NET/Types/RTreeNNCursor.g.cs
new file mode 100644
index 0000000..0c638b4
--- /dev/null
+++ b/MEOS.NET/Types/RTreeNNCursor.g.cs
@@ -0,0 +1,24 @@
+#nullable enable
+
+using System.Runtime.InteropServices;
+
+using MEOS.NET.Enums;
+using MEOS.NET.Functions;
+using MEOS.NET.Structures;
+
+namespace MEOS.NET.Types
+{
+ /// A walk over an R-tree's entries in order of distance from a query. MEOS registers it in no enum, so it names no temptype.
+ [System.CodeDom.Compiler.GeneratedCode("MEOS.NET.ObjectGen", "0.1.0")]
+ public class RTreeNNCursor : Value
+ {
+ internal RTreeNNCursor(IntPtr ptr) : base(ptr) { }
+
+ public void Close()
+ => Meos.RtreeNnCursorClose(this.Ptr);
+
+ 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
new file mode 100644
index 0000000..6ad4c3a
--- /dev/null
+++ b/MEOS.NET/Types/SPNNCursor.g.cs
@@ -0,0 +1,24 @@
+#nullable enable
+
+using System.Runtime.InteropServices;
+
+using MEOS.NET.Enums;
+using MEOS.NET.Functions;
+using MEOS.NET.Structures;
+
+namespace MEOS.NET.Types
+{
+ /// The SP-tree's nearest-neighbour walk, the R-tree cursor's sibling. MEOS registers it in no enum, so it names no temptype.
+ [System.CodeDom.Compiler.GeneratedCode("MEOS.NET.ObjectGen", "0.1.0")]
+ public class SPNNCursor : Value
+ {
+ internal SPNNCursor(IntPtr ptr) : base(ptr) { }
+
+ public void Close()
+ => Meos.SptreeNnCursorClose(this.Ptr);
+
+ public static SPNNCursor? Open(SPTree sptree, IntPtr query)
+ => MEOSFactory.WrapSPNNCursor(Meos.SptreeNnCursorOpen(sptree.Ptr, query));
+
+ }
+}
diff --git a/MEOS.NET/Types/TBigint.g.cs b/MEOS.NET/Types/TBigint.g.cs
index 5105d83..8fa2835 100644
--- a/MEOS.NET/Types/TBigint.g.cs
+++ b/MEOS.NET/Types/TBigint.g.cs
@@ -93,6 +93,9 @@ public long StartValue()
}
}
+ public long[] Values()
+ => Meos.TbigintValues(this.Ptr);
+
public static Temporal? FromBaseTemp(long i, Temporal temp)
=> MEOSFactory.WrapTemporal(Meos.TbigintFromBaseTemp(i, temp.Ptr));
diff --git a/MEOS.NET/Types/TFloat.g.cs b/MEOS.NET/Types/TFloat.g.cs
index 4b579cc..c0c0cea 100644
--- a/MEOS.NET/Types/TFloat.g.cs
+++ b/MEOS.NET/Types/TFloat.g.cs
@@ -140,6 +140,13 @@ public double StartValue()
}
}
+ public (Temporal?[], double[]) ValueSplit(double size, double origin)
+ {
+ var _answered = Meos.TfloatValueSplit(this.Ptr, size, origin);
+
+ return (MEOSFactory.WrapTemporalArray(_answered.Item1), _answered.Item2);
+ }
+
public TBox?[] ValueTimeBoxes(double vsize, Interval duration, double vorigin, DateTime torigin)
{
IntPtr _duration = Marshal.AllocHGlobal(Marshal.SizeOf());
@@ -154,6 +161,22 @@ public double StartValue()
}
}
+ public (Temporal?[], double[], DateTime[]) ValueTimeSplit(double vsize, Interval duration, double vorigin, DateTime torigin)
+ {
+ IntPtr _duration = Marshal.AllocHGlobal(Marshal.SizeOf());
+ try
+ {
+ Marshal.StructureToPtr(duration, _duration, false);
+ var _answered = Meos.TfloatValueTimeSplit(this.Ptr, vsize, _duration, vorigin, MEOSConvert.ToTimestampTz(torigin));
+
+ return (MEOSFactory.WrapTemporalArray(_answered.Item1), _answered.Item2, MEOSConvert.ToDateTimeArray(_answered.Item3));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(_duration);
+ }
+ }
+
public double[] Values()
=> Meos.TfloatValues(this.Ptr);
diff --git a/MEOS.NET/Types/TInt.g.cs b/MEOS.NET/Types/TInt.g.cs
index b4b43a5..f9c4758 100644
--- a/MEOS.NET/Types/TInt.g.cs
+++ b/MEOS.NET/Types/TInt.g.cs
@@ -110,6 +110,13 @@ public int StartValue()
}
}
+ public (Temporal?[], int[]) ValueSplit(int vsize, int vorigin)
+ {
+ var _answered = Meos.TintValueSplit(this.Ptr, vsize, vorigin);
+
+ return (MEOSFactory.WrapTemporalArray(_answered.Item1), _answered.Item2);
+ }
+
public TBox?[] ValueTimeBoxes(int vsize, Interval duration, int vorigin, DateTime torigin)
{
IntPtr _duration = Marshal.AllocHGlobal(Marshal.SizeOf());
@@ -124,6 +131,22 @@ public int StartValue()
}
}
+ public (Temporal?[], int[], DateTime[]) ValueTimeSplit(int size, Interval duration, int vorigin, DateTime torigin)
+ {
+ IntPtr _duration = Marshal.AllocHGlobal(Marshal.SizeOf());
+ try
+ {
+ Marshal.StructureToPtr(duration, _duration, false);
+ var _answered = Meos.TintValueTimeSplit(this.Ptr, size, _duration, vorigin, MEOSConvert.ToTimestampTz(torigin));
+
+ return (MEOSFactory.WrapTemporalArray(_answered.Item1), _answered.Item2, MEOSConvert.ToDateTimeArray(_answered.Item3));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(_duration);
+ }
+ }
+
public int[] Values()
=> Meos.TintValues(this.Ptr);
diff --git a/MEOS.NET/Types/TRGeometrySeqSet.g.cs b/MEOS.NET/Types/TRGeometrySeqSet.g.cs
index c67c358..e71fe04 100644
--- a/MEOS.NET/Types/TRGeometrySeqSet.g.cs
+++ b/MEOS.NET/Types/TRGeometrySeqSet.g.cs
@@ -33,5 +33,27 @@ internal TRGeometrySeqSet(IntPtr ptr) : base(ptr) { }
}
}
+ public Temporal? MakeGaps(Temporal[] instants, InterpType interp, Interval maxt, double maxdist)
+ {
+ IntPtr _maxt = Marshal.AllocHGlobal(Marshal.SizeOf());
+ IntPtr[] _instantsValues = new IntPtr[instants.Length];
+ for (int i = 0; i < instants.Length; i++)
+ {
+ _instantsValues[i] = instants[i].Ptr;
+ }
+
+ GCHandle _instants = GCHandle.Alloc(_instantsValues, GCHandleType.Pinned);
+ try
+ {
+ Marshal.StructureToPtr(maxt, _maxt, false);
+ return MEOSFactory.WrapTemporal(Meos.TrgeometryseqsetMakeGaps(this.Ptr, _instants.AddrOfPinnedObject(), instants.Length, (int) interp, _maxt, maxdist));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(_maxt);
+ _instants.Free();
+ }
+ }
+
}
}
diff --git a/MEOS.NET/Types/TSequenceSet.g.cs b/MEOS.NET/Types/TSequenceSet.g.cs
index 36ad5a4..c4e5ebb 100644
--- a/MEOS.NET/Types/TSequenceSet.g.cs
+++ b/MEOS.NET/Types/TSequenceSet.g.cs
@@ -33,5 +33,27 @@ internal TSequenceSet(IntPtr ptr) : base(ptr) { }
}
}
+ public static Temporal? MakeGaps(Temporal[] instants, InterpType interp, Interval maxt, double maxdist)
+ {
+ IntPtr _maxt = Marshal.AllocHGlobal(Marshal.SizeOf());
+ IntPtr[] _instantsValues = new IntPtr[instants.Length];
+ for (int i = 0; i < instants.Length; i++)
+ {
+ _instantsValues[i] = instants[i].Ptr;
+ }
+
+ GCHandle _instants = GCHandle.Alloc(_instantsValues, GCHandleType.Pinned);
+ try
+ {
+ Marshal.StructureToPtr(maxt, _maxt, false);
+ return MEOSFactory.WrapTemporal(Meos.TsequencesetMakeGaps(_instants.AddrOfPinnedObject(), instants.Length, (int) interp, _maxt, maxdist));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(_maxt);
+ _instants.Free();
+ }
+ }
+
}
}
diff --git a/MEOS.NET/Types/Temporal.g.cs b/MEOS.NET/Types/Temporal.g.cs
index cb39b4a..095de24 100644
--- a/MEOS.NET/Types/Temporal.g.cs
+++ b/MEOS.NET/Types/Temporal.g.cs
@@ -391,6 +391,22 @@ public DateTime StartTimestamptz()
}
}
+ public (Temporal?[], DateTime[]) TimeSplit(Interval duration, DateTime torigin)
+ {
+ IntPtr _duration = Marshal.AllocHGlobal(Marshal.SizeOf());
+ try
+ {
+ Marshal.StructureToPtr(duration, _duration, false);
+ var _answered = Meos.TemporalTimeSplit(this.Ptr, _duration, MEOSConvert.ToTimestampTz(torigin));
+
+ return (MEOSFactory.WrapTemporalArray(_answered.Item1), MEOSConvert.ToDateTimeArray(_answered.Item2));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(_duration);
+ }
+ }
+
public long[] Timestamps()
=> Meos.TemporalTimestamps(this.Ptr);
diff --git a/tools/objectgen.py b/tools/objectgen.py
index 2d5ed65..5763f01 100644
--- a/tools/objectgen.py
+++ b/tools/objectgen.py
@@ -297,6 +297,12 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]],
self.length_out = length_out
self.byte_buffer = byte_buffer
+ 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.ret.startswith("(") and self.ret.endswith(")")))
+
class Generator:
def __init__(self, model: Model):
@@ -328,6 +334,8 @@ def map_return(self, f: dict, wrapper_ret: str) -> tuple[str, str] | None:
cls = self.m.class_for_ctype(c)
if cls and wrapper_ret == "IntPtr":
return (f"{cls}?", f"MEOSFactory.Wrap{cls}($)")
+ if wrapper_ret.startswith("(") and wrapper_ret.endswith(")"):
+ return self.map_tuple_return(f, wrapper_ret)
if wrapper_ret == "IntPtr[]":
# An array of MEOS values reaches the wrapper as an array of pointers,
# whether MEOS returns pointers (`T **`) or the values themselves
@@ -362,6 +370,52 @@ def wrapped_out_reader(self, pointee: str) -> tuple[str, int, str] | None:
return None
return (cls, 8, f"MEOSFactory.Wrap{cls}(Marshal.ReadIntPtr({{0}}))")
+ def map_tuple_return(self, f: dict, wrapper_ret: str) -> tuple[str, str] | None:
+ """The C# type and per-item readers for a call answering SEVERAL arrays.
+
+ MEOS answers the primary array through the return and each parallel one
+ through a pointer it fills, and the wrapper already hands them over as
+ one tuple. Each item is then read the way a return of its own type is:
+ an array of MEOS values wraps element by element, an array of instants
+ reads as instants, and an array of plain scalars is handed on."""
+ shape = f.get("shape") or {}
+ element = ((shape.get("arrayReturn") or {}).get("element") or {}).get("c")
+ params = {p["name"]: p["cType"] for p in f.get("params", [])}
+ # `TYPE **bins` is an out-ARRAY of `TYPE`, so two stars come off: one
+ # for the handle MEOS writes through, one for the array itself.
+ c_types = [element] + [
+ clean(params.get(a["param"], "")).rstrip("* ").strip()
+ for a in shape.get("outputArrays") or []]
+ pieces = [p.strip() for p in wrapper_ret[1:-1].split(",")]
+ if len(pieces) != len(c_types) or not all(c_types):
+ return None
+ types, readers = [], []
+ for i, (piece, c_type) in enumerate(zip(pieces, c_types)):
+ mapped = self.map_array_item(piece, clean(c_type), f"$.Item{i + 1}")
+ if mapped is None:
+ return None
+ types.append(mapped[0])
+ readers.append(mapped[1])
+ # The body evaluates the call once and reads each item off it, so the
+ # template carries the two halves apart: the call, then the reads.
+ reads = ", ".join(r.replace("$", "_answered") for r in readers)
+ return (f"({', '.join(types)})", f"$|>({reads})")
+
+ def map_array_item(self, piece: str, c_type: str,
+ item: str) -> tuple[str, str] | None:
+ """One item of an answered tuple: its C# type and how it is read."""
+ if piece == "IntPtr[]":
+ cls = (self.m.class_for_ctype(c_type)
+ or self.m.class_for_ctype(f"{c_type} *"))
+ return (f"{cls}?[]", f"MEOSFactory.Wrap{cls}Array({item})") if cls else None
+ if c_type in ("TimestampTz", "Timestamp") and piece == "long[]":
+ return ("DateTime[]", f"MEOSConvert.ToDateTimeArray({item})")
+ if c_type == "DateADT" and piece == "int[]":
+ return ("DateOnly[]", f"MEOSConvert.ToDateOnlyArray({item})")
+ if piece.endswith("[]") and is_scalar(piece[:-2]):
+ return (piece, item)
+ return None
+
def value_struct(self, c_type: str) -> str | None:
"""The struct a single pointer to a scalar-only struct carries.
@@ -540,16 +594,6 @@ def method_for(self, cls: str, entry: dict) -> Method | None:
if result_out is not None:
name, (_, size, reader) = result_out
out = (name, size, reader.format(scratch(name)))
- if structs and (arrays or out or scalar_arrays or length_out):
- self.deferred[cls].append(
- f"{oo}: a struct argument beside a counted array or an "
- "out-parameter needs one body doing both")
- return None
- if length_out and (arrays or out):
- self.deferred[cls].append(
- f"{oo}: a length out-parameter beside a counted array or a "
- "value out-parameter needs one body doing both")
- return None
return Method(pascal(oo), ret_type, sig, ret_expr.replace("$", call), static,
arrays, out, structs, scalar_arrays, length_out,
ret_type == "byte[]?")
@@ -601,145 +645,108 @@ def emit_class(self, cls: str) -> str:
new = "new " if (m.name, tuple(t for t, _ in m.params)) in inherited else ""
kind = "static " if m.static else ""
lines.append(f" public {new}{kind}{m.ret} {m.name}({args})")
- if m.out_param:
- lines.extend(self.out_param_body(m))
- elif m.length_out:
- lines.extend(self.buffer_body(m))
- elif m.arrays or m.scalar_arrays:
- lines.extend(self.array_body(m))
- elif m.structs:
- lines.extend(self.struct_body(m))
+ if m.needs_a_body():
+ lines.extend(self.call_body(m))
else:
lines.append(f" => {m.body};")
lines.append("")
lines += [" }", "}", ""]
return "\n".join(lines)
- def out_param_body(self, method: Method) -> list[str]:
- """The body of a method whose value MEOS writes through an out-parameter.
+ def call_body(self, method: Method) -> list[str]:
+ """The body of a method whose call needs something around it.
- MEOS answers `false` when the value does not exist and leaves the
- out-parameter untouched, so the method answers null there and the value
- otherwise."""
- name, size, reader = method.out_param
- return [
- " {",
- f" IntPtr {scratch(name)} = Marshal.AllocHGlobal({size});",
- " try",
- " {",
- f" if (!{method.body})",
- " {",
- " return null;",
- " }",
- "",
- f" return {reader};",
- " }",
- " finally",
- " {",
- f" Marshal.FreeHGlobal({scratch(name)});",
- " }",
- " }",
- ]
-
- def struct_body(self, method: Method) -> list[str]:
- """The body of a method taking a struct MEOS reads through a pointer.
-
- The caller hands over a value, so the call gets the address of a copy
- that lives exactly as long as it does. MEOS reads what it needs before
- returning — the values it keeps it copies — so nothing outlives the
- frame."""
- lines = [" {"]
+ One call can need several of these at once — a struct argument beside a
+ counted array, an array beside the length MEOS states through a pointer
+ — so the body is composed rather than chosen: everything the call needs
+ is allocated or pinned first, the call is made ONCE, what it answers is
+ read, and everything allocated is freed however the method leaves.
+ """
+ setup, before, teardown = [], [], []
for name, struct in method.structs:
- lines.append(
+ setup.append(
f" IntPtr {scratch(name)} = "
f"Marshal.AllocHGlobal(Marshal.SizeOf<{struct}>());")
- lines.append(" try")
- lines.append(" {")
- for name, _ in method.structs:
- lines.append(
- f" Marshal.StructureToPtr({ident(name)}, {scratch(name)}, false);")
- lines.append(f" return {method.body};")
- lines.append(" }")
- lines.append(" finally")
- lines.append(" {")
- for name, _ in method.structs:
- lines.append(f" Marshal.FreeHGlobal({scratch(name)});")
- lines += [" }", " }"]
- return lines
-
- def buffer_body(self, method: Method) -> list[str]:
- """The body of a method whose call states a length through a pointer.
-
- MEOS writes the length of what it answers into a buffer the caller
- supplies. A string carries its own length, so there the buffer is
- written and dropped; a byte array does not, so there it says how much of
- what MEOS answered to copy."""
- name = method.length_out
- lines = [
- " {",
- f" IntPtr {scratch(name)} = Marshal.AllocHGlobal(sizeof(long));",
- " try",
- " {",
- ]
- if method.byte_buffer:
- lines += [
- f" IntPtr _bytes = {method.body};",
- " if (_bytes == IntPtr.Zero)",
- " {",
- " return null;",
- " }",
- "",
- f" byte[] _wkb = new byte[Marshal.ReadInt64({scratch(name)})];",
- " Marshal.Copy(_bytes, _wkb, 0, _wkb.Length);",
- " return _wkb;",
- ]
- else:
- lines.append(f" return {method.body};")
- lines += [
- " }",
- " finally",
- " {",
- f" Marshal.FreeHGlobal({scratch(name)});",
- " }",
- " }",
- ]
- return lines
-
- def array_body(self, method: Method) -> list[str]:
- """The body of a method taking a counted array.
-
- MEOS reads an array of its own values through a pointer to the first
- element, so the wrappers' pointers are gathered into one array and pinned
- for the call. MEOS copies what it keeps, so the pin lasts exactly as long
- as the call does."""
- lines = [" {"]
+ before.append(
+ f" Marshal.StructureToPtr({ident(name)}, "
+ f"{scratch(name)}, false);")
+ teardown.append(f" Marshal.FreeHGlobal({scratch(name)});")
for name in method.scalar_arrays:
- lines.append(
+ setup.append(
f" GCHandle {scratch(name)} = "
f"GCHandle.Alloc({ident(name)}, GCHandleType.Pinned);")
- for name, element in method.arrays:
- lines += [
- f" IntPtr[] {scratch(name)}Values = new IntPtr[{ident(name)}.Length];",
+ teardown.append(f" {scratch(name)}.Free();")
+ for name, _element in method.arrays:
+ setup += [
+ f" IntPtr[] {scratch(name)}Values = "
+ f"new IntPtr[{ident(name)}.Length];",
f" for (int i = 0; i < {ident(name)}.Length; i++)",
" {",
f" {scratch(name)}Values[i] = {ident(name)}[i].Ptr;",
" }",
"",
- f" GCHandle {scratch(name)} = GCHandle.Alloc({scratch(name)}Values, GCHandleType.Pinned);",
+ f" GCHandle {scratch(name)} = "
+ f"GCHandle.Alloc({scratch(name)}Values, GCHandleType.Pinned);",
]
- lines.append(" try")
- lines.append(" {")
- lines.append(f" return {method.body};")
- lines.append(" }")
- lines.append(" finally")
- lines.append(" {")
- for name in method.scalar_arrays:
- lines.append(f" {scratch(name)}.Free();")
- for name, _ in method.arrays:
- lines.append(f" {scratch(name)}.Free();")
- lines += [" }", " }"]
+ teardown.append(f" {scratch(name)}.Free();")
+ if method.length_out:
+ 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
+ setup.append(
+ f" IntPtr {scratch(name)} = Marshal.AllocHGlobal({size});")
+ teardown.append(f" Marshal.FreeHGlobal({scratch(name)});")
+
+ answer = self.answer(method)
+ if not teardown:
+ # Nothing was allocated, so there is nothing to free and no frame to
+ # free it in: the answer alone is the body.
+ return [" {"] + answer + [" }"]
+ lines = [" {"] + setup + [" try", " {"] + before
+ lines += [f" {line}" if line else "" for line in answer]
+ lines += [" }", " finally", " {"]
+ lines += teardown + [" }", " }"]
return lines
+ 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
+ return [
+ f" if (!{method.body})",
+ " {",
+ " return null;",
+ " }",
+ "",
+ f" return {reader};",
+ ]
+ if method.byte_buffer:
+ # A byte array does not carry its own length, so what MEOS wrote
+ # into the length buffer says how much of it to copy.
+ length = scratch(method.length_out)
+ return [
+ f" IntPtr _bytes = {method.body};",
+ " if (_bytes == IntPtr.Zero)",
+ " {",
+ " return null;",
+ " }",
+ "",
+ f" byte[] _wkb = new byte[Marshal.ReadInt64({length})];",
+ " Marshal.Copy(_bytes, _wkb, 0, _wkb.Length);",
+ " return _wkb;",
+ ]
+ if "|>" in method.body:
+ # Several values answered together: the call is made once and each
+ # item read off what it answered.
+ answered, reads = method.body.split("|>", 1)
+ return [f" var _answered = {answered};", "",
+ f" return {reads};"]
+ return [f" return {method.body};"]
+
def to_string(self, cls: str, methods: list) -> list[str]:
"""`ToString` over the class's own text output, where MEOS publishes one.
@@ -842,6 +849,30 @@ def convert_file(self) -> str:
=> Meos.DateIn(
day.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
+ /// Each MEOS TimestampTz of an array, as a DateTime.
+ internal static DateTime[] ToDateTimeArray(long[] moments)
+ {{
+ DateTime[] values = new DateTime[moments.Length];
+ for (int i = 0; i < moments.Length; i++)
+ {{
+ values[i] = ToDateTime(moments[i]);
+ }}
+
+ return values;
+ }}
+
+ /// Each MEOS DateADT of an array, as a DateOnly.
+ internal static DateOnly[] ToDateOnlyArray(int[] days)
+ {{
+ DateOnly[] values = new DateOnly[days.Length];
+ for (int i = 0; i < days.Length; i++)
+ {{
+ values[i] = ToDateOnly(days[i]);
+ }}
+
+ return values;
+ }}
+
/// The struct MEOS answers through a pointer, as a value. The
/// memory behind the pointer stays MEOS's, as it does for every other
/// value the layer reads back.