d88367fc74162828afaf1f306572f4f94da57079
[ruby-eet.git] / ext / ext.c
1 /*
2  * $Id: ext.c 70 2005-07-15 20:31:58Z tilman $
3  *
4  * Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 #include <Eet.h>
27 #include <ruby.h>
28 #include <st.h>
29 #include <locale.h>
30
31 #define CHECK_KEY(key) \
32         if (rb_funcall (key, id_include, 1, INT2FIX (0)) == Qtrue) \
33                 rb_raise (rb_eArgError, "key must not contain binary zeroes");
34
35 #define CHECK_CLOSED(ef) \
36         if (!*(ef)) \
37                 rb_raise (rb_eIOError, "closed stream");
38
39 #ifdef WORDS_BIGENDIAN
40 # define BSWAP32(x) \
41         ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
42         (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
43 #else /* !WORDS_BIGENDIAN */
44 # define BSWAP32(x) (x)
45 #endif /* WORDS_BIGENDIAN */
46
47 static VALUE c_close (VALUE self);
48
49 static VALUE cStream, cChunk,
50              eEetError, eNameError, ePropError, eStreamError,
51              eBadElementError,
52              sym_lossy, sym_level, sym_quality, sym_char, sym_short,
53              sym_long_long, sym_double;
54 static ID id_include, id_serialize, id_keys, id_pack,
55           id_to_eet_chunks, id_to_eet_name, id_to_eet_properties,
56           id_tag, id_data;
57
58 static void
59 c_free (Eet_File **ef)
60 {
61         if (*ef) {
62                 eet_close (*ef);
63                 *ef = NULL;
64
65                 eet_shutdown ();
66         }
67
68         free (ef);
69 }
70
71 static VALUE
72 c_alloc (VALUE klass)
73 {
74         Eet_File **ef = NULL;
75
76         return Data_Make_Struct (klass, Eet_File *, NULL, c_free, ef);
77 }
78
79 /*
80  * call-seq:
81  *  Eet::File.open(file [, mode] )                -> ef or nil
82  *  Eet::File.open(file [, mode] ) { |ef| block } -> nil
83  *
84  * If a block isn't specified, Eet::File.open is a synonym for
85  * Eet::File.new.
86  * If a block is given, it will be invoked with the
87  * Eet::File object as a parameter, and the file will be
88  * automatically closed when the block terminates. The call always
89  * returns +nil+ in this case.
90  */
91 static VALUE
92 c_open (int argc, VALUE *argv, VALUE klass)
93 {
94         VALUE obj = rb_class_new_instance (argc, argv, klass);
95
96         if (rb_block_given_p ())
97                 return rb_ensure (rb_yield, obj, c_close, obj);
98         else
99                 return obj;
100 }
101
102 /*
103  * call-seq:
104  *  Eet::File.new(file [, mode] ) -> ef or nil
105  *
106  * Creates an Eet::File object for _file_.
107  *
108  * _file_ is opened with the specified mode (defaulting to "r").
109  * Possible values for _mode_ are "r" for read-only access,
110  * "w" for write-only access and "r+" for read/write access.
111  */
112 static VALUE
113 c_init (int argc, VALUE *argv, VALUE self)
114 {
115         VALUE file = Qnil, mode = Qnil;
116         Eet_File **ef = NULL;
117         Eet_File_Mode m = EET_FILE_MODE_READ;
118         const char *tmp, *cfile;
119
120         Data_Get_Struct (self, Eet_File *, ef);
121
122         rb_scan_args (argc, argv, "11", &file, &mode);
123
124         cfile = StringValuePtr (file);
125
126         if (!NIL_P (mode)) {
127                 tmp = StringValuePtr (mode);
128                 if (!strcmp (tmp, "r+"))
129                         m = EET_FILE_MODE_READ_WRITE;
130                 else if (!strcmp (tmp, "w"))
131                         m = EET_FILE_MODE_WRITE;
132                 else if (strcmp (tmp, "r"))
133                         rb_raise (rb_eArgError, "illegal access mode %s", tmp);
134         }
135
136         eet_init ();
137
138         *ef = eet_open (cfile, m);
139         if (!*ef) {
140                 switch (m) {
141                         case EET_FILE_MODE_READ_WRITE:
142                         case EET_FILE_MODE_WRITE:
143                                 tmp = "Permission denied - %s";
144                                 break;
145                         default:
146                                 tmp = "File not found - %s";
147                                 break;
148                 }
149
150                 rb_raise (rb_eRuntimeError, tmp, cfile);
151         }
152
153         return self;
154 }
155
156 /*
157  * call-seq:
158  *  ef.close -> ef
159  *
160  * Closes _ef_ and flushes any pending writes.
161  * _ef_ is unavailable for any further data operations;
162  * an +IOError+ is raised if such an attempt is made.
163  *
164  * Eet::File objects are automatically closed when they
165  * are claimed by the garbage collector.
166  */
167 static VALUE
168 c_close (VALUE self)
169 {
170         Eet_File **ef = NULL;
171
172         Data_Get_Struct (self, Eet_File *, ef);
173         CHECK_CLOSED (ef);
174
175         eet_close (*ef);
176         *ef = NULL;
177
178         eet_shutdown ();
179
180         return self;
181 }
182
183 /*
184  * call-seq:
185  *  ef.list([glob]) -> array
186  *
187  * Returns an Array of entries in _ef_ that match the shell glob
188  * _glob_ (defaulting to "*").
189  */
190 static VALUE
191 c_list (int argc, VALUE *argv, VALUE self)
192 {
193         VALUE glob = Qnil, ret;
194         Eet_File **ef = NULL;
195         char **entries, *tmp = "*";
196         int i, count = 0;
197
198         Data_Get_Struct (self, Eet_File *, ef);
199         CHECK_CLOSED (ef);
200
201         switch (eet_mode_get (*ef)) {
202                 case EET_FILE_MODE_READ:
203                 case EET_FILE_MODE_READ_WRITE:
204                         break;
205                 default:
206                         rb_raise (rb_eIOError, "cannot list entries");
207         }
208
209         rb_scan_args (argc, argv, "01", &glob);
210
211         if (!NIL_P (glob))
212                 tmp = StringValuePtr (glob);
213
214         ret = rb_ary_new ();
215
216         entries = eet_list (*ef, tmp, &count);
217
218         for (i = 0; i < count; i++)
219                 rb_ary_push (ret, rb_str_new2 (entries[i]));
220
221         free (entries);
222
223         return ret;
224 }
225
226 /*
227  * call-seq:
228  *  ef.delete(key) -> ef
229  *
230  * Deletes the entry from _ef_ that is stored as _key_.
231  * If the entry cannot be deleted, an +IOError+ is raised,
232  * otherwise _ef_ is returned.
233  */
234 static VALUE
235 c_delete (VALUE self, VALUE key)
236 {
237         Eet_File **ef = NULL;
238         char *ckey;
239
240         Data_Get_Struct (self, Eet_File *, ef);
241         CHECK_CLOSED (ef);
242
243         ckey = StringValuePtr (key);
244         CHECK_KEY (key);
245
246         if (!eet_delete (*ef, ckey))
247                 rb_raise (rb_eIOError, "cannot delete entry - %s", ckey);
248
249         return self;
250 }
251
252 /*
253  * call-seq:
254  *  ef.read(key) -> string
255  *
256  * Reads an entry from _ef_ that is stored as _key_.
257  * If the data cannot be read, an +IOError+ is raised,
258  * otherwise a String is returned that contains the data.
259  */
260 static VALUE
261 c_read (VALUE self, VALUE key)
262 {
263         VALUE ret;
264         Eet_File **ef = NULL;
265         void *data;
266         char *ckey;
267         int size = 0;
268
269         Data_Get_Struct (self, Eet_File *, ef);
270         CHECK_CLOSED (ef);
271
272         ckey = StringValuePtr (key);
273         CHECK_KEY (key);
274
275         data = eet_read (*ef, ckey, &size);
276         if (!data)
277                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
278
279         ret = rb_str_new (data, size);
280
281         free (data);
282
283         return ret;
284 }
285
286 /*
287  * call-seq:
288  *  ef.write(key, data [, compress] ) -> integer
289  *
290  * Stores _data_ in _ef_ as _key_.
291  * If _compress_ is true (which is the default), the data will be
292  * compressed.
293  * If the data cannot be written, an +IOError+ is raised,
294  * otherwise a the number of bytes written is returned.
295  */
296 static VALUE
297 c_write (int argc, VALUE *argv, VALUE self)
298 {
299         VALUE key = Qnil, buf = Qnil, comp = Qnil;
300         Eet_File **ef = NULL;
301         char *ckey, *cbuf;
302         int n;
303
304         Data_Get_Struct (self, Eet_File *, ef);
305         CHECK_CLOSED (ef);
306
307         rb_scan_args (argc, argv, "21", &key, &buf, &comp);
308
309         if (NIL_P (comp))
310                 comp = Qtrue;
311
312         ckey = StringValuePtr (key);
313         CHECK_KEY (key);
314         cbuf = StringValuePtr (buf);
315
316         n = eet_write (*ef, ckey,
317                        cbuf, RSTRING (buf)->len,
318                        comp == Qtrue);
319         if (!n)
320                 rb_raise (rb_eIOError, "couldn't write to file");
321         else
322                 return INT2FIX (n);
323 }
324
325 /*
326  * call-seq:
327  *  ef.read_image(key) -> array
328  *
329  * Reads an image entry from _ef_ that is stored as _key_.
330  * If the data cannot be read, an +IOError+ is raised,
331  * otherwise an Array is returned that contains the image data,
332  * the image width, the image height, a boolean that indicates
333  * whether the image has an alpha channel or not and a hash
334  * that contains the compression options that were used to store
335  * the image (see Eet::File#write_image).
336  */
337 static VALUE
338 c_read_image (VALUE self, VALUE key)
339 {
340         VALUE ret, comp;
341         Eet_File **ef = NULL;
342         void *data;
343         char *ckey;
344         int w = 0, h = 0, has_alpha = 0, level = 0, quality = 0, lossy = 0;
345
346         Data_Get_Struct (self, Eet_File *, ef);
347         CHECK_CLOSED (ef);
348
349         ckey = StringValuePtr (key);
350         CHECK_KEY (key);
351
352         data = eet_data_image_read (*ef, ckey, &w, &h,
353                                     &has_alpha, &level, &quality,
354                                     &lossy);
355         if (!data)
356                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
357
358         comp = rb_hash_new ();
359         rb_hash_aset (comp, sym_lossy, INT2FIX (lossy));
360         rb_hash_aset (comp, sym_level, INT2FIX (level));
361         rb_hash_aset (comp, sym_quality, INT2FIX (quality));
362
363         ret = rb_ary_new3 (5, rb_str_new (data, w * h * 4),
364                                INT2FIX (w), INT2FIX (h),
365                                has_alpha ? Qtrue : Qfalse, comp);
366         free (data);
367
368         return ret;
369 }
370
371 /*
372  * call-seq:
373  *  ef.write_image(key, image_data, w, h [, alpha] [, comp] ) -> integer
374  *
375  * Stores _image_data_ with width _w_ and height _h_ in _ef_ as _key_.
376  * Pass true for _alpha_ if the image contains an alpha channel.
377  * You can control how the image is compressed by passing _comp_, which
378  * is a hash whose :lossy entry is true if the image should be
379  * compressed lossily. If :lossy is true, the :quality entry
380  * (0-100, default 100) sets the compression quality.
381  * If :lossy is false, the :level entry (0-9, default 9) sets the
382  * compression level. If _comp_ isn't passed, then the
383  * image is stored losslessly.
384  * If the data cannot be written, an +IOError+ is raised,
385  * otherwise a the number of bytes written is returned.
386  */
387 static VALUE
388 c_write_image (int argc, VALUE *argv, VALUE self)
389 {
390         VALUE key = Qnil, buf = Qnil, w = Qnil, h = Qnil, has_alpha = Qnil;
391         VALUE comp = Qnil, tmp;
392         Eet_File **ef = NULL;
393         char *ckey, *cbuf;
394         int n, lossy = 0, level = 9, quality = 100;
395
396         Data_Get_Struct (self, Eet_File *, ef);
397         CHECK_CLOSED (ef);
398
399         rb_scan_args (argc, argv, "42", &key, &buf, &w, &h, &has_alpha,
400                       &comp);
401
402         if (NIL_P (has_alpha))
403                 has_alpha = Qfalse;
404
405         ckey = StringValuePtr (key);
406         CHECK_KEY (key);
407         cbuf = StringValuePtr (buf);
408         Check_Type (w, T_FIXNUM);
409         Check_Type (h, T_FIXNUM);
410
411         if (!NIL_P (comp)) {
412                 Check_Type (comp, T_HASH);
413
414                 tmp = rb_hash_aref (comp, sym_lossy);
415                 if (!NIL_P (tmp))
416                         lossy = FIX2INT (tmp);
417
418                 tmp = rb_hash_aref (comp, sym_level);
419                 if (!NIL_P (tmp))
420                         level = FIX2INT (tmp);
421
422                 tmp = rb_hash_aref (comp, sym_quality);
423                 if (!NIL_P (tmp))
424                         quality = FIX2INT (tmp);
425         }
426
427         if (!RSTRING (buf)->len)
428                 return INT2FIX (0);
429
430         n = eet_data_image_write (*ef, ckey, cbuf,
431                                   FIX2INT (w), FIX2INT (h),
432                                   has_alpha == Qtrue,
433                                   level, quality, lossy);
434         if (!n)
435                 rb_raise (rb_eIOError, "couldn't write to file");
436         else
437                 return INT2FIX (n);
438 }
439
440 static VALUE
441 stream_serialize (VALUE self)
442 {
443         VALUE ret;
444         struct RArray *stream;
445         long i;
446
447         stream = RARRAY (self);
448         if (!stream->len)
449                 return rb_str_new2 ("");
450
451         ret = rb_ary_new ();
452
453         for (i = 0; i < stream->len; i++) {
454                 VALUE str;
455
456                 if (rb_obj_is_kind_of (stream->ptr[i], cChunk) == Qfalse)
457                         rb_raise (eBadElementError, "stream member is not a Chunk");
458
459                 str = rb_funcall (stream->ptr[i], id_serialize, 0, NULL);
460
461                 rb_ary_push (ret, str);
462         }
463
464         return rb_ary_join (ret, Qnil);
465 }
466
467 static VALUE
468 chunk_init (VALUE self, VALUE tag, VALUE data)
469 {
470         long tag_len, data_len, tmp;
471
472         StringValue (tag);
473         StringValue (data);
474
475         if (rb_funcall (tag, id_include, 1, INT2FIX (0)) == Qtrue)
476                 rb_raise (rb_eArgError, "tag must not contain binary zeroes");
477
478         /* libeet uses a signed 32bit integer to store the
479          * chunk size, so make sure we don't overflow it
480          */
481         tag_len = RSTRING (tag)->len;
482         data_len = RSTRING (data)->len;
483         tmp = tag_len + 1 + data_len;
484
485         if (tmp < tag_len || tmp < data_len || tmp < 1 || tmp >= 2147483647L)
486                 rb_raise (rb_eArgError, "tag or data too long");
487
488         rb_ivar_set (self, id_tag, rb_str_dup_frozen (tag));
489         rb_ivar_set (self, id_data, rb_str_dup_frozen (data));
490
491         return self;
492 }
493
494 static VALUE
495 chunk_serialize (VALUE self)
496 {
497         VALUE tmp, ret;
498         unsigned int size, buf_len;
499         unsigned char *buf;
500         struct RString *tag, *data;
501
502         tmp = rb_ivar_get (self, id_tag);
503         tag = RSTRING (tmp);
504
505         tmp = rb_ivar_get (self, id_data);
506         data = RSTRING (tmp);
507
508         buf_len = 9 + tag->len + data->len;
509         ret = rb_str_buf_new (buf_len);
510
511         buf = RSTRING (ret)->ptr;
512         RSTRING (ret)->len = buf_len;
513
514         memcpy (buf, "CHnK", 4);
515         buf += 4;
516
517         size = tag->len + data->len + 1;
518         size = BSWAP32 (size);
519         memcpy (buf, &size, 4);
520         buf += 4;
521
522         memcpy (buf, tag->ptr, tag->len);
523         buf += tag->len;
524
525         *buf++ = 0;
526
527         memcpy (buf, data->ptr, data->len);
528
529         return ret;
530 }
531
532 static int
533 for_each_prop (VALUE tag, VALUE arg, VALUE stream)
534 {
535         VALUE value, type, tmp;
536
537         if (rb_obj_is_kind_of (arg, rb_cArray) == Qfalse)
538                 rb_raise (ePropError, "hash value is not an array");
539
540         value = rb_ary_entry (arg, 0);
541         if (NIL_P (value))
542                 return ST_CONTINUE;
543
544         type = rb_ary_entry (arg, 1);
545         tmp = rb_funcall (value, id_to_eet_chunks, 2, tag, type);
546
547         rb_ary_concat (stream, tmp);
548
549         return ST_CONTINUE;
550 }
551
552 /*
553  * call-seq:
554  *  object.to_eet -> string
555  *
556  * Serializes the receiver to EET format.
557  */
558 static VALUE
559 c_to_eet (VALUE self)
560 {
561         VALUE props, name, stream, chunk, args[2];
562 #ifndef HAVE_RB_HASH_FOREACH
563         struct RArray *keys;
564         long i;
565 #endif
566
567         props = rb_funcall (self, id_to_eet_properties, 0);
568
569         if (rb_obj_is_kind_of (props, rb_cHash) == Qfalse ||
570             !RHASH (props)->tbl->num_entries)
571                 rb_raise (ePropError, "invalid EET properties");
572
573         name = rb_funcall (self, id_to_eet_name, 0);
574         StringValue (name);
575
576         if (!RSTRING (name)->len ||
577             rb_funcall (name, id_include, 1, INT2FIX (0)))
578                 rb_raise (eNameError, "invalid EET name");
579
580         stream = rb_class_new_instance (0, NULL, cStream);
581
582 #ifdef HAVE_RB_HASH_FOREACH
583         rb_hash_foreach (props, for_each_prop, stream);
584 #else
585         keys = RARRAY (rb_funcall (props, id_keys, 0));
586
587         for (i = 0; i < keys->len; i++)
588                 for_each_prop (keys->ptr[i],
589                                rb_hash_aref (props, keys->ptr[i]),
590                                stream);
591 #endif
592
593         args[0] = name;
594         args[1] = rb_funcall (stream, id_serialize, 0);
595
596         rb_ary_clear (stream); /* give the GC a hand... */
597
598         chunk = rb_class_new_instance (2, args, cChunk);
599
600         return rb_funcall (chunk, id_serialize, 0);
601 }
602
603 static VALUE
604 int_to_eet_chunks (int argc, VALUE *argv, VALUE self)
605 {
606         VALUE tag, type = Qnil, ary, args[2], chunk;
607         char *cfmt = "V";
608
609         rb_scan_args (argc, argv, "11", &tag, &type);
610
611         ary = rb_ary_new3 (1, self);
612
613         if (type == sym_char)
614                 cfmt = "c";
615         else if (type == sym_short)
616                 cfmt = "v";
617         else if (type == sym_long_long)
618                 cfmt = "q";
619
620         args[0] = tag;
621         args[1] = rb_funcall (ary, id_pack, 1, rb_str_new2 (cfmt));
622         chunk = rb_class_new_instance (2, args, cChunk);
623
624         return rb_ary_new3 (1, chunk);
625 }
626
627 static VALUE
628 float_to_eet_chunks (int argc, VALUE *argv, VALUE self)
629 {
630         VALUE tag, type = Qnil, args[2], chunk;
631         char buf[65], *loc;
632         double d;
633         int len;
634
635         rb_scan_args (argc, argv, "11", &tag, &type);
636
637         d = NUM2DBL (self);
638
639         /* switch locale to make sure we get proper snprintf output */
640         loc = setlocale (LC_NUMERIC, "C");
641
642         len = snprintf (buf, sizeof (buf) - 1, "%a",
643                         type == sym_double ? d : (float) d);
644
645         if (loc)
646                 setlocale (LC_NUMERIC, loc);
647
648         buf[++len] = '\0';
649
650         args[0] = tag;
651         args[1] = rb_str_new (buf, len);
652         chunk = rb_class_new_instance (2, args, cChunk);
653
654         return rb_ary_new3 (1, chunk);
655 }
656
657 void
658 Init_eet_ext ()
659 {
660         VALUE m, c;
661
662         m = rb_define_module ("Eet");
663
664         c = rb_define_class_under (m, "File", rb_cObject);
665         rb_define_alloc_func (c, c_alloc);
666         rb_define_singleton_method (c, "open", c_open, -1);
667         rb_define_method (c, "initialize", c_init, -1);
668         rb_define_method (c, "close", c_close, 0);
669         rb_define_method (c, "list", c_list, -1);
670         rb_define_method (c, "delete", c_delete, 1);
671         rb_define_method (c, "read", c_read, 1);
672         rb_define_method (c, "write", c_write, -1);
673         rb_define_method (c, "read_image", c_read_image, 1);
674         rb_define_method (c, "write_image", c_write_image, -1);
675
676         cStream = rb_define_class_under (m, "Stream", rb_cArray);
677         rb_define_method (cStream, "serialize", stream_serialize, 0);
678
679         cChunk = rb_define_class_under (m, "Chunk", rb_cObject);
680         rb_define_method (cChunk, "initialize", chunk_init, 2);
681         rb_define_method (cChunk, "serialize", chunk_serialize, 0);
682
683         rb_define_attr (cChunk, "tag", 1, 0);
684         rb_define_attr (cChunk, "data", 1, 0);
685
686         rb_define_method (rb_cObject, "to_eet", c_to_eet, 0);
687
688         rb_define_method (rb_cInteger, "to_eet_chunks", int_to_eet_chunks, -1);
689         rb_define_method (rb_cFloat, "to_eet_chunks", float_to_eet_chunks, -1);
690
691         eEetError = rb_define_class_under (m, "EetError", rb_eStandardError);
692         eNameError = rb_define_class_under (m, "NameError", eEetError);
693         ePropError = rb_define_class_under (m, "PropertyError", eEetError);
694         eStreamError = rb_define_class_under (m, "StreamError", eEetError);
695         eBadElementError = rb_define_class_under (m, "BadElementError",
696                                                   eStreamError);
697
698         id_include = rb_intern ("include?");
699         id_serialize = rb_intern ("serialize");
700         id_keys = rb_intern ("keys");
701         id_pack = rb_intern ("pack");
702         id_to_eet_chunks = rb_intern ("to_eet_chunks");
703         id_to_eet_name = rb_intern ("to_eet_name");
704         id_to_eet_properties = rb_intern ("to_eet_properties");
705         id_tag = rb_intern ("@tag");
706         id_data = rb_intern ("@data");
707         sym_lossy = ID2SYM (rb_intern ("lossy"));
708         sym_level = ID2SYM (rb_intern ("level"));
709         sym_quality =  ID2SYM (rb_intern ("quality"));
710         sym_char = ID2SYM (rb_intern ("char"));
711         sym_short = ID2SYM (rb_intern ("short"));
712         sym_long_long = ID2SYM (rb_intern ("long_long"));
713         sym_double = ID2SYM (rb_intern ("double"));
714 }