mucgly 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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