das2C
das core C utilities (v3)
Loading...
Searching...
No Matches
variable.h
Go to the documentation of this file.
1/* Copyright (C) 2026 Chris Piker <chris-piker@uiowa.edu>
2 *
3 * Author: C. Piker, via Claude Fable 5
4 *
5 * This file is part of das2C, the Core Das2 C Library.
6 *
7 * Das2C is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Lesser General Public License version 2.1 as published
9 * by the Free Software Foundation.
10 *
11 * Das2C is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * version 2.1 along with das2C; if not, see <http://www.gnu.org/licenses/>.
18 */
19
38#ifndef _das_var_h_
39#define _das_var_h_
40
41#include <das3/descriptor.h>
42#include <das3/buffer.h>
43#include <das3/datum.h>
44#include <das3/units.h>
45#include <das3/generator.h>
46#include <das3/form.h>
47
48#ifdef __cplusplus
49extern "C" {
50#endif
51
52/* ========================================================================= *
53 *
54 * A variable, here a DasVar, is the top level interface for data values. It
55 * pulls together multiple concepts:
56 *
57 * 1. Datum: Found at each full external index of a variable is a datum. The
58 * value can come in multiple forms, from simple integers, to rotation
59 * matrices, to strings. DasVar_get() provides datums, though it's not
60 * a fast function and there are better ways to retrieve data.
61 *
62 * 2. Elements: Each datum is composed of one or more elements. For simple
63 * variables a datum is just a single floating point number. For utf-8
64 * or byte strings, it is a byte.
65 *
66 * 3. Formalisms: Some datums obey a given set of math rules. Each one is a
67 * vector, or a complex number, or an affine point. Formalisms (DasForm)
68 * provide binary operations on their values and on related forms. To get
69 * a variable's formalism call DasVar_form().
70 *
71 * 4. Generators: (DasGen) provides elements. Generators don't know what math
72 * rules apply, they operate below that level and either create values via
73 * binary operations, provide them from a backing array store, or by simple
74 * rules based on the provided index. Application code typically does not
75 * interact with this level, but every variable has a generator.
76 *
77 * There are four variable classes, but that is an implementation detail as
78 * interactions with each one are typically via virtual functions. The only
79 * time application code needs to concern itself with the type of a variable
80 * is when constructing DasVar derived objects of their own. The constructors
81 * are:
82 *
83 * new_DasVar()
84 * Creates a variable that has no internal indices, but may have a math
85 * form. Example values are calendar times, which follow the affine
86 * (point) formalism.
87 *
88 * new_DasVarComp()
89 * Creates a variable with internal indices and an optional math formalism.
90 * Example values are geodetic locations. These have three elements for
91 * each value and follow the "position" formalism.
92 *
93 * new_DasVarBin()
94 * Creates a variable that is a binary operation on two other variables,
95 * such as providing a 2-D Qube array of time values given a reference
96 * array and an offset time array.
97 *
98 * new_DasVarBytes()
99 * Creates a variable with a single internal index for each value. These
100 * hold strings or binary blobs of bytes and may not have a math formalism.
101 *
102 * ========================================================================= */
103
104/* ========================================================================= *
105 * DasVar, the base.
106 * ========================================================================= */
107
108typedef struct das_var DasVar;
109
110/* ========================================================================= *
111 * The scalar case. One value per point, no internal index. There is NO
112 * DasVarScalar struct: a scalar IS the base DasVar, and its only formalisms so
113 * far are linear or point.
114 *
115 * Wire, a plain linear scalar. Linear is the default, so nothing is stated:
116 *
117 * <scalar semantic="real" units="Hz" index="*">
118 * <packet numItems="1" itemBytes="8" encoding="LEreal"/>
119 * </scalar>
120 *
121 * Wire, a time scalar. This is where the word "point" appears. The affine
122 * rule needs no parameters, so kind= alone carries it. semantic="datetime"
123 * still rides along as the display and calendar-units directive. Child order is
124 * properties, then ops, then generator:
125 *
126 * <scalar semantic="datetime" units="TT2000" index="*">
127 * <ops kind="point"/>
128 * <packet numItems="1" itemBytes="8" encoding="LEint"/>
129 * </scalar>
130 *
131 * A future parameter-bearing scalar formalism (a scalar with a point spread
132 * function) is no longer an open item: it is a formalism with parameters on a
133 * scalar, exactly like vector on a composite. Same struct, same store.
134 * ========================================================================= */
135
136/* The dispatch table is in var_priv.h: nothing outside das2C implements one,
137 and there is no registration path if it wanted to. */
138struct DasVar_VTbl;
139
140
141
142struct das_var {
143
144 DasDesc base; /* a variable IS a descriptor: it has properties, a
145 parent, and it serializes */
146
147 const struct DasVar_VTbl* pVTbl;
148
149 DasGen* pGen; /* The variable owns a generator, is not one.
150 Swapping array for sequence does not change the
151 variable's type. */
152
153
154 DasForm* pForm; /* Heap owned and refcounted, symmetric
155 with pGen: pGen says how values are produced,
156 pForm says what they mean to arithmetic. NEVER
157 NULL for a numeric variable -- an absent <ops> binds
158 the explicit linear form and an unrecognized
159 kind= binds DasFormGeneric, so "I do not know
160 this math" has a vtable to refuse from. Only
161 DasVarBytes leaves it NULL: a byte run has no
162 auto-math and its CLASS says so. */
163
164 /* One unit. Some DasForm objects may need more, in which
165 case they can provide storage for them */
166 das_units units;
167
168 int nRef;
169 void* pUser;
170};
171
176DAS_API const char* DasVar_serialType(const DasVar* pThis);
177
178#define DasVar_units(P) ((P)->units)
179#define DasVar_gen(P) ((P)->pGen)
180#define DasVar_form(P) ((P)->pForm)
181
195#define DasVar_formIs(P,VT) (((P) != NULL)&&DasForm_isKind(DasVar_form(P), VT))
196
213DAS_API const char* DasVar_compSym(const DasVar* pThis, int iComp);
214
242DAS_API int DasVar_compLabels(
243 const DasVar* pThis, char** psBuf, int nMax, size_t uLenEa
244);
245
257DAS_API das_elem_type DasVar_elemType(const DasVar* pThis);
258
259
264DAS_API int inc_DasVar(DasVar* pThis);
265
277DAS_API int dec_DasVar(DasVar* pThis);
278
346DAS_API int DasVar_get(
347 const DasVar* pThis, ptrdiff_t* pLoc, das_byte_seq work, das_datum* pOut
348);
349
361DAS_API bool DasVar_getNeedsBuf(const DasVar* pThis);
362
388DAS_API int DasVar_shape(const DasVar* pThis, ptrdiff_t* pShape);
389
407DAS_API int DasVar_intrShape(const DasVar* pThis, ptrdiff_t* pShape);
408
425DAS_API ptrdiff_t DasVar_lengthIn(const DasVar* pThis, int nIdx, ptrdiff_t* pLoc);
426
436DAS_API bool DasVar_degenerate(const DasVar* pThis, int iIndex);
437
453DAS_API const char* DasVar_role(const DasVar* pThis);
454
519DAS_API DasAry* DasVar_subset(
520 const DasVar* pThis, int nRank, const ptrdiff_t* pMin, const ptrdiff_t* pMax,
521 const DasVar* pShapeFrom
522);
523
536DAS_API DasAry* DasVar_materialize(
537 const DasVar* pThis, int nRank, const ptrdiff_t* pMin, const ptrdiff_t* pMax,
538 const DasVar* pShapeFrom
539);
540
565DAS_API DasAry* DasVar_subsetQube(
566 const DasVar* pThis, int nRank, const ptrdiff_t* pMin, const ptrdiff_t* pMax,
567 const DasVar* pShapeFrom
568);
569
581DAS_API DasAry* DasVar_materializeQube(
582 const DasVar* pThis, int nRank, const ptrdiff_t* pMin, const ptrdiff_t* pMax,
583 const DasVar* pShapeFrom
584);
585
592#define DasVar_allVals(P, R) DasVar_subset((P), (R), NULL, NULL, NULL)
593
594
602DAS_API bool DasVar_isNumeric(const DasVar* pThis);
603
633DAS_API char* DasVar_toStr(const DasVar* pThis, char* sBuf, size_t uLen);
634
647DAS_API DasVar* DasVar_copy(const DasVar* pThis);
648
657DAS_API das_val_type DasVar_valType(const DasVar* pThis);
658
669DAS_API DasAry* DasVar_getAry(const DasVar* pThis);
670
676#define DasVar_hasAry(P) (DasVar_getAry(P) != NULL)
677
699DAS_API bool DasVar_setAry(DasVar* pThis, DasAry* pNew);
700
709DAS_API DasErrCode DasVar_encode(DasVar* pThis, const char* sRole, DasBuf* pBuf);
710
711
729DAS_API DasVar* new_DasVar(
730 DasGen* pGen, das_units units, DasForm* pForm
731);
732
733/* ========================================================================= *
734 * DasVarBin: an operation over two variables.
735 *
736 * This is the one variable class with NO wire element. das3 has no binary
737 * element: a stream carries reference and offset as two separate variables
738 * and the dimension combines them. So a DasVarBin is never DECODED -- it is
739 * built in code, and serializing one means MATERIALIZING it: walking the
740 * operands into an array and emitting that as though it had been array-backed
741 * all along.
742 *
743 * It keeps the operand VARIABLES, not just their generators. A generator has
744 * no units and no ops, so operands reduced to generators leave nobody able to
745 * ask what was combined. The generator half still exists underneath (DasGenOp
746 * holds the operand GENERATORS and does the numeric walking); each layer keeps
747 * what it knows.
748 *
749 * The pairing resolves ONCE, at construction: what math combined the two is
750 * settled then and never asked again, so units, form and value type on the
751 * result are ordinary fields from that point on. See form.h for the rule
752 * that puts the walk here rather than in the formalisms.
753 * ========================================================================= */
754
755typedef struct das_var_bin {
756
757 DasVar base; /* base.pForm and base.units are the RESOLVED result,
758 decided once by binOpLeft/binOpRight at construction
759 and thereafter just fields -- which is why
760 _DasVar_subset() never asks what math made a value */
761
762 DasVar* pLeft; /* references held; operand identity stays askable */
763 DasVar* pRight;
764 int op; /* a D2BOP_* code from operator.h */
765
766 /* The pairing, resolved ONCE at construction. Six facts and one function
767 pointer; the walk reads them and never asks the forms again. Shared on
768 copy, never re-resolved: two copies of one variable must not disagree about
769 what math they are. */
770 DasBinOp* pRecipe;
771
772} DasVarBin;
773
783DAS_API DasVarBin* new_DasVarBin(DasVar* pLeft, char cOp, DasVar* pRight);
784
785
786
787/* ========================================================================= *
788 * Values with an internal index: DasVarComp (<composite>) and DasVarBytes
789 * (<bytes>).
790 *
791 * The internal layout is pure SHAPE. The formalism does not live in the shape.
792 * A 3;3 rotation and a 3;3 plain matrix share the same layout and differ only
793 * in their formalism. Real data includes 3;3 matrices that are not rotations
794 * (covariances, pressure tensors), so this matters.
795 *
796 * element is etUByte -> the byte run branch: string or blob. Empty
797 * formalism. No table lookup. <bytes> on the wire.
798 *
799 * element is numeric -> base.form carries the row. A hit gives a rich
800 * datum. A miss is the generic case: carry the
801 * token, hand back plain numbers.
802 * ========================================================================= */
803
804/* ========================================================================= *
805 * The byte run: DasVarBytes, the <bytes> element. Element type is etUByte.
806 *
807 * One class for one wire element. A byte run cannot carry math and there is
808 * no field saying so: the CLASS says it, which is why new_DasVarBytes() takes
809 * no formalism argument at all and why a byte run can never reach the binop
810 * registry. The structural family comes from the element name, so no
811 * semantic is needed for structure. string and blob are told apart by the
812 * packet encoding, an explicit required triplet:
813 *
814 * encoding="utf8" text
815 * encoding="base64" ASCII-armored binary, decode to get the bytes
816 * encoding="raw" no transform, opaque bytes
817 *
818 * raw is a NAMED token, not an omitted attribute, so a forgotten encoding is a
819 * loud error rather than a silent default.
820 *
821 * Wire:
822 *
823 * <bytes index="*">
824 * <packet numItems="1" itemBytes="*" encoding="utf8" valTerm=";"/>
825 * </bytes>
826 *
827 * <bytes index="*">
828 * <packet numItems="1" itemBytes="*" encoding="raw" embedded="image/png"/>
829 * </bytes>
830 *
831 * The primary encoding gets the bytes off the wire. An embedded= attribute,
832 * if present, is the extension-codec trigger: it names the format the bytes
833 * must further be decoded FROM to realize the declared values, and a reader
834 * with no handler fails loud. A purely descriptive content label rides in a
835 * property (contentType) instead. Two independent stages on two attributes.
836 *
837 * The only difference between string and blob is the sentinel, so it rides as
838 * one flag rather than two classes: both are the same wire element and the same
839 * structure. A string ends in a REQUIRED trailing null in the array's last
840 * index, which is why it can hand out a bare char*. A blob is stored AS GIVEN
841 * with no sentinel, which is why it must always be carried as pointer plus
842 * length. Fixed versus ragged is orthogonal; both can be either.
843 * ========================================================================= */
844
885typedef struct das_var_comp {
886
887 DasVar base; /* base.form: the formalism row, token, bindings */
888
889 /* Internal layout exactly as declared. "3" is a vector. "3;3" is a
890 matrix. Ragged levels are Flags. Multi-level shapes read and write
891 end to end (examples/ex40_rotation is the 3;3 case) */
892 int nIntRank;
893 ptrdiff_t aIntShape[VARIDX_MAX];
894
895 /* Per component labels live at this structural level, readable without
896 understanding the formalism (the skippability contract: a dumb client
897 still stacks N labeled lines). Per component units WOULD live here
898 too; until a holder exists the ';' list fails loud, see base.units. */
899 /* TODO structural label storage */
900
901} DasVarComp;
902
903/* The byte run: DasVarBytes, the <bytes> element. Internal rank is ALWAYS 1
904 (the index is the byte number), so one extent replaces a shape array. */
905typedef struct das_var_bytes {
906
907 DasVar base; /* base.form is unused: a byte run has no math */
908
909 ptrdiff_t nExtent; /* item length in bytes, VARIDX_RAGGED if var-width */
910
911 /* string, not blob: a required trailing null in the last index, which is
912 what lets a datum be a bare char*. A blob is stored as given and must
913 always be carried as pointer plus length. */
914 bool bSentinel;
915
916} DasVarBytes;
917
930 DasGen* pGen, das_units units, DasForm* pForm,
931 int nIntRank, const ptrdiff_t* pIntShape
932);
933
947DAS_API DasVarBytes* new_DasVarBytes(
948 DasGen* pGen, das_units units, bool bSentinel, ptrdiff_t nExtent
949);
950
951#ifdef __cplusplus
952}
953#endif
954
955#endif /* _das_var_h_ */
Utility to assist with encode and decode operations.
What a variable's values (i.e.
Value sources for the DasVar layer.
#define VARIDX_MAX
Max index count for the model.
Definition generator.h:55
int DasErrCode
return code type 0 indicates success, negative integer indicates failure
Definition defs.h:189
const char * das_units
Handle SI and other units, with accommodations for Epoch systems, from units.h.
Definition units.h:141
das_val_type
Enumeration of value classes: what one whole externally indexable thing is – a number,...
Definition value.h:82
Dynamic recursive ragged arrays.
Definition array.h:243
Buffer class to handle accumulating byte streams.
Definition buffer.h:47
Base structure for Stream Header Items.
Definition descriptor.h:74
The numeric component run: DasVarComp, the <composite> element.
Definition variable.h:885
DAS_API DasVarComp * new_DasVarComp(DasGen *pGen, das_units units, DasForm *pForm, int nIntRank, const ptrdiff_t *pIntShape)
Create a numeric composite: a vector, a complex pair, a matrix.
Defines units used for items in the stream, most notably time units that reference an epoch and a ste...