root/libswish3/trunk/bindings/perl/xs_helpers.c

Revision 2178, 18.2 kB (checked in by karpet, 2 weeks ago)

some versions of html parser were passing through extra whitespace.
seems to be a specific libxml2 issue. in any case, added a new
whitespace check in both add to buf methods and perl bindings
(the latter where t/20-metanames.t was failing due to extra whitespace)

Line 
1 /* Copyright 2008 Peter Karman (perl@peknet.com)
2  * This program is free software; you can redistribute it and/or modify
3  * under the same terms as Perl itself.
4  */
5
6 /* C code to make writing XS easier */
7
8 static AV*      sp_hv_keys(HV* hash);
9 static AV*      sp_hv_values(HV* hash);
10 static SV*      sp_hv_store( HV* h, const char* key, SV* val );
11 static SV*      sp_hv_store_char( HV* h, const char* key, char *val );
12 static SV*      sp_hvref_store( SV* h, const char* key, SV* val );
13 static SV*      sp_hvref_store_char( SV* h, const char* key, char *val );
14 static SV*      sp_hv_fetch( HV* h, const char* key );
15 static SV*      sp_hvref_fetch( SV* h, const char* key );
16 static char*    sp_hv_fetch_as_char( HV* h, const char* key );
17 static char*    sp_hvref_fetch_as_char( SV* h, const char* key );
18 static bool     sp_hv_exists( HV* h, const char* key );
19 static bool     sp_hvref_exists( SV* h, const char* key );
20 static SV*      sp_hv_delete( HV* h, const char* key );
21 static SV*      sp_hvref_delete( SV* h, const char* key );
22 static void     sp_hv_replace( HV *h, char* key, SV* value );
23 static void     sp_hvref_replace( SV * hashref, char* key, SV* value );
24 static SV*      sp_bless_ptr( char* CLASS, IV c_ptr );
25 static char*    sp_get_objects_class( SV* object );
26 static HV*      sp_extract_hash( SV* object );
27 static void     sp_dump_hash( SV* hash_ref );
28 static void     sp_describe_object( SV* object );
29 static IV       sp_extract_ptr( SV* object );
30 static AV*      sp_get_xml2_hash_keys( xmlHashTablePtr xml2_hash );
31 static void     sp_add_key_to_array(xmlChar *val, AV *keys, xmlChar *key);
32 static SV*      sp_xml2_hash_to_perl_hash( xmlHashTablePtr xml2_hash, const char* class );
33 static void     sp_perl_hash_to_xml2_hash( HV* perlhash, xmlHashTablePtr xml2hash );
34 static void     sp_nb_hash_to_phash(xmlBufferPtr buf, HV *phash, xmlChar *key);
35 static HV*      sp_nb_to_hash( swish_NamedBuffer* nb );
36 static void     sp_test_handler( swish_ParserData* parse_data );
37 static void     sp_handler( swish_ParserData* parse_data );
38 static int      sp_tokenize3( swish_TokenIterator *ti,
39                               xmlChar *buf,
40                               swish_MetaName *meta,
41                               xmlChar *context );
42 static void     sp_SV_is_qr( SV *qr );
43
44 /* implement nearly all methods for SWISH::3::Stash, a private class */
45
46 static SV*      sp_Stash_new();
47 static void     sp_Stash_set( SV *stash, const char *key, SV *value );
48 static void     sp_Stash_set_char( SV *stash, const char *key, char *value );
49 static SV*      sp_Stash_get( SV *stash, const char *key );
50 static char*    sp_Stash_get_char( SV *stash, const char *key );
51 static void     sp_Stash_replace( SV *stash, const char *key, SV *value );
52 static int      sp_Stash_inner_refcnt( SV *stash );
53 static void     sp_Stash_destroy( SV *stash );
54 static void     sp_Stash_dec_values( SV *stash );
55
56
57 static SV*
58 sp_Stash_new()
59 {
60     dTHX;
61     HV *hash;
62     SV *object;
63     hash    = newHV();
64     object  = sv_bless( newRV((SV*)hash), gv_stashpv("SWISH::3::Stash",0) );
65     //SvREFCNT_dec( hash );
66     return object;
67 }
68
69 static void
70 sp_Stash_set( SV *object, const char *key, SV *value )
71 {
72     dTHX;
73     HV *hash;
74     hash = sp_extract_hash( object );
75     sp_hv_store( hash, key, value );
76 }
77
78 static void
79 sp_Stash_set_char( SV *object, const char *key, char *value )
80 {
81     dTHX;
82     HV *hash;
83     hash = sp_extract_hash( object );
84     sp_hv_store_char( hash, key, value );
85 }
86
87 static SV*
88 sp_Stash_get( SV *object, const char *key )
89 {
90     dTHX;
91     HV *hash;
92     hash = sp_extract_hash( object );
93     //return SvREFCNT_inc( sp_hv_fetch( hash, key ) );
94     return sp_hv_fetch( hash, key );
95 }
96
97 static char*
98 sp_Stash_get_char( SV *object, const char *key )
99 {
100     dTHX;
101     HV *hash;
102     hash = sp_extract_hash( object );
103     return sp_hv_fetch_as_char( hash, key );
104 }
105
106 static void
107 sp_Stash_replace( SV *object, const char *key, SV *value )
108 {
109     dTHX;
110     HV *hash;
111     hash = sp_extract_hash( object );
112     return sp_hv_replace( hash, (char*)key, value );
113 }
114
115 static int
116 sp_Stash_inner_refcnt( SV *object )
117 {
118     dTHX;
119     return SvREFCNT((SV*)SvRV((SV*)object));
120 }
121
122 static void
123 sp_Stash_destroy( SV *object )
124 {
125     dTHX;
126     HV *hash;
127     sp_Stash_dec_values(object);
128     hash = sp_extract_hash( object );
129     if ( SWISH_DEBUG ) {
130         warn("Stash_destroy Stash object %s for class %s [%d]",
131             SvPV(object, PL_na), sp_hv_fetch_as_char(hash, SELF_CLASS_KEY), object);
132         warn("Stash object refcnt = %d", SvREFCNT(object));
133         warn("Stash hash   refcnt = %d", SvREFCNT(hash));
134     }
135     hv_undef(hash);
136     //sp_describe_object( object );
137     if (SvREFCNT( hash )) {
138         SvREFCNT_dec( hash );
139     }
140     if (SvREFCNT( object ) ) {
141         SvREFCNT_dec( object );
142     }
143 }
144
145 static void
146 sp_Stash_dec_values(SV* stash)
147 {
148     dTHX;
149     HV* hash;
150     HE* hash_entry;
151     int num_keys, i;
152     SV* sv_val;
153    
154     hash = sp_extract_hash( stash );
155     num_keys = hv_iterinit(hash);
156     //warn("Stash has %d keys", num_keys);
157     for (i = 0; i < num_keys; i++) {
158         hash_entry  = hv_iternext(hash);
159         sv_val      = hv_iterval(hash, hash_entry);
160         if( SvREFCNT(sv_val) > 1 ) { //&& SvTYPE(SvRV(sv_val)) == SVt_IV ) {
161             warn("Stash value is a ptr object with refcount = %d", SvREFCNT(sv_val));
162             SvREFCNT_dec( sv_val );
163         }
164     }
165 }
166
167
168
169 static void
170 sp_SV_is_qr( SV *qr )
171 {
172     dTHX;
173     SV *tmp;
174    
175     if (SvROK(qr)) {
176         tmp = SvRV(qr);
177         if ( !SvMAGICAL(tmp) || !mg_find(tmp, PERL_MAGIC_qr) ) {
178             croak("regex is not a qr// entity");
179         }
180     } else {
181         croak("regex is not a qr// entity");
182     }
183 }
184
185 static AV*
186 sp_hv_keys(HV* hash)
187 {
188     dTHX;
189     HE* hash_entry;
190     int num_keys, i;
191     SV* sv_key;
192     char* key;
193     SV* sv_keep;
194     AV* keys;
195    
196     keys        = newAV();
197     num_keys    = hv_iterinit(hash);
198     av_extend(keys, (I32)num_keys);   
199    
200     for (i = 0; i < num_keys; i++) {
201         hash_entry  = hv_iternext(hash);
202         sv_key      = hv_iterkeysv(hash_entry);
203         key         = SvPV(sv_key, PL_na);
204         if ( xmlStrEqual( (xmlChar*)SELF_CLASS_KEY, (xmlChar*)key ) )
205             continue;
206            
207         sv_keep     = newSVpv( key, 0 );
208         av_push(keys, sv_keep);
209     }
210    
211     //SvREFCNT_inc(keys);
212     
213     return keys;
214 }
215
216 static AV*
217 sp_hv_values(HV* hash)
218 {
219     dTHX;
220     HE* hash_entry;
221     int num_keys, i;
222     SV* sv_val;
223     SV* sv_key;
224     char* key;
225     AV* values;
226        
227     values = newAV();
228     num_keys    = hv_iterinit(hash);
229     av_extend(values, (I32)num_keys);
230    
231     for (i = 0; i < num_keys; i++) {
232         hash_entry  = hv_iternext(hash);
233         sv_key      = hv_iterkeysv(hash_entry);
234         key         = SvPV(sv_key, PL_na);
235         if ( xmlStrEqual( (xmlChar*)SELF_CLASS_KEY, (xmlChar*)key ) )
236             continue;
237            
238         sv_val      = hv_iterval(hash, hash_entry);
239         av_push(values, sv_val);
240     }
241    
242     return values;
243 }
244
245
246
247 /* store SV* in a hash, incrementing its refcnt */
248 static SV*
249 sp_hv_store( HV* h, const char* key, SV* val)
250 {
251     dTHX;
252     SV** ok;
253     ok = hv_store(h, key, strlen(key), SvREFCNT_inc(val), 0);
254     if (ok != NULL)
255     {
256        if (SWISH_DEBUG)
257         SWISH_DEBUG_MSG("stored %s ok in hash: %s", key, SvPV( *ok, PL_na ));
258     }
259     else
260     {
261         croak("failed to store %s in hash", key);
262     }
263     return *ok;
264 }
265
266 static SV*
267 sp_hv_store_char( HV* h, const char *key, char *val)
268 {
269     dTHX;
270     SV *value;
271     value = newSVpv(val, 0);
272     sp_hv_store( h, key, value );
273     SvREFCNT_dec(value);
274     return value;
275 }
276
277 static SV*
278 sp_hvref_store( SV* h, const char* key, SV* val)
279 {
280     dTHX;
281     return sp_hv_store( (HV*)SvRV(h), key, val );
282 }
283
284 static SV*
285 sp_hvref_store_char( SV* h, const char* key, char *val)
286 {
287     dTHX;
288     return sp_hv_store_char( (HV*)SvRV(h), key, val );
289 }
290
291 /* fetch SV* from hash */
292 static SV*
293 sp_hv_fetch( HV* h, const char* key )
294 {
295     dTHX;
296     SV** ok;
297     ok = hv_fetch(h, key, strlen(key), 0);
298     if (ok != NULL)
299     {
300        if (SWISH_DEBUG)
301         SWISH_DEBUG_MSG("fetched %s ok: %s", key, SvPV( *ok, PL_na ));
302     }
303     else
304     {
305         croak("failed to fetch %s", key);
306     }
307     return *ok;
308 }
309
310 static SV*
311 sp_hvref_fetch( SV* h, const char* key )
312 {
313     dTHX;
314     return sp_hv_fetch((HV*)SvRV(h), key);
315 }
316
317 static bool
318 sp_hv_exists( HV* h, const char* key )
319 {
320     dTHX;
321     return hv_exists(h, key, strlen(key));
322 }
323
324 static bool
325 sp_hvref_exists( SV* h, const char* key )
326 {
327     dTHX;
328     return sp_hv_exists((HV*)SvRV(h), key);
329 }
330
331 /* fetch SV* from hash */
332 static char*
333 sp_hv_fetch_as_char( HV* h, const char* key )
334 {
335     dTHX;
336     SV** ok;
337     ok = hv_fetch(h, key, strlen(key), 0);
338     if (ok != NULL)
339     {
340        if (SWISH_DEBUG)
341         SWISH_DEBUG_MSG("fetched %s ok from hash: %s", key, SvPV( *ok, PL_na ));
342     }
343     else
344     {
345         croak("failed to fetch %s from hash", key);
346     }
347     return SvPV((SV*)*ok, PL_na);
348 }
349
350 static char*
351 sp_hvref_fetch_as_char( SV* h, const char* key )
352 {
353     dTHX;
354     return sp_hv_fetch_as_char( (HV*)SvRV(h), key );
355 }
356
357
358 /* delete SV* from hash, returning the deleted SV* */
359 static SV*
360 sp_hv_delete( HV* h, const char* key )
361 {
362     dTHX;
363     SV* oldval;
364     oldval = hv_delete(h, key, strlen(key), 0 );
365     if (oldval != NULL)
366     {
367         if (SWISH_DEBUG)
368          SWISH_DEBUG_MSG("deleted %s ok from hash: %s", key, SvPV( oldval, PL_na ));
369     }
370     else
371     {
372         croak("failed to delete %s from hash", key);
373     }
374     return oldval;
375 }
376
377 static SV*
378 sp_hvref_delete( SV* h, const char* key )
379 {
380     dTHX;
381     return sp_hv_delete( (HV*)SvRV(h), key );
382 }
383
384
385 /* make a Perl blessed object from a C pointer */
386 static SV*
387 sp_bless_ptr( char* CLASS, IV c_ptr )
388 {
389     dTHX;
390     SV* obj = sv_newmortal();
391     sv_setref_pv(obj, CLASS, (void*)c_ptr);
392     return obj;
393 }
394
395 /* what class is an object blessed into? like Scalar::Util::blessed */
396 static char*
397 sp_get_objects_class( SV* object )
398 {
399     dTHX;
400     char* class = sv_reftype(SvRV(object), 1);
401     //warn("object belongs to %s\n", class);
402     return class;
403 }
404
405 static HV*
406 sp_extract_hash( SV* object )
407 {
408     dTHX;
409     HV* hash;
410     char* class;
411    
412     class = sp_get_objects_class( object );
413     if (SvROK(object) && SvTYPE(SvRV(object))==SVt_PVHV)
414         hash = (HV*)SvRV(object);
415     else if (SvROK(object) && SvTYPE(SvRV(object))==SVt_PVMG)
416         croak("%s is a magic reference not a hash reference", class);
417     else
418         croak("%s is reference but not a hash reference", class);
419        
420     return hash;
421 }
422
423 static void
424 sp_dump_hash(SV* hash_ref)
425 {
426     dTHX;
427     HV* hash;
428     HE* hash_entry;
429     int num_keys, i;
430     SV* sv_key;
431     SV* sv_val;
432     int refcnt;
433    
434     if (SvTYPE(SvRV(hash_ref))!=SVt_PVHV) {
435         warn("hash_ref is not a hash reference");
436         return;
437     }
438    
439     hash        = (HV*)SvRV(hash_ref);
440     num_keys    = hv_iterinit(hash);
441     for (i = 0; i < num_keys; i++) {
442         hash_entry  = hv_iternext(hash);
443         sv_key      = hv_iterkeysv(hash_entry);
444         sv_val      = hv_iterval(hash, hash_entry);
445         refcnt      = SvREFCNT(sv_val);
446         warn("  %s => %s  [%d]\n", SvPV(sv_key, PL_na), SvPV(sv_val, PL_na), refcnt);
447     }
448     return;
449 }
450
451 static void
452 sp_describe_object( SV* object )
453 {
454     dTHX;
455     char* str;
456    
457     warn("describing object\n");
458     str = SvPV( object, PL_na );
459     if (SvROK(object))
460     {
461       if (SvTYPE(SvRV(object))==SVt_PVHV)
462         warn("%s is a magic blessed reference\n", str);
463       else if (SvTYPE(SvRV(object))==SVt_PVMG)
464         warn("%s is a magic reference", str);
465       else if (SvTYPE(SvRV(object))==SVt_IV)
466         warn("%s is a IV reference (pointer)", str);
467       else
468         warn("%s is a reference of some kind", str);
469     }
470     else
471     {
472         warn("%s is not a reference", str);
473         if (sv_isobject(object))
474             warn("however, %s is an object", str);
475        
476        
477     }
478     warn("object dump");
479     Perl_sv_dump( aTHX_ object );
480     warn("object ref dump");
481     Perl_sv_dump( aTHX_ (SV*)SvRV(object) );
482     sp_dump_hash( object );
483 }
484
485 /* return the C pointer from a Perl blessed O_OBJECT */
486 static IV
487 sp_extract_ptr( SV* object )
488 {
489     dTHX;
490     return SvIV((SV*)SvRV( object ));
491 }
492
493 static void
494 sp_hv_replace( HV *hash, char *key, SV *value )
495 {
496     dTHX;
497     if (sp_hv_exists(hash, key)) {
498         sp_hv_delete(hash, key);
499     }
500     sp_hv_store( hash, key, value );
501 }
502
503 static void
504 sp_hvref_replace( SV * hashref, char* key, SV* value )
505 {
506     dTHX;
507     if (sp_hvref_exists(hashref, key)) {
508         sp_hvref_delete(hashref, key);
509     }
510     sp_hvref_store( hashref, key, value );
511 }
512
513
514 static void
515 sp_add_key_to_array(xmlChar* val, AV* mykeys, xmlChar* key)
516 {
517     dTHX;
518     av_push(mykeys, newSVpvn((char*)key, strlen((char*)key)));
519 }
520  
521 static AV*
522 sp_get_xml2_hash_keys( xmlHashTablePtr xml2_hash )
523 {
524     dTHX;
525     AV* mykeys = newAV();
526     SvREFCNT_inc((SV*)mykeys); /* needed?? */
527     xmlHashScan(xml2_hash, (xmlHashScanner)sp_add_key_to_array, mykeys);
528     return mykeys;
529 }
530
531
532 static void
533 sp_make_perl_hash(char* value, SV* stash, xmlChar* key)
534 {
535     dTHX;
536     sp_Stash_set_char(stash, (const char*)key, value );
537 }
538
539
540 static SV*
541 sp_xml2_hash_to_perl_hash( xmlHashTablePtr xml2_hash, const char* class )
542 {
543     dTHX;
544     SV* stash;
545     stash = sp_Stash_new();
546     sp_Stash_set_char(stash, SELF_CLASS_KEY, "xml2hash");
547     xmlHashScan(xml2_hash, (xmlHashScanner)sp_make_perl_hash, stash);
548     sp_describe_object( stash );
549     return stash;
550 }
551
552 static void
553 sp_perl_hash_to_xml2_hash( HV* perlhash, xmlHashTablePtr xml2hash )
554 {
555     croak("TODO");
556 }
557
558 static void
559 sp_nb_hash_to_phash(xmlBufferPtr buf, HV *phash, xmlChar *key)
560 {
561     dTHX;
562     AV* strings         = newAV();
563     const xmlChar *str  = xmlBufferContent(buf);
564     const xmlChar *tmp;
565     int bump            = strlen(SWISH_TOKENPOS_BUMPER);
566     int len;
567
568     //warn("%s nb_content: '%s'\n", key, str);
569         
570     /* analogous to @strings = split(/SWISH_TOKENPOS_BUMPER/, str) */
571     while((tmp = xmlStrstr(str, (xmlChar*)SWISH_TOKENPOS_BUMPER)) != NULL)
572     {
573         //warn("%s split: '%s'\n", key, str);
574         len = tmp - str;
575         if(len && !swish_str_all_ws_len((xmlChar*)str, len)) {
576             av_push(strings, newSVpvn((char*)str, len));
577         }
578         str = tmp + bump;  /* move the pointer up */
579     }
580    
581     /* no match and/or last match */
582     if ( !xmlStrstr(str, (xmlChar*)SWISH_TOKENPOS_BUMPER)
583       && strlen((char*)str)
584       && !swish_str_all_ws((xmlChar*)str)
585     ) {
586         av_push(strings, newSVpvn((char*)str, strlen((char*)str)));
587     }
588        
589     hv_store(phash,
590             (char*)key,
591             strlen((char*)key),
592             (void*)newRV_inc((SV*)strings),
593             0);
594 }
595
596 static HV*
597 sp_nb_to_hash( swish_NamedBuffer* nb )
598 {
599     dTHX;
600     HV* perl_hash = newHV();
601     SvREFCNT_inc((SV*)perl_hash);
602     xmlHashScan(nb->hash, (xmlHashScanner)sp_nb_hash_to_phash, perl_hash);
603     return perl_hash;
604 }
605
606
607 static void
608 sp_test_handler( swish_ParserData* parse_data )
609 {
610     dTHX;
611     warn("handler called!\n");
612     swish_debug_docinfo( parse_data->docinfo );
613     swish_debug_token_list( parse_data->token_iterator );
614     swish_debug_nb( parse_data->properties, (xmlChar*)"Property" );
615     swish_debug_nb( parse_data->metanames, (xmlChar*)"MetaName" );
616     warn("\n");
617 }
618
619 /* C wrapper for our Perl handler.
620    the parser object is passed in the parse_data stash.
621    we dereference it, pull out the SV* CODE ref, and execute
622    the Perl code.
623 */
624 static void
625 sp_handler( swish_ParserData* parse_data )
626 {
627     dTHX;
628     dSP;
629
630     swish_3    *s3;
631     SV         *handler;
632     SV         *obj;
633     char       *data_class;
634    
635     //warn("sp_handler called");
636     
637     s3          = (swish_3*)parse_data->s3;
638    
639     //warn("got s3");
640     //sp_describe_object(s3->stash);
641     
642     handler     = sp_Stash_get(s3->stash, HANDLER_KEY);
643    
644     //warn("got handler and s3");
645     
646     data_class  = sp_Stash_get_char(s3->stash, DATA_CLASS_KEY);
647    
648     //warn("data_class = %s", data_class);
649     
650     obj         = sp_bless_ptr( data_class, (IV)parse_data );
651    
652     //sp_describe_object(obj);
653     
654     PUSHMARK(SP);
655     XPUSHs(obj);
656     PUTBACK;
657
658     call_sv(handler, G_DISCARD);
659 }
660
661 /* this regex wizardry cribbed from KS - thanks Marvin! */
662 static int
663 sp_tokenize3(
664     swish_TokenIterator *ti,
665     xmlChar *buf,
666     swish_MetaName *meta,
667     xmlChar *context
668 )
669 {
670     dTHX;
671
672 /* declare */
673     unsigned int    num_tokens;
674     MAGIC           *mg;
675     REGEXP          *rx;
676     SV              *wrapper;
677     xmlChar         *str_start;
678     int              str_len;
679     int              minwordlen, maxwordlen;
680     xmlChar         *str_end;
681     SV              *token_re;
682
683 /* initialize */
684     num_tokens      = 0;
685     mg              = NULL;
686     rx              = NULL;
687     wrapper         = sv_newmortal();
688     str_start       = buf;
689     str_len         = strlen((char*)buf);
690     str_end         = str_start + str_len;
691     token_re        = ti->a->regex;
692     minwordlen      = ti->a->minwordlen;
693     maxwordlen      = ti->a->maxwordlen;
694    
695    
696 /* extract regexp struct from qr// entity */
697     if (SvROK(token_re)) {
698         SV *sv = SvRV(token_re);
699         if (SvMAGICAL(sv))
700             mg = mg_find(sv, PERL_MAGIC_qr);
701     }
702     if (!mg)
703         croak("regex is not a qr// entity");
704        
705     rx = (REGEXP*)mg->mg_obj;
706    
707 /* fake up an SV wrapper to feed to the regex engine */
708     sv_upgrade(wrapper, SVt_PV);
709     SvREADONLY_on(wrapper);
710     SvLEN(wrapper) = 0;
711     SvUTF8_on(wrapper);     /* do UTF8 matching -- we trust str is already utf-8 encoded. */
712    
713 /* wrap the string in an SV to please the regex engine */
714     SvPVX(wrapper) = (char*)str_start;
715     SvCUR_set(wrapper, str_len);
716     SvPOK_on(wrapper);
717    
718     while ( pregexec(rx, (char*)buf, (char*)str_end, (char*)buf, 1, wrapper, 1) )
719     {
720         int token_len;
721         xmlChar* start_ptr;
722         xmlChar* end_ptr;
723        
724 #if ((PERL_VERSION > 9) || (PERL_VERSION == 9 && PERL_SUBVERSION >= 5))
725         start_ptr = buf + rx->offs[0].start;
726         end_ptr   = buf + rx->offs[0].end;
727 #else
728         start_ptr = buf + rx->startp[0];
729         end_ptr   = buf + rx->endp[0];
730 #endif
731        
732         buf = end_ptr;
733
734         //warn("Token: %s", start_ptr);
735
736         token_len = (end_ptr - start_ptr) + 1;
737        
738         if (token_len < minwordlen)
739             continue;
740        
741         if (token_len > maxwordlen)
742             continue;
743        
744         swish_add_token(ti->tl, start_ptr, token_len, meta, context);
745         num_tokens++;
746        
747     }
748    
749     return num_tokens;
750 }
751
Note: See TracBrowser for help on using the browser.