1 /**
2 
3 SFSEXP: Small, Fast S-Expression Library version 1.0
4 Written by Matthew Sottile (matt@lanl.gov)
5 
6 Copyright (2003-2006). The Regents of the University of California. This
7 material was produced under U.S. Government contract W-7405-ENG-36 for Los
8 Alamos National Laboratory, which is operated by the University of
9 California for the U.S. Department of Energy. The U.S. Government has rights
10 to use, reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR
11 THE UNIVERSITY MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY
12 LIABILITY FOR THE USE OF THIS SOFTWARE. If software is modified to produce
13 derivative works, such modified software should be clearly marked, so as not
14 to confuse it with the version available from LANL.
15 
16 Additionally, this library is free software; you can redistribute it and/or
17 modify it under the terms of the GNU Lesser General Public License as
18 published by the Free Software Foundation; either version 2.1 of the
19 License, or (at your option) any later version.
20 
21 This library is distributed in the hope that it will be useful, but WITHOUT
22 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
23 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
24 for more details.
25 
26 You should have received a copy of the GNU Lesser General Public License
27 along with this library; if not, write to the Free Software Foundation,
28 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, U SA
29 
30 LA-CC-04-094
31 
32 **/
33 
34 
35 #include "ruby.h"
36 #include "sexp.h"
37 
38 /**
39  ** below are rudimentary Ruby bindings for the sexp library
40  **
41  ** Matt Sottile (matt@lanl.gov) / 5.26.2003
42  **/
43 
44 VALUE cSexp;
45 
46 /*
47  * enumeration and inferrence routine used to guess what the strings
48  * represent in detail regarding string vs int vs float.
49  */
50 typedef enum { SVAL_STRING, SVAL_INTEGER, SVAL_REAL, SVAL_NONE } sval_type;
51 
52 /* inferrence routine - about the umpteenth version of this code.  never
53  * can remember where it was written the LAST time I wrote it...
54  */
infer_sval_type(sexp_t * sx)55 static sval_type infer_sval_type(sexp_t *sx) {
56   char *c;
57   int ishex = 0;
58   int sawdecimal = 0;
59   int isnegative = 0;
60 
61   /* null sx, sx->val, or sx being a list means no type */
62   if (sx == NULL || sx->val == NULL || sx->ty != SEXP_VALUE)
63     return SVAL_NONE;
64 
65   /* beginning of val string */
66   c = sx->val;
67 
68   /* start with -?  Might be a negative number. */
69   if (c[0] == '-') {
70       isnegative = 1;
71       c++;
72   }
73 
74   /* start with 0?  Might be hex. */
75   if (c[0] == '0') {
76     c++;
77     /* follow 0 with x or X?  Better chance that it is hex */
78     if (c[0] == 'x' || c[0] == 'X') {
79       /* hex numbers don't start with a minus sign! */
80       if (isnegative == 1) {
81 	  return SVAL_STRING;
82       }
83       c++;
84       if ((c[0] >= '0' && c[0] <= '9') ||
85           ((c[0] >= 'a' && c[0] <= 'f') ||
86            (c[0] >= 'A' && c[0] <= 'F')))
87         /* starts with 0x and a hex digit.  so far, it looks like a hex
88            number */
89         ishex = 2;
90       else
91         /* string starting with 0x and a non-hex caracter -- must be
92          * a string.
93          */
94         return SVAL_STRING;
95     } else
96       ishex = 0;
97   }
98 
99   /* loop over each character.  so far, we know if sx->val starts like a
100    * hex number or not, and if not, whether or not it might be a negative
101    * number.
102    */
103   while (c[0] != '\0') {
104     if (ishex == 1) {
105       if (!((c[0] >= '0' && c[0] <= '9') ||
106             ((c[0] >= 'a' && c[0] <= 'f') ||
107              (c[0] >= 'A' && c[0] <= 'F'))))
108         return SVAL_STRING;
109     } else {
110       /* not hex */
111       if (c[0] == '.') {
112         if (sawdecimal == 0) sawdecimal = 1;
113         else return SVAL_STRING; /* 2 '.'s mean non-numeric */
114       } else {
115         if (!(c[0] >= '0' && c[0] <= '9')) {
116           return SVAL_STRING; /* not a decimal digit, and not hex, so... */
117         }
118       }
119     }
120     c++;
121   }
122 
123   if (ishex == 1) return SVAL_INTEGER;
124 
125   if (sawdecimal == 1) return SVAL_REAL;
126 
127   return SVAL_INTEGER;
128 }
129 
130 /* destructor called by ruby when cleaning up to deal with the sexp_t
131  * stashed away in the object
132  */
sexp_rubyfree(void * s)133 static void sexp_rubyfree(void *s) {
134   sexp_t *sx = (sexp_t *)s;
135   destroy_sexp(sx);
136 }
137 
138 /* given a sexp_t, recursively turn it into a ruby array of strings.
139  * This is not sufficient to deal with DQUOTE and SQUOTE atoms.
140  * This needs to be fixed eventually.  Likely by storing either:
141  *   1. A second array corresponding to the first of sexp_t types
142  *   2. Replacing string elements in the array(s) with a record type
143  *      containing the string and the type.
144  */
sexp_to_array(sexp_t * sx,int aggressive_typing)145 static VALUE sexp_to_array(sexp_t *sx, int aggressive_typing) {
146   VALUE     a = rb_ary_new(); /* create array */
147   sexp_t   *s = sx;
148   sval_type svt;
149   int       i;
150   double    d;
151 
152   while (s != NULL) {
153     if (s->ty == SEXP_LIST) {
154       rb_ary_push(a, sexp_to_array(s->list, aggressive_typing));
155     } else {
156       if (aggressive_typing == 1) {
157         svt = infer_sval_type(s);
158         switch (svt) {
159         case SVAL_INTEGER:
160           i = atoi(s->val);
161           rb_ary_push(a, INT2FIX(i));
162           break;
163         case SVAL_REAL:
164           d = strtod(s->val,NULL);
165           rb_ary_push(a, rb_float_new(d));
166           break;
167         case SVAL_NONE:
168           rb_fatal("ERROR: infer_sval_type => SVAL_NONE for array elt.\n");
169           break;
170         default:
171           rb_ary_push(a, rb_str_new2(s->val));
172         }
173       } else { /* no aggressive typing - everything is a string */
174         rb_ary_push(a, rb_str_new2(s->val));
175       }
176     }
177     s = s->next;
178   }
179 
180   return a;
181 }
182 
183 /* given a string, parse it and create the corresponding ruby object
184  * for the sexp_t that results.
185  */
sexp_new(VALUE class,VALUE str)186 VALUE sexp_new(VALUE class, VALUE str) {
187   sexp_t *sx;
188   char *ptr;
189   int len;
190   VALUE argv[2];
191   VALUE td;
192 
193   /* make sure it is a string */
194   Check_Type(str, T_STRING);
195 
196   /* grab the length and base pointer to the string */
197   ptr = rb_str2cstr(str, (long *)&len);
198 
199   /* parse the string */
200   if (len == 0) {
201     sx = NULL;
202   } else {
203     sx = parse_sexp(ptr,len);
204   }
205 
206   /* stash the sexp_t away in the ruby object */
207   td = Data_Wrap_Struct(class, 0, sexp_rubyfree, sx);
208 
209   /* set arguments to init up - argv[0] is the original string,
210      argv[1] is the array representing it in ruby space. */
211   argv[0] = str;
212 
213   /* turn the sexp_t into an array */
214   if (sx == NULL) {
215     argv[1] = rb_ary_new(); /* empty */
216   } else {
217     if (sx->ty == SEXP_LIST)
218       argv[1] = sexp_to_array(sx->list,1);
219     else
220       argv[1] = sexp_to_array(sx,1);
221   }
222 
223   /* call the ruby initialize method */
224   rb_obj_call_init(td, 2, argv);
225 
226   /* return the instance of the ruby object */
227   return td;
228 }
229 
230 /* initialize expects the original string and the array created from
231  * the parsed sexp_t structure.
232  */
sexp_init(VALUE self,VALUE str,VALUE ary)233 static VALUE sexp_init(VALUE self, VALUE str, VALUE ary) {
234   rb_iv_set(self, "@str", str);
235   rb_iv_set(self, "@ary", ary);
236   return self;
237 }
238 
239 /* given an array representing a s-expression, recursively walk it and
240  * string together an equivalent sexp_t representation.  This routine
241  * suffers from the same issues mentioned above related to atom
242  * type details.
243  */
sexp_unparse_array(VALUE val)244 static sexp_t *sexp_unparse_array(VALUE val) {
245   sexp_t *sx, *s;
246   VALUE v;
247   char *b;
248   int bs;
249   char buf[32];
250 
251   /* initialize s to be safe */
252   s = NULL;
253 
254   /* makes no sense to pass an atom in here... */
255   Check_Type(val, T_ARRAY);
256 
257   /* create a new list with nothing in it.  We know that this is an
258    * array being passed in, so we must start with a list.
259    */
260   sx = new_sexp_list(NULL);
261 
262   /* pop elements off from the front of the array one at a time */
263   v = rb_ary_shift(val);
264   while (!NIL_P(v)) {
265     switch (TYPE(v)) {
266       /* array? make recursive call */
267     case T_ARRAY:
268       if (sx->list == NULL) {
269         s = sexp_unparse_array(v);
270         sx->list = s;
271       } else {
272         s->next = sexp_unparse_array(v);
273         s = s->next;
274       }
275       break;
276 
277       /* int */
278     case T_FIXNUM:
279       sprintf(buf,"%ld",FIX2LONG(v));
280       b = buf;
281       bs = strlen(buf);
282 
283       if (sx->list == NULL) {
284         s = new_sexp_atom(b,bs);
285         sx->list = s;
286       } else {
287         s->next = new_sexp_atom(b,bs);
288         s = s->next;
289       }
290 
291       break;
292 
293       /* int */
294     case T_BIGNUM:
295       sprintf(buf,"%ld",NUM2LONG(v));
296       b = buf;
297       bs = strlen(buf);
298 
299       if (sx->list == NULL) {
300         s = new_sexp_atom(b,bs);
301         sx->list = s;
302       } else {
303         s->next = new_sexp_atom(b,bs);
304         s = s->next;
305       }
306 
307       break;
308 
309       /* floating point */
310     case T_FLOAT:
311       /* ick - there is a better way to get b and bs */
312       sprintf(buf,"%f",NUM2DBL(v));
313       b = buf;
314       bs = strlen(buf);
315 
316       if (sx->list == NULL) {
317         s = new_sexp_atom(b,bs);
318         sx->list = s;
319       } else {
320         s->next = new_sexp_atom(b,bs);
321         s = s->next;
322       }
323 
324       break;
325 
326       /* string */
327     case T_STRING:
328       b = rb_str2cstr(v,(long *)&bs);
329 
330       if (sx->list == NULL) {
331         s = new_sexp_atom(b,bs);
332         sx->list = s;
333       } else {
334         s->next = new_sexp_atom(b,bs);
335         s = s->next;
336       }
337 
338       break;
339     default:
340       /* error? */
341       /* who cares - for now, fatal error - GCC doesn't like it if this
342          bit of the switch is empty... */
343       rb_fatal("Very bad contents of array!\n");
344     }
345 
346     /* pop the next */
347     v = rb_ary_shift(val);
348   }
349 
350   /* return sx */
351   return sx;
352 }
353 
354 /* unparse the ary representation of the s-expression into a sexp_t and
355  * then into a char *.  In the process, replace @str with the new string
356  * and the sx pointer in the ruby object with the new one.  Must make
357  * sure the hack to store the new sx pointer follows the rules for
358  * playing nicely with the garbage collector
359  */
sexp_unparse(VALUE self)360 static VALUE sexp_unparse(VALUE self) {
361   /* get the ary */
362   VALUE ary = rb_iv_get(self,"@ary");
363   /* turn ary into a sexp_t */
364   sexp_t *sx = sexp_unparse_array(ary);
365   /* the CSTRING we unparse sx into, the ruby string it represents, and
366    * the old sx pointer to replace
367    */
368   CSTRING *s = NULL;
369   VALUE str;
370   sexp_t *sx_old;
371 
372   /* unparse sx */
373   print_sexp_cstr(&s, sx, 256,128);
374   /* make sure the CSTRING char * is null terminated */
375   s->base[s->curlen] = '\0';
376   /* create the ruby string */
377   str = rb_str_new2(s->base);
378   /* set the str field to the new string.  Make sure this is the correct
379    * way to do it while still making sure the original we are replacing
380    * gets garbage collected
381    */
382   rb_iv_set(self, "@str", str);
383 
384   /* assuming the string was copied in the rb_str_new2() call, dispose of
385    * the CSTRING.
386    */
387   sdestroy(s);
388 
389   /* look up the old sx pointer in the ruby object */
390   Data_Get_Struct(self, sexp_t, sx_old);
391   /* ...and destroy it */
392   destroy_sexp(sx_old);
393 
394   /* stash the new sx pointer in the object */
395   DATA_PTR(self) = (void *)sx;
396 
397   /* return self */
398   return self;
399 }
400 
401 /* setter for ary field
402  */
sexp_setAry(VALUE self,VALUE ary)403 static VALUE sexp_setAry(VALUE self, VALUE ary) {
404   rb_iv_set(self, "@ary", ary);
405   return self;
406 }
407 
408 /* accessor for ary field
409  */
sexp_getAry(VALUE self)410 static VALUE sexp_getAry(VALUE self) {
411   return rb_iv_get(self, "@ary");
412 }
413 
414 /* accessor for str field
415  */
sexp_getStr(VALUE self)416 static VALUE sexp_getStr(VALUE self) {
417   return rb_iv_get(self, "@str");
418 }
419 
420 /* call made by ruby when loading the dynamlic library of this code.
421  * defines the Sexp class and the methods on it.  They are implemented
422  * above.
423  */
Init_Sexp()424 void Init_Sexp() {
425   cSexp = rb_define_class("Sexp", rb_cObject);
426   rb_define_singleton_method(cSexp, "new", sexp_new, 1);
427   rb_define_method(cSexp, "initialize", sexp_init, 2);
428   rb_define_method(cSexp, "getAry", sexp_getAry, 0);
429   rb_define_method(cSexp, "getStr", sexp_getStr, 0);
430   rb_define_method(cSexp, "setAry", sexp_setAry, 1);
431   rb_define_method(cSexp, "unparse", sexp_unparse, 0);
432 }
433