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 "geoloc" 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_element(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
631DAS_API char* DasVar_toStr(const DasVar* pThis, char* sBuf, int nLen);
632
645DAS_API DasVar* DasVar_copy(const DasVar* pThis);
646
655DAS_API das_val_type DasVar_valType(const DasVar* pThis);
656
657/* DasVar_vecMap() and das_makeCompLabels() are RETIRED. Both were vector
658 knowledge in the generic variable layer; answering them here would mean
659 variable.h including form_vector.h. Clients use DasVar_compSym() above and
660 compose their own labels. */
661
672DAS_API DasAry* DasVar_getAry(const DasVar* pThis);
673
679#define DasVar_hasAry(P) (DasVar_getAry(P) != NULL)
680
702DAS_API bool DasVar_setAry(DasVar* pThis, DasAry* pNew);
703
712DAS_API DasErrCode DasVar_encode(DasVar* pThis, const char* sRole, DasBuf* pBuf);
713
714
715
716
717
735DAS_API DasVar* new_DasVar(
736 DasGen* pGen, das_units units, DasForm* pForm
737);
738
739/* ========================================================================= *
740 * DasVarBin: an operation over two variables.
741 *
742 * This is the one variable class with NO wire element. das3 has no binary
743 * element: a stream carries reference and offset as two separate variables
744 * and the dimension combines them. So a DasVarBin is never DECODED -- it is
745 * built in code, and serializing one means MATERIALIZING it: walking the
746 * operands into an array and emitting that as though it had been array-backed
747 * all along.
748 *
749 * It keeps the operand VARIABLES, not just their generators. A generator has
750 * no units and no ops, so operands reduced to generators leave nobody able to
751 * ask what was combined. The generator half still exists underneath (DasGenOp
752 * holds the operand GENERATORS and does the numeric walking); each layer keeps
753 * what it knows.
754 *
755 * The pairing resolves ONCE, at construction: what math combined the two is
756 * settled then and never asked again, so units, form and value type on the
757 * result are ordinary fields from that point on. See form.h for the rule
758 * that puts the walk here rather than in the formalisms.
759 * ========================================================================= */
760
761typedef struct das_var_bin {
762
763 DasVar base; /* base.pForm and base.units are the RESOLVED result,
764 decided once by binOpLeft/binOpRight at construction
765 and thereafter just fields -- which is why
766 _DasVar_subset() never asks what math made a value */
767
768 DasVar* pLeft; /* references held; operand identity stays askable */
769 DasVar* pRight;
770 int op; /* a D2BOP_* code from operator.h */
771
772 /* The pairing, resolved ONCE at construction. Six facts and one function
773 pointer; the walk reads them and never asks the forms again. Shared on
774 copy, never re-resolved: two copies of one variable must not disagree about
775 what math they are. */
776 DasBinOp* pRecipe;
777
778} DasVarBin;
779
789DAS_API DasVarBin* new_DasVarBin(DasVar* pLeft, char cOp, DasVar* pRight);
790
791
792
793/* ========================================================================= *
794 * Values with an internal index: DasVarComp (<composite>) and DasVarBytes
795 * (<bytes>).
796 *
797 * The internal layout is pure SHAPE. The formalism does not live in the shape.
798 * A 3;3 rotation and a 3;3 plain matrix share the same layout and differ only
799 * in their formalism. TRACERS ships non-rotation matrices, so this matters.
800 *
801 * element is etUByte -> the byte run branch: string or blob. Empty
802 * formalism. No table lookup. <bytes> on the wire.
803 *
804 * element is numeric -> base.form carries the row. A hit gives a rich
805 * datum. A miss is the generic case: carry the
806 * token, hand back plain numbers.
807 * ========================================================================= */
808
809/* ========================================================================= *
810 * The byte run: DasVarBytes, the <bytes> element. Element type is etUByte.
811 *
812 * One class for one wire element. A byte run cannot carry math and there is
813 * no field saying so: the CLASS says it, which is why new_DasVarBytes() takes
814 * no formalism argument at all and why a byte run can never reach the binop
815 * registry. The structural family comes from the element name, so no
816 * semantic is needed for structure. string and blob are told apart by the
817 * packet encoding, an explicit required triplet:
818 *
819 * encoding="utf8" text
820 * encoding="base64" ASCII-armored binary, decode to get the bytes
821 * encoding="raw" no transform, opaque bytes
822 *
823 * raw is a NAMED token, not an omitted attribute, so a forgotten encoding is a
824 * loud error rather than a silent default.
825 *
826 * Wire:
827 *
828 * <bytes index="*">
829 * <packet numItems="1" itemBytes="*" encoding="utf8" valTerm=";"/>
830 * </bytes>
831 *
832 * <bytes index="*">
833 * <packet numItems="1" itemBytes="*" encoding="raw" embedded="image/png"/>
834 * </bytes>
835 *
836 * The primary encoding gets the bytes off the wire. An embedded= attribute,
837 * if present, is the extension-codec trigger: it names the format the bytes
838 * must further be decoded FROM to realize the declared values, and a reader
839 * with no handler fails loud. A purely descriptive content label rides in a
840 * property (contentType) instead. Two independent stages on two attributes.
841 *
842 * The only difference between string and blob is the sentinel, so it rides as
843 * one flag rather than two classes: both are the same wire element and the same
844 * structure. A string ends in a REQUIRED trailing null in the array's last
845 * index, which is why it can hand out a bare char*. A blob is stored AS GIVEN
846 * with no sentinel, which is why it must always be carried as pointer plus
847 * length. Fixed versus ragged is orthogonal; both can be either.
848 * ========================================================================= */
849
890typedef struct das_var_comp {
891
892 DasVar base; /* base.form: the formalism row, token, bindings */
893
894 /* Internal layout exactly as declared. "3" is a vector. "3;3" is a
895 matrix. Ragged levels are Flags. Multi-level shapes read and write
896 end to end (examples/ex40_rotation is the 3;3 case) */
897 int nIntRank;
898 ptrdiff_t aIntShape[VARIDX_MAX];
899
900 /* Per component labels live at this structural level, readable without
901 understanding the formalism (the skippability contract: a dumb client
902 still stacks N labeled lines). Per component units WOULD live here
903 too; until a holder exists the ';' list fails loud, see base.units. */
904 /* TODO structural label storage */
905
906} DasVarComp;
907
908/* The byte run: DasVarBytes, the <bytes> element. Internal rank is ALWAYS 1
909 (the index is the byte number), so one extent replaces a shape array. */
910typedef struct das_var_bytes {
911
912 DasVar base; /* base.form is unused: a byte run has no math */
913
914 ptrdiff_t nExtent; /* item length in bytes, VARIDX_RAGGED if var-width */
915
916 /* string, not blob: a required trailing null in the last index, which is
917 what lets a datum be a bare char*. A blob is stored as given and must
918 always be carried as pointer plus length. */
919 bool bSentinel;
920
921} DasVarBytes;
922
935 DasGen* pGen, das_units units, DasForm* pForm,
936 int nIntRank, const ptrdiff_t* pIntShape
937);
938
952DAS_API DasVarBytes* new_DasVarBytes(
953 DasGen* pGen, das_units units, bool bSentinel, ptrdiff_t nExtent
954);
955
956#ifdef __cplusplus
957}
958#endif
959
960#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:184
const char * das_units
Handle SI and other units, with accommodations for Epoch systems, from units.h.
Definition units.h:144
das_val_type
Enumeration of types stored in Das Array (DasAry) objects from value.h.
Definition value.h:81
Dynamic recursive ragged arrays.
Definition array.h:271
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:890
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...