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