1 /*
2 * dependent.c: Manage calculation dependencies between objects
3 *
4 * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
5 * Copyright (C) 2006-2020 Morten Welinder (terra@gnome.org)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22 #include <gnumeric-config.h>
23 #include <gnumeric.h>
24 #include <dependent.h>
25
26 #include <workbook-priv.h>
27 #include <value.h>
28 #include <cell.h>
29 #include <sheet.h>
30 #include <expr.h>
31 #include <expr-impl.h>
32 #include <expr-name.h>
33 #include <application.h>
34 #include <workbook-view.h>
35 #include <ranges.h>
36 #include <gutils.h>
37 #include <sheet-view.h>
38 #include <func.h>
39
40 #include <goffice/goffice.h>
41 #include <string.h>
42
43 static gboolean gnm_cell_eval_content (GnmCell *cell);
44 static void dependent_changed (GnmDependent *dep);
45 static void dependent_clear_dynamic_deps (GnmDependent *dep);
46
47 typedef enum {
48 DEP_LINK_NON_SCALAR = 1,
49
50 DEP_LINK_LINK = 0x8000,
51 DEP_LINK_UNLINK = 0
52 } DepLinkFlags;
53
54 /* ------------------------------------------------------------------------- */
55
56 /*
57 * Note: we unconditionally use pools for
58 * deps->range_pool
59 * deps->single_pool
60 * since we need the ability to free en masse.
61 */
62
63 #ifndef USE_POOLS
64 #define USE_POOLS 0
65 #endif
66
67 #if USE_POOLS
68 static GOMemChunk *micro_few_pool;
69 static GOMemChunk *cset_pool;
70 #define CHUNK_ALLOC(T,p) ((T*)go_mem_chunk_alloc (p))
71 #define CHUNK_FREE(p,v) go_mem_chunk_free ((p), (v))
72 #define MICRO_HASH_FEW 3 /* Odd and small. */
73 #define NEW_FEW CHUNK_ALLOC (gpointer, micro_few_pool)
74 #define FREE_FEW(p) CHUNK_FREE (micro_few_pool, p)
75 #else
76 #define CHUNK_ALLOC(T,c) g_slice_new (T)
77 #define CHUNK_FREE(p,v) g_slice_free1 (sizeof(*v),(v))
78 #define MICRO_HASH_FEW 4 /* Even and small. */
79 #define NEW_FEW (gpointer *)g_slice_alloc (MICRO_HASH_FEW * sizeof (gpointer))
80 #define FREE_FEW(p) g_slice_free1 (MICRO_HASH_FEW * sizeof (gpointer), p)
81 #endif
82
83 /* ------------------------------------------------------------------------- */
84 /* Maps between row numbers and bucket numbers. */
85
86 // The bucket size grows with row number:
87 //
88 // * The first 8 buckets have size 128
89 // * The next 8 buckets have size 256
90 // * The next 8 buckets have size 512
91 // * Etc.
92 //
93 // A 64k row sheet will have 49 buckets; a 1M row sheet will have 81.
94
95 #define BUCKET_BASE_SIZE_BITS 7
96 #define BUCKET_BASE_SIZE (1u << BUCKET_BASE_SIZE_BITS)
97 #define BUCKET_REP_BITS 3
98 #define BUCKET_REP (1u << BUCKET_REP_BITS)
99
100 static inline int
bucket_of_row(unsigned row)101 bucket_of_row (unsigned row)
102 {
103 unsigned sb, sb0, i;
104 unsigned sbs = BUCKET_REP * BUCKET_BASE_SIZE;
105
106 #ifdef __GNUC__
107 // Super block
108 sb = __builtin_clz (1u) - __builtin_clz (1u + row / sbs);
109
110 // Super block start
111 sb0 = (sbs << sb) - sbs;
112 #else
113 sb0 = 0;
114 for (sb = 0; TRUE; sb++) {
115 unsigned next_sb0 = ((2u << sb) - 1) * sbs;
116 if (row < next_sb0)
117 break;
118 sb0 = next_sb0;
119 }
120 #endif
121 // Index (0-7) within super block
122 i = (row - sb0) >> (BUCKET_BASE_SIZE_BITS + sb);
123
124 return (int)(sb * BUCKET_REP + i);
125 }
126
127 static inline int
bucket_start_row(unsigned b)128 bucket_start_row (unsigned b)
129 {
130 unsigned sb = b / BUCKET_REP;
131 unsigned s = ((BUCKET_REP + (b % BUCKET_REP)) << sb) - BUCKET_REP;
132 return (int)(s << BUCKET_BASE_SIZE_BITS);
133 }
134
135 static inline int
bucket_end_row(int b)136 bucket_end_row (int b)
137 {
138 return bucket_start_row (b + 1) - 1;
139 }
140
141 /* ------------------------------------------------------------------------- */
142
143 /* Keep this odd */
144 #define CSET_SEGMENT_SIZE 29
145
146 typedef struct _CSet CSet;
147 struct _CSet {
148 int count;
149 CSet *next;
150 gpointer data[CSET_SEGMENT_SIZE];
151 /* And one pointer for allocation overhead. */
152 };
153
154 #if 0
155 static gboolean
156 cset_find (CSet *list, gpointer datum)
157 {
158 while (list) {
159 guint i = list->count;
160 while (i-- > 0)
161 if (list->data[i] == datum)
162 return TRUE;
163 list = list->next;
164 }
165 return FALSE;
166 }
167 #endif
168
169 static void
cset_free(CSet * list)170 cset_free (CSet *list)
171 {
172 while (list) {
173 CSet *next = list->next;
174 CHUNK_FREE (cset_pool, list);
175 list = next;
176 }
177 }
178
179 /* NOTE: takes reference. */
180 static void
cset_insert(CSet ** list,gpointer datum)181 cset_insert (CSet **list, gpointer datum)
182 {
183 CSet *cs = *list;
184 if (cs == NULL || cs->count == CSET_SEGMENT_SIZE) {
185 CSet *h = *list = CHUNK_ALLOC (CSet, cset_pool);
186 h->next = cs;
187 h->count = 1;
188 h->data[0] = datum;
189 } else
190 cs->data[cs->count++] = datum;
191 }
192
193 /* NOTE: takes reference. Returns %TRUE if datum was already present. */
194 static gboolean
cset_insert_checked(CSet ** list,gpointer datum)195 cset_insert_checked (CSet **list, gpointer datum)
196 {
197 CSet *cs = *list;
198 CSet *nonfull = NULL;
199
200 while (cs) {
201 guint i = cs->count;
202 if (i != CSET_SEGMENT_SIZE)
203 nonfull = cs;
204 while (i-- > 0)
205 if (cs->data[i] == datum)
206 return TRUE;
207 cs = cs->next;
208 }
209
210 if (nonfull)
211 nonfull->data[nonfull->count++] = datum;
212 else
213 cset_insert (list, datum);
214 return FALSE;
215 }
216
217
218 /* NOTE: takes reference. Returns %TRUE if removed. */
219 static gboolean
cset_remove(CSet ** list,gpointer datum)220 cset_remove (CSet **list, gpointer datum)
221 {
222 CSet *l, *last = NULL;
223
224 for (l = *list; l; l = l->next) {
225 guint i;
226
227 for (i = l->count; i-- > 0; )
228 if (l->data[i] == datum) {
229 l->count--;
230 if (l->count == 0) {
231 if (last)
232 last->next = l->next;
233 else
234 *list = l->next;
235 CHUNK_FREE (cset_pool, l);
236 } else
237 l->data[i] = l->data[l->count];
238 return TRUE;
239 }
240 last = l;
241 }
242 return FALSE;
243 }
244
245 #define CSET_FOREACH(list,var,code) \
246 do { \
247 CSet *cs_; \
248 for (cs_ = (list); cs_; cs_ = cs_->next) { \
249 guint i_; \
250 for (i_ = cs_->count; i_-- > 0; ) { \
251 var = cs_->data[i_]; \
252 code \
253 } \
254 } \
255 } while (0)
256
257
258 /* ------------------------------------------------------------------------- */
259
260 static void
gnm_dep_set_expr_undo_undo(GnmDependent * dep,GnmExprTop const * texpr)261 gnm_dep_set_expr_undo_undo (GnmDependent *dep, GnmExprTop const *texpr)
262 {
263 dependent_set_expr (dep, texpr);
264 dependent_link (dep);
265 dependent_changed (dep);
266 }
267
268 static GOUndo *
gnm_dep_set_expr_undo_new(GnmDependent * dep)269 gnm_dep_set_expr_undo_new (GnmDependent *dep)
270 {
271 gnm_expr_top_ref (dep->texpr);
272 return go_undo_binary_new (dep, (gpointer)dep->texpr,
273 (GOUndoBinaryFunc)gnm_dep_set_expr_undo_undo,
274 NULL,
275 (GFreeFunc)gnm_expr_top_unref);
276 }
277
278 static GOUndo *
gnm_dep_unlink_undo_new(GSList * deps)279 gnm_dep_unlink_undo_new (GSList *deps)
280 {
281 return go_undo_unary_new (deps,
282 (GOUndoUnaryFunc)dependents_link,
283 (GFreeFunc)g_slist_free);
284 }
285
286 /* ------------------------------------------------------------------------- */
287
288 #undef DEBUG_EVALUATION
289
290 static void
dummy_dep_eval(G_GNUC_UNUSED GnmDependent * dep)291 dummy_dep_eval (G_GNUC_UNUSED GnmDependent *dep)
292 {
293 }
294
295 static void cell_dep_eval (GnmDependent *dep);
296 static void cell_dep_set_expr (GnmDependent *dep, GnmExprTop const *new_texpr);
297 static GSList *cell_dep_changed (GnmDependent *dep);
298 static GnmCellPos *cell_dep_pos (GnmDependent const *dep);
299 static void cell_dep_debug_name (GnmDependent const *dep, GString *target);
300 static const GnmDependentClass cell_dep_class = {
301 cell_dep_eval,
302 cell_dep_set_expr,
303 cell_dep_changed,
304 cell_dep_pos,
305 cell_dep_debug_name,
306 };
307
308 static GSList *dynamic_dep_changed (GnmDependent *dep);
309 static void dynamic_dep_debug_name (GnmDependent const *dep, GString *target);
310 static const GnmDependentClass dynamic_dep_class = {
311 dummy_dep_eval,
312 NULL,
313 dynamic_dep_changed,
314 NULL,
315 dynamic_dep_debug_name,
316 };
317 typedef struct {
318 GnmDependent base;
319 GnmDependent *container;
320 GSList *ranges;
321 GSList *singles;
322 } DynamicDep;
323
324 static void name_dep_debug_name (GnmDependent const *dep, GString *target);
325 static const GnmDependentClass name_dep_class = {
326 dummy_dep_eval,
327 NULL,
328 NULL,
329 NULL,
330 name_dep_debug_name,
331 };
332
333 static void managed_dep_debug_name (GnmDependent const *dep, GString *target);
334 static const GnmDependentClass managed_dep_class = {
335 dummy_dep_eval,
336 NULL,
337 NULL,
338 NULL,
339 managed_dep_debug_name,
340 };
341
342 static GPtrArray *dep_classes = NULL;
343
344 void
dependent_types_init(void)345 dependent_types_init (void)
346 {
347 g_return_if_fail (dep_classes == NULL);
348
349 /* Init with a NULL class so we can access directly */
350 dep_classes = g_ptr_array_new ();
351 g_ptr_array_add (dep_classes, NULL); /* bogus filler */
352 g_ptr_array_add (dep_classes, (gpointer)&cell_dep_class);
353 g_ptr_array_add (dep_classes, (gpointer)&dynamic_dep_class);
354 g_ptr_array_add (dep_classes, (gpointer)&name_dep_class);
355 g_ptr_array_add (dep_classes, (gpointer)&managed_dep_class);
356
357 #if USE_POOLS
358 micro_few_pool =
359 go_mem_chunk_new ("micro few pool",
360 MICRO_HASH_FEW * sizeof (gpointer),
361 16 * 1024 - 128);
362 cset_pool =
363 go_mem_chunk_new ("cset pool",
364 sizeof (CSet),
365 16 * 1024 - 128);
366 #endif
367 }
368
369 void
dependent_types_shutdown(void)370 dependent_types_shutdown (void)
371 {
372 g_return_if_fail (dep_classes != NULL);
373 g_ptr_array_free (dep_classes, TRUE);
374 dep_classes = NULL;
375
376 #if USE_POOLS
377 go_mem_chunk_destroy (micro_few_pool, FALSE);
378 micro_few_pool = NULL;
379 go_mem_chunk_destroy (cset_pool, FALSE);
380 cset_pool = NULL;
381 #endif
382 }
383
384 /**
385 * dependent_register_type:
386 * @klass: A vtable
387 *
388 * Store the vtable and allocate an ID for a new class
389 * of dependents.
390 */
391 guint32
dependent_type_register(GnmDependentClass const * klass)392 dependent_type_register (GnmDependentClass const *klass)
393 {
394 guint32 res;
395
396 g_return_val_if_fail (dep_classes != NULL, 0);
397
398 g_ptr_array_add (dep_classes, (gpointer)klass);
399 res = dep_classes->len-1;
400
401 g_return_val_if_fail (res <= DEPENDENT_TYPE_MASK, res);
402
403 return res;
404 }
405
406 /*
407 * dependent_flag_recalc:
408 * @dep: the dependent that contains the expression needing recomputation.
409 *
410 * Marks @dep as needing recalculation
411 * NOTE : it does NOT recursively dirty dependencies.
412 */
413 #define dependent_flag_recalc(dep) \
414 do { (dep)->flags |= DEPENDENT_NEEDS_RECALC; } while (0)
415
416 /**
417 * dependent_changed:
418 * @cell: the dependent that changed
419 *
420 * Queues a recalc.
421 */
422 static void
dependent_changed(GnmDependent * dep)423 dependent_changed (GnmDependent *dep)
424 {
425 if (dep->sheet &&
426 dep->sheet->workbook->recursive_dirty_enabled)
427 dependent_queue_recalc (dep);
428 else
429 dependent_flag_recalc (dep);
430 }
431
432 /**
433 * dependent_set_expr:
434 * @dep: The dependent we are interested in.
435 * @new_texpr: new expression.
436 *
437 * When the expression associated with @dep needs to change this routine
438 * dispatches to the virtual handler, unlinking @dep if necessary beforehand.
439 * Adds a ref to @new_expr.
440 *
441 * NOTE : it does NOT relink dependents in case they are going to move later.
442 **/
443 void
dependent_set_expr(GnmDependent * dep,GnmExprTop const * new_texpr)444 dependent_set_expr (GnmDependent *dep, GnmExprTop const *new_texpr)
445 {
446 int const t = dependent_type (dep);
447 GnmDependentClass *klass = g_ptr_array_index (dep_classes, t);
448
449 if (dependent_is_linked (dep))
450 dependent_unlink (dep);
451 if (dep->flags & DEPENDENT_HAS_DYNAMIC_DEPS)
452 dependent_clear_dynamic_deps (dep);
453
454 if (klass->set_expr)
455 klass->set_expr (dep, new_texpr);
456 else {
457 if (new_texpr)
458 gnm_expr_top_ref (new_texpr);
459 if (dep->texpr)
460 gnm_expr_top_unref (dep->texpr);
461 dep->texpr = new_texpr;
462 if (new_texpr)
463 dependent_changed (dep);
464 }
465 }
466
467 gboolean
dependent_has_pos(GnmDependent const * dep)468 dependent_has_pos (GnmDependent const *dep)
469 {
470 int const t = dependent_type (dep);
471 GnmDependentClass *klass = g_ptr_array_index (dep_classes, t);
472 return klass->pos != NULL;
473 }
474
475 GnmCellPos const *
dependent_pos(GnmDependent const * dep)476 dependent_pos (GnmDependent const *dep)
477 {
478 static GnmCellPos const dummy = { 0, 0 };
479 int const t = dependent_type (dep);
480 GnmDependentClass *klass = g_ptr_array_index (dep_classes, t);
481
482 return klass->pos ? klass->pos (dep) : &dummy;
483 }
484
485 void
dependent_move(GnmDependent * dep,int dx,int dy)486 dependent_move (GnmDependent *dep, int dx, int dy)
487 {
488 int const t = dependent_type (dep);
489 GnmDependentClass *klass = g_ptr_array_index (dep_classes, t);
490 GnmCellPos *pos;
491
492 g_return_if_fail (klass->pos != NULL);
493 pos = klass->pos (dep);
494 /* Need a virtual for this? */
495 pos->col += dx;
496 pos->row += dy;
497 }
498
499
500 /**
501 * dependent_set_sheet:
502 * @dep:
503 * @sheet:
504 */
505 void
dependent_set_sheet(GnmDependent * dep,Sheet * sheet)506 dependent_set_sheet (GnmDependent *dep, Sheet *sheet)
507 {
508 g_return_if_fail (dep != NULL);
509 g_return_if_fail (dep->sheet == NULL);
510 g_return_if_fail (!dependent_is_linked (dep));
511
512 dep->sheet = sheet;
513 if (dep->texpr) {
514 dependent_link (dep);
515 dependent_changed (dep);
516 }
517 }
518
519 static void
cb_cell_list_deps(GnmDependent * dep,gpointer user)520 cb_cell_list_deps (GnmDependent *dep, gpointer user)
521 {
522 GSList **list = (GSList **)user;
523 *list = g_slist_prepend (*list, dep);
524 }
525
526 static GSList *
cell_list_deps(GnmCell const * cell)527 cell_list_deps (GnmCell const *cell)
528 {
529 GSList *deps = NULL;
530 cell_foreach_dep (cell, cb_cell_list_deps, &deps);
531 return deps;
532 }
533
534 static void
dependent_queue_recalc_main(GSList * work)535 dependent_queue_recalc_main (GSList *work)
536 {
537 /*
538 * Work is now a list of marked cells whose dependencies need
539 * to be marked. Marking early guarentees that we will not
540 * get duplicates. (And it thus limits the length of the list.)
541 * We treat work as a stack.
542 */
543
544 while (work) {
545 GnmDependent *dep = work->data;
546 int const t = dependent_type (dep);
547 GnmDependentClass *klass = g_ptr_array_index (dep_classes, t);
548
549 /* Pop the top element. */
550 work = g_slist_delete_link (work, work);
551
552 if (klass->changed) {
553 GSList *extra = klass->changed (dep);
554 if (extra) {
555 g_slist_last (extra)->next = work;
556 work = extra;
557 }
558 }
559 }
560 }
561
562
563 /**
564 * dependent_queue_recalc_list:
565 * @list:
566 *
567 * Queues any elements of @list for recalc that are not already queued,
568 * and marks all elements as needing a recalc.
569 */
570 static void
dependent_queue_recalc_list(GSList * list)571 dependent_queue_recalc_list (GSList *list)
572 {
573 GSList *work = NULL;
574
575 for (; list != NULL ; list = list->next) {
576 GnmDependent *dep = list->data;
577 if (!dependent_needs_recalc (dep)) {
578 dependent_flag_recalc (dep);
579 work = g_slist_prepend (work, dep);
580 }
581 }
582
583 dependent_queue_recalc_main (work);
584 }
585
586 void
dependent_queue_recalc(GnmDependent * dep)587 dependent_queue_recalc (GnmDependent *dep)
588 {
589 g_return_if_fail (dep != NULL);
590
591 #ifdef DEBUG_EVALUATION
592 g_printerr ("/* QUEUE (%s) */\n", cell_name (GNM_DEP_TO_CELL (dep)));
593 #endif
594 if (!dependent_needs_recalc (dep)) {
595 GSList listrec;
596 listrec.next = NULL;
597 listrec.data = dep;
598 dependent_queue_recalc_list (&listrec);
599 }
600 }
601
602 /**************************************************************************/
603
604 typedef struct {
605 gint num_buckets;
606 gint num_elements;
607 union {
608 gpointer one;
609 gpointer *few;
610 CSet **many;
611 } u;
612 } MicroHash;
613
614 #define MICRO_HASH_MIN_SIZE 11
615 #define MICRO_HASH_MAX_SIZE 13845163
616
617 #define MICRO_HASH_hash(key) ((guint)GPOINTER_TO_UINT(key))
618
619 static void
micro_hash_many_to_few(MicroHash * hash_table)620 micro_hash_many_to_few (MicroHash *hash_table)
621 {
622 CSet **buckets = hash_table->u.many;
623 int nbuckets = hash_table->num_buckets;
624 int i = 0;
625
626 hash_table->u.few = NEW_FEW;
627
628 while (nbuckets-- > 0 ) {
629 gpointer datum;
630
631 CSET_FOREACH (buckets[nbuckets], datum, {
632 hash_table->u.few[i++] = datum;
633 });
634 cset_free (buckets[nbuckets]);
635 }
636
637 g_free (buckets);
638 }
639
640 static void
micro_hash_many_resize(MicroHash * hash_table,int new_nbuckets)641 micro_hash_many_resize (MicroHash *hash_table, int new_nbuckets)
642 {
643 CSet **buckets = hash_table->u.many;
644 int nbuckets = hash_table->num_buckets;
645 CSet **new_buckets = g_new0 (CSet *, new_nbuckets);
646
647 hash_table->u.many = new_buckets;
648 hash_table->num_buckets = new_nbuckets;
649
650 while (nbuckets-- > 0 ) {
651 gpointer datum;
652
653 CSET_FOREACH (buckets[nbuckets], datum, {
654 guint bucket = MICRO_HASH_hash (datum) % new_nbuckets;
655 cset_insert (&(new_buckets[bucket]), datum);
656 });
657 cset_free (buckets[nbuckets]);
658 }
659 g_free (buckets);
660
661 #if 0
662 {
663 int nonzero = 0;
664 int capacity = 0, totlen = 0;
665 int i;
666
667 for (i = 0; i < new_nbuckets; i++) {
668 CSet *cs = new_buckets[i];
669 if (cs) {
670 nonzero++;
671 while (cs) {
672 totlen += cs->count;
673 capacity += CSET_SEGMENT_SIZE;
674 cs = cs->next;
675 }
676 }
677 }
678
679 g_printerr ("resize %p: %d [%d %.1f %.0f%%]\n",
680 hash_table,
681 new_nbuckets,
682 hash_table->num_elements,
683 (double)totlen / nonzero,
684 100.0 * totlen / capacity);
685 }
686 #endif
687 }
688
689
690 static void
micro_hash_few_to_many(MicroHash * hash_table)691 micro_hash_few_to_many (MicroHash *hash_table)
692 {
693 int nbuckets = hash_table->num_buckets = MICRO_HASH_MIN_SIZE;
694 CSet **buckets = g_new0 (CSet *, nbuckets);
695 int i;
696
697 for (i = 0; i < hash_table->num_elements; i++) {
698 gpointer datum = hash_table->u.few[i];
699 guint bucket = MICRO_HASH_hash (datum) % nbuckets;
700 cset_insert (&(buckets[bucket]), datum);
701 }
702 FREE_FEW (hash_table->u.few);
703 hash_table->u.many = buckets;
704 }
705
706
707
708 static void
micro_hash_insert(MicroHash * hash_table,gpointer key)709 micro_hash_insert (MicroHash *hash_table, gpointer key)
710 {
711 int N = hash_table->num_elements;
712
713 g_return_if_fail (key != NULL);
714
715 if (N == 0) {
716 hash_table->u.one = key;
717 } else if (N == 1) {
718 gpointer key0 = hash_table->u.one;
719 if (key == key0)
720 return;
721 /* one --> few */
722 hash_table->u.few = NEW_FEW;
723 hash_table->u.few[0] = key0;
724 hash_table->u.few[1] = key;
725 memset (hash_table->u.few + 2, 0, (MICRO_HASH_FEW - 2) * sizeof (gpointer));
726 } else if (N <= MICRO_HASH_FEW) {
727 int i;
728
729 for (i = 0; i < N; i++)
730 if (hash_table->u.few[i] == key)
731 return;
732
733 if (N == MICRO_HASH_FEW) {
734 guint bucket;
735
736 micro_hash_few_to_many (hash_table);
737 bucket = MICRO_HASH_hash (key) % hash_table->num_buckets;
738 cset_insert (&(hash_table->u.many[bucket]), key);
739 } else
740 hash_table->u.few[N] = key;
741 } else {
742 int nbuckets = hash_table->num_buckets;
743 guint bucket = MICRO_HASH_hash (key) % nbuckets;
744 CSet **buckets = hash_table->u.many;
745
746 if (cset_insert_checked (&(buckets[bucket]), key))
747 return;
748
749 if (N > CSET_SEGMENT_SIZE * nbuckets &&
750 nbuckets < MICRO_HASH_MAX_SIZE) {
751 int new_nbuckets = g_spaced_primes_closest (N / (CSET_SEGMENT_SIZE / 2));
752 if (new_nbuckets > MICRO_HASH_MAX_SIZE)
753 new_nbuckets = MICRO_HASH_MAX_SIZE;
754 micro_hash_many_resize (hash_table, new_nbuckets);
755 }
756 }
757
758 hash_table->num_elements++;
759 }
760
761 static void
micro_hash_remove(MicroHash * hash_table,gpointer key)762 micro_hash_remove (MicroHash *hash_table, gpointer key)
763 {
764 int N = hash_table->num_elements;
765 guint bucket;
766
767 if (N == 0)
768 return;
769
770 if (N == 1) {
771 if (hash_table->u.one != key)
772 return;
773 hash_table->u.one = NULL;
774 hash_table->num_elements--;
775 return;
776 }
777
778 if (N <= MICRO_HASH_FEW) {
779 int i;
780
781 for (i = 0; i < N; i++)
782 if (hash_table->u.few[i] == key) {
783 hash_table->u.few[i] = hash_table->u.few[N - 1];
784 hash_table->num_elements--;
785 if (hash_table->num_elements > 1)
786 return;
787 /* few -> one */
788 key = hash_table->u.few[0];
789 FREE_FEW (hash_table->u.few);
790 hash_table->u.one = key;
791 return;
792 }
793 return;
794 }
795
796 bucket = MICRO_HASH_hash (key) % hash_table->num_buckets;
797 if (cset_remove (&(hash_table->u.many[bucket]), key)) {
798 hash_table->num_elements--;
799
800 if (hash_table->num_elements <= MICRO_HASH_FEW)
801 micro_hash_many_to_few (hash_table);
802 else {
803 /* Maybe resize? */
804 }
805 return;
806 }
807 }
808
809
810 static void
micro_hash_release(MicroHash * hash_table)811 micro_hash_release (MicroHash *hash_table)
812 {
813 int N = hash_table->num_elements;
814
815 if (N <= 1)
816 ; /* Nothing */
817 else if (N <= MICRO_HASH_FEW)
818 FREE_FEW (hash_table->u.few);
819 else {
820 guint i = hash_table->num_buckets;
821 while (i-- > 0)
822 cset_free (hash_table->u.many[i]);
823 g_free (hash_table->u.many);
824 }
825 hash_table->num_elements = 0;
826 hash_table->num_buckets = 1;
827 hash_table->u.one = NULL;
828 }
829
830 static void
micro_hash_init(MicroHash * hash_table,gpointer key)831 micro_hash_init (MicroHash *hash_table, gpointer key)
832 {
833 hash_table->num_elements = 1;
834 hash_table->u.one = key;
835 }
836
837 static inline gboolean
micro_hash_is_empty(MicroHash const * hash_table)838 micro_hash_is_empty (MicroHash const *hash_table)
839 {
840 return hash_table->num_elements == 0;
841 }
842
843 /*************************************************************************/
844
845 #define micro_hash_foreach_dep(dc, dep, code) do { \
846 guint i_ = dc.num_elements; \
847 if (i_ <= MICRO_HASH_FEW) { \
848 const gpointer *e_ = (i_ == 1) ? &dc.u.one : dc.u.few; \
849 while (i_-- > 0) { \
850 GnmDependent *dep = e_[i_]; \
851 code \
852 } \
853 } else { \
854 GnmDependent *dep; \
855 guint b_ = dc.num_buckets; \
856 while (b_-- > 0) \
857 CSET_FOREACH (dc.u.many[b_], dep, code); \
858 } \
859 } while (0)
860
861 /**************************************************************************
862 * Data structures for managing dependencies between objects.
863 *
864 * The DependencyRange hash needs to be improved. It is a huge
865 * performance hit when there are large numbers of range depends.
866 */
867
868 /*
869 * A DependencyRange defines a range of cells whose values
870 * are used by another objects in the spreadsheet.
871 *
872 * A change in those cells will trigger a recomputation on the
873 * cells listed in deps.
874 */
875 typedef struct {
876 MicroHash deps; /* Must be first */
877 GnmRange range;
878 } DependencyRange;
879
880 /*
881 * A DependencySingle stores a list of dependents that rely
882 * on the cell at @pos.
883 *
884 * A change in this cell will trigger a recomputation on the
885 * cells listed in deps.
886 */
887 typedef struct {
888 MicroHash deps; /* Must be first */
889 GnmCellPos pos;
890 } DependencySingle;
891
892 /* A utility type */
893 typedef struct {
894 MicroHash deps; /* Must be first */
895 } DependencyAny;
896
897 static guint
deprange_hash(DependencyRange const * r)898 deprange_hash (DependencyRange const *r)
899 {
900 guint a = r->range.start.row;
901 guint b = r->range.end.row;
902 guint c = r->range.start.col;
903 guint d = r->range.end.col;
904
905 return (((((a << 8) + b) << 8) + c) << 8) + d;
906 }
907
908 static gint
deprange_equal(DependencyRange const * r1,DependencyRange const * r2)909 deprange_equal (DependencyRange const *r1, DependencyRange const *r2)
910 {
911 return range_equal (&(r1->range), &(r2->range));
912 }
913
914 static guint
depsingle_hash(DependencySingle const * depsingle)915 depsingle_hash (DependencySingle const *depsingle)
916 {
917 return (depsingle->pos.row << 8) ^ depsingle->pos.col;
918 }
919
920 static gint
depsingle_equal(DependencySingle const * a,DependencySingle const * b)921 depsingle_equal (DependencySingle const *a, DependencySingle const *b)
922 {
923 return (a->pos.row == b->pos.row && a->pos.col == b->pos.col);
924 }
925
926 static GnmDependentFlags
link_single_dep(GnmDependent * dep,GnmCellPos const * pos,GnmCellRef const * ref)927 link_single_dep (GnmDependent *dep, GnmCellPos const *pos, GnmCellRef const *ref)
928 {
929 DependencySingle lookup;
930 DependencySingle *single;
931 GnmDependentFlags flag = DEPENDENT_NO_FLAG;
932 Sheet const *sheet = eval_sheet (ref->sheet, dep->sheet);
933 GnmDepContainer *deps = sheet->deps;
934
935 if (sheet != dep->sheet)
936 flag = (sheet->workbook != dep->sheet->workbook)
937 ? DEPENDENT_GOES_INTERBOOK
938 : DEPENDENT_GOES_INTERSHEET;
939
940 /* Inserts if it is not already there */
941 gnm_cellpos_init_cellref (&lookup.pos, ref, pos, sheet);
942 single = g_hash_table_lookup (deps->single_hash, &lookup);
943 if (single == NULL) {
944 single = go_mem_chunk_alloc (deps->single_pool);
945 *single = lookup;
946 micro_hash_init (&single->deps, dep);
947 g_hash_table_insert (deps->single_hash, single, single);
948 } else
949 micro_hash_insert (&single->deps, dep);
950
951 return flag;
952 }
953
954 static GnmDependentFlags
unlink_single_dep(GnmDependent * dep,GnmCellPos const * pos,GnmCellRef const * a)955 unlink_single_dep (GnmDependent *dep, GnmCellPos const *pos, GnmCellRef const *a)
956 {
957 DependencySingle lookup;
958 DependencySingle *single;
959 GnmDependentFlags flag = DEPENDENT_NO_FLAG;
960 Sheet const *sheet = eval_sheet (a->sheet, dep->sheet);
961 GnmDepContainer *deps = sheet->deps;
962
963 if (sheet != dep->sheet)
964 flag = (sheet->workbook != dep->sheet->workbook)
965 ? DEPENDENT_GOES_INTERBOOK
966 : DEPENDENT_GOES_INTERSHEET;
967 if (!deps)
968 return flag;
969
970 gnm_cellpos_init_cellref (&lookup.pos, a, pos, sheet);
971 single = g_hash_table_lookup (deps->single_hash, &lookup);
972 if (single != NULL) {
973 micro_hash_remove (&single->deps, dep);
974 if (micro_hash_is_empty (&single->deps)) {
975 g_hash_table_remove (deps->single_hash, single);
976 micro_hash_release (&single->deps);
977 go_mem_chunk_free (deps->single_pool, single);
978 }
979 }
980
981 return flag;
982 }
983
984 static inline GnmDependentFlags
link_unlink_single_dep(GnmDependent * dep,GnmCellPos const * pos,GnmCellRef const * a,DepLinkFlags flags)985 link_unlink_single_dep (GnmDependent *dep, GnmCellPos const *pos,
986 GnmCellRef const *a, DepLinkFlags flags)
987 {
988 return (flags & DEP_LINK_LINK)
989 ? link_single_dep (dep, pos, a)
990 : unlink_single_dep (dep, pos, a);
991 }
992
993
994 static void
link_range_dep(GnmDepContainer * deps,GnmDependent * dep,GnmRange const * r)995 link_range_dep (GnmDepContainer *deps, GnmDependent *dep,
996 GnmRange const *r)
997 {
998 int i = bucket_of_row (r->start.row);
999 int end = bucket_of_row (r->end.row);
1000 DependencyRange dr;
1001 int next_start;
1002
1003 dr.range = *r;
1004
1005 /*
1006 * It is possible to see ranges bigger than the sheet when
1007 * operating with 3D ranges. See bug #704109.
1008 */
1009 end = MIN (end, deps->buckets - 1);
1010
1011 if (i > end)
1012 return;
1013
1014 next_start = bucket_start_row (i);
1015 for ( ; i <= end; i++) {
1016 DependencyRange *result;
1017 int start = next_start;
1018 next_start = bucket_start_row (i + 1);
1019
1020 /* Restrict range to bucket. */
1021 dr.range.start.row = MAX (r->start.row, start);
1022 dr.range.end.row = MIN (r->end.row, next_start - 1);
1023
1024 if (deps->range_hash[i] == NULL)
1025 deps->range_hash[i] = g_hash_table_new (
1026 (GHashFunc) deprange_hash,
1027 (GEqualFunc) deprange_equal);
1028 else {
1029 result = g_hash_table_lookup (deps->range_hash[i], &dr);
1030 if (result) {
1031 /* Inserts if it is not already there */
1032 micro_hash_insert (&result->deps, dep);
1033 continue;
1034 }
1035 }
1036
1037 /* Create a new DependencyRange structure */
1038 result = go_mem_chunk_alloc (deps->range_pool);
1039 *result = dr;
1040 micro_hash_init (&result->deps, dep);
1041 g_hash_table_insert (deps->range_hash[i], result, result);
1042 }
1043 }
1044
1045 static void
unlink_range_dep(GnmDepContainer * deps,GnmDependent * dep,GnmRange const * r)1046 unlink_range_dep (GnmDepContainer *deps, GnmDependent *dep,
1047 GnmRange const *r)
1048 {
1049 int i = bucket_of_row (r->start.row);
1050 int end = bucket_of_row (r->end.row);
1051 DependencyRange dr;
1052 int next_start;
1053
1054 if (!deps)
1055 return;
1056 dr.range = *r;
1057
1058 end = MIN (end, deps->buckets - 1);
1059
1060 if (i > end)
1061 return;
1062
1063 next_start = bucket_start_row (i);
1064 for ( ; i <= end; i++) {
1065 DependencyRange *result;
1066 int start = next_start;
1067 next_start = bucket_start_row (i + 1);
1068
1069 /* Restrict range to bucket. */
1070 dr.range.start.row = MAX (r->start.row, start);
1071 dr.range.end.row = MIN (r->end.row, next_start - 1);
1072
1073 result = g_hash_table_lookup (deps->range_hash[i], &dr);
1074 if (result) {
1075 micro_hash_remove (&result->deps, dep);
1076 if (micro_hash_is_empty (&result->deps)) {
1077 g_hash_table_remove (deps->range_hash[i], result);
1078 micro_hash_release (&result->deps);
1079 go_mem_chunk_free (deps->range_pool, result);
1080 }
1081 }
1082 }
1083 }
1084
1085 static inline void
link_unlink_range_dep(GnmDepContainer * deps,GnmDependent * dep,GnmRange const * r,DepLinkFlags flags)1086 link_unlink_range_dep (GnmDepContainer *deps, GnmDependent *dep,
1087 GnmRange const *r, DepLinkFlags flags)
1088 {
1089 if (flags & DEP_LINK_LINK)
1090 link_range_dep (deps, dep, r);
1091 else
1092 unlink_range_dep (deps, dep, r);
1093 }
1094
1095 static GnmDependentFlags
link_unlink_cellrange_dep(GnmDependent * dep,GnmCellPos const * pos,GnmCellRef const * a,GnmCellRef const * b,DepLinkFlags flags)1096 link_unlink_cellrange_dep (GnmDependent *dep, GnmCellPos const *pos,
1097 GnmCellRef const *a, GnmCellRef const *b,
1098 DepLinkFlags flags)
1099 {
1100 GnmRange range;
1101 GnmDependentFlags flag = DEPENDENT_NO_FLAG;
1102
1103 gnm_cellpos_init_cellref (&range.start, a, pos, dep->sheet);
1104 gnm_cellpos_init_cellref (&range.end, b, pos, dep->sheet);
1105 range_normalize (&range);
1106
1107 if (a->sheet != NULL) {
1108 if (a->sheet != dep->sheet)
1109 flag = (a->sheet->workbook != dep->sheet->workbook)
1110 ? DEPENDENT_GOES_INTERBOOK : DEPENDENT_GOES_INTERSHEET;
1111
1112 if (b->sheet != NULL && a->sheet != b->sheet) {
1113 Workbook const *wb = a->sheet->workbook;
1114 int i = a->sheet->index_in_wb;
1115 int stop = b->sheet->index_in_wb;
1116 if (i > stop) { int tmp = i; i = stop ; stop = tmp; }
1117
1118 g_return_val_if_fail (b->sheet->workbook == wb, flag);
1119
1120 while (i <= stop) {
1121 Sheet *sheet = g_ptr_array_index (wb->sheets, i);
1122 i++;
1123 link_unlink_range_dep (sheet->deps, dep, &range,
1124 flags);
1125 }
1126 flag |= DEPENDENT_HAS_3D;
1127 } else
1128 link_unlink_range_dep (a->sheet->deps, dep, &range,
1129 flags);
1130 } else
1131 link_unlink_range_dep (dep->sheet->deps, dep, &range, flags);
1132
1133 return flag;
1134 }
1135
1136 static GnmDependentFlags
1137 link_unlink_expr_dep (GnmEvalPos *ep, GnmExpr const *tree, DepLinkFlags flags);
1138
1139 static GnmDependentFlags
link_unlink_constant(GnmEvalPos * ep,GnmValue const * v,DepLinkFlags flags)1140 link_unlink_constant (GnmEvalPos *ep, GnmValue const *v, DepLinkFlags flags)
1141 {
1142 GnmRange r;
1143 Sheet *start_sheet, *end_sheet;
1144 int col, row;
1145 gboolean found = FALSE;
1146 GnmCellRef cr;
1147
1148 if (!VALUE_IS_CELLRANGE (v))
1149 return DEPENDENT_NO_FLAG;
1150
1151 if (!dependent_is_cell (ep->dep))
1152 goto everything; // Must figure out semantics first
1153
1154 if (flags & DEP_LINK_NON_SCALAR)
1155 goto everything;
1156
1157 if (eval_pos_is_array_context (ep))
1158 goto everything; // Potential implicit iteration -- bail
1159
1160 // Inspiration from value_intersection:
1161
1162 gnm_rangeref_normalize (&v->v_range.cell, ep, &start_sheet, &end_sheet, &r);
1163 if (!(start_sheet == end_sheet || end_sheet == NULL))
1164 return DEPENDENT_NO_FLAG; // An error
1165
1166 col = ep->eval.col;
1167 row = ep->eval.row;
1168
1169 if (range_is_singleton (&r)) {
1170 col = r.start.col;
1171 row = r.start.row;
1172 found = TRUE;
1173 } else if (r.start.row == r.end.row &&
1174 r.start.col <= col && col <= r.end.col) {
1175 row = r.start.row;
1176 found = TRUE;
1177 } else if (r.start.col == r.end.col &&
1178 r.start.row <= row && row <= r.end.row) {
1179 col = r.start.col;
1180 found = TRUE;
1181 }
1182 if (!found)
1183 goto everything;
1184
1185 cr.col_relative = cr.row_relative = FALSE;
1186 cr.sheet = ep->dep->sheet;
1187 cr.col = col;
1188 cr.row = row;
1189
1190 return link_unlink_single_dep (ep->dep, dependent_pos (ep->dep),
1191 &cr, flags);
1192
1193 everything:
1194 return link_unlink_cellrange_dep
1195 (ep->dep, dependent_pos (ep->dep),
1196 &v->v_range.cell.a,
1197 &v->v_range.cell.b,
1198 flags);
1199 }
1200
1201 static GnmDependentFlags
link_unlink_funcall(GnmEvalPos * ep,GnmExprFunction const * call,DepLinkFlags flags)1202 link_unlink_funcall (GnmEvalPos *ep, GnmExprFunction const *call, DepLinkFlags flags)
1203 {
1204 int i;
1205 GnmFuncEvalInfo fei;
1206 GnmDependentFlags flag;
1207 DepLinkFlags pass = (flags & (DEP_LINK_LINK | DEP_LINK_UNLINK));
1208
1209 gnm_func_load_if_stub (call->func);
1210 fei.pos = ep;
1211 fei.func_call = call;
1212 flag = gnm_func_link_dep (call->func, &fei, (flags & DEP_LINK_LINK) != 0);
1213 if (flag & DEPENDENT_IGNORE_ARGS)
1214 return (flag & ~DEPENDENT_IGNORE_ARGS);
1215
1216 for (i = 0; i < call->argc; i++) {
1217 char t = gnm_func_get_arg_type (call->func, i);
1218 DepLinkFlags extra =
1219 (t == 'A' || t == 'r' || t == '?')
1220 ? DEP_LINK_NON_SCALAR
1221 : 0;
1222
1223 if (0)
1224 g_printerr ("%s, arg %d: %c\n",
1225 gnm_func_get_name (call->func, FALSE),
1226 i, t);
1227 flag |= link_unlink_expr_dep (ep, call->argv[i], pass | extra);
1228 }
1229 return flag;
1230 }
1231
1232
1233 static GnmDependentFlags
link_unlink_expr_dep(GnmEvalPos * ep,GnmExpr const * tree,DepLinkFlags flags)1234 link_unlink_expr_dep (GnmEvalPos *ep, GnmExpr const *tree, DepLinkFlags flags)
1235 {
1236 g_return_val_if_fail (tree != NULL, DEPENDENT_NO_FLAG);
1237
1238 switch (GNM_EXPR_GET_OPER (tree)) {
1239 case GNM_EXPR_OP_RANGE_CTOR:
1240 case GNM_EXPR_OP_INTERSECT:
1241 return (link_unlink_expr_dep (ep, tree->binary.value_a, flags) |
1242 link_unlink_expr_dep (ep, tree->binary.value_b, flags));
1243 case GNM_EXPR_OP_ANY_BINARY:
1244 // See comments in function_iterate_argument_values
1245 if (!eval_pos_is_array_context (ep))
1246 flags &= ~DEP_LINK_NON_SCALAR;
1247 return (link_unlink_expr_dep (ep, tree->binary.value_a, flags) |
1248 link_unlink_expr_dep (ep, tree->binary.value_b, flags));
1249 case GNM_EXPR_OP_ANY_UNARY:
1250 if (!eval_pos_is_array_context (ep))
1251 flags &= ~DEP_LINK_NON_SCALAR;
1252 return link_unlink_expr_dep (ep, tree->unary.value, flags);
1253 case GNM_EXPR_OP_CELLREF:
1254 return link_unlink_single_dep (ep->dep, dependent_pos (ep->dep), &tree->cellref.ref, flags);
1255
1256 case GNM_EXPR_OP_CONSTANT:
1257 return link_unlink_constant (ep, tree->constant.value, flags);
1258
1259 case GNM_EXPR_OP_FUNCALL:
1260 return link_unlink_funcall (ep, &tree->func, flags);
1261
1262 case GNM_EXPR_OP_NAME:
1263 if (flags & DEP_LINK_LINK)
1264 expr_name_add_dep (tree->name.name, ep->dep);
1265 else
1266 expr_name_remove_dep (tree->name.name, ep->dep);
1267 if (expr_name_is_active (tree->name.name))
1268 return link_unlink_expr_dep (ep, tree->name.name->texpr->expr, flags) | DEPENDENT_USES_NAME;
1269 return DEPENDENT_USES_NAME;
1270
1271 case GNM_EXPR_OP_ARRAY_ELEM: {
1272 /* Non-corner cells depend on the corner */
1273 GnmCellRef a;
1274 GnmCellPos const *pos = dependent_pos (ep->dep);
1275 // We cannot support array expressions unless we have
1276 // a position.
1277 g_return_val_if_fail (pos != NULL, DEPENDENT_NO_FLAG);
1278
1279 a.col_relative = a.row_relative = FALSE;
1280 a.sheet = ep->dep->sheet;
1281 a.col = pos->col - tree->array_elem.x;
1282 a.row = pos->row - tree->array_elem.y;
1283
1284 return link_unlink_single_dep (ep->dep, pos, &a, flags);
1285 }
1286
1287 case GNM_EXPR_OP_ARRAY_CORNER: {
1288 // It's mildly unclean to do this here. We need the texpr, so get the cell.
1289 GnmCellPos const *cpos = dependent_pos (ep->dep);
1290 GnmCell const *cell = sheet_cell_get (ep->dep->sheet, cpos->col, cpos->row);
1291 GnmEvalPos pos = *ep;
1292 pos.array_texpr = cell->base.texpr;
1293 /* Corner cell depends on the contents of the expr */
1294 return link_unlink_expr_dep (&pos, tree->array_corner.expr,
1295 flags | DEP_LINK_NON_SCALAR);
1296 }
1297
1298 case GNM_EXPR_OP_SET: {
1299 int i;
1300 GnmDependentFlags res = DEPENDENT_NO_FLAG;
1301
1302 // gnm_expr_eval ignores arguments unless non-scalar
1303 if (flags & DEP_LINK_NON_SCALAR) {
1304 for (i = 0; i < tree->set.argc; i++)
1305 res |= link_unlink_expr_dep (ep, tree->set.argv[i], flags);
1306 }
1307
1308 return res;
1309 }
1310 #ifndef DEBUG_SWITCH_ENUM
1311 default:
1312 g_assert_not_reached ();
1313 #endif
1314 }
1315 return DEPENDENT_NO_FLAG;
1316 }
1317
1318 static void
workbook_link_3d_dep(GnmDependent * dep)1319 workbook_link_3d_dep (GnmDependent *dep)
1320 {
1321 Workbook *wb = dep->sheet->workbook;
1322
1323 if (wb->being_reordered)
1324 return;
1325 if (wb->sheet_order_dependents == NULL)
1326 wb->sheet_order_dependents =
1327 g_hash_table_new (g_direct_hash, g_direct_equal);
1328 g_hash_table_insert (wb->sheet_order_dependents, dep, dep);
1329 }
1330
1331 static void
workbook_unlink_3d_dep(GnmDependent * dep)1332 workbook_unlink_3d_dep (GnmDependent *dep)
1333 {
1334 Workbook *wb = dep->sheet->workbook;
1335
1336 /* during destruction */
1337 if (wb->sheet_order_dependents == NULL)
1338 return;
1339
1340 if (wb->being_reordered)
1341 return;
1342
1343 g_hash_table_remove (wb->sheet_order_dependents, dep);
1344 }
1345
1346 /*****************************************************************************/
1347
1348 static void
cell_dep_eval(GnmDependent * dep)1349 cell_dep_eval (GnmDependent *dep)
1350 {
1351 gboolean finished = gnm_cell_eval_content (GNM_DEP_TO_CELL (dep));
1352 (void)finished; /* We don't currently care */
1353 dep->flags &= ~GNM_CELL_HAS_NEW_EXPR;
1354 }
1355
1356 static void
cell_dep_set_expr(GnmDependent * dep,GnmExprTop const * new_texpr)1357 cell_dep_set_expr (GnmDependent *dep, GnmExprTop const *new_texpr)
1358 {
1359 /*
1360 * Explicitly do not check for array subdivision, we may be
1361 * replacing the corner of an array.
1362 */
1363 gnm_cell_set_expr_unsafe (GNM_DEP_TO_CELL (dep), new_texpr);
1364 }
1365
1366 static GSList *
cell_dep_changed(GnmDependent * dep)1367 cell_dep_changed (GnmDependent *dep)
1368 {
1369 /* When a cell changes, so do its dependents. */
1370
1371 GSList *deps = cell_list_deps (GNM_DEP_TO_CELL (dep));
1372 GSList *waste = NULL, *work = NULL;
1373 GSList *next, *list;
1374 for (list = deps; list != NULL ; list = next) {
1375 GnmDependent *dep = list->data;
1376 next = list->next;
1377 if (dependent_needs_recalc (dep)) {
1378 list->next = waste;
1379 waste = list;
1380 } else {
1381 dependent_flag_recalc (dep);
1382 list->next = work;
1383 work = list;
1384 }
1385 }
1386 g_slist_free (waste);
1387 return work;
1388 }
1389
1390 static GnmCellPos *
cell_dep_pos(GnmDependent const * dep)1391 cell_dep_pos (GnmDependent const *dep)
1392 {
1393 return &GNM_DEP_TO_CELL (dep)->pos;
1394 }
1395
1396 static void
cell_dep_debug_name(GnmDependent const * dep,GString * target)1397 cell_dep_debug_name (GnmDependent const *dep, GString *target)
1398 {
1399 g_string_append (target, cell_name (GNM_DEP_TO_CELL (dep)));
1400 }
1401
1402 /*****************************************************************************/
1403 /*
1404 * "Managed" dependent handling.
1405 *
1406 * A managed dependent is simply an expression set up to be maintained by
1407 * the dependency system so it follows row/column insert/delete nicely.
1408 *
1409 * The expression being managed is typically a cell range, but can be any
1410 * expression. There are two versions: only with a position and one without.
1411 * In the no-position version, everything is interpreted relative to A1 in the
1412 * sheet.
1413 */
1414
1415 void
dependent_managed_init(GnmDepManaged * dep,Sheet * sheet)1416 dependent_managed_init (GnmDepManaged *dep, Sheet *sheet)
1417 {
1418 memset (dep, 0, sizeof (*dep));
1419 dep->base.flags = DEPENDENT_MANAGED;
1420 dep->base.sheet = sheet;
1421 }
1422
1423 GnmExprTop const *
dependent_managed_get_expr(GnmDepManaged const * dep)1424 dependent_managed_get_expr (GnmDepManaged const *dep)
1425 {
1426 g_return_val_if_fail (dep != NULL, NULL);
1427 return dep->base.texpr;
1428 }
1429
1430 void
dependent_managed_set_expr(GnmDepManaged * dep,GnmExprTop const * texpr)1431 dependent_managed_set_expr (GnmDepManaged *dep, GnmExprTop const *texpr)
1432 {
1433 g_return_if_fail (dep != NULL);
1434 #if 0
1435 /* We need some kind of IS_A */
1436 g_return_if_fail (dependent_type (dep) == DEPENDENT_MANAGED);
1437 #endif
1438
1439 dependent_set_expr (&dep->base, texpr);
1440 if (texpr && dep->base.sheet)
1441 dependent_link (&dep->base);
1442 }
1443
1444 void
dependent_managed_set_sheet(GnmDepManaged * dep,Sheet * sheet)1445 dependent_managed_set_sheet (GnmDepManaged *dep, Sheet *sheet)
1446 {
1447 GnmExprTop const *texpr;
1448
1449 g_return_if_fail (dep != NULL);
1450 #if 0
1451 /* We need some kind of IS_A */
1452 g_return_if_fail (dependent_type (dep) == DEPENDENT_MANAGED);
1453 #endif
1454
1455 if (dep->base.sheet == sheet)
1456 return;
1457
1458 texpr = dep->base.texpr;
1459 if (texpr) gnm_expr_top_ref (texpr);
1460 dependent_set_expr (&dep->base, NULL);
1461 /* We're now unlinked from everything. */
1462 dep->base.sheet = sheet;
1463 dependent_managed_set_expr (dep, texpr);
1464 if (texpr) gnm_expr_top_unref (texpr);
1465 }
1466
1467 static void
managed_dep_debug_name(GnmDependent const * dep,GString * target)1468 managed_dep_debug_name (GnmDependent const *dep, GString *target)
1469 {
1470 g_string_append_printf (target, "Managed%p", (void *)dep);
1471 }
1472
1473 /*****************************************************************************/
1474
1475 static void
name_dep_debug_name(GnmDependent const * dep,GString * target)1476 name_dep_debug_name (GnmDependent const *dep, GString *target)
1477 {
1478 g_string_append_printf (target, "Name%p", (void *)dep);
1479 }
1480
1481 /*****************************************************************************/
1482
1483 static GSList *
dynamic_dep_changed(GnmDependent * dep)1484 dynamic_dep_changed (GnmDependent *dep)
1485 {
1486 DynamicDep const *dyn = (DynamicDep *)dep;
1487
1488 /* When a dynamic dependent changes, we mark its container. */
1489
1490 if (dependent_needs_recalc (dyn->container))
1491 return NULL;
1492
1493 dependent_flag_recalc (dyn->container);
1494 return g_slist_prepend (NULL, dyn->container);
1495 }
1496
1497 static void
dynamic_dep_debug_name(GnmDependent const * dep,GString * target)1498 dynamic_dep_debug_name (GnmDependent const *dep, GString *target)
1499 {
1500 g_string_append_printf (target, "DynamicDep%p", (void *)dep);
1501 }
1502
1503 void
dependent_add_dynamic_dep(GnmDependent * dep,GnmRangeRef const * rr)1504 dependent_add_dynamic_dep (GnmDependent *dep, GnmRangeRef const *rr)
1505 {
1506 GnmDependentFlags flags;
1507 DynamicDep *dyn;
1508 GnmCellPos const *pos;
1509 DependencyRange range;
1510
1511 g_return_if_fail (dep != NULL);
1512
1513 pos = dependent_pos (dep);
1514
1515 if (dep->flags & DEPENDENT_HAS_DYNAMIC_DEPS)
1516 dyn = g_hash_table_lookup (dep->sheet->deps->dynamic_deps, dep);
1517 else {
1518 dep->flags |= DEPENDENT_HAS_DYNAMIC_DEPS;
1519 dyn = g_new (DynamicDep, 1);
1520 dyn->base.flags = DEPENDENT_DYNAMIC_DEP;
1521 dyn->base.sheet = dep->sheet;
1522 dyn->base.texpr = NULL;
1523 dyn->container = dep;
1524 dyn->ranges = NULL;
1525 dyn->singles = NULL;
1526 g_hash_table_insert (dep->sheet->deps->dynamic_deps, dep, dyn);
1527 }
1528
1529 gnm_cellpos_init_cellref (&range.range.start, &rr->a, pos, dep->sheet);
1530 gnm_cellpos_init_cellref (&range.range.end, &rr->b, pos, dep->sheet);
1531 if (range_is_singleton (&range.range)) {
1532 flags = link_single_dep (&dyn->base, pos, &rr->a);
1533 dyn->singles = g_slist_prepend (dyn->singles, gnm_rangeref_dup (rr));
1534 } else {
1535 flags = link_unlink_cellrange_dep (&dyn->base, pos, &rr->a, &rr->b, DEP_LINK_LINK);
1536 dyn->ranges = g_slist_prepend (dyn->ranges, gnm_rangeref_dup (rr));
1537 }
1538 if (flags & DEPENDENT_HAS_3D)
1539 workbook_link_3d_dep (dep);
1540 }
1541
1542 static void
dependent_clear_dynamic_deps(GnmDependent * dep)1543 dependent_clear_dynamic_deps (GnmDependent *dep)
1544 {
1545 g_hash_table_remove (dep->sheet->deps->dynamic_deps, dep);
1546 }
1547
1548 /*****************************************************************************/
1549
1550 /**
1551 * dependent_link:
1552 * @dep: the dependent that changed
1553 *
1554 * Adds the dependent to the workbook wide list of dependents.
1555 */
1556 void
dependent_link(GnmDependent * dep)1557 dependent_link (GnmDependent *dep)
1558 {
1559 Sheet *sheet;
1560 GnmEvalPos ep;
1561
1562 g_return_if_fail (dep != NULL);
1563 g_return_if_fail (dep->texpr != NULL);
1564 g_return_if_fail (!(dep->flags & DEPENDENT_IS_LINKED));
1565 g_return_if_fail (IS_SHEET (dep->sheet));
1566 g_return_if_fail (dep->sheet->deps != NULL);
1567
1568 sheet = dep->sheet;
1569
1570 /* Make this the new tail of the dependent list. */
1571 dep->prev_dep = sheet->deps->tail;
1572 dep->next_dep = NULL;
1573 if (dep->prev_dep)
1574 dep->prev_dep->next_dep = dep;
1575 else
1576 sheet->deps->head = dep; /* first element */
1577 sheet->deps->tail = dep;
1578 dep->flags |= DEPENDENT_IS_LINKED |
1579 link_unlink_expr_dep (eval_pos_init_dep (&ep, dep),
1580 dep->texpr->expr, DEP_LINK_LINK);
1581
1582 if (dep->flags & DEPENDENT_HAS_3D)
1583 workbook_link_3d_dep (dep);
1584 }
1585
1586 /**
1587 * dependent_unlink:
1588 * @dep: the dependent that changed
1589 *
1590 * Removes the dependent from its container's set of dependents and always
1591 * removes the linkages to what it depends on.
1592 */
1593 void
dependent_unlink(GnmDependent * dep)1594 dependent_unlink (GnmDependent *dep)
1595 {
1596 GnmDepContainer *contain;
1597 GnmEvalPos ep;
1598
1599 g_return_if_fail (dep != NULL);
1600 g_return_if_fail (dependent_is_linked (dep));
1601 g_return_if_fail (dep->texpr != NULL);
1602 g_return_if_fail (IS_SHEET (dep->sheet));
1603
1604 link_unlink_expr_dep (eval_pos_init_dep (&ep, dep),
1605 dep->texpr->expr, DEP_LINK_UNLINK);
1606 contain = dep->sheet->deps;
1607 if (contain != NULL) {
1608 if (contain->head == dep)
1609 contain->head = dep->next_dep;
1610 if (contain->tail == dep)
1611 contain->tail = dep->prev_dep;
1612 if (dep->next_dep)
1613 dep->next_dep->prev_dep = dep->prev_dep;
1614 if (dep->prev_dep)
1615 dep->prev_dep->next_dep = dep->next_dep;
1616
1617 if (dep->flags & DEPENDENT_HAS_DYNAMIC_DEPS)
1618 dependent_clear_dynamic_deps (dep);
1619 }
1620
1621 if (dep->flags & DEPENDENT_HAS_3D)
1622 workbook_unlink_3d_dep (dep);
1623 dep->flags &= ~DEPENDENT_LINK_FLAGS;
1624 }
1625
1626 /**
1627 * gnm_cell_eval_content:
1628 * @cell: the cell to evaluate.
1629 *
1630 * This function evaluates the contents of the cell,
1631 * it should not be used by anyone. It is an internal
1632 * function.
1633 **/
1634 static gboolean
gnm_cell_eval_content(GnmCell * cell)1635 gnm_cell_eval_content (GnmCell *cell)
1636 {
1637 static GnmCell *iterating = NULL;
1638 GnmValue *v;
1639 GnmEvalPos pos;
1640 int max_iteration;
1641
1642 if (!gnm_cell_has_expr (cell) || /* plain cells without expr */
1643 !dependent_is_linked (&cell->base)) /* special case within TABLE */
1644 return TRUE;
1645
1646 #ifdef DEBUG_EVALUATION
1647 {
1648 GnmParsePos pp;
1649 char *str = gnm_expr_top_as_string
1650 (cell->base.texpr,
1651 parse_pos_init_cell (&pp, cell),
1652 sheet_get_conventions (cell->base.sheet));
1653 g_printerr ("{\nEvaluating %s!%s: %s;\n",
1654 cell->base.sheet->name_quoted, cell_name (cell),
1655 str);
1656 g_free (str);
1657 }
1658 #endif
1659
1660 /* This is the bottom of a cycle */
1661 if (cell->base.flags & DEPENDENT_BEING_CALCULATED) {
1662 if (!cell->base.sheet->workbook->iteration.enabled)
1663 return TRUE;
1664
1665 /* but not the first bottom */
1666 if (cell->base.flags & DEPENDENT_BEING_ITERATED) {
1667 #ifdef DEBUG_EVALUATION
1668 g_printerr ("}; /* already-iterate (%d) */\n", iterating == NULL);
1669 #endif
1670 return iterating == NULL;
1671 }
1672
1673 /* if we are still marked as iterating then make this the last
1674 * time through.
1675 */
1676 if (iterating == cell) {
1677 #ifdef DEBUG_EVALUATION
1678 puts ("}; /* NO-iterate (1) */");
1679 #endif
1680 return TRUE;
1681 } else if (iterating == NULL) {
1682 cell->base.flags |= DEPENDENT_BEING_ITERATED;
1683 iterating = cell;
1684 #ifdef DEBUG_EVALUATION
1685 puts ("}; /* START iterate = TRUE (0) */");
1686 #endif
1687 return FALSE;
1688 } else {
1689 #ifdef DEBUG_EVALUATION
1690 puts ("}; /* other-iterate (0) */");
1691 #endif
1692 return FALSE;
1693 }
1694 }
1695
1696 /* Prepare to calculate */
1697 eval_pos_init_cell (&pos, cell);
1698 cell->base.flags |= DEPENDENT_BEING_CALCULATED;
1699
1700 /*
1701 * I'm somewhat afraid of thinking about the semantics of iteration
1702 * if different workbooks have different settings.
1703 */
1704 max_iteration = cell->base.sheet->workbook->iteration.max_number;
1705
1706 iterate:
1707 v = gnm_expr_top_eval (cell->base.texpr, &pos,
1708 GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
1709 if (v == NULL)
1710 v = value_new_error (&pos, "Internal error");
1711
1712 #ifdef DEBUG_EVALUATION
1713 {
1714 char *valtxt = v
1715 ? value_get_as_string (v)
1716 : g_strdup ("NULL");
1717 g_printerr ("Evaluation(%d) %s := %s\n",
1718 max_iteration, cell_name (cell), valtxt);
1719 g_free (valtxt);
1720 }
1721 #endif
1722
1723 /* The top of a cycle */
1724 if (cell->base.flags & DEPENDENT_BEING_ITERATED) {
1725 cell->base.flags &= ~DEPENDENT_BEING_ITERATED;
1726
1727 /* We just completed the last iteration, don't change things */
1728 if (iterating && max_iteration-- > 0) {
1729 /* If we are within bounds make this the last round */
1730 if (value_diff (cell->value, v) < cell->base.sheet->workbook->iteration.tolerance)
1731 max_iteration = 0;
1732 else {
1733 #ifdef DEBUG_EVALUATION
1734 puts ("/* iterate == NULL */");
1735 #endif
1736 iterating = NULL;
1737 }
1738 value_release (cell->value);
1739 cell->value = v;
1740
1741 gnm_cell_unrender (cell);
1742 #ifdef DEBUG_EVALUATION
1743 puts ("/* LOOP */");
1744 #endif
1745 goto iterate;
1746 }
1747 g_return_val_if_fail (iterating, TRUE);
1748 iterating = NULL;
1749 } else {
1750 gboolean had_value = (cell->value != NULL);
1751 if (had_value && value_equal (v, cell->value)) {
1752 /* Value didn't change. */
1753 value_release (v);
1754 } else {
1755 gboolean was_string = had_value && (VALUE_IS_STRING (cell->value) || VALUE_IS_ERROR (cell->value));
1756 gboolean is_string = VALUE_IS_STRING (v) || VALUE_IS_ERROR (v);
1757
1758 if ((was_string || is_string))
1759 sheet_cell_queue_respan (cell);
1760
1761 if (had_value)
1762 value_release (cell->value);
1763 cell->value = v;
1764
1765 gnm_cell_unrender (cell);
1766 }
1767 }
1768
1769 if (iterating == cell)
1770 iterating = NULL;
1771
1772 #ifdef DEBUG_EVALUATION
1773 g_printerr ("} (%d)\n", iterating == NULL);
1774 #endif
1775 cell->base.flags &= ~DEPENDENT_BEING_CALCULATED;
1776 return iterating == NULL;
1777 }
1778
1779 /**
1780 * dependent_eval:
1781 * @dep:
1782 */
1783 static void
dependent_eval(GnmDependent * dep)1784 dependent_eval (GnmDependent *dep)
1785 {
1786 int const t = dependent_type (dep);
1787 GnmDependentClass *klass = g_ptr_array_index (dep_classes, t);
1788
1789 if (dep->flags & DEPENDENT_HAS_DYNAMIC_DEPS) {
1790 dependent_clear_dynamic_deps (dep);
1791 dep->flags &= ~DEPENDENT_HAS_DYNAMIC_DEPS;
1792 }
1793
1794 /*
1795 * Problem: this really should be a tail call.
1796 */
1797 klass->eval (dep);
1798
1799 /* Don't clear flag until after in case we iterate */
1800 dep->flags &= ~DEPENDENT_NEEDS_RECALC;
1801 }
1802
1803 void
gnm_cell_eval(GnmCell * cell)1804 gnm_cell_eval (GnmCell *cell)
1805 {
1806 if (gnm_cell_needs_recalc (cell)) {
1807 /*
1808 * This should be a tail call so the stack frame can be
1809 * eliminated before the call.
1810 */
1811 dependent_eval (GNM_CELL_TO_DEP (cell));
1812 }
1813 }
1814
1815
1816 /**
1817 * cell_queue_recalc:
1818 * @cell:
1819 *
1820 * Queue the cell and everything that depends on it for recalculation.
1821 * If a dependency is already queued ignore it.
1822 */
1823 void
cell_queue_recalc(GnmCell * cell)1824 cell_queue_recalc (GnmCell *cell)
1825 {
1826 g_return_if_fail (cell != NULL);
1827
1828 if (!gnm_cell_needs_recalc (cell)) {
1829 GSList *deps;
1830
1831 if (gnm_cell_has_expr (cell))
1832 dependent_flag_recalc (GNM_CELL_TO_DEP (cell));
1833
1834 deps = cell_list_deps (cell);
1835 dependent_queue_recalc_list (deps);
1836 g_slist_free (deps);
1837 }
1838 }
1839
1840 static void
cell_foreach_range_dep(GnmCell const * cell,GnmDepFunc func,gpointer user)1841 cell_foreach_range_dep (GnmCell const *cell, GnmDepFunc func, gpointer user)
1842 {
1843 Sheet *sheet = cell->base.sheet;
1844 GHashTable *bucket =
1845 sheet->deps->range_hash[bucket_of_row (cell->pos.row)];
1846 GHashTableIter hiter;
1847 gpointer key;
1848
1849 if (!bucket)
1850 return;
1851
1852 g_hash_table_iter_init (&hiter, bucket);
1853 while (g_hash_table_iter_next (&hiter, &key, NULL)) {
1854 DependencyRange const *deprange = key;
1855 GnmRange const *range = &(deprange->range);
1856
1857 if (!range_contains (range, cell->pos.col, cell->pos.row))
1858 continue;
1859
1860 micro_hash_foreach_dep (deprange->deps, dep,
1861 func (dep, user););
1862 }
1863 }
1864
1865 static void
cell_foreach_single_dep(Sheet const * sheet,int col,int row,GnmDepFunc func,gpointer user)1866 cell_foreach_single_dep (Sheet const *sheet, int col, int row,
1867 GnmDepFunc func, gpointer user)
1868 {
1869 DependencySingle lookup, *single;
1870 GnmDepContainer *deps = sheet->deps;
1871
1872 lookup.pos.col = col;
1873 lookup.pos.row = row;
1874
1875 single = g_hash_table_lookup (deps->single_hash, &lookup);
1876 if (single != NULL)
1877 micro_hash_foreach_dep (single->deps, dep,
1878 (*func) (dep, user););
1879 }
1880
1881 /**
1882 * cell_foreach_dep:
1883 * @cell: #GnmCell
1884 * @func: (scope call):
1885 * @user: user data.
1886 *
1887 **/
1888 void
cell_foreach_dep(GnmCell const * cell,GnmDepFunc func,gpointer user)1889 cell_foreach_dep (GnmCell const *cell, GnmDepFunc func, gpointer user)
1890 {
1891 g_return_if_fail (cell != NULL);
1892
1893 /* accelerate exit */
1894 if (!cell->base.sheet->deps)
1895 return;
1896
1897 cell_foreach_range_dep (cell, func, user);
1898 cell_foreach_single_dep (cell->base.sheet, cell->pos.col, cell->pos.row,
1899 func, user);
1900 }
1901
1902 /**
1903 * sheet_region_queue_recalc:
1904 * @sheet: The sheet.
1905 * @range: (nullable): Range
1906 *
1907 * Queues things that depend on @sheet!@range for recalc.
1908 *
1909 * If @range is %NULL the entire sheet is used.
1910 */
1911 void
sheet_region_queue_recalc(Sheet const * sheet,GnmRange const * r)1912 sheet_region_queue_recalc (Sheet const *sheet, GnmRange const *r)
1913 {
1914 int i, sb, eb;
1915 GList *keys, *l;
1916
1917 g_return_if_fail (IS_SHEET (sheet));
1918 g_return_if_fail (sheet->deps != NULL);
1919
1920 sb = r ? bucket_of_row (r->start.row) : 0;
1921 eb = r ? bucket_of_row (r->end.row) : sheet->deps->buckets - 1;
1922
1923 /* mark the contained depends dirty non recursively */
1924 SHEET_FOREACH_DEPENDENT (sheet, dep, {
1925 GnmCell *cell = GNM_DEP_TO_CELL (dep);
1926 if (!r || (dependent_is_cell (dep) &&
1927 range_contains (r, cell->pos.col, cell->pos.row)))
1928 dependent_flag_recalc (dep);
1929 });
1930
1931 // Look for things that depend on target region
1932 // Note: we gather the keys first; we may change the hashes as we
1933 // queue deps.
1934 for (i = eb; i >= sb; i--) {
1935 GHashTable *hash = sheet->deps->range_hash[i];
1936 if (!hash) continue;
1937 keys = g_hash_table_get_keys (hash);
1938 for (l = keys; l; l = l->next) {
1939 DependencyRange const *dr = l->data;
1940 GSList *work = NULL;
1941
1942 if (r && !range_overlap (r, &dr->range))
1943 continue;
1944 micro_hash_foreach_dep (dr->deps, dep, {
1945 if (!dependent_needs_recalc (dep)) {
1946 dependent_flag_recalc (dep);
1947 work = g_slist_prepend (work, dep);
1948 }
1949 });
1950 dependent_queue_recalc_main (work);
1951 }
1952 g_list_free (keys);
1953 }
1954
1955 keys = g_hash_table_get_keys (sheet->deps->single_hash);
1956 for (l = keys; l; l = l->next) {
1957 DependencySingle const *ds = l->data;
1958 GSList *work = NULL;
1959 if (r && !range_contains (r, ds->pos.col, ds->pos.row))
1960 continue;
1961 micro_hash_foreach_dep (ds->deps, dep, {
1962 if (!dependent_needs_recalc (dep)) {
1963 dependent_flag_recalc (dep);
1964 work = g_slist_prepend (work, dep);
1965 }
1966 });
1967 dependent_queue_recalc_main (work);
1968 }
1969 g_list_free (keys);
1970 }
1971
1972 typedef struct
1973 {
1974 int dep_type;
1975 union {
1976 GnmParsePos pos;
1977 GnmDependent *dep;
1978 } u;
1979 GnmExprTop const *oldtree;
1980 } ExprRelocateStorage;
1981
1982 /**
1983 * dependents_unrelocate_free:
1984 * @info:
1985 *
1986 * Free the undo info associated with a dependent relocation.
1987 */
1988 static void
dependents_unrelocate_free(GSList * info)1989 dependents_unrelocate_free (GSList *info)
1990 {
1991 GSList *ptr = info;
1992 for (; ptr != NULL ; ptr = ptr->next) {
1993 ExprRelocateStorage *tmp = ptr->data;
1994 gnm_expr_top_unref (tmp->oldtree);
1995 g_free (tmp);
1996 }
1997 g_slist_free (info);
1998 }
1999
2000 /**
2001 * dependents_unrelocate:
2002 * @info:
2003 *
2004 * Apply the undo info associated with a dependent relocation.
2005 */
2006 static void
dependents_unrelocate(GSList * info)2007 dependents_unrelocate (GSList *info)
2008 {
2009 GSList *ptr = info;
2010 for (; ptr != NULL ; ptr = ptr->next) {
2011 ExprRelocateStorage *tmp = ptr->data;
2012
2013 if (tmp->dep_type == DEPENDENT_CELL) {
2014 if (!IS_SHEET (tmp->u.pos.sheet)) {
2015 /* FIXME : happens when undoing a move from a deleted
2016 * sheet. Do we want to do something here */
2017 } else {
2018 GnmCell *cell = sheet_cell_get
2019 (tmp->u.pos.sheet,
2020 tmp->u.pos.eval.col, tmp->u.pos.eval.row);
2021
2022 /* It is possible to have a NULL if the relocation info
2023 * is not really relevant. eg when undoing a pasted
2024 * cut that was relocated but also saved to a buffer */
2025 if (cell != NULL) {
2026 if (gnm_expr_top_is_array_corner (tmp->oldtree)) {
2027 int cols, rows;
2028
2029 gnm_expr_top_get_array_size (tmp->oldtree, &cols, &rows);
2030 gnm_cell_set_array_formula (tmp->u.pos.sheet,
2031 tmp->u.pos.eval.col,
2032 tmp->u.pos.eval.row,
2033 tmp->u.pos.eval.col + cols - 1,
2034 tmp->u.pos.eval.row + rows - 1,
2035 gnm_expr_top_new (gnm_expr_copy (gnm_expr_top_get_array_expr (tmp->oldtree))));
2036 cell_queue_recalc (cell);
2037 sheet_flag_status_update_cell (cell);
2038 } else
2039 sheet_cell_set_expr (cell, tmp->oldtree);
2040 }
2041 }
2042 } else if (tmp->dep_type == DEPENDENT_NAME) {
2043 } else {
2044 dependent_set_expr (tmp->u.dep, tmp->oldtree);
2045 dependent_flag_recalc (tmp->u.dep);
2046 dependent_link (tmp->u.dep);
2047 }
2048 }
2049 }
2050
2051 /**
2052 * dependents_link:
2053 * @deps: (element-type GnmDependent): An slist of dependents.
2054 *
2055 * link a list of dependents. (The list used to get freed, but is not
2056 * freed anymore.)
2057 */
2058 void
dependents_link(GSList * deps)2059 dependents_link (GSList *deps)
2060 {
2061 GSList *ptr = deps;
2062
2063 /* put them back */
2064 for (; ptr != NULL ; ptr = ptr->next) {
2065 GnmDependent *dep = ptr->data;
2066 if (dep->sheet->being_invalidated)
2067 continue;
2068 if (dep->sheet->deps != NULL && !dependent_is_linked (dep)) {
2069 dependent_link (dep);
2070 dependent_queue_recalc (dep);
2071 }
2072 }
2073 }
2074
2075 typedef struct {
2076 GnmRange const *target;
2077 GSList *list;
2078 } CollectClosure;
2079
2080 static void
cb_range_contained_collect(DependencyRange const * deprange,G_GNUC_UNUSED gpointer ignored,CollectClosure * user)2081 cb_range_contained_collect (DependencyRange const *deprange,
2082 G_GNUC_UNUSED gpointer ignored,
2083 CollectClosure *user)
2084 {
2085 GnmRange const *range = &deprange->range;
2086
2087 if (range_overlap (user->target, range))
2088 micro_hash_foreach_dep (deprange->deps, dep, {
2089 if (!(dep->flags & (DEPENDENT_FLAGGED | DEPENDENT_CAN_RELOCATE)) &&
2090 dependent_type (dep) != DEPENDENT_DYNAMIC_DEP) {
2091 dep->flags |= DEPENDENT_FLAGGED;
2092 user->list = g_slist_prepend (user->list, dep);
2093 }});
2094 }
2095
2096 static void
cb_single_contained_collect(DependencySingle const * depsingle,G_GNUC_UNUSED gpointer ignored,CollectClosure * user)2097 cb_single_contained_collect (DependencySingle const *depsingle,
2098 G_GNUC_UNUSED gpointer ignored,
2099 CollectClosure *user)
2100 {
2101 if (range_contains (user->target, depsingle->pos.col, depsingle->pos.row))
2102 micro_hash_foreach_dep (depsingle->deps, dep, {
2103 if (!(dep->flags & (DEPENDENT_FLAGGED | DEPENDENT_CAN_RELOCATE)) &&
2104 dependent_type (dep) != DEPENDENT_DYNAMIC_DEP) {
2105 dep->flags |= DEPENDENT_FLAGGED;
2106 user->list = g_slist_prepend (user->list, dep);
2107 }});
2108 }
2109
2110 struct cb_remote_names {
2111 GSList *names;
2112 Workbook *wb;
2113 };
2114
2115 static void
cb_remote_names1(G_GNUC_UNUSED gconstpointer key,GnmNamedExpr * nexpr,struct cb_remote_names * data)2116 cb_remote_names1 (G_GNUC_UNUSED gconstpointer key,
2117 GnmNamedExpr *nexpr,
2118 struct cb_remote_names *data)
2119 {
2120 data->names = g_slist_prepend (data->names, nexpr);
2121 }
2122
2123 static void
cb_remote_names2(GnmNamedExpr * nexpr,G_GNUC_UNUSED gpointer value,struct cb_remote_names * data)2124 cb_remote_names2 (GnmNamedExpr *nexpr,
2125 G_GNUC_UNUSED gpointer value,
2126 struct cb_remote_names *data)
2127 {
2128 Workbook *wb =
2129 nexpr->pos.sheet ? nexpr->pos.sheet->workbook : nexpr->pos.wb;
2130 if (wb != data->wb)
2131 data->names = g_slist_prepend (data->names, nexpr);
2132 }
2133
2134 /*
2135 * Get a list of all names that (may) reference data in a given sheet.
2136 * This is approximated as all names in the sheet, all global names in
2137 * its workbook, and all external references that actually mention the
2138 * sheet explicitly.
2139 */
2140 static GSList *
names_referencing_sheet(Sheet * sheet)2141 names_referencing_sheet (Sheet *sheet)
2142 {
2143 struct cb_remote_names data;
2144
2145 data.names = NULL;
2146 data.wb = sheet->workbook;
2147
2148 workbook_foreach_name (sheet->workbook, TRUE,
2149 (GHFunc)cb_remote_names1,
2150 &data);
2151 gnm_sheet_foreach_name (sheet, (GHFunc)cb_remote_names1, &data);
2152
2153 if (sheet->deps->referencing_names)
2154 g_hash_table_foreach (sheet->deps->referencing_names,
2155 (GHFunc)cb_remote_names2,
2156 &data);
2157
2158 return data.names;
2159 }
2160
2161
2162 /**
2163 * dependents_relocate:
2164 * @info: the descriptor record for what is being moved where.
2165 *
2166 * Fixes references to or from a region that is going to be moved.
2167 * Returns: (transfer full): a list of the locations and expressions that were changed outside of
2168 * the region.
2169 **/
2170 GOUndo *
dependents_relocate(GnmExprRelocateInfo const * rinfo)2171 dependents_relocate (GnmExprRelocateInfo const *rinfo)
2172 {
2173 GnmExprRelocateInfo local_rinfo;
2174 GSList *l, *dependents = NULL, *undo_info = NULL;
2175 Sheet *sheet;
2176 GnmRange const *r;
2177 int i;
2178 CollectClosure collect;
2179 GOUndo *u_exprs, *u_names;
2180
2181 g_return_val_if_fail (rinfo != NULL, NULL);
2182
2183 /* short circuit if nothing would move */
2184 if (rinfo->col_offset == 0 && rinfo->row_offset == 0 &&
2185 rinfo->origin_sheet == rinfo->target_sheet)
2186 return NULL;
2187
2188 sheet = rinfo->origin_sheet;
2189 r = &rinfo->origin;
2190
2191 /* collect contained cells with expressions */
2192 SHEET_FOREACH_DEPENDENT (rinfo->origin_sheet, dep, {
2193 GnmCell *cell = GNM_DEP_TO_CELL (dep);
2194 if (dependent_is_cell (dep) &&
2195 range_contains (r, cell->pos.col, cell->pos.row)) {
2196 dependents = g_slist_prepend (dependents, dep);
2197 dep->flags |= DEPENDENT_FLAGGED;
2198 }
2199 });
2200
2201 /* collect the things that depend on source region */
2202 collect.target = r;
2203 collect.list = dependents;
2204 g_hash_table_foreach (sheet->deps->single_hash,
2205 (GHFunc) &cb_single_contained_collect,
2206 (gpointer)&collect);
2207 {
2208 int const first = bucket_of_row (r->start.row);
2209 GHashTable *hash;
2210 for (i = bucket_of_row (r->end.row); i >= first ; i--) {
2211 hash = sheet->deps->range_hash[i];
2212 if (hash != NULL)
2213 g_hash_table_foreach (hash,
2214 (GHFunc) &cb_range_contained_collect,
2215 (gpointer)&collect);
2216 }
2217 }
2218 dependents = collect.list;
2219 local_rinfo = *rinfo;
2220 for (l = dependents; l; l = l->next) {
2221 GnmExprTop const *newtree;
2222 GnmDependent *dep = l->data;
2223
2224 dep->flags &= ~DEPENDENT_FLAGGED;
2225 sheet_flag_status_update_range (dep->sheet, NULL);
2226
2227 parse_pos_init_dep (&local_rinfo.pos, dep);
2228
2229 /* it is possible nothing changed for contained deps
2230 * using absolute references */
2231 newtree = gnm_expr_top_relocate (dep->texpr, &local_rinfo, FALSE);
2232 if (newtree != NULL) {
2233 int const t = dependent_type (dep);
2234 ExprRelocateStorage *tmp =
2235 g_new (ExprRelocateStorage, 1);
2236
2237 tmp->dep_type = t;
2238 if (t == DEPENDENT_NAME) {
2239 #warning "What should we do here and why do we leak tmp?"
2240 } else {
2241 if (t == DEPENDENT_CELL)
2242 tmp->u.pos = local_rinfo.pos;
2243 else
2244 tmp->u.dep = dep;
2245 tmp->oldtree = dep->texpr;
2246 gnm_expr_top_ref (tmp->oldtree);
2247 undo_info = g_slist_prepend (undo_info, tmp);
2248
2249 dependent_set_expr (dep, newtree); /* unlinks */
2250 gnm_expr_top_unref (newtree);
2251
2252 /* queue the things that depend on the changed dep
2253 * even if it is going to move.
2254 */
2255 dependent_queue_recalc (dep);
2256
2257 /* relink if it is not going to move, if it is moving
2258 * then the caller is responsible for relinking.
2259 * This avoids a link/unlink/link tuple
2260 */
2261 if (t == DEPENDENT_CELL) {
2262 GnmCellPos const *pos = &GNM_DEP_TO_CELL (dep)->pos;
2263 if (dep->sheet != sheet ||
2264 !range_contains (r, pos->col, pos->row))
2265 dependent_link (dep);
2266 } else
2267 dependent_link (dep);
2268 }
2269 } else {
2270 /*
2271 * The expression may not be changing, but it depends
2272 * on something that is. Not-corner array cells go here
2273 * too
2274 */
2275 dependent_queue_recalc (dep);
2276 }
2277
2278 /* Not the most efficient, but probably not too bad. It is
2279 * definitely cheaper than finding the set of effected sheets. */
2280 sheet_flag_status_update_range (dep->sheet, NULL);
2281 }
2282 g_slist_free (dependents);
2283
2284 u_exprs = go_undo_unary_new (undo_info,
2285 (GOUndoUnaryFunc)dependents_unrelocate,
2286 (GFreeFunc)dependents_unrelocate_free);
2287
2288 u_names = NULL;
2289
2290 switch (rinfo->reloc_type) {
2291 case GNM_EXPR_RELOCATE_INVALIDATE_SHEET:
2292 case GNM_EXPR_RELOCATE_MOVE_RANGE:
2293 break;
2294
2295 case GNM_EXPR_RELOCATE_COLS:
2296 case GNM_EXPR_RELOCATE_ROWS: {
2297 GSList *l, *names = names_referencing_sheet (sheet);
2298 GnmExprRelocateInfo rinfo2 = *rinfo;
2299
2300 for (l = names; l; l = l->next) {
2301 GnmNamedExpr *nexpr = l->data;
2302 GnmExprTop const *newtree;
2303 rinfo2.pos = nexpr->pos;
2304 newtree = gnm_expr_top_relocate (nexpr->texpr,
2305 &rinfo2, TRUE);
2306 if (newtree) {
2307 GOUndo *u = expr_name_set_expr_undo_new (nexpr);
2308 u_names = go_undo_combine (u_names, u);
2309 expr_name_set_expr (nexpr, newtree);
2310 }
2311 }
2312 g_slist_free (names);
2313 break;
2314
2315 default:
2316 g_assert_not_reached ();
2317 }
2318 }
2319
2320 return go_undo_combine (u_exprs, u_names);
2321 }
2322
2323 /*******************************************************************/
2324
2325 static gboolean
cb_collect_range(G_GNUC_UNUSED gpointer key,DependencyAny * depany,GSList ** collector)2326 cb_collect_range (G_GNUC_UNUSED gpointer key,
2327 DependencyAny *depany,
2328 GSList **collector)
2329 {
2330 *collector = g_slist_prepend (*collector, depany);
2331 return TRUE;
2332 }
2333
2334 static void
dep_hash_destroy(GHashTable * hash,GSList ** dyn_deps,Sheet * sheet)2335 dep_hash_destroy (GHashTable *hash, GSList **dyn_deps, Sheet *sheet)
2336 {
2337 GSList *deps = NULL, *l;
2338 GnmExprRelocateInfo rinfo;
2339 GSList *deplist = NULL;
2340 gboolean destroy = (sheet->revive == NULL);
2341
2342 /* We collect first because we will be changing the hash. */
2343 if (destroy) {
2344 g_hash_table_foreach_remove (hash,
2345 (GHRFunc)cb_collect_range,
2346 &deps);
2347 g_hash_table_destroy (hash);
2348 } else {
2349 g_hash_table_foreach (hash, (GHFunc)cb_collect_range, &deps);
2350 }
2351
2352 for (l = deps; l; l = l->next) {
2353 DependencyAny *depany = l->data;
2354
2355 micro_hash_foreach_dep (depany->deps, dep, {
2356 if (dependent_type (dep) == DEPENDENT_DYNAMIC_DEP) {
2357 GnmDependent *c = ((DynamicDep *)dep)->container;
2358 if (!c->sheet->being_invalidated)
2359 *dyn_deps =
2360 g_slist_prepend (*dyn_deps, c);
2361 } else if (!dep->sheet->being_invalidated) {
2362 /*
2363 * We collect here instead of doing right away as
2364 * the dependent_set_expr below can change the
2365 * container we are looping over.
2366 */
2367 deplist = g_slist_prepend (deplist, dep);
2368 }
2369 });
2370
2371 if (destroy)
2372 micro_hash_release (&depany->deps);
2373 }
2374 g_slist_free (deps);
2375
2376 /*
2377 * We do this after the above loop as this loop will
2378 * invalidate some of the DependencyAny pointers from
2379 * above. The testcase for that is 314207, deleting
2380 * all but the first sheet in one go.
2381 */
2382 rinfo.reloc_type = GNM_EXPR_RELOCATE_INVALIDATE_SHEET;
2383 for (l = deplist; l; l = l->next) {
2384 GnmDependent *dep = l->data;
2385 GnmExprTop const *te = dep->texpr;
2386 /* We are told this dependent depends on this region, hence if
2387 * newtree is null then either
2388 * 1) we did not depend on it (ie., serious breakage )
2389 * 2) we had a duplicate reference and we have already removed it.
2390 * 3) We depended on things via a name which will be
2391 * invalidated elsewhere */
2392 GnmExprTop const *newtree = gnm_expr_top_relocate (te, &rinfo, FALSE);
2393 if (newtree != NULL) {
2394 if (sheet->revive)
2395 go_undo_group_add
2396 (sheet->revive,
2397 gnm_dep_set_expr_undo_new (dep));
2398 dependent_set_expr (dep, newtree);
2399 gnm_expr_top_unref (newtree);
2400 dependent_link (dep);
2401 }
2402 }
2403 g_slist_free (deplist);
2404 }
2405
2406 static void
cb_collect_deps_of_name(GnmDependent * dep,G_GNUC_UNUSED gpointer value,GSList ** accum)2407 cb_collect_deps_of_name (GnmDependent *dep, G_GNUC_UNUSED gpointer value,
2408 GSList **accum)
2409 {
2410 /* grab unflagged linked depends */
2411 if ((dep->flags & (DEPENDENT_FLAGGED|DEPENDENT_IS_LINKED)) == DEPENDENT_IS_LINKED) {
2412 dep->flags |= DEPENDENT_FLAGGED;
2413 *accum = g_slist_prepend (*accum, dep);
2414 }
2415 }
2416
2417 struct cb_collect_deps_of_names {
2418 GSList *names;
2419 GSList *deps;
2420 };
2421
2422 static void
cb_collect_deps_of_names(GnmNamedExpr * nexpr,G_GNUC_UNUSED gpointer value,struct cb_collect_deps_of_names * accum)2423 cb_collect_deps_of_names (GnmNamedExpr *nexpr,
2424 G_GNUC_UNUSED gpointer value,
2425 struct cb_collect_deps_of_names *accum)
2426 {
2427 accum->names = g_slist_prepend (accum->names, nexpr);
2428 if (nexpr->dependents)
2429 g_hash_table_foreach (nexpr->dependents,
2430 (GHFunc)cb_collect_deps_of_name,
2431 &accum->deps);
2432 }
2433
2434 static void
invalidate_name(GnmNamedExpr * nexpr,Sheet * sheet)2435 invalidate_name (GnmNamedExpr *nexpr, Sheet *sheet)
2436 {
2437 GnmExprTop const *old_expr = nexpr->texpr;
2438 GnmExprTop const *new_expr = NULL;
2439 gboolean scope_being_killed =
2440 nexpr->pos.sheet
2441 ? nexpr->pos.sheet->being_invalidated
2442 : nexpr->pos.wb->during_destruction;
2443
2444 if (!scope_being_killed) {
2445 GnmExprRelocateInfo rinfo;
2446 rinfo.reloc_type = GNM_EXPR_RELOCATE_INVALIDATE_SHEET;
2447 new_expr = gnm_expr_top_relocate (old_expr, &rinfo, FALSE);
2448 g_return_if_fail (new_expr != NULL);
2449 }
2450
2451 if (nexpr->dependents && g_hash_table_size (nexpr->dependents))
2452 g_warning ("Left-over name dependencies\n");
2453
2454 if (sheet->revive)
2455 go_undo_group_add (sheet->revive,
2456 expr_name_set_expr_undo_new (nexpr));
2457
2458 expr_name_set_expr (nexpr, new_expr);
2459 }
2460
2461 static void
handle_dynamic_deps(GSList * dyn_deps)2462 handle_dynamic_deps (GSList *dyn_deps)
2463 {
2464 GSList *ptr;
2465
2466 for (ptr = dyn_deps; ptr != NULL ; ptr = ptr->next) {
2467 GnmDependent *dep = ptr->data;
2468 if (dep->flags & DEPENDENT_HAS_DYNAMIC_DEPS) {
2469 dependent_clear_dynamic_deps (dep);
2470 dep->flags &= ~DEPENDENT_HAS_DYNAMIC_DEPS;
2471 }
2472 }
2473 dependent_queue_recalc_list (dyn_deps);
2474 g_slist_free (dyn_deps);
2475 }
2476
2477 static void
handle_referencing_names(GnmDepContainer * deps,Sheet * sheet)2478 handle_referencing_names (GnmDepContainer *deps, Sheet *sheet)
2479 {
2480 GSList *ptr;
2481 GHashTable *names = deps->referencing_names;
2482 struct cb_collect_deps_of_names accum;
2483 gboolean destroy = (sheet->revive == NULL);
2484
2485 if (!names)
2486 return;
2487
2488 if (destroy)
2489 deps->referencing_names = NULL;
2490
2491 accum.deps = NULL;
2492 accum.names = NULL;
2493 g_hash_table_foreach (names,
2494 (GHFunc)cb_collect_deps_of_names,
2495 (gpointer)&accum);
2496
2497 for (ptr = accum.deps; ptr; ptr = ptr->next) {
2498 GnmDependent *dep = ptr->data;
2499 dep->flags &= ~DEPENDENT_FLAGGED;
2500 dependent_unlink (dep);
2501 }
2502
2503 /* now that all of the dependents of these names are unlinked.
2504 * change the references in the names to avoid this sheet */
2505 for (ptr = accum.names; ptr; ptr = ptr->next) {
2506 GnmNamedExpr *nexpr = ptr->data;
2507 invalidate_name (nexpr, sheet);
2508 }
2509 g_slist_free (accum.names);
2510
2511 /* then relink things en-mass in case one of the deps outside
2512 * this sheet used multiple names that referenced us */
2513 dependents_link (accum.deps);
2514
2515 if (destroy) {
2516 g_slist_free (accum.deps);
2517 g_hash_table_destroy (names);
2518 } else {
2519 go_undo_group_add (sheet->revive,
2520 gnm_dep_unlink_undo_new (accum.deps));
2521 }
2522 }
2523
2524 static void
handle_outgoing_references(GnmDepContainer * deps,Sheet * sheet)2525 handle_outgoing_references (GnmDepContainer *deps, Sheet *sheet)
2526 {
2527 GnmDependentFlags what = DEPENDENT_USES_NAME;
2528 GSList *accum = NULL;
2529
2530 what |= (sheet->workbook && sheet->workbook->during_destruction)
2531 ? DEPENDENT_GOES_INTERBOOK
2532 : DEPENDENT_GOES_INTERSHEET;
2533 DEPENDENT_CONTAINER_FOREACH_DEPENDENT (deps, dep, {
2534 if (dependent_is_linked (dep) && (dep->flags & what)) {
2535 dependent_unlink (dep);
2536 if (sheet->revive)
2537 accum = g_slist_prepend (accum, dep);
2538 }
2539 });
2540
2541 if (accum)
2542 go_undo_group_add (sheet->revive,
2543 gnm_dep_unlink_undo_new (accum));
2544 }
2545
2546 /*
2547 * do_deps_destroy:
2548 * Invalidate references of all kinds to the sheet.
2549 *
2550 * Also destroy the dependency container.
2551 */
2552 static void
do_deps_destroy(Sheet * sheet)2553 do_deps_destroy (Sheet *sheet)
2554 {
2555 GnmDepContainer *deps;
2556 GSList *dyn_deps = NULL;
2557 int i;
2558
2559 g_return_if_fail (IS_SHEET (sheet));
2560 g_return_if_fail (sheet->being_invalidated);
2561
2562 /* The GnmDepContainer (i.e., sheet->deps) contains the names that
2563 * reference this, not the names it contains. Remove the locally
2564 * defined names here.
2565 *
2566 * NOTE : they may continue to exist inactively for a bit. Be
2567 * careful to remove them _before_ destroying the deps. This is
2568 * a bit wasteful in that we unlink and relink a few things that
2569 * are going to be deleted. However, it is necessary to catch
2570 * all the different life cycles.
2571 */
2572 gnm_named_expr_collection_unref (sheet->names);
2573 sheet->names = NULL;
2574
2575 deps = sheet->deps;
2576 if (deps == NULL)
2577 return;
2578
2579 /* Destroy the records of what depends on this sheet. There is no need
2580 * to delicately remove individual items from the lists. The only
2581 * purpose that serves is to validate the state of our data structures.
2582 * If required this optimization can be disabled for debugging.
2583 */
2584 sheet->deps = NULL;
2585 if (sheet->revive) {
2586 g_object_unref (sheet->revive);
2587 sheet->revive = NULL;
2588 }
2589
2590 for (i = deps->buckets - 1; i >= 0 ; i--) {
2591 GHashTable *hash = deps->range_hash[i];
2592 if (hash != NULL)
2593 dep_hash_destroy (hash, &dyn_deps, sheet);
2594 }
2595 dep_hash_destroy (deps->single_hash, &dyn_deps, sheet);
2596
2597 g_free (deps->range_hash);
2598 deps->range_hash = NULL;
2599 /*
2600 * Note: we have not freed the elements in the pool. This call
2601 * frees everything in one go.
2602 */
2603 go_mem_chunk_destroy (deps->range_pool, TRUE);
2604 deps->range_pool = NULL;
2605
2606 deps->single_hash = NULL;
2607 /*
2608 * Note: we have not freed the elements in the pool. This call
2609 * frees everything in one go.
2610 */
2611 go_mem_chunk_destroy (deps->single_pool, TRUE);
2612 deps->single_pool = NULL;
2613
2614 /* Now that we have tossed all deps to this sheet we can queue the
2615 * external dyn deps for recalc and free them */
2616 handle_dynamic_deps (dyn_deps);
2617
2618 g_hash_table_destroy (deps->dynamic_deps);
2619 deps->dynamic_deps = NULL;
2620
2621 handle_referencing_names (deps, sheet);
2622
2623 /* Now we remove any links from dependents in this sheet to
2624 * to other containers. If the entire workbook is going away
2625 * just look for inter-book links.
2626 */
2627 handle_outgoing_references (deps, sheet);
2628
2629 g_free (deps);
2630 }
2631
2632 /*
2633 * do_deps_invalidate:
2634 * Invalidate references of all kinds to the sheet.
2635 */
2636 static void
do_deps_invalidate(Sheet * sheet)2637 do_deps_invalidate (Sheet *sheet)
2638 {
2639 GnmDepContainer *deps;
2640 GSList *dyn_deps = NULL;
2641 int i;
2642
2643 g_return_if_fail (IS_SHEET (sheet));
2644 g_return_if_fail (sheet->being_invalidated);
2645 g_return_if_fail (sheet->revive == NULL);
2646
2647 sheet->revive = go_undo_group_new ();
2648
2649 gnm_named_expr_collection_unlink (sheet->names);
2650
2651 deps = sheet->deps;
2652
2653 for (i = deps->buckets - 1; i >= 0 ; i--) {
2654 GHashTable *hash = deps->range_hash[i];
2655 if (hash != NULL)
2656 dep_hash_destroy (hash, &dyn_deps, sheet);
2657 }
2658 dep_hash_destroy (deps->single_hash, &dyn_deps, sheet);
2659
2660 /* Now that we have tossed all deps to this sheet we can queue the
2661 * external dyn deps for recalc and free them */
2662 handle_dynamic_deps (dyn_deps);
2663
2664 handle_referencing_names (deps, sheet);
2665
2666 /* Now we remove any links from dependents in this sheet to
2667 * to other containers. If the entire workbook is going away
2668 * just look for inter-book links.
2669 */
2670 handle_outgoing_references (deps, sheet);
2671 }
2672
2673 static void
cb_tweak_3d(GnmDependent * dep,G_GNUC_UNUSED gpointer value,GSList ** deps)2674 cb_tweak_3d (GnmDependent *dep, G_GNUC_UNUSED gpointer value, GSList **deps)
2675 {
2676 *deps = g_slist_prepend (*deps, dep);
2677 }
2678
2679 static void
tweak_3d(Sheet * sheet)2680 tweak_3d (Sheet *sheet)
2681 {
2682 Workbook *wb = sheet->workbook;
2683 GSList *deps = NULL, *l;
2684 GnmExprRelocateInfo rinfo;
2685
2686 if (!wb->sheet_order_dependents)
2687 return;
2688
2689 g_hash_table_foreach (wb->sheet_order_dependents,
2690 (GHFunc)cb_tweak_3d,
2691 &deps);
2692
2693 rinfo.reloc_type = GNM_EXPR_RELOCATE_INVALIDATE_SHEET;
2694 for (l = deps; l; l = l->next) {
2695 GnmDependent *dep = l->data;
2696 GnmExprTop const *te = dep->texpr;
2697 GnmExprTop const *newtree = gnm_expr_top_relocate (te, &rinfo, FALSE);
2698
2699 if (newtree != NULL) {
2700 if (sheet->revive)
2701 go_undo_group_add
2702 (sheet->revive,
2703 gnm_dep_set_expr_undo_new (dep));
2704 dependent_set_expr (dep, newtree);
2705 gnm_expr_top_unref (newtree);
2706 dependent_link (dep);
2707 dependent_changed (dep);
2708 }
2709 }
2710 g_slist_free (deps);
2711 }
2712
2713 static void
dependents_invalidate_sheets(GSList * sheets,gboolean destroy)2714 dependents_invalidate_sheets (GSList *sheets, gboolean destroy)
2715 {
2716 GSList *tmp;
2717 Workbook *last_wb;
2718
2719 /* Mark all first. */
2720 for (tmp = sheets; tmp; tmp = tmp->next) {
2721 Sheet *sheet = tmp->data;
2722 sheet->being_invalidated = TRUE;
2723 }
2724
2725 /*
2726 * Fixup 3d refs that start or end on one of these sheets.
2727 * Ideally we do this one per workbook, but that is not critical
2728 * so we are not going to outright sort the sheet list.
2729 */
2730 last_wb = NULL;
2731 for (tmp = sheets; tmp; tmp = tmp->next) {
2732 Sheet *sheet = tmp->data;
2733 Workbook *wb = sheet->workbook;
2734
2735 if (wb != last_wb)
2736 tweak_3d (sheet);
2737 last_wb = wb;
2738 }
2739
2740 /* Now invalidate. */
2741 for (tmp = sheets; tmp; tmp = tmp->next) {
2742 Sheet *sheet = tmp->data;
2743 if (destroy)
2744 do_deps_destroy (sheet);
2745 else
2746 do_deps_invalidate (sheet);
2747 }
2748
2749 /* Unmark. */
2750 for (tmp = sheets; tmp; tmp = tmp->next) {
2751 Sheet *sheet = tmp->data;
2752 sheet->being_invalidated = FALSE;
2753 }
2754 }
2755
2756 void
dependents_invalidate_sheet(Sheet * sheet,gboolean destroy)2757 dependents_invalidate_sheet (Sheet *sheet, gboolean destroy)
2758 {
2759 GSList l;
2760
2761 g_return_if_fail (IS_SHEET (sheet));
2762
2763 l.next = NULL;
2764 l.data = sheet;
2765 dependents_invalidate_sheets (&l, destroy);
2766 }
2767
2768 void
dependents_workbook_destroy(Workbook * wb)2769 dependents_workbook_destroy (Workbook *wb)
2770 {
2771 g_return_if_fail (GNM_IS_WORKBOOK (wb));
2772 g_return_if_fail (wb->during_destruction);
2773 g_return_if_fail (wb->sheets != NULL);
2774
2775 /* Mark all first. */
2776 WORKBOOK_FOREACH_SHEET (wb, sheet, sheet->being_invalidated = TRUE;);
2777
2778 /* Kill 3d deps and workbook-level names, if needed. */
2779 if (wb->sheet_order_dependents) {
2780 g_hash_table_destroy (wb->sheet_order_dependents);
2781 wb->sheet_order_dependents = NULL;
2782 }
2783 gnm_named_expr_collection_unref (wb->names);
2784 wb->names = NULL;
2785
2786 WORKBOOK_FOREACH_SHEET (wb, sheet, do_deps_destroy (sheet););
2787
2788 /* Unmark. */
2789 WORKBOOK_FOREACH_SHEET (wb, sheet, sheet->being_invalidated = FALSE;);
2790 }
2791
2792 /*
2793 * dependents_revive_sheet:
2794 * Undo the effects of dependents_invalidate_sheet (sheet, FALSE).
2795 */
2796 void
dependents_revive_sheet(Sheet * sheet)2797 dependents_revive_sheet (Sheet *sheet)
2798 {
2799 go_undo_undo (GO_UNDO (sheet->revive));
2800 g_object_unref (sheet->revive);
2801 sheet->revive = NULL;
2802
2803 /* Re-link local names. */
2804 gnm_named_expr_collection_relink (sheet->names);
2805
2806 gnm_dep_container_sanity_check (sheet->deps);
2807 }
2808
2809 void
workbook_queue_all_recalc(Workbook * wb)2810 workbook_queue_all_recalc (Workbook *wb)
2811 {
2812 /* FIXME: what about dependents in other workbooks? */
2813 WORKBOOK_FOREACH_DEPENDENT (wb, dep, dependent_flag_recalc (dep););
2814 }
2815
2816 void
workbook_queue_volatile_recalc(Workbook * wb)2817 workbook_queue_volatile_recalc (Workbook *wb)
2818 {
2819 WORKBOOK_FOREACH_DEPENDENT (wb, dep, {
2820 if (dependent_is_volatile (dep))
2821 dependent_flag_recalc (dep);
2822 });
2823 }
2824
2825
2826 /**
2827 * workbook_recalc:
2828 * @wb:
2829 *
2830 * Computes all dependents in @wb that have been flaged as requiring
2831 * recomputation.
2832 *
2833 * NOTE! This does not recalc dependents in other workbooks.
2834 */
2835 void
workbook_recalc(Workbook * wb)2836 workbook_recalc (Workbook *wb)
2837 {
2838 gboolean redraw = FALSE;
2839
2840 g_return_if_fail (GNM_IS_WORKBOOK (wb));
2841
2842 gnm_app_recalc_start ();
2843
2844 // Do a pass computing only cells; this allows style deps to see
2845 // updated values as needed.
2846 WORKBOOK_FOREACH_DEPENDENT (wb, dep, {
2847 if (dependent_is_cell (dep) && dependent_needs_recalc (dep)) {
2848 redraw = TRUE;
2849 dependent_eval (dep);
2850 }
2851 });
2852
2853 WORKBOOK_FOREACH_DEPENDENT (wb, dep, {
2854 if (dependent_needs_recalc (dep)) {
2855 redraw = TRUE;
2856 dependent_eval (dep);
2857 }
2858 });
2859
2860 gnm_app_recalc_finish ();
2861
2862 /*
2863 * This is a bit of a band-aid. If anything is recalculated, we
2864 * force a full redraw. The alternative is to ask for updates
2865 * of every cell that is changed and that is probably more
2866 * expensive.
2867 */
2868 if (redraw) {
2869 WORKBOOK_FOREACH_SHEET (wb, sheet, {
2870 SHEET_FOREACH_VIEW (sheet, sv, gnm_sheet_view_flag_selection_change (sv););
2871 sheet_redraw_all (sheet, FALSE);});
2872 }
2873 }
2874
2875 /**
2876 * workbook_recalc_all:
2877 * @wb:
2878 *
2879 * Queues all dependents for recalc and recalculates.
2880 */
2881 void
workbook_recalc_all(Workbook * wb)2882 workbook_recalc_all (Workbook *wb)
2883 {
2884 workbook_queue_all_recalc (wb);
2885
2886 /* Recalculate this workbook unconditionally. */
2887 workbook_recalc (wb);
2888
2889 /* Recalculate other workbooks when needed. */
2890 gnm_app_recalc ();
2891
2892 WORKBOOK_FOREACH_VIEW (wb, view,
2893 sheet_update (wb_view_cur_sheet (view)););
2894 }
2895
2896 static void
dynamic_dep_free(DynamicDep * dyn)2897 dynamic_dep_free (DynamicDep *dyn)
2898 {
2899 GnmDependent *dep = dyn->container;
2900 GnmCellPos const *pos = dependent_pos (dep);
2901 GSList *ptr;
2902
2903 for (ptr = dyn->singles ; ptr != NULL ; ptr = ptr->next) {
2904 GnmRangeRef *rr = ptr->data;
2905 unlink_single_dep (&dyn->base, pos, &rr->a);
2906 g_free (rr);
2907 }
2908 g_slist_free (dyn->singles);
2909 dyn->singles = NULL;
2910
2911 for (ptr = dyn->ranges ; ptr != NULL ; ptr = ptr->next) {
2912 GnmRangeRef *rr = ptr->data;
2913 link_unlink_cellrange_dep (&dyn->base, pos,
2914 &rr->a, &rr->b, DEP_LINK_UNLINK);
2915 g_free (rr);
2916 }
2917 g_slist_free (dyn->ranges);
2918 dyn->ranges = NULL;
2919
2920 if (dyn->base.flags & DEPENDENT_HAS_3D)
2921 workbook_unlink_3d_dep (&dyn->base);
2922 g_free (dyn);
2923 }
2924
2925
2926 gboolean
dependent_is_volatile(GnmDependent * dep)2927 dependent_is_volatile (GnmDependent *dep)
2928 {
2929 /* This might need to be a virtual call. */
2930 return dep->texpr && gnm_expr_top_is_volatile (dep->texpr);
2931 }
2932
2933 /**
2934 * gnm_dep_container_new: (skip)
2935 * @sheet: #Sheet
2936 *
2937 * Returns: (transfer full):
2938 **/
2939 GnmDepContainer *
gnm_dep_container_new(Sheet * sheet)2940 gnm_dep_container_new (Sheet *sheet)
2941 {
2942 GnmDepContainer *deps = g_new (GnmDepContainer, 1);
2943
2944 if (gnm_debug_flag ("dep-buckets")) {
2945 int r, lastb = 0;
2946 g_assert (bucket_start_row (0) == 0);
2947 g_assert (bucket_end_row (0) >= 0);
2948 g_assert (bucket_of_row (0) == 0);
2949 for (r = 1; r < gnm_sheet_get_max_rows (sheet); r++) {
2950 int b = bucket_of_row (r);
2951 if (b > lastb)
2952 g_printerr ("%d -> %d\n", r, b);
2953 g_assert (b == lastb || b == lastb + 1);
2954 g_assert (bucket_start_row (b) <= r);
2955 g_assert (r <= bucket_end_row (b));
2956 lastb = b;
2957 }
2958 }
2959
2960 deps->head = deps->tail = NULL;
2961
2962 deps->buckets = 1 + bucket_of_row (gnm_sheet_get_last_row (sheet));
2963 deps->range_hash = g_new0 (GHashTable *, deps->buckets);
2964 deps->range_pool = go_mem_chunk_new ("range pool",
2965 sizeof (DependencyRange),
2966 16 * 1024 - 100);
2967 deps->single_hash = g_hash_table_new ((GHashFunc) depsingle_hash,
2968 (GEqualFunc) depsingle_equal);
2969 deps->single_pool = go_mem_chunk_new ("single pool",
2970 sizeof (DependencySingle),
2971 16 * 1024 - 100);
2972 deps->referencing_names = g_hash_table_new (g_direct_hash,
2973 g_direct_equal);
2974
2975 deps->dynamic_deps = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2976 NULL, (GDestroyNotify) dynamic_dep_free);
2977
2978 return deps;
2979 }
2980
2981 void
gnm_dep_container_resize(GnmDepContainer * deps,int rows)2982 gnm_dep_container_resize (GnmDepContainer *deps, int rows)
2983 {
2984 int i, buckets = 1 + bucket_of_row (rows - 1);
2985
2986 for (i = buckets; i < deps->buckets; i++) {
2987 GHashTable *hash = deps->range_hash[i];
2988 if (hash != NULL) {
2989 int s = g_hash_table_size (hash);
2990 if (s)
2991 g_printerr ("Hash table size: %d\n", s);
2992 g_hash_table_destroy (hash);
2993 deps->range_hash[i] = NULL;
2994 }
2995 }
2996
2997 deps->range_hash = g_renew (GHashTable *, deps->range_hash, buckets);
2998
2999 for (i = deps->buckets; i < buckets; i++)
3000 deps->range_hash[i] = NULL;
3001
3002 deps->buckets = buckets;
3003 }
3004
3005 /****************************************************************************
3006 * Debug utils
3007 */
3008
3009 static void
dependent_debug_name_for_sheet(GnmDependent const * dep,Sheet * sheet,GString * target)3010 dependent_debug_name_for_sheet (GnmDependent const *dep, Sheet *sheet,
3011 GString *target)
3012 {
3013 int t;
3014 GnmDependentClass *klass;
3015
3016 g_return_if_fail (dep != NULL);
3017 g_return_if_fail (dep_classes);
3018
3019 if (!dep->sheet)
3020 g_warning ("Invalid dep, missing sheet");
3021
3022 if (sheet == dep->sheet) {
3023 /* Nothing */
3024 } else {
3025 g_string_append (target,
3026 dep->sheet ? dep->sheet->name_quoted : "?");
3027 g_string_append_c (target, '!');
3028 }
3029
3030 t = dependent_type (dep);
3031 klass = g_ptr_array_index (dep_classes, t);
3032 klass->debug_name (dep, target);
3033
3034 if (dependent_has_pos (dep) && !dependent_is_cell (dep)) {
3035 g_string_append_c (target, '@');
3036 g_string_append (target, cellpos_as_string (dependent_pos (dep)));
3037 }
3038 }
3039
3040
3041 /**
3042 * dependent_debug_name:
3043 * @dep: The dependent we are interested in.
3044 * @target: (inout): location to append @dep's name.
3045 *
3046 * A useful little debugging utility.
3047 */
3048 void
dependent_debug_name(GnmDependent const * dep,GString * target)3049 dependent_debug_name (GnmDependent const *dep, GString *target)
3050 {
3051 dependent_debug_name_for_sheet (dep, NULL, target);
3052 }
3053
3054
3055 static void
dump_range_dep(DependencyRange const * deprange,Sheet * sheet,GHashTable * alldeps)3056 dump_range_dep (DependencyRange const *deprange, Sheet *sheet, GHashTable *alldeps)
3057 {
3058 GnmRange const *range = &(deprange->range);
3059 GString *target = g_string_sized_new (10000);
3060 gboolean first = TRUE;
3061
3062 g_string_append (target, " ");
3063 g_string_append (target, range_as_string (range));
3064 g_string_append (target, " -> (");
3065
3066 micro_hash_foreach_dep (deprange->deps, dep, {
3067 if (first)
3068 first = FALSE;
3069 else
3070 g_string_append (target, ", ");
3071 g_hash_table_remove (alldeps, dep);
3072 dependent_debug_name_for_sheet (dep, sheet, target);
3073 });
3074 g_string_append_c (target, ')');
3075
3076 g_printerr ("%s\n", target->str);
3077 g_string_free (target, TRUE);
3078 }
3079
3080 static void
dump_single_dep(DependencySingle * depsingle,Sheet * sheet,GHashTable * alldeps)3081 dump_single_dep (DependencySingle *depsingle, Sheet *sheet, GHashTable *alldeps)
3082 {
3083 GString *target = g_string_sized_new (10000);
3084 gboolean first = TRUE;
3085
3086 g_string_append (target, " ");
3087 g_string_append (target, cellpos_as_string (&depsingle->pos));
3088 g_string_append (target, " -> ");
3089
3090 micro_hash_foreach_dep (depsingle->deps, dep, {
3091 if (first)
3092 first = FALSE;
3093 else
3094 g_string_append (target, ", ");
3095 g_hash_table_remove (alldeps, dep);
3096 dependent_debug_name_for_sheet (dep, sheet, target);
3097 });
3098
3099 g_printerr ("%s\n", target->str);
3100 g_string_free (target, TRUE);
3101 }
3102
3103 static void
dump_dynamic_dep(GnmDependent * dep,DynamicDep * dyn,GHashTable * alldeps)3104 dump_dynamic_dep (GnmDependent *dep, DynamicDep *dyn, GHashTable *alldeps)
3105 {
3106 GSList *l;
3107 GnmParsePos pp;
3108 GnmConventionsOut out;
3109
3110 out.accum = g_string_new (NULL);
3111 out.pp = &pp;
3112 out.convs = gnm_conventions_default;
3113
3114 pp.wb = dep->sheet->workbook;
3115 pp.sheet = dep->sheet;
3116 pp.eval = *dependent_pos (dyn->container);
3117
3118 g_string_append (out.accum, " ");
3119 dependent_debug_name (dep, out.accum);
3120 g_hash_table_remove (alldeps, dep); // ???
3121 g_string_append (out.accum, " -> ");
3122 dependent_debug_name (&dyn->base, out.accum);
3123 g_string_append (out.accum, " { c=");
3124 dependent_debug_name (dyn->container, out.accum);
3125
3126 g_string_append (out.accum, ", s=[");
3127 for (l = dyn->singles; l; l = l->next) {
3128 rangeref_as_string (&out, l->data);
3129 if (l->next)
3130 g_string_append (out.accum, ", ");
3131 }
3132
3133 g_string_append (out.accum, "], r=[");
3134 for (l = dyn->ranges; l; l = l->next) {
3135 rangeref_as_string (&out, l->data);
3136 if (l->next)
3137 g_string_append (out.accum, ", ");
3138 }
3139
3140 g_string_append (out.accum, "] }");
3141 g_printerr ("%s\n", out.accum->str);
3142 g_string_free (out.accum, TRUE);
3143 }
3144
3145 /**
3146 * gnm_dep_container_dump:
3147 * @deps:
3148 * @sheet:
3149 *
3150 * A useful utility for checking the state of the dependency data structures.
3151 */
3152 void
gnm_dep_container_dump(GnmDepContainer const * deps,Sheet * sheet)3153 gnm_dep_container_dump (GnmDepContainer const *deps,
3154 Sheet *sheet)
3155 {
3156 int i;
3157 GHashTable *alldeps;
3158
3159 g_return_if_fail (deps != NULL);
3160
3161 gnm_dep_container_sanity_check (deps);
3162
3163 alldeps = g_hash_table_new (g_direct_hash, g_direct_equal);
3164 SHEET_FOREACH_DEPENDENT (sheet, dep,
3165 if (!dependent_is_cell (dep))
3166 g_hash_table_insert (alldeps, dep, dep);
3167 );
3168
3169 for (i = 0; i < deps->buckets; i++) {
3170 GHashTable *hash = deps->range_hash[i];
3171 if (hash != NULL && g_hash_table_size (hash) > 0) {
3172 GHashTableIter hiter;
3173 gpointer key;
3174
3175 g_printerr (" Bucket %d (rows %d-%d): Range hash size %d: range over which cells in list depend\n",
3176 i,
3177 bucket_start_row (i) + 1,
3178 bucket_end_row (i) + 1,
3179 g_hash_table_size (hash));
3180 g_hash_table_iter_init (&hiter, hash);
3181 while (g_hash_table_iter_next (&hiter, &key, NULL)) {
3182 DependencyRange *deprange = key;
3183 dump_range_dep (deprange, sheet, alldeps);
3184 }
3185 }
3186 }
3187
3188 if (deps->single_hash && g_hash_table_size (deps->single_hash) > 0) {
3189 GHashTableIter hiter;
3190 gpointer key;
3191
3192 g_printerr (" Single hash size %d: cell on which list of cells depend\n",
3193 g_hash_table_size (deps->single_hash));
3194
3195 g_hash_table_iter_init (&hiter, deps->single_hash);
3196 while (g_hash_table_iter_next (&hiter, &key, NULL)) {
3197 DependencySingle *depsingle = key;
3198 dump_single_dep (depsingle, sheet, alldeps);
3199 }
3200 }
3201
3202 if (deps->dynamic_deps && g_hash_table_size (deps->dynamic_deps) > 0) {
3203 GHashTableIter hiter;
3204 gpointer key, value;
3205
3206 g_printerr (" Dynamic hash size %d: dependents that depend on dynamic dependencies\n",
3207 g_hash_table_size (deps->dynamic_deps));
3208 g_hash_table_iter_init (&hiter, deps->dynamic_deps);
3209 while (g_hash_table_iter_next (&hiter, &key, &value)) {
3210 GnmDependent *dep = key;
3211 DynamicDep *dyn = value;
3212 dump_dynamic_dep (dep, dyn, alldeps);
3213 }
3214 }
3215
3216 if (deps->referencing_names && g_hash_table_size (deps->referencing_names) > 0) {
3217 GList *l, *names = g_hash_table_get_keys (deps->referencing_names);
3218
3219 g_printerr (" Names whose expressions explicitly reference this sheet\n ");
3220 for (l = names; l; l = l->next) {
3221 GnmNamedExpr *nexpr = l->data;
3222 g_printerr ("%s%s",
3223 expr_name_name (nexpr),
3224 l->next ? ", " : "\n");
3225 }
3226 g_list_free (names);
3227 }
3228
3229 if (g_hash_table_size (alldeps) > 0) {
3230 GHashTableIter hiter;
3231 gpointer key;
3232
3233 g_printerr (" Dependencies of sheet not listed above (excluding cells):\n");
3234 g_hash_table_iter_init (&hiter, alldeps);
3235 while (g_hash_table_iter_next (&hiter, &key, NULL)) {
3236 GnmDependent *dep = key;
3237 GString *nstr = g_string_new (NULL);
3238 GnmParsePos pp;
3239 char *estr;
3240
3241 parse_pos_init_dep (&pp, dep);
3242 estr = dep->texpr
3243 ? gnm_expr_top_as_string (dep->texpr, &pp, sheet_get_conventions (dep->sheet))
3244 : g_strdup ("(null)");
3245
3246 dependent_debug_name (dep, nstr);
3247 g_printerr (" %s: %s\n", nstr->str, estr);
3248 g_string_free (nstr, TRUE);
3249 g_free (estr);
3250 }
3251 }
3252
3253 g_hash_table_destroy (alldeps);
3254 }
3255
3256 void
dependents_dump(Workbook * wb)3257 dependents_dump (Workbook *wb)
3258 {
3259 WORKBOOK_FOREACH_SHEET
3260 (wb, sheet,
3261 {
3262 int count = 0;
3263 SHEET_FOREACH_DEPENDENT (sheet, dep, count++;);
3264 g_printerr ("Dependencies for %s (count=%d):\n",
3265 sheet->name_unquoted, count);
3266 gnm_dep_container_dump (sheet->deps, sheet);
3267 });
3268 }
3269
3270 void
gnm_dep_container_sanity_check(GnmDepContainer const * deps)3271 gnm_dep_container_sanity_check (GnmDepContainer const *deps)
3272 {
3273 GnmDependent const *dep;
3274 GHashTable *seenb4;
3275
3276 if (deps->head && !deps->tail)
3277 g_warning ("Dependency container %p has head, but no tail.", (void *)deps);
3278 if (deps->tail && !deps->head)
3279 g_warning ("Dependency container %p has tail, but no head.", (void *)deps);
3280 if (deps->head && deps->head->prev_dep)
3281 g_warning ("Dependency container %p has head, but not at the beginning.", (void *)deps);
3282 if (deps->tail && deps->tail->next_dep)
3283 g_warning ("Dependency container %p has tail, but not at the end.", (void *)deps);
3284
3285 seenb4 = g_hash_table_new (g_direct_hash, g_direct_equal);
3286 for (dep = deps->head; dep; dep = dep->next_dep) {
3287 if (dep->prev_dep && (dep->prev_dep->next_dep != dep))
3288 g_warning ("Dependency container %p has left double-link failure at %p.", (void *)deps, (void *)dep);
3289 if (dep->next_dep && (dep->next_dep->prev_dep != dep))
3290 g_warning ("Dependency container %p has right double-link failure at %p.", (void *)deps, (void *)dep);
3291 if (!dep->next_dep && dep != deps->tail)
3292 g_warning ("Dependency container %p ends before its tail.", (void *)deps);
3293 if (!dependent_is_linked (dep))
3294 g_warning ("Dependency container %p contains unlinked dependency %p.", (void *)deps, (void *)dep);
3295 if (g_hash_table_lookup (seenb4, dep)) {
3296 g_warning ("Dependency container %p is circular.", (void *)deps);
3297 break;
3298 }
3299 g_hash_table_insert (seenb4, (gpointer)dep, (gpointer)dep);
3300 }
3301 g_hash_table_destroy (seenb4);
3302 }
3303
3304 /* ------------------------------------------------------------------------- */
3305