mucgly 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +8 -0
  3. data/LICENSE +1 -1
  4. data/README.rdoc +160 -130
  5. data/bin/mucgly +4 -217
  6. data/doc/Mucgly.html +24 -82
  7. data/doc/_index.html +5 -153
  8. data/doc/class_list.html +7 -2
  9. data/doc/css/style.css +2 -1
  10. data/doc/file.CHANGELOG.html +16 -8
  11. data/doc/file.README.html +210 -187
  12. data/doc/file_list.html +6 -1
  13. data/doc/frames.html +5 -7
  14. data/doc/index.html +210 -187
  15. data/doc/js/app.js +7 -2
  16. data/doc/js/full_list.js +9 -6
  17. data/doc/method_list.html +7 -686
  18. data/doc/top-level-namespace.html +5 -5
  19. data/ext/mucgly/extconf.rb +11 -0
  20. data/ext/mucgly/mucgly.c +2095 -0
  21. data/lib/version.rb +6 -0
  22. data/test/golden/test_basic.txt +18 -0
  23. data/test/golden/test_specials_cli.txt +11 -0
  24. data/test/golden/test_specials_cmd.txt +36 -0
  25. data/test/result/test_basic.txt +18 -0
  26. data/test/result/test_specials_cli.txt +11 -0
  27. data/test/result/test_specials_cmd.txt +36 -0
  28. data/test/result/test_specials_cmd2.txt +1 -0
  29. data/test/test_basic.rx.txt +10 -6
  30. data/test/test_mucgly.rb +4 -6
  31. data/test/test_specials_cli.rx.txt +4 -4
  32. data/test/test_specials_cmd.rx.txt +5 -5
  33. metadata +45 -63
  34. data/Rakefile +0 -29
  35. data/doc/EasyFile/InOut.html +0 -2097
  36. data/doc/EasyFile/Read.html +0 -1334
  37. data/doc/EasyFile/ReadStack.html +0 -461
  38. data/doc/EasyFile/Stacked.html +0 -411
  39. data/doc/EasyFile/String.html +0 -570
  40. data/doc/EasyFile/Write.html +0 -1084
  41. data/doc/EasyFile/WriteStack.html +0 -305
  42. data/doc/EasyFile.html +0 -155
  43. data/doc/Mucgly/Env.html +0 -1675
  44. data/doc/Mucgly/MucglyFile/ParseState.html +0 -1662
  45. data/doc/Mucgly/MucglyFile/Token.html +0 -529
  46. data/doc/Mucgly/MucglyFile.html +0 -545
  47. data/doc/Mucgly/Separators.html +0 -521
  48. data/lib/easyfile.rb +0 -720
  49. data/lib/mucgly.rb +0 -627
  50. data/test/test_multi.rx.txt +0 -4
@@ -0,0 +1,2095 @@
1
+ #include <glib.h>
2
+ #include <glib/gstdio.h>
3
+ #include <ruby.h>
4
+
5
+
6
+ /*
7
+ * If MUCGLY_LIB is defined, mucgly is compiled in Ruby-top (e.g. for
8
+ * gems) mode. Otherwise mucgly is in C-top (main) mode,
9
+ * i.e. standalone binary.
10
+ */
11
+ #ifdef MUCGLY_LIB
12
+ void mucgly_main( int argc, char** argv );
13
+ #endif
14
+
15
+
16
+
17
+ /* ------------------------------------------------------------
18
+ * Debugging:
19
+ * ------------------------------------------------------------ */
20
+
21
+ /* Enable the define is input char based debugging is required. Set
22
+ GDB breakpoint at mucgly_debug. */
23
+ void mucgly_debug( void ) {}
24
+ #if 1
25
+ # define DEBUG_CHAR '~'
26
+ #endif
27
+
28
+
29
+
30
+ /* ------------------------------------------------------------
31
+ * Data models:
32
+ * ------------------------------------------------------------ */
33
+
34
+ /*
35
+ * Mucgly uses Pstate to store the parsing state of the input
36
+ * files. The parser has to know whether we are in or out of macro
37
+ * definition. The "hooks" in the input stream change the state within
38
+ * parsing.
39
+ *
40
+ * Pstate contains Filestack. Filestack is a stack of input files. The
41
+ * input file stack grows when the "lower" level files performs
42
+ * include command.
43
+ *
44
+ * Filestack contains Stackfiles. Stackfile presents the read status
45
+ * of an input file. It contains character position information for
46
+ * error reporting.
47
+ *
48
+ * Outfile is part of the Pstate output file stack. Each Outfile
49
+ * represents the output file state. Outfile stack is needed to
50
+ * implement redirection of output stream to different files. Output
51
+ * files has to be controlled explicitly from Mucgly files.
52
+ */
53
+
54
+
55
+ #define HOOKBEG_DEFAULT "-<"
56
+ #define HOOKEND_DEFAULT ">-"
57
+ #define HOOKESC_DEFAULT "\\"
58
+
59
+
60
+
61
+ /**
62
+ * Stackfile is an entry in the Filestack. Stackfile is the input file
63
+ * for Mucgly.
64
+ */
65
+ struct stackfile_s {
66
+
67
+ gchar* filename; /**< Filename. */
68
+ FILE* fh; /**< Stream handle. */
69
+ GString* buf; /**< Put-back buffer (oldest-is-last). */
70
+
71
+ int lineno; /**< Line number (0->). */
72
+ int column; /**< Line column (0->). */
73
+ int old_column; /**< Prev column (used with putback). */
74
+
75
+ gboolean macro; /**< Macro active. */
76
+ int macro_line; /**< Macro start line. */
77
+ int macro_col; /**< Macro start column. */
78
+
79
+ gchar* hookbeg; /**< Hookbeg for input file. */
80
+ gchar* hookend; /**< Hookend for input file. */
81
+ gchar* hookesc; /**< Hookesc for input file. */
82
+
83
+ /** Hookesc is same as hookbeg. Speeds up input processing. */
84
+ gboolean hook_esc_eq_beg;
85
+
86
+ /** Hookesc is same as hookend. Speeds up input processing. */
87
+ gboolean hook_esc_eq_end;
88
+
89
+ /** Hookesc is same as hookend. Speeds up input processing. */
90
+ guchar hook_1st_chars[ 256 ];
91
+
92
+ };
93
+ typedef struct stackfile_s stackfile_t;
94
+
95
+
96
+
97
+ /**
98
+ * Filestack is a stack of files (stackfile_t). Macro processing
99
+ * starts at base file. The base file is allowed to include other
100
+ * files (as input with ":include" command). The included file is
101
+ * added to the top of stack as current file at inclusion. When file
102
+ * EOF is encountered, it is popped of the stack. This makes the
103
+ * character flow continuous from the parser point of view.
104
+ */
105
+ struct filestack_s {
106
+ GList* file; /**< Stack of files. */
107
+ };
108
+ typedef struct filestack_s filestack_t;
109
+
110
+
111
+
112
+ /**
113
+ * Outfile is output file stream descriptor. Output can be diverted
114
+ * from the default output to another output file temporarely. Note
115
+ * that the diverted output stream has to be closed as well.
116
+ */
117
+ struct outfile_s {
118
+ gchar* filename; /**< Filename. */
119
+ FILE* fh; /**< Stream handle. */
120
+ int lineno; /**< Line number (0->). */
121
+ };
122
+ typedef struct outfile_s outfile_t;
123
+
124
+
125
+
126
+ /**
127
+ * Parser state for Mucgly.
128
+ */
129
+ struct pstate_s {
130
+ filestack_t* fs; /**< Stack of input streams. */
131
+
132
+ GString* check_buf; /**< Preview buffer. */
133
+ GString* macro_buf; /**< Macro content buffer. */
134
+ GString* match_buf; /**< Match str buffer. */
135
+
136
+ int in_macro; /**< Processing within macro. */
137
+
138
+ GList* output; /**< Stack of output streams. */
139
+
140
+ gboolean flush;
141
+ };
142
+ typedef struct pstate_s pstate_t;
143
+
144
+
145
+ /** Hook type enum. */
146
+ typedef enum hook_e { hook_none, hook_end, hook_beg, hook_esc } hook_t;
147
+
148
+
149
+ /** Current filestack from pstate_t (for convenience). */
150
+ #define ps_has_file(ps) ((stackfile_t*)(ps)->fs->file)
151
+
152
+ /** Current stackfile from pstate_t (for convenience). */
153
+ #define ps_topfile(ps) ((stackfile_t*)(ps)->fs->file->data)
154
+
155
+ /** Current stackfile from filestack_t (for convenience). */
156
+ #define fs_topfile(fs) ((stackfile_t*)(fs)->file->data)
157
+
158
+
159
+
160
+ /* ------------------------------------------------------------
161
+ * Global variables:
162
+ * ------------------------------------------------------------ */
163
+
164
+ /** Default hook settings. */
165
+ stackfile_t* stack_default = NULL;
166
+
167
+ /** Pointer to current file. Usefull for debugging. */
168
+ stackfile_t* csf;
169
+
170
+ /** Ruby side reference to parser. */
171
+ pstate_t* ruby_ps;
172
+
173
+
174
+
175
+ /* ------------------------------------------------------------
176
+ * Function declarations:
177
+ * ------------------------------------------------------------ */
178
+
179
+ void sf_set_hook( stackfile_t* sf, hook_t hook, char* value );
180
+
181
+
182
+
183
+ /* ------------------------------------------------------------
184
+ * Mucgly C-functions:
185
+ * ------------------------------------------------------------ */
186
+
187
+
188
+ /**
189
+ * Compare two strings upto length of str1.
190
+ *
191
+ * @param str1 String 1.
192
+ * @param str2 String 2.
193
+ *
194
+ * @return Match length (0 for no match).
195
+ */
196
+ int len_str_cmp( char* str1, char* str2 )
197
+ {
198
+ int len = strlen( str1 );
199
+
200
+ if ( !strncmp( str1, str2, len ) )
201
+ return len;
202
+ else
203
+ return 0;
204
+ }
205
+
206
+
207
+ /**
208
+ * Common utility function to output user info messages.
209
+ *
210
+ * @param sf Currently processed file (or NULL).
211
+ * @param infotype Severity type.
212
+ * @param format Message (printf) formatter.
213
+ * @param ap Message args.
214
+ */
215
+ void mucgly_user_info( stackfile_t* sf, char* infotype, char* format, va_list ap )
216
+ {
217
+ GString* out = g_string_sized_new( 0 );
218
+
219
+ if ( sf )
220
+ {
221
+ int lineno, column;
222
+
223
+ if ( sf->macro )
224
+ {
225
+ lineno = sf->macro_line;
226
+ column = sf->macro_col;
227
+ }
228
+ else
229
+ {
230
+ lineno = sf->lineno;
231
+ column = sf->column;
232
+ }
233
+
234
+ g_string_append_printf( out, "mucgly %s in \"%s:%d:%d\": ",
235
+ infotype,
236
+ sf->filename,
237
+ lineno+1,
238
+ column+1
239
+ );
240
+ }
241
+ else
242
+ {
243
+ g_string_append_printf( out, "mucgly %s: ", infotype );
244
+ }
245
+
246
+ g_string_append_vprintf( out, format, ap );
247
+ fputs( out->str, stderr );
248
+ fputc( '\n', stderr );
249
+ fflush( stderr );
250
+ g_string_free( out, TRUE );
251
+ }
252
+
253
+
254
+ /**
255
+ * Report warning to user (no exit).
256
+ *
257
+ * @param sf Current input file.
258
+ * @param format Message formatter.
259
+ */
260
+ void mucgly_warn( stackfile_t* sf, char* format, ... )
261
+ {
262
+ va_list ap;
263
+ va_start( ap, format );
264
+ mucgly_user_info( sf, "warning", format, ap );
265
+ va_end( ap );
266
+ }
267
+
268
+
269
+ /**
270
+ * Report error to user (and exit).
271
+ *
272
+ * @param sf Current input file.
273
+ * @param format Message formatter.
274
+ */
275
+ void mucgly_error( stackfile_t* sf, char* format, ... )
276
+ {
277
+ va_list ap;
278
+ va_start( ap, format );
279
+ mucgly_user_info( sf, "error", format, ap );
280
+ va_end( ap );
281
+ exit( EXIT_FAILURE );
282
+ }
283
+
284
+
285
+ /**
286
+ * Report fatal error to user (and exit).
287
+ *
288
+ * @param sf Current input file.
289
+ * @param format Message formatter.
290
+ */
291
+ void mucgly_fatal( stackfile_t* sf, char* format, ... )
292
+ {
293
+ va_list ap;
294
+ va_start( ap, format );
295
+ mucgly_user_info( sf, "fatal error", format, ap );
296
+ va_end( ap );
297
+ exit( EXIT_FAILURE );
298
+ }
299
+
300
+
301
+ /**
302
+ * Create new Stackfile.
303
+ *
304
+ * @param filename Filename (NULL for stdin).
305
+ * @param inherit Inherit hooks source (NULL for defaults).
306
+ *
307
+ * @return Stackfile.
308
+ */
309
+ stackfile_t* sf_new( gchar* filename, stackfile_t* inherit )
310
+ {
311
+ stackfile_t* sf;
312
+
313
+ sf = g_new0( stackfile_t, 1 );
314
+
315
+ if ( filename )
316
+ {
317
+ /* Real file. */
318
+
319
+ sf->fh = g_fopen( filename, (gchar*) "r" );
320
+ sf->filename = g_strdup( filename );
321
+
322
+ if ( sf->fh == NULL )
323
+ {
324
+ stackfile_t* err_sf;
325
+
326
+ /* Get the includer. */
327
+ err_sf = ruby_ps->fs->file->data;
328
+
329
+ mucgly_fatal( err_sf, "Can't open \"%s\"", filename );
330
+ }
331
+
332
+ }
333
+ else
334
+ {
335
+ /* Use stdin. */
336
+ sf->fh = stdin;
337
+ sf->filename = g_strdup( "<STDIN>" );
338
+ }
339
+
340
+ sf->buf = g_string_sized_new( 0 );
341
+ sf->lineno = 0;
342
+ sf->column = 0;
343
+ sf->old_column = 0;
344
+
345
+ sf->macro = FALSE;
346
+ sf->macro_line = 0;
347
+ sf->macro_col = 0;
348
+
349
+
350
+ if ( inherit )
351
+ {
352
+ /* Inherited hook values. */
353
+ sf->hookbeg = g_strdup( inherit->hookbeg );
354
+ sf->hookend = g_strdup( inherit->hookend );
355
+ sf->hookesc = g_strdup( inherit->hookesc );
356
+ }
357
+ else
358
+ {
359
+ /* Default hook values. */
360
+ sf->hookbeg = g_strdup( HOOKBEG_DEFAULT );
361
+ sf->hookend = g_strdup( HOOKEND_DEFAULT );
362
+ sf->hookesc = g_strdup( HOOKESC_DEFAULT );
363
+ }
364
+
365
+ /* Setup fast-lookup caches. */
366
+ sf_set_hook( sf, hook_none, NULL );
367
+
368
+ return sf;
369
+ }
370
+
371
+
372
+ /**
373
+ * Store macro start info.
374
+ *
375
+ * @param sf Stackfile.
376
+ */
377
+ void sf_mark_macro( stackfile_t* sf )
378
+ {
379
+ sf->macro = TRUE;
380
+ sf->macro_line = sf->lineno;
381
+ sf->macro_col = sf->column;
382
+ }
383
+
384
+
385
+ /**
386
+ * Reset macro start info.
387
+ *
388
+ * @param sf Stackfile.
389
+ */
390
+ void sf_unmark_macro( stackfile_t* sf )
391
+ {
392
+ sf->macro = FALSE;
393
+ }
394
+
395
+
396
+ /**
397
+ * Free Stackfile and close the file stream.
398
+ *
399
+ * @param sf Stackfile.
400
+ */
401
+ void sf_rem( stackfile_t* sf )
402
+ {
403
+ g_string_free( sf->buf, TRUE );
404
+
405
+ if ( sf->fh != stdin )
406
+ {
407
+ fclose( sf->fh );
408
+ }
409
+
410
+ g_free( sf->filename );
411
+
412
+ g_free( sf->hookbeg );
413
+ g_free( sf->hookend );
414
+ g_free( sf->hookesc );
415
+
416
+ g_free( sf );
417
+ }
418
+
419
+
420
+ /**
421
+ * Get char from Stackfile.
422
+ *
423
+ * @param sf Stackfile.
424
+ *
425
+ * @return Char or EOF.
426
+ */
427
+ int sf_get( stackfile_t* sf )
428
+ {
429
+ int ret;
430
+
431
+ if ( sf->buf->len > 0 )
432
+ {
433
+
434
+ /* Pending chars in buffer, don't need to read file (yet). */
435
+
436
+ int pos = sf->buf->len-1;
437
+
438
+ /* Use buffer as stack with oldest (first to use) on right. */
439
+ ret = sf->buf->str[ pos ];
440
+ g_string_truncate( sf->buf, pos );
441
+
442
+ }
443
+ else
444
+ {
445
+
446
+ /* Read from actual file stream. */
447
+
448
+ #ifdef DEBUG_CHAR
449
+ ret = fgetc( sf->fh );
450
+ if ( DEBUG_CHAR == (char) ret )
451
+ {
452
+ mucgly_debug();
453
+ ret = fgetc( sf->fh );
454
+ }
455
+ #else
456
+ ret = fgetc( sf->fh );
457
+ #endif
458
+
459
+ }
460
+
461
+ /* Update file point info. */
462
+ if ( ret != EOF )
463
+ {
464
+ if ( ret == '\n' )
465
+ {
466
+ sf->old_column = sf->column;
467
+ sf->lineno++;
468
+ sf->column = 0;
469
+ }
470
+ else
471
+ {
472
+ sf->column++;
473
+ }
474
+ }
475
+
476
+ return ret;
477
+ }
478
+
479
+
480
+ /**
481
+ * Put char back to Stackfile.
482
+ *
483
+ * @param sf Stackfile.
484
+ * @param c char.
485
+ *
486
+ * @return TRUE.
487
+ */
488
+ gboolean sf_put( stackfile_t* sf, char c )
489
+ {
490
+ if ( c == '\n' )
491
+ {
492
+ sf->lineno--;
493
+ sf->column = sf->old_column;
494
+ sf->old_column = 0;
495
+ }
496
+ else
497
+ {
498
+ sf->column--;
499
+ }
500
+
501
+ /* Use buffer as stack with oldest (first to use) on right. */
502
+ g_string_append_c( sf->buf, c );
503
+
504
+ return TRUE;
505
+ }
506
+
507
+
508
+ /**
509
+ * Set hook value.
510
+ *
511
+ * @param sf Stackfile.
512
+ * @param hook Hook type.
513
+ * @param value New hook value.
514
+ */
515
+ void sf_set_hook( stackfile_t* sf, hook_t hook, char* value )
516
+ {
517
+
518
+ /* Set values. */
519
+ switch ( hook )
520
+ {
521
+ case hook_beg: g_free( sf->hookbeg ); sf->hookbeg = g_strdup( value ); break;
522
+ case hook_end: g_free( sf->hookend ); sf->hookend = g_strdup( value ); break;
523
+ case hook_esc: g_free( sf->hookesc ); sf->hookesc = g_strdup( value ); break;
524
+ default: break;
525
+ }
526
+
527
+
528
+ /* Store these equalities for speed-up. */
529
+
530
+ if ( !g_strcmp0( sf->hookesc, sf->hookbeg ) )
531
+ sf->hook_esc_eq_beg = TRUE;
532
+ else
533
+ sf->hook_esc_eq_beg = FALSE;
534
+
535
+ if ( !g_strcmp0( sf->hookesc, sf->hookend ) )
536
+ sf->hook_esc_eq_end = TRUE;
537
+ else
538
+ sf->hook_esc_eq_end = FALSE;
539
+
540
+ /* Hook 1st char lookup. */
541
+ memset( sf->hook_1st_chars, 0, 256 * sizeof( guchar ) );
542
+ sf->hook_1st_chars[ sf->hookbeg[0] ] = 1;
543
+ sf->hook_1st_chars[ sf->hookend[0] ] = 1;
544
+ sf->hook_1st_chars[ sf->hookesc[0] ] = 1;
545
+ }
546
+
547
+
548
+ /**
549
+ * Create new Filestack.
550
+ *
551
+ * @return Filestack.
552
+ */
553
+ filestack_t* fs_new( void )
554
+ {
555
+ filestack_t* fs;
556
+
557
+ fs = g_new0( filestack_t, 1 );
558
+ fs->file = NULL;
559
+ return fs;
560
+ }
561
+
562
+
563
+ /**
564
+ * Free Filestack and close all Stackfiles.
565
+ *
566
+ * @param fs Filestack.
567
+ *
568
+ * @return NULL.
569
+ */
570
+ filestack_t* fs_rem( filestack_t* fs )
571
+ {
572
+ if ( fs )
573
+ {
574
+ for ( GList* p = fs->file; p; p = p->next )
575
+ {
576
+ sf_rem( (stackfile_t*) p->data );
577
+ }
578
+
579
+ g_free( fs );
580
+ }
581
+
582
+ return NULL;
583
+ }
584
+
585
+
586
+ /**
587
+ * Push file on top of Filestack.
588
+ *
589
+ * @param fs Filestack.
590
+ * @param filename New top file.
591
+ */
592
+ void fs_push_file( filestack_t* fs, gchar* filename )
593
+ {
594
+ stackfile_t* sf;
595
+
596
+ if ( fs->file )
597
+ /* Additinal file. */
598
+ sf = sf_new( filename, fs_topfile(fs) );
599
+ else
600
+ /* First file, i.e. inherit stack_default hooks. */
601
+ sf = sf_new( filename, stack_default );
602
+
603
+ /* Push file. */
604
+ fs->file = g_list_prepend( fs->file, sf );
605
+
606
+ /* Set global CurrentStackfile handle. */
607
+ csf = (stackfile_t*) fs->file->data;
608
+ }
609
+
610
+
611
+ /**
612
+ * Pop file from top of Filestack. File is closed.
613
+ *
614
+ * @param fs Filestack.
615
+ */
616
+ void fs_pop_file( filestack_t* fs )
617
+ {
618
+ stackfile_t* sf;
619
+
620
+ sf = fs->file->data;
621
+ sf_rem( sf );
622
+ fs->file = g_list_delete_link( fs->file, fs->file );
623
+
624
+ /* Set global CurrentStackfile handle. */
625
+ if ( fs->file )
626
+ csf = (stackfile_t*) fs->file->data;
627
+ else
628
+ csf = NULL;
629
+ }
630
+
631
+
632
+ /**
633
+ * Get char from (through) Filestack (i.e. top file).
634
+ *
635
+ * @param fs Filestack.
636
+ *
637
+ * @return Char or EOF.
638
+ */
639
+ int fs_get( filestack_t* fs )
640
+ {
641
+ int ret;
642
+
643
+ if ( fs->file != NULL )
644
+ {
645
+ ret = sf_get( fs_topfile(fs) );
646
+
647
+ /* Pop Stackfiles until no files or non-EOF char is received. */
648
+ while ( ret == EOF )
649
+ {
650
+ fs_pop_file( fs );
651
+ if ( fs->file == NULL )
652
+ return EOF;
653
+ ret = sf_get( fs_topfile(fs) );
654
+ }
655
+
656
+ return ret;
657
+ }
658
+ else
659
+ {
660
+ return EOF;
661
+ }
662
+ }
663
+
664
+
665
+ /**
666
+ * Put back char.
667
+ *
668
+ * @param fs Filestack.
669
+ * @param c Char.
670
+ *
671
+ * @return TRUE.
672
+ */
673
+ gboolean fs_put( filestack_t* fs, char c )
674
+ {
675
+ sf_put( fs_topfile(fs), c );
676
+ return TRUE;
677
+ }
678
+
679
+
680
+ /**
681
+ * Get n chars from Filestack. If ret is non-null, use that for
682
+ * storage.
683
+ *
684
+ * @param fs Filestack.
685
+ * @param n Number of chars to get.
686
+ * @param ret Storage for data.
687
+ *
688
+ * @return Return data.
689
+ */
690
+ GString* fs_get_n( filestack_t* fs, int n, GString* ret )
691
+ {
692
+
693
+ if ( ret == NULL )
694
+ {
695
+ /* New GString is needed for storage. */
696
+ ret = g_string_sized_new( 0 );
697
+ }
698
+ else
699
+ {
700
+ /* Reset size of the existing storage. */
701
+ g_string_set_size( ret, 0 );
702
+ }
703
+
704
+
705
+ /* Get n chars from current file. If EOF is encountered, reading is
706
+ terminated. */
707
+
708
+ for ( int i = 0, c; ; )
709
+ {
710
+ if ( i >= n )
711
+ break;
712
+
713
+ c = fs_get( fs );
714
+
715
+ if ( c != EOF )
716
+ {
717
+ g_string_append_c( ret, (char) c );
718
+ i++;
719
+ }
720
+ else
721
+ {
722
+ break;
723
+ }
724
+ }
725
+
726
+ return ret;
727
+ }
728
+
729
+
730
+ /**
731
+ * Put chars in str back to Filestack. If n is 0, then use strlen to
732
+ * get the count.
733
+ *
734
+ * @param fs Filestack.
735
+ * @param str Content to add.
736
+ * @param n Number of chars from content.
737
+ *
738
+ * @return True if success.
739
+ */
740
+ gboolean fs_put_n( filestack_t* fs, gchar* str, int n )
741
+ {
742
+ if ( n == 0 )
743
+ n = strlen( str );
744
+
745
+ /* Put chars with newest first (i.e. reverse order), so that oldest
746
+ comes out first. */
747
+ for ( int i = n-1; i >= 0; i-- )
748
+ {
749
+ fs_put( fs, str[ i ] );
750
+ }
751
+
752
+ return TRUE;
753
+ }
754
+
755
+
756
+ /**
757
+ * Create new Outfile. If filename is NULL, then stream is stdout.
758
+ *
759
+ * @param filename File name (or NULL for stdout).
760
+ *
761
+ * @return Outfile.
762
+ */
763
+ outfile_t* outfile_new( gchar* filename )
764
+ {
765
+ outfile_t* of;
766
+
767
+ of = g_new0( outfile_t, 1 );
768
+ of->lineno = 0;
769
+
770
+ if ( filename )
771
+ {
772
+
773
+ /* Disk file output. */
774
+
775
+ of->filename = g_strdup( filename );
776
+
777
+ of->fh = g_fopen( filename, (gchar*) "w" );
778
+
779
+ if ( of->fh == NULL )
780
+ {
781
+ stackfile_t* err_sf;
782
+
783
+ /* Get the includer. */
784
+ err_sf = ruby_ps->fs->file->data;
785
+
786
+ mucgly_fatal( err_sf, "Can't open \"%s\"", err_sf->filename );
787
+ }
788
+
789
+ }
790
+ else
791
+ {
792
+
793
+ /* STDOUT output. */
794
+ of->filename = g_strdup( "<STDOUT>" );
795
+ of->fh = stdout;
796
+
797
+ }
798
+
799
+ return of;
800
+ }
801
+
802
+
803
+ /**
804
+ * Free Outfile and close output stream if non-stdout.
805
+ *
806
+ * @param of Outfile.
807
+ */
808
+ void outfile_rem( outfile_t* of )
809
+ {
810
+ if ( of->fh != stdout )
811
+ fclose( of->fh );
812
+
813
+ g_free( of->filename );
814
+ g_free( of );
815
+ }
816
+
817
+
818
+ /**
819
+ * Create Pstate. Create input file stack and output file
820
+ * stack. Initialize parsing state.
821
+ *
822
+ * @param outfile Initial input file name.
823
+ *
824
+ * @return Pstate.
825
+ */
826
+ pstate_t* ps_new( gchar* outfile )
827
+ {
828
+ pstate_t* ps;
829
+
830
+ ps = g_new0( pstate_t, 1 );
831
+
832
+ ps->fs = fs_new();
833
+
834
+ ps->in_macro = 0;
835
+
836
+ /* Preview input buffer. */
837
+ ps->check_buf = g_string_sized_new( 0 );
838
+
839
+ /* Top level input buffer. */
840
+ ps->macro_buf = g_string_sized_new( 0 );
841
+
842
+ /* Match string buffer. */
843
+ ps->match_buf = g_string_sized_new( 0 );
844
+
845
+
846
+ ps->output = g_list_prepend( ps->output, outfile_new( outfile ) );
847
+ ps->flush = FALSE;
848
+
849
+ return ps;
850
+ }
851
+
852
+
853
+ /**
854
+ * Free Pstate. Close all input and output files.
855
+ *
856
+ * @param ps Pstate.
857
+ */
858
+ void ps_rem( pstate_t* ps )
859
+ {
860
+ fs_rem( ps->fs );
861
+
862
+ g_string_free( ps->check_buf, TRUE );
863
+ g_string_free( ps->macro_buf, TRUE );
864
+
865
+ for ( GList* of = ps->output; of; of = of->next )
866
+ {
867
+ outfile_rem( (outfile_t*) of->data );
868
+ }
869
+
870
+ g_free( ps );
871
+ }
872
+
873
+
874
+ /**
875
+ * Fast check for current input char being first char of any of the
876
+ * hooks.
877
+ *
878
+ * @param ps Pstate.
879
+ *
880
+ * @return TRUE if next char matches 1st char of any hook.
881
+ */
882
+ gboolean ps_check_hook( pstate_t* ps, int c )
883
+ {
884
+ stackfile_t* sf;
885
+
886
+ if ( c == EOF )
887
+ return FALSE;
888
+
889
+ sf = ps_topfile(ps);
890
+
891
+ if ( sf->hook_1st_chars[ c ] )
892
+ return TRUE;
893
+ else
894
+ return FALSE;
895
+ }
896
+
897
+
898
+ /**
899
+ * Check if input has the match string coming next. Erase the matched
900
+ * string if erase is true and input is matched.
901
+ *
902
+ * @param ps Pstate.
903
+ * @param match Match string.
904
+ * @param erase Erase matched string (but only if matched).
905
+ *
906
+ * @return True if match.
907
+ */
908
+ gboolean ps_check( pstate_t* ps, gchar* match, gboolean erase )
909
+ {
910
+ GString* input;
911
+ int len;
912
+ gboolean ret;
913
+
914
+ /* Dup match string since it might disappear if Filestack stack is
915
+ popped. */
916
+
917
+ g_string_assign( ps->match_buf, match );
918
+
919
+ len = strlen( match );
920
+ input = fs_get_n( ps->fs, len, ps->check_buf );
921
+ ret = !g_strcmp0( input->str, ps->match_buf->str );
922
+
923
+ if ( !ret || !erase )
924
+ /* Put back checked chars. */
925
+ fs_put_n( ps->fs, input->str, input->len );
926
+
927
+ return ret;
928
+ }
929
+
930
+
931
+ /**
932
+ * Check input for hookesc.
933
+ *
934
+ * @param ps Pstate.
935
+ *
936
+ * @return TRUE on match.
937
+ */
938
+ gboolean ps_check_hookesc( pstate_t* ps )
939
+ {
940
+ if ( !ps_has_file(ps) )
941
+ return FALSE;
942
+
943
+ return ps_check( ps, ps_topfile(ps)->hookesc, TRUE );
944
+ }
945
+
946
+
947
+ /**
948
+ * Check input for hookbeg.
949
+ *
950
+ * @param ps Pstate.
951
+ *
952
+ * @return TRUE on match.
953
+ */
954
+ gboolean ps_check_hookbeg( pstate_t* ps )
955
+ {
956
+ if ( !ps_has_file(ps) )
957
+ return FALSE;
958
+
959
+ return ps_check( ps, ps_topfile(ps)->hookbeg, TRUE );
960
+ }
961
+
962
+
963
+ /**
964
+ * Check input for hookend.
965
+ *
966
+ * @param ps Pstate.
967
+ *
968
+ * @return TRUE on match.
969
+ */
970
+ gboolean ps_check_hookend( pstate_t* ps )
971
+ {
972
+ if ( !ps_has_file(ps) )
973
+ return FALSE;
974
+
975
+ return ps_check( ps, ps_topfile(ps)->hookend, TRUE );
976
+ }
977
+
978
+
979
+ /**
980
+ * Get char (through Filestack).
981
+ *
982
+ * @param ps Pstate.
983
+ *
984
+ * @return Char or EOF.
985
+ */
986
+ int ps_in( pstate_t* ps )
987
+ {
988
+ return fs_get( ps->fs );
989
+ }
990
+
991
+
992
+ /**
993
+ * Output char to current output stream.
994
+ *
995
+ * @param ps Pstate.
996
+ * @param c Char.
997
+ */
998
+ void ps_out( pstate_t* ps, int c )
999
+ {
1000
+ outfile_t* of = ps->output->data;
1001
+
1002
+ if ( c == '\n' )
1003
+ of->lineno++;
1004
+
1005
+ fputc( c, of->fh );
1006
+ if ( ps->flush )
1007
+ fflush( of->fh );
1008
+ }
1009
+
1010
+
1011
+ /**
1012
+ * Output chars with ps_out.
1013
+ *
1014
+ * @param ps Pstate.
1015
+ * @param str Output string (or NULL for no output).
1016
+ */
1017
+ void ps_out_str( pstate_t* ps, gchar* str )
1018
+ {
1019
+ if ( str )
1020
+ {
1021
+ int len = strlen( str );
1022
+
1023
+ for ( int i = 0; i < len; i++ )
1024
+ {
1025
+ ps_out( ps, str[ i ] );
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+
1031
+ /**
1032
+ * Push new output stream on top of output file stack.
1033
+ *
1034
+ * @param ps Pstate.
1035
+ * @param filename File name.
1036
+ */
1037
+ void ps_push_file( pstate_t* ps, gchar* filename )
1038
+ {
1039
+ ps->output = g_list_prepend( ps->output,
1040
+ outfile_new( filename ) );
1041
+ }
1042
+
1043
+
1044
+ /**
1045
+ * Pop file from output file stack. Close the stream.
1046
+ *
1047
+ * @param ps Pstate.
1048
+ */
1049
+ void ps_pop_file( pstate_t* ps )
1050
+ {
1051
+ outfile_t* of;
1052
+
1053
+ of = ps->output->data;
1054
+ outfile_rem( of );
1055
+ ps->output = g_list_delete_link( ps->output, ps->output );
1056
+ }
1057
+
1058
+
1059
+ /**
1060
+ * Return current Stackfile (input stream).
1061
+ *
1062
+ * @param ps Pstate.
1063
+ *
1064
+ * @return Stackfile (or NULL if none).
1065
+ */
1066
+ stackfile_t* ps_current_file( pstate_t* ps )
1067
+ {
1068
+ if ( ps == NULL )
1069
+ return NULL;
1070
+ else if ( !ps_has_file(ps) )
1071
+ return NULL;
1072
+ else
1073
+ return ps_topfile(ps);
1074
+ }
1075
+
1076
+
1077
+ /**
1078
+ * Initialize macro content collection.
1079
+ *
1080
+ * @param ps Pstate.
1081
+ */
1082
+ void ps_start_collect( pstate_t* ps )
1083
+ {
1084
+ g_string_set_size( ps->macro_buf, 0 );
1085
+ }
1086
+
1087
+
1088
+ /**
1089
+ * Add content to macro.
1090
+ *
1091
+ * @param ps Pstate.
1092
+ * @param c Char to add.
1093
+ */
1094
+ void ps_collect( pstate_t* ps, int c )
1095
+ {
1096
+ g_string_append_c( ps->macro_buf, c );
1097
+ }
1098
+
1099
+
1100
+ /**
1101
+ * Get current macro content.
1102
+ *
1103
+ * @param ps Pstate.
1104
+ *
1105
+ * @return Macro content.
1106
+ */
1107
+ char* ps_get_macro( pstate_t* ps )
1108
+ {
1109
+ return ps->macro_buf->str;
1110
+ }
1111
+
1112
+
1113
+ /**
1114
+ * Evaluate str as Ruby code and convert result to string. Conversion
1115
+ * is performed if execution was successful and conversion was
1116
+ * requested.
1117
+ *
1118
+ * @param ps Pstate.
1119
+ * @param str Ruby code string.
1120
+ * @param to_str Result convert enable.
1121
+ * @param ctxt Context (name) for execution.
1122
+ *
1123
+ * @return Ruby code execution as string (or NULL).
1124
+ */
1125
+ gchar* ps_eval_ruby_str( pstate_t* ps, gchar* str, gboolean to_str, char* ctxt )
1126
+ {
1127
+ int status = 0;
1128
+ VALUE ret;
1129
+
1130
+ if ( !ctxt )
1131
+ /* Used default context. */
1132
+ ctxt = "macro";
1133
+
1134
+ ret = rb_eval_string_protect( (char*) str, &status );
1135
+
1136
+ if ( status == 0 )
1137
+ {
1138
+ /* Successfull execution. */
1139
+ if ( to_str )
1140
+ {
1141
+ if ( TYPE( ret ) == T_STRING )
1142
+ {
1143
+ return ( (gchar*) StringValueCStr( ret ) );
1144
+ }
1145
+ else
1146
+ {
1147
+ /* Convert to Ruby String. */
1148
+ ret = rb_inspect( ret );
1149
+ return ( (gchar*) StringValueCStr( ret ) );
1150
+ }
1151
+ }
1152
+ else
1153
+ {
1154
+ return NULL;
1155
+ }
1156
+ }
1157
+ else
1158
+ {
1159
+ mucgly_error( ps_current_file( ps ), "Error in %s: \n in ruby (%s): %s",
1160
+ ctxt,
1161
+ RSTRING_PTR(rb_inspect(RARRAY_AREF(rb_gv_get("$@"),0))),
1162
+ RSTRING_PTR(rb_inspect(rb_gv_get("$!"))) );
1163
+ return NULL;
1164
+ }
1165
+ }
1166
+
1167
+
1168
+ /**
1169
+ * Load file Ruby intepreter.
1170
+ *
1171
+ * @param filename Source file.
1172
+ */
1173
+ void ps_load_ruby_file( pstate_t* ps, gchar* filename )
1174
+ {
1175
+ GString* cmd;
1176
+
1177
+ cmd = g_string_sized_new( 0 );
1178
+ g_string_printf( cmd, "load \"%s\"", filename );
1179
+ ps_eval_ruby_str( ps, cmd->str, FALSE, "load" );
1180
+ g_string_free( cmd, TRUE );
1181
+ }
1182
+
1183
+
1184
+ /**
1185
+ * Execute Mucgly command/macro.
1186
+ *
1187
+ * @param ps Pstate.
1188
+ *
1189
+ * @return TRUE if input processing should be aborted.
1190
+ */
1191
+ gboolean ps_eval_cmd( pstate_t* ps )
1192
+ {
1193
+ char* cmd;
1194
+
1195
+ cmd = ps_get_macro( ps );
1196
+
1197
+ if ( cmd[0] == ':' )
1198
+ {
1199
+
1200
+ /* Mucgly internal command. */
1201
+
1202
+ int len;
1203
+
1204
+ if ( 0 ) {}
1205
+ else if ( ( len = len_str_cmp( ":hookbeg", cmd ) ) )
1206
+ sf_set_hook( ps_topfile(ps), hook_beg, &cmd[ len+1 ] );
1207
+ else if ( ( len = len_str_cmp( ":hookend", cmd ) ) )
1208
+ sf_set_hook( ps_topfile(ps), hook_end, &cmd[ len+1 ] );
1209
+ else if ( ( len = len_str_cmp( ":hookesc", cmd ) ) )
1210
+ sf_set_hook( ps_topfile(ps), hook_esc, &cmd[ len+1 ] );
1211
+
1212
+ else if ( ( len = len_str_cmp( ":hookall", cmd ) ) )
1213
+ {
1214
+ sf_set_hook( ps_topfile(ps), hook_beg, &cmd[ len+1 ] );
1215
+ sf_set_hook( ps_topfile(ps), hook_end, &cmd[ len+1 ] );
1216
+ sf_set_hook( ps_topfile(ps), hook_esc, &cmd[ len+1 ] );
1217
+ }
1218
+
1219
+ else if ( ( len = len_str_cmp( ":hook", cmd ) ) )
1220
+ {
1221
+ gchar** pieces;
1222
+
1223
+ pieces = g_strsplit( &cmd[ len+1 ], " ", 2 );
1224
+ if ( g_strv_length( pieces ) == 2 )
1225
+ {
1226
+ /* Two hooks separated by space. */
1227
+ sf_set_hook( ps_topfile(ps), hook_beg, pieces[0] );
1228
+ sf_set_hook( ps_topfile(ps), hook_end, pieces[1] );
1229
+ }
1230
+ else
1231
+ {
1232
+ /* Only one hook specified. */
1233
+ sf_set_hook( ps_topfile(ps), hook_beg, pieces[0] );
1234
+ sf_set_hook( ps_topfile(ps), hook_end, pieces[0] );
1235
+ }
1236
+ g_strfreev( pieces );
1237
+ }
1238
+
1239
+ else if ( ( len = len_str_cmp( ":include", cmd ) ) )
1240
+ fs_push_file( ps->fs, &cmd[ len+1 ] );
1241
+
1242
+ else if ( ( len = len_str_cmp( ":source", cmd ) ) )
1243
+ ps_load_ruby_file( ps, &cmd[ len+1 ] );
1244
+
1245
+ else if ( ( len = len_str_cmp( ":comment", cmd ) ) )
1246
+ {
1247
+ /* Do nothing. */
1248
+ }
1249
+
1250
+ else if ( ( len = len_str_cmp( ":exit", cmd ) ) )
1251
+ {
1252
+ /* Exit processing. */
1253
+ return TRUE;
1254
+ }
1255
+
1256
+ else
1257
+ {
1258
+ mucgly_error( ps_topfile(ps), "Unknown internal command: \"%s\"", &cmd[len+1] );
1259
+ }
1260
+ }
1261
+
1262
+ else if ( cmd[0] == '.' )
1263
+ {
1264
+ /* Mucgly variable output. */
1265
+ ps_out_str( ps, ps_eval_ruby_str( ps, &cmd[1], TRUE, NULL ) );
1266
+ }
1267
+
1268
+ else if ( cmd[0] == '/' )
1269
+ {
1270
+ /* Mucgly comment, do nothing. */
1271
+ }
1272
+
1273
+ else if ( cmd[0] == '#' )
1274
+ {
1275
+ /* Postpone evaluation (to next round). */
1276
+
1277
+ ps_out_str( ps, ps_topfile(ps)->hookbeg );
1278
+
1279
+ /* Remove one # sign. */
1280
+ ps_out_str( ps, &cmd[1] );
1281
+
1282
+ ps_out_str( ps, ps_topfile(ps)->hookend );
1283
+ }
1284
+
1285
+ else
1286
+ {
1287
+ /* Ruby code execution. */
1288
+ ps_eval_ruby_str( ps, cmd, FALSE, NULL );
1289
+ }
1290
+
1291
+ return FALSE;
1292
+ }
1293
+
1294
+
1295
+ /** Convenience macro for hookend. */
1296
+ #define HOOK_END_SEQ { \
1297
+ ps->in_macro--; \
1298
+ if ( ps->in_macro ) \
1299
+ { \
1300
+ /* Multilevel macro decrement. */ \
1301
+ /* Output the consumed hookend. */ \
1302
+ ps_out_str( ps, ps_topfile(ps)->hookend ); \
1303
+ } \
1304
+ else \
1305
+ { \
1306
+ /* Back to base level from macro, eval the macro. */ \
1307
+ gboolean do_break; \
1308
+ do_break = ps_eval_cmd( ps ); \
1309
+ sf_unmark_macro( ps_topfile( ps ) ); \
1310
+ if ( do_break ) \
1311
+ break; \
1312
+ } \
1313
+ }
1314
+
1315
+
1316
+ /* Convenience macro for non-hook processing. */
1317
+ #define NON_HOOK_SEQ { \
1318
+ /* Non-hook input. */ \
1319
+ if ( ps->in_macro ) \
1320
+ { \
1321
+ if ( c == EOF ) \
1322
+ mucgly_error( ps_current_file(ps), "Got EOF within macro!" ); \
1323
+ /* Add content to macro. */ \
1324
+ ps_collect( ps, c ); \
1325
+ } \
1326
+ else \
1327
+ { \
1328
+ if ( c == EOF ) \
1329
+ break; \
1330
+ else \
1331
+ /* Output to current output file. */ \
1332
+ ps_out( ps, c ); \
1333
+ } \
1334
+ }
1335
+
1336
+
1337
+ /**
1338
+ * Process input file (or stack of files).
1339
+ *
1340
+ * @param ps Pstate.
1341
+ * @param infile Input file name.
1342
+ * @param outfile Output file name (or NULL). Outfile string is freed.
1343
+ */
1344
+ void ps_process_file( pstate_t* ps, gchar* infile, gchar* outfile )
1345
+ {
1346
+ int c;
1347
+
1348
+ fs_push_file( ps->fs, infile );
1349
+
1350
+ if ( outfile )
1351
+ {
1352
+ ps_push_file( ps, outfile );
1353
+ g_free( outfile );
1354
+ }
1355
+
1356
+ /* ------------------------------------------------------------
1357
+ * Process input:
1358
+ * ------------------------------------------------------------ */
1359
+
1360
+ for (;;)
1361
+ {
1362
+
1363
+ /* For each input char, we must explicitly read it since
1364
+ otherwise the Filestack does not operate correctly, i.e. we
1365
+ are not allowed to put back to stream after EOF has been
1366
+ encountered. Files are popped automatically after EOF. */
1367
+
1368
+ c = ps_in( ps );
1369
+
1370
+ /* Check if next char starts one of the hooks. */
1371
+ if ( ps_check_hook( ps, c ) )
1372
+ {
1373
+
1374
+ fs_put( ps->fs, c );
1375
+
1376
+ /* Escape is always checked before other hooks. */
1377
+ if ( ps_check_hookesc( ps ) )
1378
+ {
1379
+ /* Handle ESCAPEs depending whether in macro or not. */
1380
+
1381
+ if ( ps->in_macro )
1382
+ {
1383
+
1384
+ /* Escape in macro. */
1385
+
1386
+ /* Just transfer the following char to output. */
1387
+ c = ps_in( ps );
1388
+
1389
+ /* Error, if EOF in macro. */
1390
+ if ( c == EOF )
1391
+ {
1392
+ mucgly_error( ps_current_file(ps), "Got EOF within macro!" );
1393
+ }
1394
+ else
1395
+ {
1396
+
1397
+ if ( ( c == ' ' || c == '\n' ) &&
1398
+ ps_topfile(ps)->hook_esc_eq_end )
1399
+ {
1400
+
1401
+ /* Space/newline and hookesc is same as hookbeg. */
1402
+
1403
+ ps->in_macro--;
1404
+
1405
+ if ( ps->in_macro )
1406
+ {
1407
+ /* Multilevel macro decrement. */
1408
+
1409
+ /* Output the consumed hookend. */
1410
+ ps_out_str( ps, ps_topfile(ps)->hookend );
1411
+ }
1412
+ else
1413
+ {
1414
+ /* Back to base level from macro, eval the macro. */
1415
+ if ( ps_eval_cmd( ps ) )
1416
+ break;
1417
+ }
1418
+ }
1419
+ else
1420
+ {
1421
+ ps_collect( ps, c );
1422
+ }
1423
+ }
1424
+ }
1425
+ else
1426
+ {
1427
+
1428
+ /* Escape outside macro. */
1429
+
1430
+ /* Map next char according to escape rules. */
1431
+ c = ps_in( ps );
1432
+
1433
+ if ( c == EOF )
1434
+ {
1435
+ /* EOF */
1436
+ break;
1437
+ }
1438
+ else
1439
+ {
1440
+
1441
+ switch ( (char)c )
1442
+ {
1443
+
1444
+ case '\n':
1445
+ /* Eat newlines. */
1446
+ break;
1447
+
1448
+ case ' ':
1449
+ /* Eat spaces. */
1450
+ break;
1451
+
1452
+ default:
1453
+
1454
+ if ( ps_topfile(ps)->hook_esc_eq_beg )
1455
+ {
1456
+
1457
+ if ( ps_topfile(ps)->hookesc[1] == 0
1458
+ && c == ps_topfile(ps)->hookesc[0] )
1459
+ {
1460
+ /* Escape is one char long and following
1461
+ char was ESC (i.e. escaped escape). */
1462
+ ps_out( ps, c );
1463
+ }
1464
+ else
1465
+ {
1466
+ /* Escape is same as hookbeg and escape is
1467
+ not used to eat out spaces. */
1468
+ ps->in_macro++;
1469
+
1470
+ /* Put back the extra char (for safety/clarity). */
1471
+ fs_put( ps->fs, c );
1472
+
1473
+ /* Start collect the macro content. */
1474
+ ps_start_collect( ps );
1475
+ }
1476
+ }
1477
+ else
1478
+ {
1479
+ /* Literal output. */
1480
+ ps_out( ps, c );
1481
+ }
1482
+ }
1483
+ }
1484
+
1485
+ }
1486
+ }
1487
+ else if ( ps->in_macro && ps_check_hookend( ps ) )
1488
+ {
1489
+ /* Hookend has priority over hookbeg if we are in macro. */
1490
+ HOOK_END_SEQ;
1491
+ }
1492
+ else if ( ps_check_hookbeg( ps ) )
1493
+ {
1494
+
1495
+ if ( ps->in_macro )
1496
+ {
1497
+ /* Macro in macro. */
1498
+
1499
+ /* Increase macro level. */
1500
+ ps->in_macro++;
1501
+
1502
+ /* Output the consumed hookbeg. */
1503
+ ps_out_str( ps, ps_topfile(ps)->hookbeg );
1504
+ }
1505
+ else
1506
+ {
1507
+ /* New macro starting. */
1508
+ ps->in_macro++;
1509
+
1510
+ /* Store macro start info. */
1511
+ sf_mark_macro( ps_topfile( ps ) );
1512
+
1513
+ ps_start_collect( ps );
1514
+ }
1515
+
1516
+ }
1517
+ else if ( ps_check_hookend( ps ) )
1518
+ {
1519
+ HOOK_END_SEQ;
1520
+ }
1521
+ else
1522
+ {
1523
+ c = ps_in( ps );
1524
+ NON_HOOK_SEQ;
1525
+ }
1526
+ }
1527
+ else
1528
+ {
1529
+ NON_HOOK_SEQ;
1530
+ }
1531
+ }
1532
+
1533
+ if ( outfile )
1534
+ ps_pop_file( ps );
1535
+
1536
+ }
1537
+
1538
+
1539
+
1540
+
1541
+ /* ------------------------------------------------------------
1542
+ * Mucgly Ruby-functions:
1543
+ * ------------------------------------------------------------ */
1544
+
1545
+
1546
+ /**
1547
+ * Mucgly.write method. Write output current output without NL.
1548
+ *
1549
+ * @param obj Not used.
1550
+ * @param rstr Written string (Ruby String).
1551
+ *
1552
+ * @return Qnil.
1553
+ */
1554
+ VALUE mucgly_write( VALUE obj, VALUE rstr )
1555
+ {
1556
+ char* str;
1557
+ VALUE tmp;
1558
+
1559
+ if ( TYPE( rstr ) == T_STRING )
1560
+ tmp = rstr;
1561
+ else
1562
+ /* Convert to Ruby String. */
1563
+ tmp = rb_inspect( rstr );
1564
+
1565
+ str = RSTRING_PTR( tmp );
1566
+ ps_out_str( ruby_ps, str );
1567
+ return Qnil;
1568
+ }
1569
+
1570
+
1571
+ /**
1572
+ * Mucgly.puts method. Write output current output with NL.
1573
+ *
1574
+ * @param obj Not used.
1575
+ * @param rstr Written string (Ruby String).
1576
+ *
1577
+ * @return Qnil.
1578
+ */
1579
+ VALUE mucgly_puts( VALUE obj, VALUE rstr )
1580
+ {
1581
+ char* str;
1582
+ VALUE tmp;
1583
+
1584
+ if ( TYPE( rstr ) == T_STRING )
1585
+ tmp = rstr;
1586
+ else
1587
+ /* Convert to Ruby String. */
1588
+ tmp = rb_inspect( rstr );
1589
+
1590
+ str = RSTRING_PTR( tmp );
1591
+
1592
+ ps_out_str( ruby_ps, str );
1593
+ ps_out( ruby_ps, '\n' );
1594
+ return Qnil;
1595
+ }
1596
+
1597
+
1598
+ /**
1599
+ * Mucgly.hookbeg method. Get hookbeg.
1600
+ *
1601
+ * @param obj Not used.
1602
+ *
1603
+ * @return Hookbeg as Ruby String.
1604
+ */
1605
+ VALUE mucgly_hookbeg( VALUE obj )
1606
+ {
1607
+ return rb_str_new_cstr( ps_current_file( ruby_ps )->hookbeg );
1608
+ }
1609
+
1610
+
1611
+ /**
1612
+ * Mucgly.hookend method. Get hookend.
1613
+ *
1614
+ * @param obj Not used.
1615
+ *
1616
+ * @return Hookend as Ruby String.
1617
+ */
1618
+ VALUE mucgly_hookend( VALUE obj )
1619
+ {
1620
+ return rb_str_new_cstr( ps_current_file( ruby_ps )->hookend );
1621
+ }
1622
+
1623
+
1624
+ /**
1625
+ * Mucgly.hookesc method. Get hookesc.
1626
+ *
1627
+ * @param obj Not used.
1628
+ *
1629
+ * @return Hookesc as Ruby String.
1630
+ */
1631
+ VALUE mucgly_hookesc( VALUE obj )
1632
+ {
1633
+ return rb_str_new_cstr( ps_current_file( ruby_ps )->hookesc );
1634
+ }
1635
+
1636
+
1637
+ /**
1638
+ * Mucgly.sethookbeg method. Set hookbeg.
1639
+ *
1640
+ * @param obj Not used.
1641
+ * @param rstr Hookbeg string (Ruby String).
1642
+ *
1643
+ * @return Qnil.
1644
+ */
1645
+ VALUE mucgly_sethookbeg( VALUE obj, VALUE rstr )
1646
+ {
1647
+ char* str;
1648
+ str = RSTRING_PTR( rstr );
1649
+ sf_set_hook( ps_topfile( ruby_ps ), hook_beg, str );
1650
+ return Qnil;
1651
+ }
1652
+
1653
+
1654
+ /**
1655
+ * Mucgly.sethookend method. Set hookend.
1656
+ *
1657
+ * @param obj Not used.
1658
+ * @param rstr Hookend string (Ruby String).
1659
+ *
1660
+ * @return Qnil.
1661
+ */
1662
+ VALUE mucgly_sethookend( VALUE obj, VALUE rstr )
1663
+ {
1664
+ char* str;
1665
+ str = RSTRING_PTR( rstr );
1666
+ sf_set_hook( ps_topfile( ruby_ps ), hook_end, str );
1667
+ return Qnil;
1668
+ }
1669
+
1670
+
1671
+ /**
1672
+ * Mucgly.sethookesc method. Set hookesc.
1673
+ *
1674
+ * @param obj Not used.
1675
+ * @param rstr Hookesc string (Ruby String).
1676
+ *
1677
+ * @return Qnil.
1678
+ */
1679
+ VALUE mucgly_sethookesc( VALUE obj, VALUE rstr )
1680
+ {
1681
+ char* str;
1682
+ str = RSTRING_PTR( rstr );
1683
+ sf_set_hook( ps_topfile( ruby_ps ), hook_esc, str );
1684
+ return Qnil;
1685
+ }
1686
+
1687
+
1688
+ /**
1689
+ * Mucgly.ifilename method. Gets current input file name.
1690
+ *
1691
+ * @param obj Not used.
1692
+ *
1693
+ * @return Input file name (Ruby String).
1694
+ */
1695
+ VALUE mucgly_ifilename( VALUE obj )
1696
+ {
1697
+ return rb_str_new_cstr( ps_topfile(ruby_ps)->filename );
1698
+ }
1699
+
1700
+
1701
+ /**
1702
+ * Mucgly.ilinenumber method. Gets current input file line.
1703
+ *
1704
+ * @param obj Not used.
1705
+ *
1706
+ * @return Input file line number (Ruby Fixnum).
1707
+ */
1708
+ VALUE mucgly_ilinenumber( VALUE obj )
1709
+ {
1710
+ return INT2FIX( ps_topfile(ruby_ps)->lineno+1 );
1711
+ }
1712
+
1713
+
1714
+ /**
1715
+ * Mucgly.ofilename method. Gets current output file name.
1716
+ *
1717
+ * @param obj Not used.
1718
+ *
1719
+ * @return Input file name (Ruby String).
1720
+ */
1721
+ VALUE mucgly_ofilename( VALUE obj )
1722
+ {
1723
+ outfile_t* of;
1724
+ of = ruby_ps->output->data;
1725
+ return rb_str_new_cstr( of->filename );
1726
+ }
1727
+
1728
+
1729
+ /**
1730
+ * Mucgly.olinenumber method. Gets current output file line.
1731
+ *
1732
+ * @param obj Not used.
1733
+ *
1734
+ * @return Output file line number (Ruby Fixnum).
1735
+ */
1736
+ VALUE mucgly_olinenumber( VALUE obj )
1737
+ {
1738
+ outfile_t* of;
1739
+ of = ruby_ps->output->data;
1740
+ return INT2FIX( (of->lineno+1) );
1741
+ }
1742
+
1743
+
1744
+ /**
1745
+ * Mucgly.pushinput method. Push new input stream.
1746
+ *
1747
+ * @param obj Not used.
1748
+ * @param rstr Input file name (Ruby String).
1749
+ *
1750
+ * @return Qnil.
1751
+ */
1752
+ VALUE mucgly_pushinput( VALUE obj, VALUE rstr )
1753
+ {
1754
+ char* str;
1755
+
1756
+ str = RSTRING_PTR( rstr );
1757
+ fs_push_file( ruby_ps->fs, str );
1758
+ return Qnil;
1759
+ }
1760
+
1761
+
1762
+ /**
1763
+ * Mucgly.closeinput method. Pop input stream and close file stream.
1764
+ *
1765
+ * @param obj Not used.
1766
+ *
1767
+ * @return Qnil.
1768
+ */
1769
+ VALUE mucgly_closeinput( VALUE obj )
1770
+ {
1771
+ fs_pop_file( ruby_ps->fs );
1772
+ return Qnil;
1773
+ }
1774
+
1775
+
1776
+ /**
1777
+ * Mucgly.pushoutput method. Push new output stream.
1778
+ *
1779
+ * @param obj Not used.
1780
+ * @param rstr Output file name (Ruby String).
1781
+ *
1782
+ * @return Qnil.
1783
+ */
1784
+ VALUE mucgly_pushoutput( VALUE obj, VALUE rstr )
1785
+ {
1786
+ char* str;
1787
+
1788
+ str = RSTRING_PTR( rstr );
1789
+ ps_push_file( ruby_ps, str );
1790
+ return Qnil;
1791
+ }
1792
+
1793
+
1794
+ /**
1795
+ * Mucgly.closeoutput method. Pop output stream and close file stream.
1796
+ *
1797
+ * @param obj Not used.
1798
+ *
1799
+ * @return Qnil.
1800
+ */
1801
+ VALUE mucgly_closeoutput( VALUE obj )
1802
+ {
1803
+ ps_pop_file( ruby_ps );
1804
+ return Qnil;
1805
+ }
1806
+
1807
+
1808
+ #ifdef MUCGLY_LIB
1809
+ /**
1810
+ * Call mucgly C-side entry point. Convert Ruby ARGV array to C-array
1811
+ * for C-side command line arg parsing.
1812
+ *
1813
+ * @param obj Not used.
1814
+ * @param cli Ruby side ARGV.
1815
+ *
1816
+ * @return Qnil.
1817
+ */
1818
+ VALUE mucgly_run_mucgly( VALUE obj, VALUE cli )
1819
+ {
1820
+ int argc;
1821
+ char** argv;
1822
+
1823
+ argc = RARRAY_LEN( cli );
1824
+ argv = (char**) g_new0( gpointer, argc );
1825
+
1826
+ for ( int i = 0; i < argc; i++ )
1827
+ {
1828
+ argv[ i ] = RSTRING_PTR( RARRAY_AREF(cli,i) );
1829
+ }
1830
+
1831
+ mucgly_main( argc, argv );
1832
+
1833
+ return Qnil;
1834
+ }
1835
+ #endif
1836
+
1837
+
1838
+ /**
1839
+ * Create Mucgly Ruby module and register all methods to it.
1840
+ *
1841
+ */
1842
+ void Init_mucgly( void )
1843
+ {
1844
+ VALUE mMucgly;
1845
+
1846
+ mMucgly = rb_define_module( "Mucgly" );
1847
+
1848
+ #ifdef MUCGLY_LIB
1849
+ rb_define_module_function( mMucgly, "run_mucgly", mucgly_run_mucgly, 1 );
1850
+ #endif
1851
+
1852
+ rb_define_module_function( mMucgly, "write", mucgly_write, 1 );
1853
+ rb_define_module_function( mMucgly, "puts", mucgly_puts, 1 );
1854
+ rb_define_module_function( mMucgly, "hookbeg", mucgly_hookbeg, 0 );
1855
+ rb_define_module_function( mMucgly, "hookend", mucgly_hookend, 0 );
1856
+ rb_define_module_function( mMucgly, "hookesc", mucgly_hookesc, 0 );
1857
+ rb_define_module_function( mMucgly, "sethookbeg", mucgly_sethookbeg, 1 );
1858
+ rb_define_module_function( mMucgly, "sethookend", mucgly_sethookend, 1 );
1859
+ rb_define_module_function( mMucgly, "sethookesc", mucgly_sethookesc, 1 );
1860
+ rb_define_module_function( mMucgly, "ifilename", mucgly_ifilename, 0 );
1861
+ rb_define_module_function( mMucgly, "ilinenumber", mucgly_ilinenumber, 0 );
1862
+ rb_define_module_function( mMucgly, "ofilename", mucgly_ofilename, 0 );
1863
+ rb_define_module_function( mMucgly, "olinenumber", mucgly_olinenumber, 0 );
1864
+ rb_define_module_function( mMucgly, "pushinput", mucgly_pushinput, 1 );
1865
+ rb_define_module_function( mMucgly, "closeinput", mucgly_closeinput, 0 );
1866
+ rb_define_module_function( mMucgly, "pushoutput", mucgly_pushoutput, 1 );
1867
+ rb_define_module_function( mMucgly, "closeoutput", mucgly_closeoutput, 0 );
1868
+
1869
+ }
1870
+
1871
+
1872
+ /**
1873
+ * Generate outfile name from infile by removing the outfile_tag from
1874
+ * infile name.
1875
+ *
1876
+ * @param infile Input file name.
1877
+ * @param outfile_tag Tag to remove from infile to get outfile.
1878
+ *
1879
+ * @return Outfile name.
1880
+ */
1881
+ gchar* infile_to_outfile( gchar* infile, gchar* outfile_tag )
1882
+ {
1883
+ gchar* tag = NULL;
1884
+ gchar* ret;
1885
+
1886
+ if ( !outfile_tag )
1887
+ return NULL;
1888
+
1889
+ if ( g_strrstr( infile, outfile_tag ) )
1890
+ {
1891
+ ret = g_strdup( infile );
1892
+ tag = g_strrstr( ret, outfile_tag );
1893
+ g_stpcpy( tag, tag + strlen( outfile_tag ) );
1894
+ return ret;
1895
+ }
1896
+ else
1897
+ {
1898
+ return NULL;
1899
+ }
1900
+ }
1901
+
1902
+
1903
+
1904
+ /* ------------------------------------------------------------
1905
+ * Main program:
1906
+ * ------------------------------------------------------------ */
1907
+
1908
+ void mucgly_main( int argc, char** argv )
1909
+ {
1910
+
1911
+ /* Setup default hooks etc. */
1912
+ stack_default = sf_new( NULL, NULL );
1913
+
1914
+ /* ------------------------------------------------------------
1915
+ * Setup Ruby features:
1916
+ * ------------------------------------------------------------ */
1917
+
1918
+ #ifndef MUCGLY_LIB
1919
+ ruby_init();
1920
+ Init_mucgly();
1921
+ #endif
1922
+
1923
+
1924
+ /* ------------------------------------------------------------
1925
+ * Command line parsing:
1926
+ * ------------------------------------------------------------ */
1927
+
1928
+ gchar** opt_files = NULL;
1929
+ gchar** opt_configs = NULL;
1930
+ gchar* opt_output = NULL;
1931
+ gchar* opt_genout = NULL;
1932
+ gchar** opt_cli_cmds = NULL;
1933
+ gchar* opt_beg_sep = NULL;
1934
+ gchar* opt_end_sep = NULL;
1935
+ gchar* opt_esc_sep = NULL;
1936
+ gchar* opt_all_sep = NULL;
1937
+ gboolean opt_module = FALSE;
1938
+ gboolean opt_interact = FALSE;
1939
+ gboolean opt_no_init = FALSE;
1940
+
1941
+
1942
+ GOptionEntry opts_table[] =
1943
+ {
1944
+ { "files", 'f', 0, G_OPTION_ARG_FILENAME_ARRAY,
1945
+ &opt_files, "Input file(s) (default: stdin).", NULL },
1946
+ { "configs", 'c', 0, G_OPTION_ARG_FILENAME_ARRAY,
1947
+ &opt_configs, "Ruby configuration file(s).", NULL },
1948
+ { "output", 'o', 0, G_OPTION_ARG_FILENAME,
1949
+ &opt_output, "Output file(s) (default: stdout).", NULL },
1950
+ { "genout", 'g', 0, G_OPTION_ARG_STRING,
1951
+ &opt_genout, "Generate output file names (remove arg from input filename).", NULL },
1952
+ { "cli_cmds", 'l', 0, G_OPTION_ARG_STRING_ARRAY,
1953
+ &opt_cli_cmds, "Command line configuration(s).", NULL },
1954
+ { "beg_sep", 'b', 0, G_OPTION_ARG_STRING,
1955
+ &opt_beg_sep, "Set hookbeg separator (default: '-<').", NULL },
1956
+ { "end_sep", 'e', 0, G_OPTION_ARG_STRING,
1957
+ &opt_end_sep, "Set hookend separator (default: '>-').", NULL },
1958
+ { "esc_sep", 's', 0, G_OPTION_ARG_STRING,
1959
+ &opt_esc_sep, "Set hookesc separator (default: '\\').", NULL },
1960
+ { "all_sep", 'a', 0, G_OPTION_ARG_STRING,
1961
+ &opt_all_sep, "Set all separators.", NULL },
1962
+ { "module", 'm', 0, G_OPTION_ARG_NONE,
1963
+ &opt_module, "Include Mucgly module at start.", NULL },
1964
+ { "interact", 'i', 0, G_OPTION_ARG_NONE,
1965
+ &opt_interact, "Flush output for proper interaction.", NULL },
1966
+ { "no_init", 'n', 0, G_OPTION_ARG_NONE,
1967
+ &opt_no_init, "No init file (MUCGLY env var).", NULL },
1968
+ { NULL }
1969
+ };
1970
+
1971
+
1972
+ GError* opts_error = NULL;
1973
+ GOptionContext* opts;
1974
+
1975
+ opts = g_option_context_new( "- Inline Macro processor" );
1976
+ g_option_context_add_main_entries( opts, opts_table, NULL );
1977
+ if ( !g_option_context_parse( opts, &argc, &argv, &opts_error ) )
1978
+ {
1979
+ g_print( "mucgly: option parsing failed: %s\n", opts_error->message );
1980
+ exit( EXIT_FAILURE );
1981
+ }
1982
+
1983
+
1984
+ /* Create initial input state. */
1985
+ pstate_t* ps = ps_new( opt_output );
1986
+ ruby_ps = ps;
1987
+
1988
+
1989
+ /* Read init file if existing. */
1990
+ if ( !opt_no_init )
1991
+ {
1992
+ gchar* init;
1993
+ if ( ( init = (gchar*) g_environ_getenv( g_get_environ(), "MUCGLY" ) ) )
1994
+ {
1995
+ if ( !g_file_test( init, G_FILE_TEST_EXISTS ) )
1996
+ mucgly_warn( ps_current_file(ps),
1997
+ "MUCGLY env set, but the file \"%s\" does not exist!",
1998
+ init );
1999
+ else
2000
+ {
2001
+ ps_load_ruby_file( ps, init );
2002
+ }
2003
+ }
2004
+ }
2005
+
2006
+
2007
+ /* Include Mucgly module to flatten the namespace. */
2008
+ if ( opt_module )
2009
+ rb_eval_string( "include Mucgly" );
2010
+
2011
+
2012
+ /* Read Ruby configuration files. */
2013
+ if ( opt_configs )
2014
+ {
2015
+ while ( *opt_configs )
2016
+ {
2017
+ ps_load_ruby_file( ps, *opt_configs );
2018
+ opt_configs++;
2019
+ }
2020
+ }
2021
+
2022
+
2023
+ /* Process Ruby command line options. */
2024
+ if ( opt_cli_cmds )
2025
+ {
2026
+ while ( *opt_cli_cmds )
2027
+ {
2028
+ ps_eval_ruby_str( ps, *opt_cli_cmds, FALSE, "cli" );
2029
+ opt_cli_cmds++;
2030
+ }
2031
+ }
2032
+
2033
+
2034
+ /* Set hooks. */
2035
+
2036
+ if ( opt_beg_sep )
2037
+ sf_set_hook( stack_default, hook_beg, opt_beg_sep );
2038
+
2039
+ if ( opt_end_sep )
2040
+ sf_set_hook( stack_default, hook_end, opt_end_sep );
2041
+
2042
+ if ( opt_esc_sep )
2043
+ sf_set_hook( stack_default, hook_esc, opt_esc_sep );
2044
+
2045
+ if ( opt_all_sep )
2046
+ {
2047
+ sf_set_hook( stack_default, hook_beg, opt_all_sep );
2048
+ sf_set_hook( stack_default, hook_end, opt_all_sep );
2049
+ sf_set_hook( stack_default, hook_esc, opt_all_sep );
2050
+ }
2051
+
2052
+
2053
+ if ( opt_interact )
2054
+ ps->flush = TRUE;
2055
+
2056
+ /* Process input files. */
2057
+ if ( opt_files )
2058
+ {
2059
+ /* Named files. */
2060
+ while ( *opt_files )
2061
+ {
2062
+ ps_process_file( ps,
2063
+ *opt_files,
2064
+ infile_to_outfile(
2065
+ *opt_files,
2066
+ opt_genout )
2067
+ );
2068
+ opt_files++;
2069
+ }
2070
+ }
2071
+ else
2072
+ {
2073
+ /* Process STDIN. */
2074
+ ps_process_file( ps, NULL, NULL );
2075
+ }
2076
+
2077
+ ps_rem( ps );
2078
+
2079
+ }
2080
+
2081
+
2082
+ #ifndef MUCGLY_LIB
2083
+ /**
2084
+ * C-top entry point.
2085
+ *
2086
+ * @param argc Cli arg count.
2087
+ * @param argv Cli arg values.
2088
+ *
2089
+ * @return Not used.
2090
+ */
2091
+ int main( int argc, char** argv )
2092
+ {
2093
+ mucgly_main( argc, argv );
2094
+ }
2095
+ #endif