Fix duplicate removal of with-fluids.
[bpt/guile.git] / libguile / fluids.c
1 /* Copyright (C) 1996,1997,2000,2001, 2004, 2006, 2007, 2008, 2009, 2010,
2 * 2011, 2012, 2013 Free Software Foundation, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 3 of
7 * the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <alloca.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include "libguile/_scm.h"
29 #include "libguile/print.h"
30 #include "libguile/dynwind.h"
31 #include "libguile/fluids.h"
32 #include "libguile/alist.h"
33 #include "libguile/eval.h"
34 #include "libguile/ports.h"
35 #include "libguile/deprecation.h"
36 #include "libguile/validate.h"
37 #include "libguile/bdw-gc.h"
38
39 /* Number of additional slots to allocate when ALLOCATED_FLUIDS is full. */
40 #define FLUID_GROW 128
41
42 /* Vector of allocated fluids indexed by fluid numbers. Access is protected by
43 FLUID_ADMIN_MUTEX. */
44 static void **allocated_fluids = NULL;
45 static size_t allocated_fluids_len = 0;
46
47 static scm_i_pthread_mutex_t fluid_admin_mutex = SCM_I_PTHREAD_MUTEX_INITIALIZER;
48
49 #define IS_FLUID(x) SCM_FLUID_P (x)
50 #define FLUID_NUM(x) SCM_I_FLUID_NUM (x)
51
52 #define IS_DYNAMIC_STATE(x) SCM_I_DYNAMIC_STATE_P (x)
53 #define DYNAMIC_STATE_FLUIDS(x) SCM_I_DYNAMIC_STATE_FLUIDS (x)
54 #define SET_DYNAMIC_STATE_FLUIDS(x, y) SCM_SET_CELL_WORD_1 ((x), (SCM_UNPACK (y)))
55
56
57 \f
58 /* Grow STATE so that it can hold up to ALLOCATED_FLUIDS_LEN fluids. This may
59 be more than necessary since ALLOCATED_FLUIDS is sparse and the current
60 thread may not access all the fluids anyway. Memory usage could be improved
61 by using a 2-level array as is done in glibc for pthread keys (TODO). */
62 static void
63 grow_dynamic_state (SCM state)
64 {
65 SCM new_fluids;
66 SCM old_fluids = DYNAMIC_STATE_FLUIDS (state);
67 size_t i, len, old_len = SCM_SIMPLE_VECTOR_LENGTH (old_fluids);
68
69 /* Assume the assignment below is atomic. */
70 len = allocated_fluids_len;
71
72 new_fluids = scm_c_make_vector (len, SCM_UNDEFINED);
73
74 for (i = 0; i < old_len; i++)
75 SCM_SIMPLE_VECTOR_SET (new_fluids, i,
76 SCM_SIMPLE_VECTOR_REF (old_fluids, i));
77 SET_DYNAMIC_STATE_FLUIDS (state, new_fluids);
78 }
79
80 void
81 scm_i_fluid_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
82 {
83 scm_puts ("#<fluid ", port);
84 scm_intprint ((int) FLUID_NUM (exp), 10, port);
85 scm_putc ('>', port);
86 }
87
88 void
89 scm_i_dynamic_state_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
90 {
91 scm_puts ("#<dynamic-state ", port);
92 scm_intprint (SCM_UNPACK (exp), 16, port);
93 scm_putc ('>', port);
94 }
95
96 void
97 scm_i_with_fluids_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
98 {
99 scm_puts ("#<with-fluids ", port);
100 scm_intprint (SCM_UNPACK (exp), 16, port);
101 scm_putc ('>', port);
102 }
103
104 \f
105 /* Return a new fluid. */
106 static SCM
107 new_fluid (SCM init)
108 {
109 SCM fluid;
110 size_t trial, n;
111
112 /* Fluids hold the type tag and the fluid number in the first word,
113 and the default value in the second word. */
114 fluid = scm_cell (scm_tc7_fluid, SCM_UNPACK (init));
115 SCM_SET_CELL_TYPE (fluid, scm_tc7_fluid);
116
117 scm_dynwind_begin (0);
118 scm_i_dynwind_pthread_mutex_lock (&fluid_admin_mutex);
119
120 for (trial = 0; trial < 2; trial++)
121 {
122 /* Look for a free fluid number. */
123 for (n = 0; n < allocated_fluids_len; n++)
124 /* TODO: Use `__sync_bool_compare_and_swap' where available. */
125 if (allocated_fluids[n] == NULL)
126 break;
127
128 if (trial == 0 && n >= allocated_fluids_len)
129 /* All fluid numbers are in use. Run a GC and retry. Explicitly
130 running the GC is costly and bad-style. We only do this because
131 dynamic state fluid vectors would grow unreasonably if fluid numbers
132 weren't reused. */
133 scm_i_gc ("fluids");
134 }
135
136 if (n >= allocated_fluids_len)
137 {
138 /* Grow the vector of allocated fluids. */
139 void **new_allocated_fluids =
140 scm_gc_malloc_pointerless ((allocated_fluids_len + FLUID_GROW)
141 * sizeof (*allocated_fluids),
142 "allocated fluids");
143
144 /* Copy over old values and initialize rest. GC can not run
145 during these two operations since there is no safe point in
146 them. */
147 memcpy (new_allocated_fluids, allocated_fluids,
148 allocated_fluids_len * sizeof (*allocated_fluids));
149 memset (new_allocated_fluids + allocated_fluids_len, 0,
150 FLUID_GROW * sizeof (*allocated_fluids));
151 n = allocated_fluids_len;
152
153 /* Update the vector of allocated fluids. Dynamic states will
154 eventually be lazily grown to accomodate the new value of
155 ALLOCATED_FLUIDS_LEN in `fluid-ref' and `fluid-set!'. */
156 allocated_fluids = new_allocated_fluids;
157 allocated_fluids_len += FLUID_GROW;
158 }
159
160 allocated_fluids[n] = SCM2PTR (fluid);
161 SCM_SET_CELL_WORD_0 (fluid, (scm_tc7_fluid | (n << 8)));
162
163 GC_GENERAL_REGISTER_DISAPPEARING_LINK (&allocated_fluids[n],
164 SCM2PTR (fluid));
165
166 scm_dynwind_end ();
167
168 /* Now null out values. We could (and probably should) do this when
169 the fluid is collected instead of now. */
170 scm_i_reset_fluid (n);
171
172 return fluid;
173 }
174
175 SCM
176 scm_make_fluid (void)
177 {
178 return new_fluid (SCM_BOOL_F);
179 }
180
181 SCM_DEFINE (scm_make_fluid_with_default, "make-fluid", 0, 1, 0,
182 (SCM dflt),
183 "Return a newly created fluid, whose initial value is @var{dflt},\n"
184 "or @code{#f} if @var{dflt} is not given.\n"
185 "Fluids are objects that can hold one\n"
186 "value per dynamic state. That is, modifications to this value are\n"
187 "only visible to code that executes with the same dynamic state as\n"
188 "the modifying code. When a new dynamic state is constructed, it\n"
189 "inherits the values from its parent. Because each thread normally executes\n"
190 "with its own dynamic state, you can use fluids for thread local storage.")
191 #define FUNC_NAME s_scm_make_fluid_with_default
192 {
193 return new_fluid (SCM_UNBNDP (dflt) ? SCM_BOOL_F : dflt);
194 }
195 #undef FUNC_NAME
196
197 SCM_DEFINE (scm_make_unbound_fluid, "make-unbound-fluid", 0, 0, 0,
198 (),
199 "Make a fluid that is initially unbound.")
200 #define FUNC_NAME s_scm_make_unbound_fluid
201 {
202 return new_fluid (SCM_UNDEFINED);
203 }
204 #undef FUNC_NAME
205
206 SCM_DEFINE (scm_fluid_p, "fluid?", 1, 0, 0,
207 (SCM obj),
208 "Return @code{#t} iff @var{obj} is a fluid; otherwise, return\n"
209 "@code{#f}.")
210 #define FUNC_NAME s_scm_fluid_p
211 {
212 return scm_from_bool (IS_FLUID (obj));
213 }
214 #undef FUNC_NAME
215
216 int
217 scm_is_fluid (SCM obj)
218 {
219 return IS_FLUID (obj);
220 }
221
222 /* Does not check type of `fluid'! */
223 static SCM
224 fluid_ref (SCM fluid)
225 {
226 SCM ret;
227 SCM fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
228
229 if (SCM_UNLIKELY (FLUID_NUM (fluid) >= SCM_SIMPLE_VECTOR_LENGTH (fluids)))
230 {
231 /* Lazily grow the current thread's dynamic state. */
232 grow_dynamic_state (SCM_I_CURRENT_THREAD->dynamic_state);
233
234 fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
235 }
236
237 ret = SCM_SIMPLE_VECTOR_REF (fluids, FLUID_NUM (fluid));
238 if (SCM_UNBNDP (ret))
239 return SCM_I_FLUID_DEFAULT (fluid);
240 else
241 return ret;
242 }
243
244 SCM_DEFINE (scm_fluid_ref, "fluid-ref", 1, 0, 0,
245 (SCM fluid),
246 "Return the value associated with @var{fluid} in the current\n"
247 "dynamic root. If @var{fluid} has not been set, then return\n"
248 "@code{#f}.")
249 #define FUNC_NAME s_scm_fluid_ref
250 {
251 SCM val;
252 SCM_VALIDATE_FLUID (1, fluid);
253 val = fluid_ref (fluid);
254 if (SCM_UNBNDP (val))
255 SCM_MISC_ERROR ("unbound fluid: ~S",
256 scm_list_1 (fluid));
257 return val;
258 }
259 #undef FUNC_NAME
260
261 SCM_DEFINE (scm_fluid_set_x, "fluid-set!", 2, 0, 0,
262 (SCM fluid, SCM value),
263 "Set the value associated with @var{fluid} in the current dynamic root.")
264 #define FUNC_NAME s_scm_fluid_set_x
265 {
266 SCM fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
267
268 SCM_VALIDATE_FLUID (1, fluid);
269
270 if (SCM_UNLIKELY (FLUID_NUM (fluid) >= SCM_SIMPLE_VECTOR_LENGTH (fluids)))
271 {
272 /* Lazily grow the current thread's dynamic state. */
273 grow_dynamic_state (SCM_I_CURRENT_THREAD->dynamic_state);
274
275 fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
276 }
277
278 SCM_SIMPLE_VECTOR_SET (fluids, FLUID_NUM (fluid), value);
279 return SCM_UNSPECIFIED;
280 }
281 #undef FUNC_NAME
282
283 SCM_DEFINE (scm_fluid_unset_x, "fluid-unset!", 1, 0, 0,
284 (SCM fluid),
285 "Unset the value associated with @var{fluid}.")
286 #define FUNC_NAME s_scm_fluid_unset_x
287 {
288 /* FIXME: really unset the default value, too? The current test
289 suite demands it, but I would prefer not to. */
290 SCM_SET_CELL_OBJECT_1 (fluid, SCM_UNDEFINED);
291 return scm_fluid_set_x (fluid, SCM_UNDEFINED);
292 }
293 #undef FUNC_NAME
294
295 SCM_DEFINE (scm_fluid_bound_p, "fluid-bound?", 1, 0, 0,
296 (SCM fluid),
297 "Return @code{#t} iff @var{fluid} is bound to a value.\n"
298 "Throw an error if @var{fluid} is not a fluid.")
299 #define FUNC_NAME s_scm_fluid_bound_p
300 {
301 SCM val;
302 SCM_VALIDATE_FLUID (1, fluid);
303 val = fluid_ref (fluid);
304 return scm_from_bool (! (SCM_UNBNDP (val)));
305 }
306 #undef FUNC_NAME
307
308 static SCM
309 apply_thunk (void *thunk)
310 {
311 return scm_call_0 (SCM_PACK (thunk));
312 }
313
314 SCM
315 scm_i_make_with_fluids (size_t n, SCM *fluids, SCM *vals)
316 {
317 SCM ret;
318
319 /* Ensure that there are no duplicates in the fluids set -- an N^2 operation,
320 but N will usually be small, so perhaps that's OK. */
321 {
322 size_t i, j = n;
323
324 while (j--)
325 for (i = 0; i < j; i++)
326 if (scm_is_eq (fluids[i], fluids[j]))
327 {
328 vals[i] = vals[j]; /* later bindings win */
329 n--;
330 fluids[j] = fluids[n];
331 vals[j] = vals[n];
332 break;
333 }
334 }
335
336 ret = scm_words (scm_tc7_with_fluids | (n << 8), 1 + n*2);
337 SCM_SET_CELL_WORD_1 (ret, n);
338
339 while (n--)
340 {
341 if (SCM_UNLIKELY (!IS_FLUID (fluids[n])))
342 scm_wrong_type_arg ("with-fluids", 0, fluids[n]);
343 SCM_SET_CELL_OBJECT (ret, 1 + n * 2, fluids[n]);
344 SCM_SET_CELL_OBJECT (ret, 2 + n * 2, vals[n]);
345 }
346
347 return ret;
348 }
349
350 void
351 scm_i_swap_with_fluids (SCM wf, SCM dynstate)
352 {
353 SCM fluids;
354 size_t i, max = 0;
355
356 fluids = DYNAMIC_STATE_FLUIDS (dynstate);
357
358 /* We could cache the max in the with-fluids, but that would take more mem,
359 and we're touching all the fluids anyway, so this per-swap traversal should
360 be OK. */
361 for (i = 0; i < SCM_WITH_FLUIDS_LEN (wf); i++)
362 {
363 size_t num = FLUID_NUM (SCM_WITH_FLUIDS_NTH_FLUID (wf, i));
364 max = (max > num) ? max : num;
365 }
366
367 if (SCM_UNLIKELY (max >= SCM_SIMPLE_VECTOR_LENGTH (fluids)))
368 {
369 /* Lazily grow the current thread's dynamic state. */
370 grow_dynamic_state (dynstate);
371
372 fluids = DYNAMIC_STATE_FLUIDS (dynstate);
373 }
374
375 /* Bind the fluids. Order doesn't matter, as all fluids are distinct. */
376 for (i = 0; i < SCM_WITH_FLUIDS_LEN (wf); i++)
377 {
378 size_t fluid_num;
379 SCM x;
380
381 fluid_num = FLUID_NUM (SCM_WITH_FLUIDS_NTH_FLUID (wf, i));
382 x = SCM_SIMPLE_VECTOR_REF (fluids, fluid_num);
383 SCM_SIMPLE_VECTOR_SET (fluids, fluid_num,
384 SCM_WITH_FLUIDS_NTH_VAL (wf, i));
385 SCM_WITH_FLUIDS_SET_NTH_VAL (wf, i, x);
386 }
387 }
388
389 SCM_DEFINE (scm_with_fluids, "with-fluids*", 3, 0, 0,
390 (SCM fluids, SCM values, SCM thunk),
391 "Set @var{fluids} to @var{values} temporary, and call @var{thunk}.\n"
392 "@var{fluids} must be a list of fluids and @var{values} must be the same\n"
393 "number of their values to be applied. Each substitution is done\n"
394 "one after another. @var{thunk} must be a procedure with no argument.")
395 #define FUNC_NAME s_scm_with_fluids
396 {
397 return scm_c_with_fluids (fluids, values,
398 apply_thunk, (void *) SCM_UNPACK (thunk));
399 }
400 #undef FUNC_NAME
401
402 SCM
403 scm_c_with_fluids (SCM fluids, SCM values, SCM (*cproc) (), void *cdata)
404 #define FUNC_NAME "scm_c_with_fluids"
405 {
406 SCM wf, ans;
407 long flen, vlen, i;
408 SCM *fluidsv, *valuesv;
409
410 SCM_VALIDATE_LIST_COPYLEN (1, fluids, flen);
411 SCM_VALIDATE_LIST_COPYLEN (2, values, vlen);
412 if (flen != vlen)
413 scm_out_of_range (s_scm_with_fluids, values);
414
415 if (SCM_UNLIKELY (flen == 0))
416 return cproc (cdata);
417
418 fluidsv = alloca (sizeof(SCM)*flen);
419 valuesv = alloca (sizeof(SCM)*flen);
420
421 for (i = 0; i < flen; i++)
422 {
423 fluidsv[i] = SCM_CAR (fluids);
424 fluids = SCM_CDR (fluids);
425 valuesv[i] = SCM_CAR (values);
426 values = SCM_CDR (values);
427 }
428
429 wf = scm_i_make_with_fluids (flen, fluidsv, valuesv);
430 scm_i_swap_with_fluids (wf, SCM_I_CURRENT_THREAD->dynamic_state);
431 scm_i_set_dynwinds (scm_cons (wf, scm_i_dynwinds ()));
432 ans = cproc (cdata);
433 scm_i_swap_with_fluids (wf, SCM_I_CURRENT_THREAD->dynamic_state);
434 scm_i_set_dynwinds (scm_cdr (scm_i_dynwinds ()));
435
436 return ans;
437 }
438 #undef FUNC_NAME
439
440 SCM_DEFINE (scm_with_fluid, "with-fluid*", 3, 0, 0,
441 (SCM fluid, SCM value, SCM thunk),
442 "Set @var{fluid} to @var{value} temporarily, and call @var{thunk}.\n"
443 "@var{thunk} must be a procedure with no argument.")
444 #define FUNC_NAME s_scm_with_fluid
445 {
446 return scm_c_with_fluid (fluid, value,
447 apply_thunk, (void *) SCM_UNPACK (thunk));
448 }
449 #undef FUNC_NAME
450
451 SCM
452 scm_c_with_fluid (SCM fluid, SCM value, SCM (*cproc) (), void *cdata)
453 #define FUNC_NAME "scm_c_with_fluid"
454 {
455 SCM ans, wf;
456
457 wf = scm_i_make_with_fluids (1, &fluid, &value);
458 scm_i_swap_with_fluids (wf, SCM_I_CURRENT_THREAD->dynamic_state);
459 scm_i_set_dynwinds (scm_cons (wf, scm_i_dynwinds ()));
460 ans = cproc (cdata);
461 scm_i_swap_with_fluids (wf, SCM_I_CURRENT_THREAD->dynamic_state);
462 scm_i_set_dynwinds (scm_cdr (scm_i_dynwinds ()));
463
464 return ans;
465 }
466 #undef FUNC_NAME
467
468 static void
469 swap_fluid (SCM data)
470 {
471 SCM f = SCM_CAR (data);
472 SCM t = fluid_ref (f);
473 scm_fluid_set_x (f, SCM_CDR (data));
474 SCM_SETCDR (data, t);
475 }
476
477 void
478 scm_dynwind_fluid (SCM fluid, SCM value)
479 {
480 SCM data = scm_cons (fluid, value);
481 scm_dynwind_rewind_handler_with_scm (swap_fluid, data, SCM_F_WIND_EXPLICITLY);
482 scm_dynwind_unwind_handler_with_scm (swap_fluid, data, SCM_F_WIND_EXPLICITLY);
483 }
484
485 SCM
486 scm_i_make_initial_dynamic_state ()
487 {
488 SCM fluids = scm_c_make_vector (allocated_fluids_len, SCM_BOOL_F);
489 return scm_cell (scm_tc7_dynamic_state, SCM_UNPACK (fluids));
490 }
491
492 SCM_DEFINE (scm_make_dynamic_state, "make-dynamic-state", 0, 1, 0,
493 (SCM parent),
494 "Return a copy of the dynamic state object @var{parent}\n"
495 "or of the current dynamic state when @var{parent} is omitted.")
496 #define FUNC_NAME s_scm_make_dynamic_state
497 {
498 SCM fluids;
499
500 if (SCM_UNBNDP (parent))
501 parent = scm_current_dynamic_state ();
502
503 SCM_ASSERT (IS_DYNAMIC_STATE (parent), parent, SCM_ARG1, FUNC_NAME);
504 fluids = scm_vector_copy (DYNAMIC_STATE_FLUIDS (parent));
505 return scm_cell (scm_tc7_dynamic_state, SCM_UNPACK (fluids));
506 }
507 #undef FUNC_NAME
508
509 SCM_DEFINE (scm_dynamic_state_p, "dynamic-state?", 1, 0, 0,
510 (SCM obj),
511 "Return @code{#t} if @var{obj} is a dynamic state object;\n"
512 "return @code{#f} otherwise")
513 #define FUNC_NAME s_scm_dynamic_state_p
514 {
515 return scm_from_bool (IS_DYNAMIC_STATE (obj));
516 }
517 #undef FUNC_NAME
518
519 int
520 scm_is_dynamic_state (SCM obj)
521 {
522 return IS_DYNAMIC_STATE (obj);
523 }
524
525 SCM_DEFINE (scm_current_dynamic_state, "current-dynamic-state", 0, 0, 0,
526 (),
527 "Return the current dynamic state object.")
528 #define FUNC_NAME s_scm_current_dynamic_state
529 {
530 return SCM_I_CURRENT_THREAD->dynamic_state;
531 }
532 #undef FUNC_NAME
533
534 SCM_DEFINE (scm_set_current_dynamic_state, "set-current-dynamic-state", 1,0,0,
535 (SCM state),
536 "Set the current dynamic state object to @var{state}\n"
537 "and return the previous current dynamic state object.")
538 #define FUNC_NAME s_scm_set_current_dynamic_state
539 {
540 scm_i_thread *t = SCM_I_CURRENT_THREAD;
541 SCM old = t->dynamic_state;
542 SCM_ASSERT (IS_DYNAMIC_STATE (state), state, SCM_ARG1, FUNC_NAME);
543 t->dynamic_state = state;
544 return old;
545 }
546 #undef FUNC_NAME
547
548 static void
549 swap_dynamic_state (SCM loc)
550 {
551 SCM_SETCAR (loc, scm_set_current_dynamic_state (SCM_CAR (loc)));
552 }
553
554 void
555 scm_dynwind_current_dynamic_state (SCM state)
556 {
557 SCM loc = scm_cons (state, SCM_EOL);
558 SCM_ASSERT (IS_DYNAMIC_STATE (state), state, SCM_ARG1, NULL);
559 scm_dynwind_rewind_handler_with_scm (swap_dynamic_state, loc,
560 SCM_F_WIND_EXPLICITLY);
561 scm_dynwind_unwind_handler_with_scm (swap_dynamic_state, loc,
562 SCM_F_WIND_EXPLICITLY);
563 }
564
565 void *
566 scm_c_with_dynamic_state (SCM state, void *(*func)(void *), void *data)
567 {
568 void *result;
569 scm_dynwind_begin (SCM_F_DYNWIND_REWINDABLE);
570 scm_dynwind_current_dynamic_state (state);
571 result = func (data);
572 scm_dynwind_end ();
573 return result;
574 }
575
576 SCM_DEFINE (scm_with_dynamic_state, "with-dynamic-state", 2, 0, 0,
577 (SCM state, SCM proc),
578 "Call @var{proc} while @var{state} is the current dynamic\n"
579 "state object.")
580 #define FUNC_NAME s_scm_with_dynamic_state
581 {
582 SCM result;
583 scm_dynwind_begin (SCM_F_DYNWIND_REWINDABLE);
584 scm_dynwind_current_dynamic_state (state);
585 result = scm_call_0 (proc);
586 scm_dynwind_end ();
587 return result;
588 }
589 #undef FUNC_NAME
590
591
592 void
593 scm_init_fluids ()
594 {
595 #include "libguile/fluids.x"
596 }
597
598 /*
599 Local Variables:
600 c-file-style: "gnu"
601 End:
602 */