tranfi 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +177 -21
  2. package/NOTICE +8 -0
  3. package/README.md +627 -0
  4. package/app/assets/index-6quYZ5Ap.css +5 -0
  5. package/app/assets/index-BIAIKnrp.js +160 -0
  6. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  7. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  8. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  9. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  10. package/app/index.html +13 -0
  11. package/binding.gyp +121 -0
  12. package/csrc/arena.c +93 -0
  13. package/csrc/batch.c +976 -0
  14. package/csrc/buffer.c +154 -0
  15. package/csrc/cJSON.c +3386 -0
  16. package/csrc/cJSON.h +316 -0
  17. package/csrc/codec_csv.c +1951 -0
  18. package/csrc/codec_jsonl.c +1086 -0
  19. package/csrc/codec_table.c +248 -0
  20. package/csrc/codec_text.c +447 -0
  21. package/csrc/compiler.c +130 -0
  22. package/csrc/config.h +21 -0
  23. package/csrc/date_utils.h +94 -0
  24. package/csrc/dsl.c +5417 -0
  25. package/csrc/dsl.h +22 -0
  26. package/csrc/expr.c +1553 -0
  27. package/csrc/expr.h +58 -0
  28. package/csrc/internal.h +539 -0
  29. package/csrc/ir.c +166 -0
  30. package/csrc/ir.h +208 -0
  31. package/csrc/ir_schema.c +75 -0
  32. package/csrc/ir_serialize.c +166 -0
  33. package/csrc/ir_sql.c +1822 -0
  34. package/csrc/ir_validate.c +576 -0
  35. package/csrc/json_path.c +210 -0
  36. package/csrc/main.c +1241 -0
  37. package/csrc/memory_estimate.c +477 -0
  38. package/csrc/op_acf.c +283 -0
  39. package/csrc/op_across.c +477 -0
  40. package/csrc/op_anomaly.c +255 -0
  41. package/csrc/op_assert.c +761 -0
  42. package/csrc/op_bin.c +248 -0
  43. package/csrc/op_cast.c +523 -0
  44. package/csrc/op_clip.c +99 -0
  45. package/csrc/op_date_trunc.c +355 -0
  46. package/csrc/op_datetime.c +394 -0
  47. package/csrc/op_derive.c +216 -0
  48. package/csrc/op_diff.c +250 -0
  49. package/csrc/op_ewma.c +222 -0
  50. package/csrc/op_explode.c +206 -0
  51. package/csrc/op_fill_down.c +235 -0
  52. package/csrc/op_fill_null.c +268 -0
  53. package/csrc/op_filter.c +181 -0
  54. package/csrc/op_frequency.c +721 -0
  55. package/csrc/op_grep.c +181 -0
  56. package/csrc/op_group_agg.c +1956 -0
  57. package/csrc/op_hash.c +159 -0
  58. package/csrc/op_head.c +84 -0
  59. package/csrc/op_interpolate.c +445 -0
  60. package/csrc/op_join.c +2902 -0
  61. package/csrc/op_json_extract.c +227 -0
  62. package/csrc/op_json_filter.c +384 -0
  63. package/csrc/op_json_flatten.c +293 -0
  64. package/csrc/op_json_schema.c +503 -0
  65. package/csrc/op_label_encode.c +419 -0
  66. package/csrc/op_lag.c +181 -0
  67. package/csrc/op_lead.c +242 -0
  68. package/csrc/op_normalize.c +510 -0
  69. package/csrc/op_onehot.c +457 -0
  70. package/csrc/op_pivot.c +1754 -0
  71. package/csrc/op_quarantine.c +189 -0
  72. package/csrc/op_registry.c +3044 -0
  73. package/csrc/op_rename.c +129 -0
  74. package/csrc/op_replace.c +354 -0
  75. package/csrc/op_rleid.c +297 -0
  76. package/csrc/op_rowid.c +559 -0
  77. package/csrc/op_sample.c +158 -0
  78. package/csrc/op_schema.c +1341 -0
  79. package/csrc/op_schema_infer.c +252 -0
  80. package/csrc/op_select.c +340 -0
  81. package/csrc/op_set.c +3449 -0
  82. package/csrc/op_skip.c +95 -0
  83. package/csrc/op_sort.c +819 -0
  84. package/csrc/op_source_name.c +120 -0
  85. package/csrc/op_split.c +151 -0
  86. package/csrc/op_split_data.c +119 -0
  87. package/csrc/op_stack.c +271 -0
  88. package/csrc/op_stats.c +875 -0
  89. package/csrc/op_step.c +333 -0
  90. package/csrc/op_tail.c +105 -0
  91. package/csrc/op_tee.c +338 -0
  92. package/csrc/op_top.c +357 -0
  93. package/csrc/op_trim.c +138 -0
  94. package/csrc/op_unique.c +1343 -0
  95. package/csrc/op_unpivot.c +193 -0
  96. package/csrc/op_validate.c +648 -0
  97. package/csrc/op_window.c +591 -0
  98. package/csrc/path_policy.c +85 -0
  99. package/csrc/pipeline.c +1088 -0
  100. package/csrc/recipes.c +104 -0
  101. package/csrc/recipes.h +27 -0
  102. package/csrc/report.c +506 -0
  103. package/csrc/report.h +22 -0
  104. package/csrc/selector.c +1097 -0
  105. package/csrc/size_utils.c +348 -0
  106. package/csrc/spill.c +317 -0
  107. package/csrc/spill.h +21 -0
  108. package/csrc/tranfi.h +291 -0
  109. package/csrc/transform.h +209 -0
  110. package/csrc/transform_api.c +2237 -0
  111. package/csrc/transform_categorical.c +923 -0
  112. package/csrc/transform_internal.h +472 -0
  113. package/csrc/transform_json.c +3812 -0
  114. package/csrc/transform_numeric.c +1966 -0
  115. package/csrc/transform_sha256.c +154 -0
  116. package/csrc/transform_wasm.h +162 -0
  117. package/csrc/transform_wasm_api.c +1373 -0
  118. package/csrc/wasm_api.c +218 -0
  119. package/napi_api.c +534 -0
  120. package/napi_transform.c +1648 -0
  121. package/napi_transform.h +8 -0
  122. package/package.json +64 -59
  123. package/scripts/install-native.js +76 -0
  124. package/scripts/prepack.js +64 -0
  125. package/scripts/sync-csrc.js +23 -0
  126. package/src/cli.js +190 -0
  127. package/src/engines/duckdb.js +142 -0
  128. package/src/index.js +925 -0
  129. package/src/memory_policy.js +411 -0
  130. package/src/native.js +18 -0
  131. package/src/pipeline.js +709 -0
  132. package/src/recipe_json.js +80 -0
  133. package/src/server.js +279 -0
  134. package/src/transform.js +403 -0
  135. package/src/transform_error.js +10 -0
  136. package/src/wasm.js +21 -0
  137. package/wasm/index.js +732 -0
  138. package/wasm/package.json +1 -0
  139. package/wasm/tranfi_core.js +0 -0
  140. package/wasm/transform.js +1156 -0
  141. package/wasm/worker.js +786 -0
  142. package/dist/bundle.js +0 -1
  143. package/index.html +0 -18
  144. package/src/app.css +0 -169
  145. package/src/app.js +0 -203
  146. package/src/app.vue +0 -250
  147. package/src/bulma-input.vue +0 -110
  148. package/src/common-inputs.js +0 -28
  149. package/src/main.js +0 -20
  150. package/src/transforms.js +0 -166
  151. package/webpack.config.js +0 -108
package/csrc/ir.c ADDED
@@ -0,0 +1,166 @@
1
+ /*
2
+ * ir.c — IR plan construction, cloning, and memory management.
3
+ */
4
+
5
+ #include "internal.h"
6
+ #include "cJSON.h"
7
+ #include <stdlib.h>
8
+ #include <string.h>
9
+
10
+ /* ---- Schema helpers ---- */
11
+
12
+ void tf_schema_free(tf_schema *s) {
13
+ if (!s) return;
14
+ if (s->col_names) {
15
+ for (size_t i = 0; i < s->n_cols; i++) {
16
+ free(s->col_names[i]);
17
+ }
18
+ }
19
+ free(s->col_names);
20
+ free(s->col_types);
21
+ s->col_names = NULL;
22
+ s->col_types = NULL;
23
+ s->n_cols = 0;
24
+ s->known = false;
25
+ }
26
+
27
+ int tf_schema_copy_checked(tf_schema *dst, const tf_schema *src) {
28
+ if (!dst) return TF_ERROR;
29
+ dst->known = false;
30
+ dst->n_cols = 0;
31
+ dst->col_names = NULL;
32
+ dst->col_types = NULL;
33
+ if (!src) return TF_OK;
34
+
35
+ dst->known = src->known;
36
+ dst->n_cols = src->n_cols;
37
+ if (src->n_cols == 0 || !src->known) return TF_OK;
38
+
39
+ dst->col_names = calloc(src->n_cols, sizeof(char *));
40
+ dst->col_types = calloc(src->n_cols, sizeof(tf_type));
41
+ if (!dst->col_names || !dst->col_types) {
42
+ tf_schema_free(dst);
43
+ return TF_ERROR;
44
+ }
45
+ for (size_t i = 0; i < src->n_cols; i++) {
46
+ dst->col_names[i] = strdup(src->col_names[i] ? src->col_names[i] : "");
47
+ if (!dst->col_names[i]) {
48
+ tf_schema_free(dst);
49
+ return TF_ERROR;
50
+ }
51
+ dst->col_types[i] = src->col_types[i];
52
+ }
53
+ dst->known = true;
54
+ return TF_OK;
55
+ }
56
+
57
+ void tf_schema_copy(tf_schema *dst, const tf_schema *src) {
58
+ (void)tf_schema_copy_checked(dst, src);
59
+ }
60
+
61
+ /* ---- IR node helpers ---- */
62
+
63
+ static void ir_node_clear(tf_ir_node *node) {
64
+ free((char *)node->op);
65
+ if (node->args) cJSON_Delete(node->args);
66
+ tf_schema_free(&node->input_schema);
67
+ tf_schema_free(&node->output_schema);
68
+ }
69
+
70
+ /* ---- IR plan API ---- */
71
+
72
+ tf_ir_plan *tf_ir_plan_create(void) {
73
+ tf_ir_plan *plan = calloc(1, sizeof(tf_ir_plan));
74
+ if (!plan) return NULL;
75
+ plan->capacity = 8;
76
+ plan->nodes = calloc(plan->capacity, sizeof(tf_ir_node));
77
+ if (!plan->nodes) { free(plan); return NULL; }
78
+ return plan;
79
+ }
80
+
81
+ int tf_ir_plan_add_node(tf_ir_plan *plan, const char *op, cJSON *args) {
82
+ if (!plan || !op) return -1;
83
+ if (plan->n_nodes >= plan->capacity) {
84
+ size_t new_cap = 0;
85
+ if (tf_size_mul(plan->capacity, 2, &new_cap) != TF_OK) return -1;
86
+ tf_ir_node *new_nodes = tf_reallocarray_checked(plan->nodes, new_cap,
87
+ sizeof(tf_ir_node));
88
+ if (!new_nodes) return -1;
89
+ memset(new_nodes + plan->capacity, 0,
90
+ (new_cap - plan->capacity) * sizeof(tf_ir_node));
91
+ plan->nodes = new_nodes;
92
+ plan->capacity = new_cap;
93
+ }
94
+
95
+ char *op_copy = strdup(op);
96
+ cJSON *args_copy = args ? cJSON_Duplicate(args, 1) : cJSON_CreateObject();
97
+ if (!op_copy || !args_copy) {
98
+ free(op_copy);
99
+ if (args_copy) cJSON_Delete(args_copy);
100
+ return -1;
101
+ }
102
+
103
+ tf_ir_node *node = &plan->nodes[plan->n_nodes];
104
+ memset(node, 0, sizeof(tf_ir_node));
105
+ node->op = op_copy;
106
+ node->args = args_copy;
107
+ node->index = plan->n_nodes;
108
+
109
+ plan->n_nodes++;
110
+ plan->validated = false;
111
+ plan->schema_inferred = false;
112
+ return 0;
113
+ }
114
+
115
+ tf_ir_plan *tf_ir_plan_clone(const tf_ir_plan *plan) {
116
+ tf_ir_plan *clone = tf_ir_plan_create();
117
+ if (!clone) return NULL;
118
+
119
+ for (size_t i = 0; i < plan->n_nodes; i++) {
120
+ const tf_ir_node *src = &plan->nodes[i];
121
+ if (tf_ir_plan_add_node(clone, src->op, src->args) != 0) {
122
+ tf_ir_plan_free(clone);
123
+ return NULL;
124
+ }
125
+ /* Copy schemas if they were inferred */
126
+ tf_ir_node *dst = &clone->nodes[i];
127
+ if (tf_schema_copy_checked(&dst->input_schema, &src->input_schema) != TF_OK ||
128
+ tf_schema_copy_checked(&dst->output_schema, &src->output_schema) != TF_OK) {
129
+ tf_ir_plan_free(clone);
130
+ return NULL;
131
+ }
132
+ dst->caps = src->caps;
133
+ dst->memory_class = src->memory_class;
134
+ dst->emit_class = src->emit_class;
135
+ dst->schema_class = src->schema_class;
136
+ dst->state_estimate = src->state_estimate;
137
+ }
138
+
139
+ if (tf_schema_copy_checked(&clone->final_schema, &plan->final_schema) != TF_OK) {
140
+ tf_ir_plan_free(clone);
141
+ return NULL;
142
+ }
143
+ clone->plan_caps = plan->plan_caps;
144
+ clone->validated = plan->validated;
145
+ clone->schema_inferred = plan->schema_inferred;
146
+ if (plan->error) {
147
+ clone->error = strdup(plan->error);
148
+ if (!clone->error) {
149
+ tf_ir_plan_free(clone);
150
+ return NULL;
151
+ }
152
+ }
153
+
154
+ return clone;
155
+ }
156
+
157
+ void tf_ir_plan_free(tf_ir_plan *plan) {
158
+ if (!plan) return;
159
+ for (size_t i = 0; i < plan->n_nodes; i++) {
160
+ ir_node_clear(&plan->nodes[i]);
161
+ }
162
+ free(plan->nodes);
163
+ tf_schema_free(&plan->final_schema);
164
+ free(plan->error);
165
+ free(plan);
166
+ }
package/csrc/ir.h ADDED
@@ -0,0 +1,208 @@
1
+ /*
2
+ * ir.h — L2 Intermediate Representation types for the Tranfi compilation pipeline.
3
+ *
4
+ * Defines the op registry, schema, IR node, and IR plan types.
5
+ * The IR is the contract between authoring (L3) and execution (L1).
6
+ */
7
+
8
+ #ifndef TF_IR_H
9
+ #define TF_IR_H
10
+
11
+ #include "config.h"
12
+ #include <stddef.h>
13
+ #include <stdint.h>
14
+ #include <stdbool.h>
15
+
16
+ /* Forward declarations */
17
+ typedef struct cJSON cJSON;
18
+ typedef struct tf_ir_node tf_ir_node;
19
+ typedef struct tf_schema tf_schema;
20
+ typedef struct tf_host_policy tf_host_policy;
21
+
22
+ /* ---- Capability flags ---- */
23
+
24
+ #define TF_CAP_STREAMING (1 << 0) /* processes batches without buffering all data */
25
+ #define TF_CAP_BOUNDED_MEMORY (1 << 1) /* memory usage independent of input size */
26
+ #define TF_CAP_BROWSER_SAFE (1 << 2) /* no filesystem/network access */
27
+ #define TF_CAP_DETERMINISTIC (1 << 3) /* same input always produces same output */
28
+ #define TF_CAP_FS (1 << 4) /* requires filesystem access */
29
+ #define TF_CAP_NET (1 << 5) /* requires network access */
30
+
31
+ /* ---- Op tier ---- */
32
+
33
+ typedef enum {
34
+ TF_TIER_CORE, /* built into the C library */
35
+ TF_TIER_ECOSYSTEM, /* provided by host-side plugins */
36
+ } tf_op_tier;
37
+
38
+ /* ---- Op kind ---- */
39
+
40
+ typedef enum {
41
+ TF_OP_DECODER,
42
+ TF_OP_ENCODER,
43
+ TF_OP_TRANSFORM,
44
+ } tf_op_kind;
45
+
46
+ /* ---- Streaming contract metadata ---- */
47
+
48
+ typedef enum {
49
+ TF_MEM_ROW_LOCAL, /* O(batch_rows * columns) */
50
+ TF_MEM_BOUNDED_STATE, /* O(parameter), e.g. window K or tail N */
51
+ TF_MEM_KEY_STATE, /* O(distinct keys/categories) */
52
+ TF_MEM_BLOCKING, /* needs full input in native mode */
53
+ TF_MEM_EXTERNAL, /* uses spill/SQL/external bounded storage */
54
+ } tf_memory_class;
55
+
56
+ typedef enum {
57
+ TF_EMIT_PER_BATCH, /* can emit during push() */
58
+ TF_EMIT_ON_FLUSH, /* emits only at finish(), e.g. tail/top/stats */
59
+ TF_EMIT_SIDE_ONLY, /* writes side channels, not main output */
60
+ TF_EMIT_MIXED, /* emits both during push() and flush/side output */
61
+ } tf_emit_class;
62
+
63
+ typedef enum {
64
+ TF_SCHEMA_STABLE, /* output columns known from input schema/args */
65
+ TF_SCHEMA_PARAMETRIC, /* output schema depends on explicit parameters */
66
+ TF_SCHEMA_DATA_DEPENDENT, /* output schema depends on observed data */
67
+ } tf_schema_class;
68
+
69
+ /* ---- Argument descriptor ---- */
70
+
71
+ typedef struct {
72
+ const char *name; /* "delimiter", "expr", "columns", etc. */
73
+ const char *type; /* "string", "int", "bool", "string[]", "map" */
74
+ bool required;
75
+ const char *default_val; /* JSON-encoded default, or NULL */
76
+ } tf_arg_desc;
77
+
78
+ /* ---- Value types (shared with batch system) ---- */
79
+
80
+ #ifndef TF_TYPE_DEFINED
81
+ #define TF_TYPE_DEFINED
82
+ typedef enum tf_type {
83
+ TF_TYPE_NULL = 0,
84
+ TF_TYPE_BOOL,
85
+ TF_TYPE_INT64,
86
+ TF_TYPE_FLOAT64,
87
+ TF_TYPE_STRING,
88
+ TF_TYPE_DATE, /* int32_t: days since 1970-01-01 */
89
+ TF_TYPE_TIMESTAMP, /* int64_t: microseconds since 1970-01-01T00:00:00Z */
90
+ } tf_type;
91
+ #endif
92
+
93
+ /* ---- Schema ---- */
94
+
95
+ struct tf_schema {
96
+ char **col_names;
97
+ tf_type *col_types;
98
+ size_t n_cols;
99
+ bool known; /* false if schema can't be determined until runtime */
100
+ };
101
+
102
+ void tf_schema_free(tf_schema *s);
103
+ void tf_schema_copy(tf_schema *dst, const tf_schema *src);
104
+ int tf_schema_copy_checked(tf_schema *dst, const tf_schema *src);
105
+
106
+ /* ---- Op registry entry ---- */
107
+
108
+ typedef struct {
109
+ const char *name; /* "codec.csv.decode", "filter", etc. */
110
+ tf_op_kind kind;
111
+ tf_op_tier tier;
112
+ uint32_t caps; /* TF_CAP_* bitfield */
113
+ tf_memory_class memory_class;
114
+ tf_emit_class emit_class;
115
+ tf_schema_class schema_class;
116
+ const char *state_estimate; /* Big-O state retained by the op */
117
+ tf_arg_desc *args;
118
+ size_t n_args;
119
+ /* Schema transform: given input schema, compute output schema.
120
+ * Returns TF_OK or TF_ERROR. */
121
+ int (*infer_schema)(const tf_ir_node *node,
122
+ const tf_schema *in, tf_schema *out);
123
+ /* Native target constructor (NULL for ecosystem ops).
124
+ * Returns an opaque pointer (tf_decoder*, tf_step*, or tf_encoder*). */
125
+ void *(*create_native)(const cJSON *args);
126
+ } tf_op_entry;
127
+
128
+ /* ---- Op registry API ---- */
129
+
130
+ const tf_op_entry *tf_op_registry_find(const char *name);
131
+ size_t tf_op_registry_count(void);
132
+ const tf_op_entry *tf_op_registry_get(size_t index);
133
+ const char *tf_memory_class_name(tf_memory_class cls);
134
+ const char *tf_emit_class_name(tf_emit_class cls);
135
+ const char *tf_schema_class_name(tf_schema_class cls);
136
+
137
+ /* ---- IR node ---- */
138
+
139
+ struct tf_ir_node {
140
+ const char *op; /* op name (owned, freed with node) */
141
+ cJSON *args; /* argument values (owned, freed with node) */
142
+ tf_schema input_schema;
143
+ tf_schema output_schema;
144
+ uint32_t caps; /* resolved capability flags from registry */
145
+ tf_memory_class memory_class;
146
+ tf_emit_class emit_class;
147
+ tf_schema_class schema_class;
148
+ const char *state_estimate; /* Big-O state estimate, static string */
149
+ size_t index; /* position in plan */
150
+ };
151
+
152
+ /* ---- IR plan ---- */
153
+
154
+ typedef struct tf_ir_plan {
155
+ tf_ir_node *nodes;
156
+ size_t n_nodes;
157
+ size_t capacity; /* allocated node slots */
158
+ tf_schema final_schema; /* schema after last transform, before encoder */
159
+ uint32_t plan_caps; /* intersection of all node caps */
160
+ char *error; /* validation error, if any */
161
+ bool validated;
162
+ bool schema_inferred;
163
+ } tf_ir_plan;
164
+
165
+ /* ---- IR plan API ---- */
166
+
167
+ tf_ir_plan *tf_ir_plan_create(void);
168
+ int tf_ir_plan_add_node(tf_ir_plan *plan, const char *op, cJSON *args);
169
+ tf_ir_plan *tf_ir_plan_clone(const tf_ir_plan *plan);
170
+ void tf_ir_plan_free(tf_ir_plan *plan);
171
+
172
+ /* ---- IR serialization ---- */
173
+
174
+ tf_ir_plan *tf_ir_from_json(const char *json, size_t len, char **error);
175
+ char *tf_ir_to_json(const tf_ir_plan *plan);
176
+ char *tf_ir_to_sql(const tf_ir_plan *plan, char **error);
177
+
178
+ /* ---- IR passes ---- */
179
+
180
+ int tf_ir_validate(tf_ir_plan *plan);
181
+ int tf_ir_validate_with_host_policy(tf_ir_plan *plan, const tf_host_policy *policy);
182
+ int tf_ir_infer_schema(tf_ir_plan *plan);
183
+
184
+ /* ---- Expression eval result ---- */
185
+
186
+ typedef struct tf_eval_result {
187
+ tf_type type;
188
+ union {
189
+ int64_t i; /* INT64 or TIMESTAMP (microseconds since epoch) */
190
+ double f;
191
+ const char *s;
192
+ bool b;
193
+ int32_t date; /* DATE: days since epoch */
194
+ };
195
+ } tf_eval_result;
196
+
197
+ /* ---- Compiler ---- */
198
+
199
+ /* Forward declarations of internal types */
200
+ typedef struct tf_decoder tf_decoder;
201
+ typedef struct tf_step tf_step;
202
+ typedef struct tf_encoder tf_encoder;
203
+
204
+ int tf_compile_native(const tf_ir_plan *plan,
205
+ tf_decoder **decoder, tf_step ***steps,
206
+ size_t *n_steps, tf_encoder **encoder, char **error);
207
+
208
+ #endif /* TF_IR_H */
@@ -0,0 +1,75 @@
1
+ /*
2
+ * ir_schema.c — Forward schema inference pass.
3
+ *
4
+ * Walks nodes in execution order, calling each op's infer_schema callback
5
+ * to propagate schema information forward through the plan.
6
+ * Best-effort and non-fatal — unknown schemas propagate as known=false.
7
+ */
8
+
9
+ #include "ir.h"
10
+ #include <stdlib.h>
11
+
12
+ #define TF_OK 0
13
+ #define TF_ERROR (-1)
14
+
15
+ int tf_ir_infer_schema(tf_ir_plan *plan) {
16
+ if (plan->n_nodes == 0) return TF_OK;
17
+
18
+ /* Start with an empty, unknown input schema for the first node (decoder) */
19
+ tf_schema current = {0};
20
+ current.known = false;
21
+
22
+ for (size_t i = 0; i < plan->n_nodes; i++) {
23
+ tf_ir_node *node = &plan->nodes[i];
24
+ const tf_op_entry *entry = tf_op_registry_find(node->op);
25
+
26
+ /* Copy current schema as this node's input */
27
+ tf_schema_free(&node->input_schema);
28
+ if (tf_schema_copy_checked(&node->input_schema, &current) != TF_OK) {
29
+ tf_schema_free(&current);
30
+ plan->schema_inferred = false;
31
+ return TF_ERROR;
32
+ }
33
+
34
+ if (entry && entry->infer_schema) {
35
+ /* Clear old output schema */
36
+ tf_schema_free(&node->output_schema);
37
+ if (entry->infer_schema(node, &current, &node->output_schema) != TF_OK) {
38
+ tf_schema_free(&current);
39
+ plan->schema_inferred = false;
40
+ return TF_ERROR;
41
+ }
42
+
43
+ /* Advance current to this node's output */
44
+ tf_schema_free(&current);
45
+ if (tf_schema_copy_checked(&current, &node->output_schema) != TF_OK) {
46
+ plan->schema_inferred = false;
47
+ return TF_ERROR;
48
+ }
49
+ } else {
50
+ /* No schema inference available — propagate unknown */
51
+ tf_schema_free(&node->output_schema);
52
+ node->output_schema.known = false;
53
+ tf_schema_free(&current);
54
+ current.known = false;
55
+ }
56
+ }
57
+
58
+ /* Store final schema (schema before encoder, i.e., last transform's output) */
59
+ tf_schema_free(&plan->final_schema);
60
+ if (plan->n_nodes >= 2) {
61
+ /* The schema before the encoder is the second-to-last node's output */
62
+ if (tf_schema_copy_checked(&plan->final_schema,
63
+ &plan->nodes[plan->n_nodes - 2].output_schema) != TF_OK) {
64
+ tf_schema_free(&current);
65
+ plan->schema_inferred = false;
66
+ return TF_ERROR;
67
+ }
68
+ } else {
69
+ plan->final_schema.known = false;
70
+ }
71
+
72
+ tf_schema_free(&current);
73
+ plan->schema_inferred = true;
74
+ return TF_OK;
75
+ }
@@ -0,0 +1,166 @@
1
+ /*
2
+ * ir_serialize.c — IR plan ↔ JSON serialization.
3
+ *
4
+ * JSON format (the .tfp format):
5
+ * {
6
+ * "steps": [
7
+ * {"op": "codec.csv.decode", "args": {"delimiter": ","}},
8
+ * {"op": "filter", "args": {"expr": "col('age') > 25"}},
9
+ * {"op": "codec.csv.encode", "args": {}}
10
+ * ]
11
+ * }
12
+ */
13
+
14
+ #include "internal.h"
15
+ #include "cJSON.h"
16
+ #include <stdlib.h>
17
+ #include <string.h>
18
+ #include <stdio.h>
19
+
20
+ static void set_error(char **error, const char *msg) {
21
+ if (error) {
22
+ free(*error);
23
+ size_t len = strlen(msg) + 1;
24
+ *error = malloc(len);
25
+ if (*error) memcpy(*error, msg, len);
26
+ }
27
+ }
28
+
29
+ tf_ir_plan *tf_ir_from_json(const char *json, size_t len, char **error) {
30
+ if (error) *error = NULL;
31
+
32
+ cJSON *root = cJSON_ParseWithLength(json, len);
33
+ if (!root) {
34
+ set_error(error, "invalid JSON in plan");
35
+ return NULL;
36
+ }
37
+
38
+ cJSON *steps_arr = cJSON_GetObjectItemCaseSensitive(root, "steps");
39
+ if (!cJSON_IsArray(steps_arr)) {
40
+ set_error(error, "plan must have a 'steps' array");
41
+ cJSON_Delete(root);
42
+ return NULL;
43
+ }
44
+
45
+ int n = cJSON_GetArraySize(steps_arr);
46
+ if (n == 0) {
47
+ set_error(error, "plan has no steps");
48
+ cJSON_Delete(root);
49
+ return NULL;
50
+ }
51
+
52
+ tf_ir_plan *plan = tf_ir_plan_create();
53
+ if (!plan) {
54
+ set_error(error, "out of memory");
55
+ cJSON_Delete(root);
56
+ return NULL;
57
+ }
58
+
59
+ for (int i = 0; i < n; i++) {
60
+ cJSON *step = cJSON_GetArrayItem(steps_arr, i);
61
+ cJSON *op = cJSON_GetObjectItemCaseSensitive(step, "op");
62
+ cJSON *args = cJSON_GetObjectItemCaseSensitive(step, "args");
63
+
64
+ if (!cJSON_IsString(op)) {
65
+ char buf[64];
66
+ snprintf(buf, sizeof(buf), "step %d missing 'op' string", i);
67
+ set_error(error, buf);
68
+ tf_ir_plan_free(plan);
69
+ cJSON_Delete(root);
70
+ return NULL;
71
+ }
72
+
73
+ if (tf_ir_plan_add_node(plan, op->valuestring, args) != 0) {
74
+ set_error(error, "out of memory adding node");
75
+ tf_ir_plan_free(plan);
76
+ cJSON_Delete(root);
77
+ return NULL;
78
+ }
79
+ }
80
+
81
+ cJSON_Delete(root);
82
+ return plan;
83
+ }
84
+
85
+ char *tf_ir_to_json(const tf_ir_plan *plan) {
86
+ cJSON *root = cJSON_CreateObject();
87
+ if (!root) return NULL;
88
+ cJSON *steps = cJSON_CreateArray();
89
+ if (!steps || tf_json_add_item(root, "steps", steps) != TF_OK) {
90
+ cJSON_Delete(root);
91
+ return NULL;
92
+ }
93
+
94
+ for (size_t i = 0; i < plan->n_nodes; i++) {
95
+ const tf_ir_node *node = &plan->nodes[i];
96
+ cJSON *step = cJSON_CreateObject();
97
+ if (!step) {
98
+ cJSON_Delete(root);
99
+ return NULL;
100
+ }
101
+ if (tf_json_add_string(step, "op", node->op) != TF_OK) {
102
+ cJSON_Delete(step);
103
+ cJSON_Delete(root);
104
+ return NULL;
105
+ }
106
+ if (node->args) {
107
+ cJSON *args = cJSON_Duplicate(node->args, 1);
108
+ if (args) {
109
+ cJSON_DeleteItemFromObjectCaseSensitive(args, TF_POLICY_VALIDATED_FILE_PATH_ARG);
110
+ cJSON_DeleteItemFromObjectCaseSensitive(args, TF_POLICY_VALIDATED_RULES_FILE_PATH_ARG);
111
+ }
112
+ if (!args || tf_json_add_item(step, "args", args) != TF_OK) {
113
+ cJSON_Delete(step);
114
+ cJSON_Delete(root);
115
+ return NULL;
116
+ }
117
+ } else {
118
+ cJSON *args = cJSON_CreateObject();
119
+ if (!args || tf_json_add_item(step, "args", args) != TF_OK) {
120
+ cJSON_Delete(step);
121
+ cJSON_Delete(root);
122
+ return NULL;
123
+ }
124
+ }
125
+ if (tf_json_add_string(step, "memory_class", tf_memory_class_name(node->memory_class)) != TF_OK ||
126
+ tf_json_add_string(step, "emit_class", tf_emit_class_name(node->emit_class)) != TF_OK ||
127
+ tf_json_add_string(step, "schema_class", tf_schema_class_name(node->schema_class)) != TF_OK ||
128
+ tf_json_add_string(step, "state_estimate",
129
+ node->state_estimate ? node->state_estimate : "unknown") != TF_OK) {
130
+ cJSON_Delete(step);
131
+ cJSON_Delete(root);
132
+ return NULL;
133
+ }
134
+ size_t state_bytes = 0;
135
+ char state_reason[192] = {0};
136
+ if (tf_estimate_step_state_bytes(node, &state_bytes, state_reason, sizeof(state_reason))) {
137
+ if (tf_json_add_number(step, "state_bytes_estimate", (double)state_bytes) != TF_OK) {
138
+ cJSON_Delete(step);
139
+ cJSON_Delete(root);
140
+ return NULL;
141
+ }
142
+ } else {
143
+ if (tf_json_add_null(step, "state_bytes_estimate") != TF_OK) {
144
+ cJSON_Delete(step);
145
+ cJSON_Delete(root);
146
+ return NULL;
147
+ }
148
+ if (node->memory_class == TF_MEM_KEY_STATE || node->memory_class == TF_MEM_BLOCKING) {
149
+ if (tf_json_add_string(step, "state_bytes_reason",
150
+ state_reason[0] ? state_reason : "no native byte estimator") != TF_OK) {
151
+ cJSON_Delete(step);
152
+ cJSON_Delete(root);
153
+ return NULL;
154
+ }
155
+ }
156
+ }
157
+ if (tf_json_add_array_item(steps, step) != TF_OK) {
158
+ cJSON_Delete(root);
159
+ return NULL;
160
+ }
161
+ }
162
+
163
+ char *out = cJSON_PrintUnformatted(root);
164
+ cJSON_Delete(root);
165
+ return out;
166
+ }