]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob-property.cc
Doc: Web: introduction - added href to OOoLilypond
[lilypond.git] / lily / grob-property.cc
1 /*
2   Implement storage and manipulation of grob properties.
3 */
4
5 #include <cstring>
6
7 #include "main.hh"
8 #include "input.hh"
9 #include "pointer-group-interface.hh"
10 #include "misc.hh"
11 #include "paper-score.hh"
12 #include "output-def.hh"
13 #include "spanner.hh"
14 #include "international.hh"
15 #include "item.hh"
16 #include "program-option.hh"
17 #include "profile.hh"
18 #include "simple-closure.hh"
19 #include "unpure-pure-container.hh"
20 #include "warn.hh"
21 #include "protected-scm.hh"
22
23 Protected_scm grob_property_callback_stack (SCM_EOL);
24
25 extern bool debug_property_callbacks;
26
27 #ifdef DEBUG
28 static void
29 print_property_callback_stack ()
30 {
31   int frame = 0;
32   for (SCM s = grob_property_callback_stack; scm_is_pair (s); s = scm_cdr (s))
33     message (_f ("%d: %s", frame++, ly_scm_write_string (scm_car (s)).c_str ()));
34 }
35 #endif
36
37 static Protected_scm modification_callback (SCM_EOL);
38 static Protected_scm cache_callback (SCM_EOL);
39
40 /*
41 FIXME: this should use ly:set-option interface instead.
42 */
43
44 LY_DEFINE (ly_set_grob_modification_callback, "ly:set-grob-modification-callback",
45            1, 0, 0, (SCM cb),
46            "Specify a procedure that will be called every time LilyPond"
47            " modifies a grob property.  The callback will receive as"
48            " arguments the grob that is being modified, the name of the"
49            " C++ file in which the modification was requested, the line"
50            " number in the C++ file in which the modification was requested,"
51            " the name of the function in which the modification was"
52            " requested, the property to be changed, and the new value for"
53            " the property.")
54 {
55   modification_callback = (ly_is_procedure (cb)) ? cb : SCM_BOOL_F;
56   return SCM_UNSPECIFIED;
57 }
58
59 LY_DEFINE (ly_set_property_cache_callback, "ly:set-property-cache-callback",
60            1, 0, 0, (SCM cb),
61            "Specify a procedure that will be called whenever lilypond"
62            " calculates a callback function and caches the result.  The"
63            " callback will receive as arguments the grob whose property it"
64            " is, the name of the property, the name of the callback that"
65            " calculated the property, and the new (cached) value of the"
66            " property.")
67 {
68   cache_callback = (ly_is_procedure (cb)) ? cb : SCM_BOOL_F;
69   return SCM_UNSPECIFIED;
70 }
71
72 void
73 Grob::instrumented_set_property (SCM sym, SCM v,
74                                  char const *file,
75                                  int line,
76                                  char const *fun)
77 {
78 #ifdef DEBUG
79   if (ly_is_procedure (modification_callback))
80     scm_apply_0 (modification_callback,
81                  scm_list_n (self_scm (),
82                              scm_from_locale_string (file),
83                              scm_from_int (line),
84                              scm_from_ascii_string (fun),
85                              sym, v, SCM_UNDEFINED));
86 #else
87   (void) file;
88   (void) line;
89   (void) fun;
90 #endif
91
92   internal_set_property (sym, v);
93 }
94
95 SCM
96 Grob::get_property_alist_chain (SCM def) const
97 {
98   return scm_list_n (mutable_property_alist_,
99                      immutable_property_alist_,
100                      def,
101                      SCM_UNDEFINED);
102 }
103
104 extern void check_interfaces_for_property (Grob const *me, SCM sym);
105
106 void
107 Grob::internal_set_property (SCM sym, SCM v)
108 {
109   internal_set_value_on_alist (&mutable_property_alist_,
110                                sym, v);
111
112 }
113
114 void
115 Grob::internal_set_value_on_alist (SCM *alist, SCM sym, SCM v)
116 {
117   /* Perhaps we simply do the assq_set, but what the heck. */
118   if (!is_live ())
119     return;
120
121   if (do_internal_type_checking_global)
122     {
123       if (!ly_is_procedure (v)
124           && !unsmob<Simple_closure> (v)
125           && !unsmob<Unpure_pure_container> (v)
126           && !scm_is_eq (v, ly_symbol2scm ("calculation-in-progress")))
127         type_check_assignment (sym, v, ly_symbol2scm ("backend-type?"));
128
129       check_interfaces_for_property (this, sym);
130     }
131
132   *alist = scm_assq_set_x (*alist, sym, v);
133 }
134
135 SCM
136 Grob::internal_get_property_data (SCM sym) const
137 {
138 #ifdef DEBUG
139   if (profile_property_accesses)
140     note_property_access (&grob_property_lookup_table, sym);
141 #endif
142
143   SCM handle = scm_sloppy_assq (sym, mutable_property_alist_);
144   if (scm_is_true (handle))
145     return scm_cdr (handle);
146
147   handle = scm_sloppy_assq (sym, immutable_property_alist_);
148
149   if (do_internal_type_checking_global && scm_is_pair (handle))
150     {
151       SCM val = scm_cdr (handle);
152       if (!ly_is_procedure (val) && !unsmob<Simple_closure> (val)
153           && !unsmob<Unpure_pure_container> (val))
154         type_check_assignment (sym, val, ly_symbol2scm ("backend-type?"));
155
156       check_interfaces_for_property (this, sym);
157     }
158
159   return scm_is_false (handle) ? SCM_EOL : scm_cdr (handle);
160 }
161
162 SCM
163 Grob::internal_get_property (SCM sym) const
164 {
165   SCM val = get_property_data (sym);
166
167 #ifdef DEBUG
168   if (scm_is_eq (val, ly_symbol2scm ("calculation-in-progress")))
169     {
170       programming_error (to_string ("cyclic dependency: calculation-in-progress encountered for #'%s (%s)",
171                                     ly_symbol2string (sym).c_str (),
172                                     name ().c_str ()));//assert (1==0);
173       if (debug_property_callbacks)
174         {
175           message ("backtrace: ");
176           print_property_callback_stack ();
177         }
178     }
179 #endif
180
181   if (Unpure_pure_container *upc = unsmob<Unpure_pure_container> (val))
182     val = upc->unpure_part ();
183
184   if (ly_is_procedure (val)
185       || unsmob<Simple_closure> (val))
186     {
187       Grob *me = ((Grob *)this);
188       val = me->try_callback_on_alist (&me->mutable_property_alist_, sym, val);
189     }
190
191   return val;
192 }
193
194 /* Unlike internal_get_property, this function does no caching. Use it, therefore, with caution. */
195 SCM
196 Grob::internal_get_pure_property (SCM sym, int start, int end) const
197 {
198   SCM val = internal_get_property_data (sym);
199   if (ly_is_procedure (val))
200     return call_pure_function (val, scm_list_1 (self_scm ()), start, end);
201
202   if (Unpure_pure_container *upc = unsmob<Unpure_pure_container> (val)) {
203     // Do cache, if the function ignores 'start' and 'end'
204     if (upc->is_unchanging ())
205       return internal_get_property (sym);
206     else
207       return call_pure_function (val, scm_list_1 (self_scm ()), start, end);
208   }
209
210   if (Simple_closure *sc = unsmob<Simple_closure> (val))
211     return evaluate_with_simple_closure (self_scm (),
212                                          sc->expression (),
213                                          true, start, end);
214   return val;
215 }
216
217 SCM
218 Grob::internal_get_maybe_pure_property (SCM sym, bool pure, int start, int end) const
219 {
220   return pure ? internal_get_pure_property (sym, start, end) : internal_get_property (sym);
221 }
222
223 SCM
224 Grob::try_callback_on_alist (SCM *alist, SCM sym, SCM proc)
225 {
226   SCM marker = ly_symbol2scm ("calculation-in-progress");
227   /*
228     need to put a value in SYM to ensure that we don't get a
229     cyclic call chain.
230   */
231   *alist = scm_assq_set_x (*alist, sym, marker);
232
233 #ifdef DEBUG
234   if (debug_property_callbacks)
235     grob_property_callback_stack = scm_cons (scm_list_3 (self_scm (), sym, proc), grob_property_callback_stack);
236 #endif
237
238   SCM value = SCM_EOL;
239   if (ly_is_procedure (proc))
240     value = scm_call_1 (proc, self_scm ());
241   else if (Simple_closure *sc = unsmob<Simple_closure> (proc))
242     {
243       value = evaluate_with_simple_closure (self_scm (),
244                                             sc->expression (),
245                                             false, 0, 0);
246     }
247
248 #ifdef DEBUG
249   if (debug_property_callbacks)
250     grob_property_callback_stack = scm_cdr (grob_property_callback_stack);
251 #endif
252
253   if (scm_is_eq (value, SCM_UNSPECIFIED))
254     {
255       value = get_property_data (sym);
256       assert (scm_is_null (value) || scm_is_eq (value, marker));
257       if (scm_is_eq (value, marker))
258         *alist = scm_assq_remove_x (*alist, sym);
259     }
260   else
261     {
262 #ifdef DEBUG
263       if (ly_is_procedure (cache_callback))
264         scm_apply_0 (cache_callback,
265                      scm_list_n (self_scm (),
266                                  sym,
267                                  proc,
268                                  value,
269                                  SCM_UNDEFINED));
270 #endif
271       internal_set_value_on_alist (alist, sym, value);
272     }
273
274   return value;
275 }
276
277 void
278 Grob::internal_set_object (SCM s, SCM v)
279 {
280   /* Perhaps we simply do the assq_set, but what the heck. */
281   if (!is_live ())
282     return;
283
284   object_alist_ = scm_assq_set_x (object_alist_, s, v);
285 }
286
287 void
288 Grob::internal_del_property (SCM sym)
289 {
290   mutable_property_alist_ = scm_assq_remove_x (mutable_property_alist_, sym);
291 }
292
293 SCM
294 Grob::internal_get_object (SCM sym) const
295 {
296   if (profile_property_accesses)
297     note_property_access (&grob_property_lookup_table, sym);
298
299   SCM s = scm_sloppy_assq (sym, object_alist_);
300
301   if (scm_is_true (s))
302     {
303       SCM val = scm_cdr (s);
304       if (ly_is_procedure (val)
305           || unsmob<Simple_closure> (val)
306           || unsmob<Unpure_pure_container> (val))
307         {
308           Grob *me = ((Grob *)this);
309           val = me->try_callback_on_alist (&me->object_alist_, sym, val);
310         }
311
312       return val;
313     }
314
315   return SCM_EOL;
316 }
317
318 bool
319 Grob::is_live () const
320 {
321   return scm_is_pair (immutable_property_alist_);
322 }
323
324 bool
325 Grob::internal_has_interface (SCM k)
326 {
327   return scm_is_true (scm_c_memq (k, interfaces_));
328 }
329
330 SCM
331 call_pure_function (SCM unpure, SCM args, int start, int end)
332 {
333   if (Unpure_pure_container *upc = unsmob<Unpure_pure_container> (unpure))
334     {
335       SCM pure = upc->pure_part ();
336
337       if (Simple_closure *sc = unsmob<Simple_closure> (pure))
338         {
339           SCM expr = sc->expression ();
340           return evaluate_with_simple_closure (scm_car (args), expr, true, start, end);
341         }
342
343       if (ly_is_procedure (pure))
344         return scm_apply_0 (pure,
345                             scm_append (scm_list_2 (scm_list_3 (scm_car (args),
346                                                                 scm_from_int (start),
347                                                                 scm_from_int (end)),
348                                                     scm_cdr (args))));
349
350       return pure;
351     }
352
353   if (Simple_closure *sc = unsmob<Simple_closure> (unpure))
354     {
355       SCM expr = sc->expression ();
356       return evaluate_with_simple_closure (scm_car (args), expr, true, start, end);
357     }
358
359   if (!ly_is_procedure (unpure))
360     return unpure;
361
362   return SCM_BOOL_F;
363 }
364