Merge branch 'master' into boehm-demers-weiser-gc
[bpt/guile.git] / libguile / fluids.c
CommitLineData
dbb605f5 1/* Copyright (C) 1996,1997,2000,2001, 2004, 2006, 2007, 2008 Free Software Foundation, Inc.
9482a297 2 *
73be1d9e
MV
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version.
9482a297 7 *
73be1d9e
MV
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
9482a297 12 *
73be1d9e
MV
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
92205699 15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
73be1d9e 16 */
9482a297 17
dbb605f5
LC
18#ifdef HAVE_CONFIG_H
19# include <config.h>
20#endif
21
9de87eea
MV
22#include <stdio.h>
23#include <string.h>
8b039053 24#include <assert.h>
1bbd0b84 25
a0599745
MD
26#include "libguile/_scm.h"
27#include "libguile/print.h"
28#include "libguile/smob.h"
29#include "libguile/dynwind.h"
30#include "libguile/fluids.h"
31#include "libguile/alist.h"
32#include "libguile/eval.h"
33#include "libguile/ports.h"
143e0902 34#include "libguile/deprecation.h"
c96d76b8 35#include "libguile/lang.h"
a0599745 36#include "libguile/validate.h"
9482a297 37
9de87eea
MV
38#define FLUID_GROW 20
39
40/* A lot of the complexity below stems from the desire to reuse fluid
41 slots. Normally, fluids should be pretty global and long-lived
42 things, so that reusing their slots should not be overly critical,
43 but it is the right thing to do nevertheless. The code therefore
44 puts the burdon on allocating and collection fluids and keeps
45 accessing fluids lock free. This is achieved by manipulating the
46 global state of the fluid machinery mostly in single threaded
47 sections.
48
49 Reusing a fluid slot means that it must be reset to #f in all
50 dynamic states. We do this by maintaining a weak list of all
51 dynamic states, which is used after a GC to do the resetting.
52
53 Also, the fluid vectors in the dynamic states need to grow from
54 time to time when more fluids are created. We do this in a single
55 threaded section so that threads do not need to lock when accessing
56 a fluid in the normal way.
57*/
9482a297 58
9de87eea
MV
59static scm_i_pthread_mutex_t fluid_admin_mutex = SCM_I_PTHREAD_MUTEX_INITIALIZER;
60
61/* Protected by fluid_admin_mutex, but also accessed during GC. See
62 next_fluid_num for a discussion of this.
63 */
64static size_t allocated_fluids_len = 0;
65static size_t allocated_fluids_num = 0;
66static char *allocated_fluids = NULL;
67
68static scm_t_bits tc16_fluid;
69
70#define IS_FLUID(x) SCM_SMOB_PREDICATE(tc16_fluid, (x))
71#define FLUID_NUM(x) ((size_t)SCM_SMOB_DATA(x))
72#define FLUID_NEXT(x) SCM_SMOB_OBJECT_2(x)
645dd3fc 73#define FLUID_NEXT_LOC(x) SCM_SMOB_OBJECT_2_LOC(x)
9de87eea
MV
74#define SET_FLUID_NEXT(x,y) SCM_SET_SMOB_OBJECT_2((x), (y))
75
76static scm_t_bits tc16_dynamic_state;
77
78#define IS_DYNAMIC_STATE(x) SCM_SMOB_PREDICATE(tc16_dynamic_state, (x))
79#define DYNAMIC_STATE_FLUIDS(x) SCM_SMOB_OBJECT(x)
80#define SET_DYNAMIC_STATE_FLUIDS(x, y) SCM_SET_SMOB_OBJECT((x), (y))
81#define DYNAMIC_STATE_NEXT(x) SCM_SMOB_OBJECT_2(x)
645dd3fc 82#define DYNAMIC_STATE_NEXT_LOC(x) SCM_SMOB_OBJECT_2_LOC(x)
9de87eea
MV
83#define SET_DYNAMIC_STATE_NEXT(x, y) SCM_SET_SMOB_OBJECT_2((x), (y))
84
9de87eea 85
8b039053
LC
86\f
87/* Grow STATE so that it can hold up to ALLOCATED_FLUIDS_NUM fluids. */
9482a297 88static void
8b039053 89grow_dynamic_state (SCM state)
9de87eea 90{
8b039053
LC
91 SCM new_fluids;
92 SCM old_fluids = DYNAMIC_STATE_FLUIDS (state);
93 size_t i, new_len, old_len = SCM_SIMPLE_VECTOR_LENGTH (old_fluids);
9de87eea 94
8b039053
LC
95 retry:
96 new_len = allocated_fluids_num;
97 new_fluids = scm_c_make_vector (new_len, SCM_BOOL_F);
9de87eea 98
8b039053
LC
99 scm_i_pthread_mutex_lock (&fluid_admin_mutex);
100 if (new_len != allocated_fluids_num)
9482a297 101 {
8b039053
LC
102 /* We lost the race. */
103 scm_i_pthread_mutex_unlock (&fluid_admin_mutex);
104 goto retry;
9482a297
MV
105 }
106
8b039053
LC
107 assert (allocated_fluids_num > old_len);
108
109 for (i = 0; i < old_len; i++)
110 SCM_SIMPLE_VECTOR_SET (new_fluids, i,
111 SCM_SIMPLE_VECTOR_REF (old_fluids, i));
112 SET_DYNAMIC_STATE_FLUIDS (state, new_fluids);
113
114 scm_i_pthread_mutex_unlock (&fluid_admin_mutex);
9482a297
MV
115}
116
9482a297 117static int
e81d98ec 118fluid_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
9482a297 119{
ed4d7cee 120 scm_puts ("#<fluid ", port);
9de87eea 121 scm_intprint ((int) FLUID_NUM (exp), 10, port);
ed4d7cee
GB
122 scm_putc ('>', port);
123 return 1;
9482a297
MV
124}
125
9de87eea 126static size_t
ed4d7cee 127next_fluid_num ()
9482a297 128{
9de87eea
MV
129 size_t n;
130
661ae7ab
MV
131 scm_dynwind_begin (0);
132 scm_i_dynwind_pthread_mutex_lock (&fluid_admin_mutex);
9de87eea 133
29295b0c
NJ
134 if ((allocated_fluids_len > 0) &&
135 (allocated_fluids_num == allocated_fluids_len))
9de87eea
MV
136 {
137 /* All fluid numbers are in use. Run a GC to try to free some
138 up.
139 */
140 scm_gc ();
141 }
142
143 if (allocated_fluids_num < allocated_fluids_len)
144 {
145 for (n = 0; n < allocated_fluids_len; n++)
146 if (allocated_fluids[n] == 0)
147 break;
148 }
149 else
150 {
8b039053
LC
151 /* Grow the vector of allocated fluids. */
152 /* FIXME: Since we use `scm_malloc ()', ALLOCATED_FLUIDS is scanned by
153 the GC; therefore, all fluids remain reachable for the entire
154 program lifetime. Hopefully this is not a problem in practice. */
d3075c52
LC
155 char *prev_allocated_fluids;
156 char *new_allocated_fluids =
9de87eea
MV
157 scm_malloc (allocated_fluids_len + FLUID_GROW);
158
159 /* Copy over old values and initialize rest. GC can not run
160 during these two operations since there is no safe point in
161 them.
162 */
163 memcpy (new_allocated_fluids, allocated_fluids, allocated_fluids_len);
164 memset (new_allocated_fluids + allocated_fluids_len, 0, FLUID_GROW);
165 n = allocated_fluids_len;
d3075c52
LC
166
167 prev_allocated_fluids = allocated_fluids;
8b039053
LC
168
169 /* Update the vector of allocated fluids. Dynamic states will
170 eventually be lazily grown to accomodate the new value of
171 ALLOCATED_FLUIDS_LEN in `fluid-ref' and `fluid-set!'. */
9de87eea
MV
172 allocated_fluids = new_allocated_fluids;
173 allocated_fluids_len += FLUID_GROW;
d3075c52
LC
174
175 if (prev_allocated_fluids != NULL)
176 free (prev_allocated_fluids);
9de87eea
MV
177 }
178
179 allocated_fluids_num += 1;
180 allocated_fluids[n] = 1;
181
661ae7ab 182 scm_dynwind_end ();
9482a297
MV
183 return n;
184}
185
a1ec6916 186SCM_DEFINE (scm_make_fluid, "make-fluid", 0, 0, 0,
ed4d7cee
GB
187 (),
188 "Return a newly created fluid.\n"
9de87eea
MV
189 "Fluids are objects that can hold one\n"
190 "value per dynamic state. That is, modifications to this value are\n"
191 "only visible to code that executes with the same dynamic state as\n"
192 "the modifying code. When a new dynamic state is constructed, it\n"
193 "inherits the values from its parent. Because each thread normally executes\n"
194 "with its own dynamic state, you can use fluids for thread local storage.")
1bbd0b84 195#define FUNC_NAME s_scm_make_fluid
9482a297 196{
9de87eea 197 SCM fluid;
9482a297 198
9de87eea
MV
199 SCM_NEWSMOB2 (fluid, tc16_fluid,
200 (scm_t_bits) next_fluid_num (), SCM_UNPACK (SCM_EOL));
201
9de87eea 202 return fluid;
9482a297 203}
1bbd0b84 204#undef FUNC_NAME
9482a297 205
a1ec6916 206SCM_DEFINE (scm_fluid_p, "fluid?", 1, 0, 0,
ed4d7cee 207 (SCM obj),
1e6808ea
MG
208 "Return @code{#t} iff @var{obj} is a fluid; otherwise, return\n"
209 "@code{#f}.")
1bbd0b84 210#define FUNC_NAME s_scm_fluid_p
b3460a50 211{
9de87eea 212 return scm_from_bool (IS_FLUID (obj));
b3460a50 213}
1bbd0b84 214#undef FUNC_NAME
b3460a50 215
9de87eea
MV
216int
217scm_is_fluid (SCM obj)
218{
219 return IS_FLUID (obj);
220}
221
8b039053 222
9de87eea 223
a1ec6916 224SCM_DEFINE (scm_fluid_ref, "fluid-ref", 1, 0, 0,
ed4d7cee 225 (SCM fluid),
1e6808ea
MG
226 "Return the value associated with @var{fluid} in the current\n"
227 "dynamic root. If @var{fluid} has not been set, then return\n"
228 "@code{#f}.")
1bbd0b84 229#define FUNC_NAME s_scm_fluid_ref
9482a297 230{
9de87eea 231 SCM fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
9482a297 232
ed4d7cee 233 SCM_VALIDATE_FLUID (1, fluid);
8b039053
LC
234
235 if (SCM_UNLIKELY (FLUID_NUM (fluid) >= SCM_SIMPLE_VECTOR_LENGTH (fluids)))
236 {
237 /* We should only get there when the current thread's dynamic state
238 turns out to be too small compared to the set of currently allocated
239 fluids. */
240 assert (SCM_SIMPLE_VECTOR_LENGTH (fluids) < allocated_fluids_num);
241
242 /* Lazily grow the current thread's dynamic state. */
243 grow_dynamic_state (SCM_I_CURRENT_THREAD->dynamic_state);
244
245 fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
246 }
247
9de87eea 248 return SCM_SIMPLE_VECTOR_REF (fluids, FLUID_NUM (fluid));
9482a297 249}
1bbd0b84 250#undef FUNC_NAME
9482a297 251
a1ec6916 252SCM_DEFINE (scm_fluid_set_x, "fluid-set!", 2, 0, 0,
ed4d7cee
GB
253 (SCM fluid, SCM value),
254 "Set the value associated with @var{fluid} in the current dynamic root.")
1bbd0b84 255#define FUNC_NAME s_scm_fluid_set_x
9482a297 256{
9de87eea 257 SCM fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
9482a297 258
ed4d7cee 259 SCM_VALIDATE_FLUID (1, fluid);
8b039053
LC
260
261 if (SCM_UNLIKELY (FLUID_NUM (fluid) >= SCM_SIMPLE_VECTOR_LENGTH (fluids)))
262 {
263 /* We should only get there when the current thread's dynamic state
264 turns out to be too small compared to the set of currently allocated
265 fluids. */
266 assert (SCM_SIMPLE_VECTOR_LENGTH (fluids) < allocated_fluids_num);
267
268 /* Lazily grow the current thread's dynamic state. */
269 grow_dynamic_state (SCM_I_CURRENT_THREAD->dynamic_state);
270
271 fluids = DYNAMIC_STATE_FLUIDS (SCM_I_CURRENT_THREAD->dynamic_state);
272 }
273
9de87eea 274 SCM_SIMPLE_VECTOR_SET (fluids, FLUID_NUM (fluid), value);
86f9f9ae 275 return SCM_UNSPECIFIED;
9482a297 276}
1bbd0b84 277#undef FUNC_NAME
9482a297 278
bebd3fba
MV
279static void
280swap_fluids (SCM data)
b3460a50 281{
bebd3fba
MV
282 SCM fluids = SCM_CAR (data), vals = SCM_CDR (data);
283
c96d76b8 284 while (!SCM_NULL_OR_NIL_P (fluids))
b3460a50
MV
285 {
286 SCM fl = SCM_CAR (fluids);
287 SCM old_val = scm_fluid_ref (fl);
288 scm_fluid_set_x (fl, SCM_CAR (vals));
289 SCM_SETCAR (vals, old_val);
290 fluids = SCM_CDR (fluids);
291 vals = SCM_CDR (vals);
292 }
293}
294
295/* Swap the fluid values in reverse order. This is important when the
9de87eea
MV
296 same fluid appears multiple times in the fluids list.
297*/
b3460a50 298
bebd3fba
MV
299static void
300swap_fluids_reverse_aux (SCM fluids, SCM vals)
b3460a50 301{
c96d76b8 302 if (!SCM_NULL_OR_NIL_P (fluids))
b3460a50
MV
303 {
304 SCM fl, old_val;
305
bebd3fba 306 swap_fluids_reverse_aux (SCM_CDR (fluids), SCM_CDR (vals));
b3460a50
MV
307 fl = SCM_CAR (fluids);
308 old_val = scm_fluid_ref (fl);
309 scm_fluid_set_x (fl, SCM_CAR (vals));
310 SCM_SETCAR (vals, old_val);
311 }
312}
313
bebd3fba
MV
314static void
315swap_fluids_reverse (SCM data)
316{
317 swap_fluids_reverse_aux (SCM_CAR (data), SCM_CDR (data));
318}
1bbd0b84
GB
319
320static SCM
321apply_thunk (void *thunk)
322{
fdc28395 323 return scm_call_0 (SCM_PACK (thunk));
1bbd0b84
GB
324}
325
a1ec6916 326SCM_DEFINE (scm_with_fluids, "with-fluids*", 3, 0, 0,
ed4d7cee
GB
327 (SCM fluids, SCM values, SCM thunk),
328 "Set @var{fluids} to @var{values} temporary, and call @var{thunk}.\n"
329 "@var{fluids} must be a list of fluids and @var{values} must be the same\n"
330 "number of their values to be applied. Each substitution is done\n"
331 "one after another. @var{thunk} must be a procedure with no argument.")
1bbd0b84
GB
332#define FUNC_NAME s_scm_with_fluids
333{
bebd3fba
MV
334 return scm_c_with_fluids (fluids, values,
335 apply_thunk, (void *) SCM_UNPACK (thunk));
1bbd0b84
GB
336}
337#undef FUNC_NAME
b3460a50
MV
338
339SCM
143e0902
MV
340scm_c_with_fluids (SCM fluids, SCM values, SCM (*cproc) (), void *cdata)
341#define FUNC_NAME "scm_c_with_fluids"
b3460a50 342{
bebd3fba 343 SCM ans, data;
c014a02e 344 long flen, vlen;
b3460a50 345
c1bfcf60 346 SCM_VALIDATE_LIST_COPYLEN (1, fluids, flen);
ed4d7cee 347 SCM_VALIDATE_LIST_COPYLEN (2, values, vlen);
b3460a50 348 if (flen != vlen)
ed4d7cee 349 scm_out_of_range (s_scm_with_fluids, values);
b3460a50 350
bebd3fba
MV
351 if (flen == 1)
352 return scm_c_with_fluid (SCM_CAR (fluids), SCM_CAR (values),
353 cproc, cdata);
354
355 data = scm_cons (fluids, values);
661ae7ab
MV
356 scm_dynwind_begin (SCM_F_DYNWIND_REWINDABLE);
357 scm_dynwind_rewind_handler_with_scm (swap_fluids, data,
16c5cac2 358 SCM_F_WIND_EXPLICITLY);
661ae7ab 359 scm_dynwind_unwind_handler_with_scm (swap_fluids_reverse, data,
16c5cac2 360 SCM_F_WIND_EXPLICITLY);
b3460a50 361 ans = cproc (cdata);
661ae7ab 362 scm_dynwind_end ();
b3460a50
MV
363 return ans;
364}
c1bfcf60 365#undef FUNC_NAME
b3460a50 366
bebd3fba
MV
367SCM_DEFINE (scm_with_fluid, "with-fluid*", 3, 0, 0,
368 (SCM fluid, SCM value, SCM thunk),
369 "Set @var{fluid} to @var{value} temporarily, and call @var{thunk}.\n"
370 "@var{thunk} must be a procedure with no argument.")
371#define FUNC_NAME s_scm_with_fluid
372{
373 return scm_c_with_fluid (fluid, value,
374 apply_thunk, (void *) SCM_UNPACK (thunk));
375}
376#undef FUNC_NAME
377
143e0902
MV
378SCM
379scm_c_with_fluid (SCM fluid, SCM value, SCM (*cproc) (), void *cdata)
380#define FUNC_NAME "scm_c_with_fluid"
381{
bebd3fba
MV
382 SCM ans;
383
661ae7ab
MV
384 scm_dynwind_begin (SCM_F_DYNWIND_REWINDABLE);
385 scm_dynwind_fluid (fluid, value);
bebd3fba 386 ans = cproc (cdata);
661ae7ab 387 scm_dynwind_end ();
bebd3fba 388 return ans;
143e0902
MV
389}
390#undef FUNC_NAME
b3460a50 391
ef20bf70
MV
392static void
393swap_fluid (SCM data)
394{
395 SCM f = SCM_CAR (data);
396 SCM t = scm_fluid_ref (f);
397 scm_fluid_set_x (f, SCM_CDR (data));
398 SCM_SETCDR (data, t);
399}
400
401void
661ae7ab 402scm_dynwind_fluid (SCM fluid, SCM value)
ef20bf70
MV
403{
404 SCM data = scm_cons (fluid, value);
661ae7ab
MV
405 scm_dynwind_rewind_handler_with_scm (swap_fluid, data, SCM_F_WIND_EXPLICITLY);
406 scm_dynwind_unwind_handler_with_scm (swap_fluid, data, SCM_F_WIND_EXPLICITLY);
ef20bf70
MV
407}
408
9de87eea
MV
409SCM
410scm_i_make_initial_dynamic_state ()
411{
412 SCM fluids = scm_c_make_vector (allocated_fluids_len, SCM_BOOL_F);
413 SCM state;
414 SCM_NEWSMOB2 (state, tc16_dynamic_state,
415 SCM_UNPACK (fluids), SCM_UNPACK (SCM_EOL));
9de87eea
MV
416 return state;
417}
418
419SCM_DEFINE (scm_make_dynamic_state, "make-dynamic-state", 0, 1, 0,
420 (SCM parent),
421 "Return a copy of the dynamic state object @var{parent}\n"
422 "or of the current dynamic state when @var{parent} is omitted.")
423#define FUNC_NAME s_scm_make_dynamic_state
424{
425 SCM fluids, state;
426
427 if (SCM_UNBNDP (parent))
428 parent = scm_current_dynamic_state ();
429
430 scm_assert_smob_type (tc16_dynamic_state, parent);
431 fluids = scm_vector_copy (DYNAMIC_STATE_FLUIDS (parent));
432 SCM_NEWSMOB2 (state, tc16_dynamic_state,
433 SCM_UNPACK (fluids), SCM_UNPACK (SCM_EOL));
434
9de87eea
MV
435 return state;
436}
437#undef FUNC_NAME
438
439SCM_DEFINE (scm_dynamic_state_p, "dynamic-state?", 1, 0, 0,
440 (SCM obj),
441 "Return @code{#t} if @var{obj} is a dynamic state object;\n"
442 "return @code{#f} otherwise")
443#define FUNC_NAME s_scm_dynamic_state_p
444{
445 return scm_from_bool (IS_DYNAMIC_STATE (obj));
446}
447#undef FUNC_NAME
448
449int
450scm_is_dynamic_state (SCM obj)
451{
452 return IS_DYNAMIC_STATE (obj);
453}
454
455SCM_DEFINE (scm_current_dynamic_state, "current-dynamic-state", 0, 0, 0,
456 (),
457 "Return the current dynamic state object.")
458#define FUNC_NAME s_scm_current_dynamic_state
459{
460 return SCM_I_CURRENT_THREAD->dynamic_state;
461}
462#undef FUNC_NAME
463
464SCM_DEFINE (scm_set_current_dynamic_state, "set-current-dynamic-state", 1,0,0,
465 (SCM state),
466 "Set the current dynamic state object to @var{state}\n"
467 "and return the previous current dynamic state object.")
468#define FUNC_NAME s_scm_set_current_dynamic_state
469{
470 scm_i_thread *t = SCM_I_CURRENT_THREAD;
471 SCM old = t->dynamic_state;
472 scm_assert_smob_type (tc16_dynamic_state, state);
473 t->dynamic_state = state;
474 return old;
475}
476#undef FUNC_NAME
477
478static void
479swap_dynamic_state (SCM loc)
480{
481 SCM_SETCAR (loc, scm_set_current_dynamic_state (SCM_CAR (loc)));
482}
483
484void
661ae7ab 485scm_dynwind_current_dynamic_state (SCM state)
9de87eea
MV
486{
487 SCM loc = scm_cons (state, SCM_EOL);
488 scm_assert_smob_type (tc16_dynamic_state, state);
661ae7ab 489 scm_dynwind_rewind_handler_with_scm (swap_dynamic_state, loc,
9de87eea 490 SCM_F_WIND_EXPLICITLY);
661ae7ab 491 scm_dynwind_unwind_handler_with_scm (swap_dynamic_state, loc,
9de87eea
MV
492 SCM_F_WIND_EXPLICITLY);
493}
494
495void *
496scm_c_with_dynamic_state (SCM state, void *(*func)(void *), void *data)
497{
498 void *result;
661ae7ab
MV
499 scm_dynwind_begin (SCM_F_DYNWIND_REWINDABLE);
500 scm_dynwind_current_dynamic_state (state);
9de87eea 501 result = func (data);
661ae7ab 502 scm_dynwind_end ();
9de87eea
MV
503 return result;
504}
505
506SCM_DEFINE (scm_with_dynamic_state, "with-dynamic-state", 2, 0, 0,
507 (SCM state, SCM proc),
508 "Call @var{proc} while @var{state} is the current dynamic\n"
509 "state object.")
510#define FUNC_NAME s_scm_with_dynamic_state
511{
512 SCM result;
661ae7ab
MV
513 scm_dynwind_begin (SCM_F_DYNWIND_REWINDABLE);
514 scm_dynwind_current_dynamic_state (state);
9de87eea 515 result = scm_call_0 (proc);
661ae7ab 516 scm_dynwind_end ();
9de87eea
MV
517 return result;
518}
519#undef FUNC_NAME
520
521void
522scm_fluids_prehistory ()
523{
524 tc16_fluid = scm_make_smob_type ("fluid", 0);
9de87eea
MV
525 scm_set_smob_print (tc16_fluid, fluid_print);
526
527 tc16_dynamic_state = scm_make_smob_type ("dynamic-state", 0);
9de87eea
MV
528}
529
9482a297
MV
530void
531scm_init_fluids ()
532{
a0599745 533#include "libguile/fluids.x"
9482a297 534}
89e00824
ML
535
536/*
537 Local Variables:
538 c-file-style: "gnu"
539 End:
540*/