File Coverage

include/litavis_ast.h
Criterion Covered Total %
statement 210 210 100.0
branch 100 124 80.6
condition n/a
subroutine n/a
pod n/a
total 310 334 92.8


line stmt bran cond sub pod time code
1             #ifndef LITAVIS_AST_H
2             #define LITAVIS_AST_H
3              
4             #include
5             #include
6             #include
7              
8             /* ── Error handling (overridable by XS layer) ──────────────── */
9              
10             #ifndef LITAVIS_FATAL
11             #define LITAVIS_FATAL(msg) do { fprintf(stderr, "litavis: %s\n", (msg)); abort(); } while(0)
12             #endif
13              
14             /* ── Forward declarations ─────────────────────────────────── */
15              
16             typedef struct LitavisProp LitavisProp;
17             typedef struct LitavisRule LitavisRule;
18             typedef struct LitavisAST LitavisAST;
19             typedef struct LitavisBucket LitavisBucket;
20              
21             /* ── Property (key-value pair, ordered) ───────────────────── */
22              
23             struct LitavisProp {
24             char *key; /* property name, e.g. "color" */
25             char *value; /* property value, e.g. "red" */
26             };
27              
28             /* ── Rule (selector + ordered properties + optional children) */
29              
30             struct LitavisRule {
31             char *selector; /* e.g. ".card", ".card:hover" */
32             LitavisProp *props; /* ordered array of properties */
33             int prop_count;
34             int prop_cap;
35             LitavisRule *children; /* nested rules (before flattening) */
36             int child_count;
37             int child_cap;
38             int is_at_rule; /* 1 if @media, @keyframes, etc. */
39             char *at_prelude; /* e.g. "(max-width: 768px)" */
40             char *source_file; /* origin file for error reporting */
41             int source_line;
42             };
43              
44             /* ── Hash bucket for O(1) selector lookup ─────────────────── */
45              
46             struct LitavisBucket {
47             char *key;
48             int index; /* index into rules[] array */
49             LitavisBucket *next; /* chaining for collisions */
50             };
51              
52             /* ── AST (top-level ordered collection of rules) ──────────── */
53              
54             struct LitavisAST {
55             LitavisRule *rules; /* ordered array — insertion order */
56             int count;
57             int capacity;
58             LitavisBucket **buckets; /* hash table for lookup by selector */
59             int bucket_count;
60             };
61              
62             /* ── Internal: hash function ──────────────────────────────── */
63              
64 10366           static unsigned int litavis_hash(const char *key, int bucket_count) {
65 10366           unsigned int h = 5381;
66 111787 100         while (*key)
67 101421           h = ((h << 5) + h) + (unsigned char)*key++;
68 10366           return h % (unsigned int)bucket_count;
69             }
70              
71             /* ── Internal: strdup portable ────────────────────────────── */
72              
73 19557           static char* litavis_strdup(const char *s) {
74 19557           size_t len = strlen(s);
75 19557           char *dup = (char*)malloc(len + 1);
76 19557 50         if (!dup) LITAVIS_FATAL("out of memory");
77 19557           memcpy(dup, s, len + 1);
78 19557           return dup;
79             }
80              
81             /* ── Internal: initialise a rule struct ───────────────────── */
82              
83 3441           static void litavis_rule_init(LitavisRule *rule) {
84 3441           rule->selector = NULL;
85 3441           rule->props = NULL;
86 3441           rule->prop_count = 0;
87 3441           rule->prop_cap = 0;
88 3441           rule->children = NULL;
89 3441           rule->child_count = 0;
90 3441           rule->child_cap = 0;
91 3441           rule->is_at_rule = 0;
92 3441           rule->at_prelude = NULL;
93 3441           rule->source_file = NULL;
94 3441           rule->source_line = 0;
95 3441           }
96              
97             /* ── Internal: free a rule's contents (not the rule itself) ── */
98              
99 3441           static void litavis_rule_cleanup(LitavisRule *rule) {
100             int i;
101 3441 50         if (rule->selector) free(rule->selector);
102 3441 100         if (rule->at_prelude) free(rule->at_prelude);
103 3441 100         if (rule->source_file) free(rule->source_file);
104 7388 100         for (i = 0; i < rule->prop_count; i++) {
105 3947           free(rule->props[i].key);
106 3947           free(rule->props[i].value);
107             }
108 3441 100         if (rule->props) free(rule->props);
109 3525 100         for (i = 0; i < rule->child_count; i++) {
110 84           litavis_rule_cleanup(&rule->children[i]);
111             }
112 3441 100         if (rule->children) free(rule->children);
113 3441           }
114              
115             /* ── Internal: free all hash buckets ─────────────────────── */
116              
117 1943           static void litavis_ast_free_buckets(LitavisAST *ast) {
118             int i;
119 66511 100         for (i = 0; i < ast->bucket_count; i++) {
120 64568           LitavisBucket *b = ast->buckets[i];
121 70772 100         while (b) {
122 6204           LitavisBucket *next = b->next;
123 6204           free(b->key);
124 6204           free(b);
125 6204           b = next;
126             }
127             }
128 1943           free(ast->buckets);
129 1943           ast->buckets = NULL;
130 1943           }
131              
132             /* ── Internal: insert into hash table ────────────────────── */
133              
134 6204           static void litavis_ast_hash_insert(LitavisAST *ast, const char *key, int index) {
135 6204           unsigned int h = litavis_hash(key, ast->bucket_count);
136 6204           LitavisBucket *b = (LitavisBucket*)malloc(sizeof(LitavisBucket));
137 6204 50         if (!b) LITAVIS_FATAL("out of memory");
138 6204           b->key = litavis_strdup(key);
139 6204           b->index = index;
140 6204           b->next = ast->buckets[h];
141 6204           ast->buckets[h] = b;
142 6204           }
143              
144             /* ── Internal: rehash when load factor exceeds 0.75 ──────── */
145              
146 16           static void litavis_ast_rehash(LitavisAST *ast) {
147 16           int new_bc = ast->bucket_count * 2;
148 16           LitavisBucket **new_buckets = (LitavisBucket**)calloc((size_t)new_bc, sizeof(LitavisBucket*));
149             int i;
150 16 50         if (!new_buckets) LITAVIS_FATAL("out of memory");
151              
152             /* Re-insert all existing entries */
153 1520 100         for (i = 0; i < ast->bucket_count; i++) {
154 1504           LitavisBucket *b = ast->buckets[i];
155 2648 100         while (b) {
156 1144           LitavisBucket *next = b->next;
157 1144           unsigned int h = litavis_hash(b->key, new_bc);
158 1144           b->next = new_buckets[h];
159 1144           new_buckets[h] = b;
160 1144           b = next;
161             }
162             }
163 16           free(ast->buckets);
164 16           ast->buckets = new_buckets;
165 16           ast->bucket_count = new_bc;
166 16           }
167              
168             /* ── AST lifecycle ────────────────────────────────────────── */
169              
170 1686           static LitavisAST* litavis_ast_new(int initial_capacity) {
171 1686           LitavisAST *ast = (LitavisAST*)malloc(sizeof(LitavisAST));
172 1686 50         if (!ast) LITAVIS_FATAL("out of memory");
173              
174 1686 100         if (initial_capacity < 8) initial_capacity = 8;
175 1686           ast->rules = (LitavisRule*)malloc(sizeof(LitavisRule) * (size_t)initial_capacity);
176 1686           ast->count = 0;
177 1686           ast->capacity = initial_capacity;
178              
179 1686           ast->bucket_count = initial_capacity * 2;
180 1686           ast->buckets = (LitavisBucket**)calloc((size_t)ast->bucket_count, sizeof(LitavisBucket*));
181              
182 1686 50         if (!ast->rules || !ast->buckets) LITAVIS_FATAL("out of memory");
    50          
183 1686           return ast;
184             }
185              
186 1686           static void litavis_ast_free(LitavisAST *ast) {
187             int i;
188 1686 50         if (!ast) return;
189 4872 100         for (i = 0; i < ast->count; i++) {
190 3186           litavis_rule_cleanup(&ast->rules[i]);
191             }
192 1686           free(ast->rules);
193 1686           litavis_ast_free_buckets(ast);
194 1686           free(ast);
195             }
196              
197             /* ── Deep clone ───────────────────────────────────────────── */
198              
199             static void litavis_rule_clone_into(LitavisRule *dst, const LitavisRule *src);
200              
201 590           static void litavis_rule_clone_into(LitavisRule *dst, const LitavisRule *src) {
202             int i;
203 590           litavis_rule_init(dst);
204 590 50         if (src->selector) dst->selector = litavis_strdup(src->selector);
205 590 100         if (src->at_prelude) dst->at_prelude = litavis_strdup(src->at_prelude);
206 590 100         if (src->source_file) dst->source_file = litavis_strdup(src->source_file);
207 590           dst->is_at_rule = src->is_at_rule;
208 590           dst->source_line = src->source_line;
209              
210 590 100         if (src->prop_count > 0) {
211 579           dst->prop_cap = src->prop_count;
212 579           dst->prop_count = src->prop_count;
213 579           dst->props = (LitavisProp*)malloc(sizeof(LitavisProp) * (size_t)dst->prop_cap);
214 579 50         if (!dst->props) LITAVIS_FATAL("out of memory");
215 1270 100         for (i = 0; i < src->prop_count; i++) {
216 691           dst->props[i].key = litavis_strdup(src->props[i].key);
217 691           dst->props[i].value = litavis_strdup(src->props[i].value);
218             }
219             }
220              
221 590 100         if (src->child_count > 0) {
222 8           dst->child_cap = src->child_count;
223 8           dst->child_count = src->child_count;
224 8           dst->children = (LitavisRule*)malloc(sizeof(LitavisRule) * (size_t)dst->child_cap);
225 8 50         if (!dst->children) LITAVIS_FATAL("out of memory");
226 18 100         for (i = 0; i < src->child_count; i++) {
227 10           litavis_rule_clone_into(&dst->children[i], &src->children[i]);
228             }
229             }
230 590           }
231              
232 177           static LitavisAST* litavis_ast_clone(LitavisAST *ast) {
233             int i;
234             LitavisAST *clone;
235 177 50         if (!ast) return NULL;
236 177           clone = litavis_ast_new(ast->capacity);
237 757 100         for (i = 0; i < ast->count; i++) {
238 580           litavis_rule_clone_into(&clone->rules[i], &ast->rules[i]);
239 580           litavis_ast_hash_insert(clone, ast->rules[i].selector, i);
240             }
241 177           clone->count = ast->count;
242 177           return clone;
243             }
244              
245             /* ── Rule operations ──────────────────────────────────────── */
246              
247             /* O(1) lookup by selector */
248 3018           static LitavisRule* litavis_ast_get_rule(LitavisAST *ast, const char *selector) {
249 3018           unsigned int h = litavis_hash(selector, ast->bucket_count);
250 3018           LitavisBucket *b = ast->buckets[h];
251 3329 100         while (b) {
252 534 100         if (strcmp(b->key, selector) == 0)
253 223           return &ast->rules[b->index];
254 311           b = b->next;
255             }
256 2795           return NULL;
257             }
258              
259             /* Check existence */
260 38           static int litavis_ast_has_rule(LitavisAST *ast, const char *selector) {
261 38           return litavis_ast_get_rule(ast, selector) != NULL;
262             }
263              
264             /* Append a new rule or return existing if selector matches */
265 2810           static LitavisRule* litavis_ast_add_rule(LitavisAST *ast, const char *selector) {
266 2810           LitavisRule *existing = litavis_ast_get_rule(ast, selector);
267 2810 100         if (existing) return existing;
268              
269             /* Grow array if needed */
270 2777 100         if (ast->count >= ast->capacity) {
271 16           int new_cap = ast->capacity * 2;
272 16           LitavisRule *new_rules = (LitavisRule*)realloc(ast->rules, sizeof(LitavisRule) * (size_t)new_cap);
273 16 50         if (!new_rules) LITAVIS_FATAL("out of memory");
274 16           ast->rules = new_rules;
275 16           ast->capacity = new_cap;
276             }
277              
278             /* Rehash if load factor > 0.75 */
279 2777 100         if (ast->count * 4 > ast->bucket_count * 3) {
280 16           litavis_ast_rehash(ast);
281             }
282              
283             /* Init new rule */
284 2777           litavis_rule_init(&ast->rules[ast->count]);
285 2777           ast->rules[ast->count].selector = litavis_strdup(selector);
286              
287             /* Insert into hash */
288 2777           litavis_ast_hash_insert(ast, selector, ast->count);
289              
290 2777           return &ast->rules[ast->count++];
291             }
292              
293             /* Remove rule, shift remaining to preserve order */
294 171           static void litavis_ast_remove_rule(LitavisAST *ast, int index) {
295             int i;
296 171 50         if (index < 0 || index >= ast->count) return;
    50          
297              
298             /* Clean up the rule being removed */
299 171           litavis_rule_cleanup(&ast->rules[index]);
300              
301             /* Shift remaining rules down */
302 1510 100         for (i = index; i < ast->count - 1; i++) {
303 1339           ast->rules[i] = ast->rules[i + 1];
304             }
305 171           ast->count--;
306              
307             /* Rebuild hash table (indices changed) */
308 171           litavis_ast_free_buckets(ast);
309 171           ast->bucket_count = (ast->capacity > 8 ? ast->capacity : 8) * 2;
310 171           ast->buckets = (LitavisBucket**)calloc((size_t)ast->bucket_count, sizeof(LitavisBucket*));
311 171 50         if (!ast->buckets) LITAVIS_FATAL("out of memory");
312 1613 100         for (i = 0; i < ast->count; i++) {
313 1442           litavis_ast_hash_insert(ast, ast->rules[i].selector, i);
314             }
315             }
316              
317             /* Rename a rule's selector (for dedup merging) */
318 86           static void litavis_ast_rename_rule(LitavisAST *ast, int index, const char *new_selector) {
319             int i;
320 86 50         if (index < 0 || index >= ast->count) return;
    50          
321              
322 86           free(ast->rules[index].selector);
323 86           ast->rules[index].selector = litavis_strdup(new_selector);
324              
325             /* Rebuild hash table */
326 86           litavis_ast_free_buckets(ast);
327 86           ast->bucket_count = (ast->capacity > 8 ? ast->capacity : 8) * 2;
328 86           ast->buckets = (LitavisBucket**)calloc((size_t)ast->bucket_count, sizeof(LitavisBucket*));
329 86 50         if (!ast->buckets) LITAVIS_FATAL("out of memory");
330 1491 100         for (i = 0; i < ast->count; i++) {
331 1405           litavis_ast_hash_insert(ast, ast->rules[i].selector, i);
332             }
333             }
334              
335             /* ── Property operations on a rule ────────────────────────── */
336              
337 3255           static void litavis_rule_add_prop(LitavisRule *rule, const char *key, const char *value) {
338             int i;
339              
340             /* Update existing property if key matches */
341 4554 100         for (i = 0; i < rule->prop_count; i++) {
342 1323 100         if (strcmp(rule->props[i].key, key) == 0) {
343 24           free(rule->props[i].value);
344 24           rule->props[i].value = litavis_strdup(value);
345 24           return;
346             }
347             }
348              
349             /* Grow array if needed */
350 3231 100         if (rule->prop_count >= rule->prop_cap) {
351 2583 100         int new_cap = rule->prop_cap < 4 ? 4 : rule->prop_cap * 2;
352 2583           LitavisProp *new_props = (LitavisProp*)realloc(rule->props, sizeof(LitavisProp) * (size_t)new_cap);
353 2583 50         if (!new_props) LITAVIS_FATAL("out of memory");
354 2583           rule->props = new_props;
355 2583           rule->prop_cap = new_cap;
356             }
357              
358 3231           rule->props[rule->prop_count].key = litavis_strdup(key);
359 3231           rule->props[rule->prop_count].value = litavis_strdup(value);
360 3231           rule->prop_count++;
361             }
362              
363 135           static char* litavis_rule_get_prop(LitavisRule *rule, const char *key) {
364             int i;
365 199 100         for (i = 0; i < rule->prop_count; i++) {
366 196 100         if (strcmp(rule->props[i].key, key) == 0)
367 132           return rule->props[i].value;
368             }
369 3           return NULL;
370             }
371              
372 3           static int litavis_rule_has_prop(LitavisRule *rule, const char *key) {
373 3           return litavis_rule_get_prop(rule, key) != NULL;
374             }
375              
376             /* ── Child rule operations ────────────────────────────────── */
377              
378 74           static LitavisRule* litavis_rule_add_child(LitavisRule *rule, const char *selector) {
379 74 100         if (rule->child_count >= rule->child_cap) {
380 63 50         int new_cap = rule->child_cap < 4 ? 4 : rule->child_cap * 2;
381 63           LitavisRule *new_children = (LitavisRule*)realloc(rule->children, sizeof(LitavisRule) * (size_t)new_cap);
382 63 50         if (!new_children) LITAVIS_FATAL("out of memory");
383 63           rule->children = new_children;
384 63           rule->child_cap = new_cap;
385             }
386              
387 74           litavis_rule_init(&rule->children[rule->child_count]);
388 74           rule->children[rule->child_count].selector = litavis_strdup(selector);
389 74           return &rule->children[rule->child_count++];
390             }
391              
392             /* ── Utility ──────────────────────────────────────────────── */
393              
394             /* Deep compare two rules' properties (same keys, same values, same order) */
395 2           static int litavis_rules_props_equal(LitavisRule *a, LitavisRule *b) {
396             int i;
397 2 100         if (a->prop_count != b->prop_count) return 0;
398 3 100         for (i = 0; i < a->prop_count; i++) {
399 2 50         if (strcmp(a->props[i].key, b->props[i].key) != 0) return 0;
400 2 50         if (strcmp(a->props[i].value, b->props[i].value) != 0) return 0;
401             }
402 1           return 1;
403             }
404              
405             /* Merge properties from src into dst (src wins on conflict) */
406 887           static void litavis_rule_merge_props(LitavisRule *dst, LitavisRule *src) {
407             int i;
408 1979 100         for (i = 0; i < src->prop_count; i++) {
409 1092           litavis_rule_add_prop(dst, src->props[i].key, src->props[i].value);
410             }
411 887           }
412              
413             #endif /* LITAVIS_AST_H */