2006-09-26 [colin] 2.5.1cvs5
[claws.git] / src / quote_fmt_parse.y
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 %{
21
22 #include "defs.h"
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26
27 #include <ctype.h>
28
29 #include "procmsg.h"
30 #include "procmime.h"
31 #include "utils.h"
32 #include "codeconv.h"
33 #include "procheader.h"
34 #include "gtk/inputdialog.h"
35
36 #include "quote_fmt.h"
37 #include "quote_fmt_lex.h"
38
39 /* decl */
40 /*
41 flex quote_fmt.l
42 bison -p quote_fmt quote_fmt.y
43 */
44
45 int yylex(void);
46
47 static MsgInfo *msginfo = NULL;
48 static gboolean *visible = NULL;
49 static gboolean dry_run = FALSE;
50 static gint maxsize = 0;
51 static gint stacksize = 0;
52 static GHashTable *var_table = NULL;
53
54 typedef struct st_buffer
55 {
56         gchar *buffer;
57         gint bufsize;
58         gint bufmax;
59 } st_buffer;
60
61 static struct st_buffer main_expr = { NULL, 0, 0 };
62 static struct st_buffer sub_expr = { NULL, 0, 0 };
63 static struct st_buffer* current = NULL;
64
65 static const gchar *quote_str = NULL;
66 static const gchar *body = NULL;
67 static gint error = 0;
68
69 static gint cursor_pos = -1;
70
71 extern int quote_fmt_firsttime;
72
73 static void add_visibility(gboolean val)
74 {
75         stacksize++;
76         if (maxsize < stacksize) {
77                 maxsize += 128;
78                 visible = g_realloc(visible, maxsize * sizeof(gboolean));
79                 if (visible == NULL)
80                         maxsize = 0;
81         }
82
83         visible[stacksize - 1] = val;
84 }
85
86 static void remove_visibility(void)
87 {
88         stacksize--;
89         if (stacksize < 0) {
90                 g_warning("Error: visibility stack underflow\n");
91                 stacksize = 0;
92         }
93 }
94
95 static void add_buffer(const gchar *s)
96 {
97         gint len;
98
99         if (s == NULL)
100                 return;
101
102         len = strlen(s);
103         if (current->bufsize + len + 1 > current->bufmax) {
104                 if (current->bufmax == 0)
105                         current->bufmax = 128;
106                 while (current->bufsize + len + 1 > current->bufmax)
107                         current->bufmax *= 2;
108                 current->buffer = g_realloc(current->buffer, current->bufmax);
109         }
110         strcpy(current->buffer + current->bufsize, s);
111         current->bufsize += len;
112 }
113
114 static void clear_buffer(void)
115 {
116         if (current->buffer)
117                 *current->buffer = '\0';
118         current->bufsize = 0;
119 }
120
121 gchar *quote_fmt_get_buffer(void)
122 {
123         if (current != &main_expr)
124                 g_warning("Error: parser still in sub-expr mode\n");
125
126         if (error != 0)
127                 return NULL;
128         else
129                 return current->buffer;
130 }
131
132 gint quote_fmt_get_cursor_pos(void)
133 {
134         return cursor_pos;      
135 }
136
137 #define INSERT(buf) \
138         if (stacksize != 0 && visible[stacksize - 1])\
139                 add_buffer(buf); \
140
141 #define INSERT_CHARACTER(chr) \
142         if (stacksize != 0 && visible[stacksize - 1]) { \
143                 gchar tmp[2]; \
144                 tmp[0] = (chr); \
145                 tmp[1] = '\0'; \
146                 add_buffer(tmp); \
147         }
148
149 void quote_fmt_reset_vartable(void)
150 {
151         if (var_table) {
152                 g_hash_table_destroy(var_table);
153                 var_table = NULL;
154         }
155 }
156
157 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
158                     const gchar *my_body, gboolean my_dry_run)
159 {
160         quote_str = my_quote_str;
161         body = my_body;
162         msginfo = info;
163         dry_run = my_dry_run;
164         stacksize = 0;
165         add_visibility(TRUE);
166         main_expr.bufmax = 0;
167         sub_expr.bufmax = 0;
168         current = &main_expr;
169         clear_buffer();
170         error = 0;
171
172         if (!var_table)
173                 var_table = g_hash_table_new_full(g_str_hash, g_str_equal, 
174                                 g_free, g_free);
175
176         /*
177          * force LEX initialization
178          */
179         quote_fmt_firsttime = 1;
180         cursor_pos = -1;
181 }
182
183 void quote_fmterror(char *str)
184 {
185         g_warning("Error: %s\n", str);
186         error = 1;
187 }
188
189 int quote_fmtwrap(void)
190 {
191         return 1;
192 }
193
194 static int isseparator(int ch)
195 {
196         return g_ascii_isspace(ch) || ch == '.' || ch == '-';
197 }
198
199 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
200 {
201         char  result[100];
202         char *rptr;
203         char  zone[6];
204         struct tm lt;
205         const char *fptr;
206         const char *zptr;
207
208         if (!msginfo->date)
209                 return;
210         
211         /* 
212          * ALF - GNU C's strftime() has a nice format specifier 
213          * for time zone offset (%z). Non-standard however, so 
214          * emulate it.
215          */
216
217 #define RLEFT (sizeof result) - (rptr - result) 
218 #define STR_SIZE(x) (sizeof (x) - 1)
219
220         zone[0] = 0;
221
222         if (procheader_date_parse_to_tm(msginfo->date, &lt, zone)) {
223                 /*
224                  * break up format string in tiny bits delimited by valid %z's and 
225                  * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
226                  */
227                 for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
228                         int         perc;
229                         const char *p;
230                         char       *tmp;
231                         
232                         if (NULL != (zptr = strstr(fptr, "%z"))) {
233                                 /*
234                                  * count nr. of prepended percent chars
235                                  */
236                                 for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
237                                         ;
238                                 /*
239                                  * feed to strftime()
240                                  */
241                                 tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : STR_SIZE("%z")));
242                                 if (tmp) {
243                                         rptr += strftime(rptr, RLEFT, tmp, &lt);
244                                         g_free(tmp);
245                                 }
246                                 /*
247                                  * append time zone offset
248                                  */
249                                 if (zone[0] && perc % 2) 
250                                         rptr += g_snprintf(rptr, RLEFT, "%s", zone);
251                                 fptr = zptr + STR_SIZE("%z");
252                         } else {
253                                 rptr += strftime(rptr, RLEFT, fptr, &lt);
254                                 fptr  = NULL;
255                         }
256                 }
257                 
258                 if (g_utf8_validate(result, -1, NULL)) {
259                         INSERT(result);
260                 } else {
261                         gchar *utf = conv_codeset_strdup(result, 
262                                 conv_get_locale_charset_str_no_utf8(),
263                                 CS_INTERNAL);
264                         if (utf == NULL || 
265                             !g_utf8_validate(utf, -1, NULL)) {
266                                 g_free(utf);
267                                 utf = g_malloc(strlen(result)*2+1);
268                                 conv_localetodisp(utf, 
269                                         strlen(result)*2+1, result);
270                         }
271                         if (g_utf8_validate(utf, -1, NULL)) {
272                                 INSERT(utf);
273                         }
274                         g_free(utf);
275                 }
276         }
277 #undef STR_SIZE                 
278 #undef RLEFT                    
279 }               
280
281 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
282 {
283         guchar *p;
284         gchar *str;
285
286         if (!msginfo->fromname)
287                 return; 
288         
289         p = (guchar*)strchr(msginfo->fromname, ',');
290         if (p != NULL) {
291                 /* fromname is like "Duck, Donald" */
292                 p++;
293                 while (*p && isspace(*p)) p++;
294                 str = alloca(strlen((char *)p) + 1);
295                 if (str != NULL) {
296                         strcpy(str, (char *)p);
297                         INSERT(str);
298                 }
299         } else {
300                 /* fromname is like "Donald Duck" */
301                 str = alloca(strlen(msginfo->fromname) + 1);
302                 if (str != NULL) {
303                         strcpy(str, msginfo->fromname);
304                         p = (guchar *)str;
305                         while (*p && !isspace(*p)) p++;
306                         *p = '\0';
307                         INSERT(str);
308                 }
309         }
310 }
311
312 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
313 {
314         gchar *p;
315         gchar *str;
316
317         /* This probably won't work together very well with Middle
318            names and the like - thth */
319         if (!msginfo->fromname) 
320                 return;
321
322         str = alloca(strlen(msginfo->fromname) + 1);
323         if (str != NULL) {
324                 strcpy(str, msginfo->fromname);
325                 p = strchr(str, ',');
326                 if (p != NULL) {
327                         /* fromname is like "Duck, Donald" */
328                         *p = '\0';
329                         INSERT(str);
330                 } else {
331                         /* fromname is like "Donald Duck" */
332                         p = str;
333                         while (*p && !isspace(*p)) p++;
334                         if (*p) {
335                             /* We found a space. Get first 
336                              none-space char and insert
337                              rest of string from there. */
338                             while (*p && isspace(*p)) p++;
339                             if (*p) {
340                                 INSERT(p);
341                             } else {
342                                 /* If there is no none-space 
343                                  char, just insert whole 
344                                  fromname. */
345                                 INSERT(str);
346                             }
347                         } else {
348                             /* If there is no space, just 
349                              insert whole fromname. */
350                             INSERT(str);
351                         }
352                 }
353         }
354 }
355
356 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
357 {
358 #define MAX_SENDER_INITIAL 20
359         gchar tmp[MAX_SENDER_INITIAL];
360         guchar *p;
361         gchar *cur;
362         gint len = 0;
363
364         if (!msginfo->fromname) 
365                 return;
366
367         p = (guchar *)msginfo->fromname;
368         cur = tmp;
369         while (*p) {
370                 if (*p && g_utf8_validate((gchar *)p, 1, NULL)) {
371                         *cur = toupper(*p);
372                                 cur++;
373                         len++;
374                         if (len >= MAX_SENDER_INITIAL - 1)
375                                 break;
376                 } else
377                         break;
378                 while (*p && !isseparator(*p)) p++;
379                 while (*p && isseparator(*p)) p++;
380         }
381         *cur = '\0';
382         INSERT(tmp);
383 }
384
385 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
386                                gboolean quoted, gboolean signature,
387                                const gchar *quote_str)
388 {
389         gchar buf[BUFFSIZE];
390         FILE *fp;
391
392         if (!(msginfo->folder || body))
393                 return;
394
395         if (body)
396                 fp = str_open_as_stream(body);
397         else {
398                 if (procmime_msginfo_is_encrypted(msginfo))
399                         fp = procmime_get_first_encrypted_text_content(msginfo);
400                 else
401                         fp = procmime_get_first_text_content(msginfo);
402         }
403
404         if (fp == NULL)
405                 g_warning("Can't get text part\n");
406         else {
407                 while (fgets(buf, sizeof(buf), fp) != NULL) {
408                         strcrchomp(buf);
409                         
410                         if (!signature && strncmp(buf, "-- \n", 4) == 0)
411                                 break;
412                 
413                         if (quoted && quote_str)
414                                 INSERT(quote_str);
415                         
416                         INSERT(buf);
417                 }
418                 fclose(fp);
419         }
420 }
421
422 static void quote_fmt_insert_file(const gchar *filename)
423 {
424         FILE *file;
425         char buffer[256];
426         
427         if ((file = g_fopen(filename, "rb")) != NULL) {
428                 while (fgets(buffer, sizeof(buffer), file)) {
429                         INSERT(buffer);
430                 }
431                 fclose(file);
432         }
433
434 }
435
436 static void quote_fmt_insert_program_output(const gchar *progname)
437 {
438         FILE *file;
439         char buffer[256];
440
441         if ((file = popen(progname, "r")) != NULL) {
442                 while (fgets(buffer, sizeof(buffer), file)) {
443                         INSERT(buffer);
444                 }
445                 pclose(file);
446         }
447 }
448
449 static void quote_fmt_insert_user_input(const gchar *varname)
450 {
451         gchar *buf = NULL;
452         gchar *text = NULL;
453         
454         if (dry_run) 
455                 return;
456
457         if ((text = g_hash_table_lookup(var_table, varname)) == NULL) {
458                 buf = g_strdup_printf(_("Enter text to replace '%s'"), varname);
459                 text = input_dialog(_("Enter variable"), buf, "");
460                 g_free(buf);
461                 if (!text)
462                         return;
463                 g_hash_table_insert(var_table, g_strdup(varname), g_strdup(text));
464         } else {
465                 /* don't free the one in hashtable at the end */
466                 text = g_strdup(text);
467         }
468
469         if (!text)
470                 return;
471         INSERT(text);
472         g_free(text);
473 }
474
475 %}
476
477 %union {
478         char chr;
479         char str[256];
480 }
481
482 %token SHOW_NEWSGROUPS
483 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
484 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
485 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
486 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB
487 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
488 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
489 %token QUERY_DATE QUERY_FROM
490 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
491 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
492 %token QUERY_NOT_DATE QUERY_NOT_FROM
493 %token QUERY_NOT_FULLNAME QUERY_NOT_SUBJECT QUERY_NOT_TO QUERY_NOT_NEWSGROUPS
494 %token QUERY_NOT_MESSAGEID QUERY_NOT_CC QUERY_NOT_REFERENCES
495 %token INSERT_FILE INSERT_PROGRAMOUTPUT INSERT_USERINPUT
496 %token OPARENT CPARENT
497 %token CHARACTER
498 %token SHOW_DATE_EXPR
499 %token SET_CURSOR_POS
500
501 %start quote_fmt
502
503 %token <chr> CHARACTER
504 %type <chr> character
505 %type <str> string
506
507 %%
508
509 quote_fmt:
510         character_or_special_or_insert_or_query_list ;
511
512 sub_expr:
513         character_or_special_list ;
514
515 character_or_special_or_insert_or_query_list:
516         character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
517         | character_or_special_or_insert_or_query ;
518
519 character_or_special_list:
520         character_or_special character_or_special_list
521         | character_or_special ;
522
523 character_or_special_or_insert_or_query:
524         character_or_special
525         | query
526         | query_not
527         | insert ;
528
529 character_or_special:
530         special
531         | character
532         {
533                 INSERT_CHARACTER($1);
534         };
535
536 character:
537         CHARACTER
538         ;
539
540 string:
541         CHARACTER
542         {
543                 $$[0] = $1;
544                 $$[1] = '\0';
545         }
546         | string CHARACTER
547         {
548                 int len;
549                 
550                 strncpy($$, $1, sizeof($$));
551                 $$[sizeof($$) - 1] = '\0';
552                 len = strlen($$);
553                 if (len + 1 < sizeof($$)) {
554                         $$[len + 1] = '\0';
555                         $$[len] = $2;
556                 }
557         };
558
559 special:
560         SHOW_NEWSGROUPS
561         {
562                 if (msginfo->newsgroups)
563                         INSERT(msginfo->newsgroups);
564         }
565         | SHOW_DATE_EXPR OPARENT string CPARENT
566         {
567                 quote_fmt_show_date(msginfo, $3);
568         }
569         | SHOW_DATE
570         {
571                 if (msginfo->date)
572                         INSERT(msginfo->date);
573         }
574         | SHOW_FROM
575         {
576                 if (msginfo->from)
577                         INSERT(msginfo->from);
578         }
579         | SHOW_FULLNAME
580         {
581                 if (msginfo->fromname)
582                         INSERT(msginfo->fromname);
583         }
584         | SHOW_FIRST_NAME
585         {
586                 quote_fmt_show_first_name(msginfo);
587         }
588         | SHOW_LAST_NAME
589     {
590                 quote_fmt_show_last_name(msginfo);
591         }
592         | SHOW_SENDER_INITIAL
593         {
594                 quote_fmt_show_sender_initial(msginfo);
595         }
596         | SHOW_SUBJECT
597         {
598                 if (msginfo->subject)
599                         INSERT(msginfo->subject);
600         }
601         | SHOW_TO
602         {
603                 if (msginfo->to)
604                         INSERT(msginfo->to);
605         }
606         | SHOW_MESSAGEID
607         {
608                 if (msginfo->msgid)
609                         INSERT(msginfo->msgid);
610         }
611         | SHOW_PERCENT
612         {
613                 INSERT("%");
614         }
615         | SHOW_CC
616         {
617                 if (msginfo->cc)
618                         INSERT(msginfo->cc);
619         }
620         | SHOW_REFERENCES
621         {
622                 GSList *item;
623
624                 INSERT(msginfo->inreplyto);
625                 for (item = msginfo->references; item != NULL; item = g_slist_next(item))
626                         if (item->data)
627                                 INSERT(item->data);
628         }
629         | SHOW_MESSAGE
630         {
631                 quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
632         }
633         | SHOW_QUOTED_MESSAGE
634         {
635                 quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
636         }
637         | SHOW_MESSAGE_NO_SIGNATURE
638         {
639                 quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
640         }
641         | SHOW_QUOTED_MESSAGE_NO_SIGNATURE
642         {
643                 quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
644         }
645         | SHOW_BACKSLASH
646         {
647                 INSERT("\\");
648         }
649         | SHOW_TAB
650         {
651                 INSERT("\t");
652         }
653         | SHOW_EOL
654         {
655                 INSERT("\n");
656         }
657         | SHOW_QUESTION_MARK
658         {
659                 INSERT("?");
660         }
661         | SHOW_EXCLAMATION_MARK
662         {
663                 INSERT("!");
664         }
665         | SHOW_PIPE
666         {
667                 INSERT("|");
668         }
669         | SHOW_OPARENT
670         {
671                 INSERT("{");
672         }
673         | SHOW_CPARENT
674         {
675                 INSERT("}");
676         }
677         | SET_CURSOR_POS
678         {
679                 cursor_pos = current->bufsize;
680         };
681
682 query:
683         QUERY_DATE
684         {
685                 add_visibility(msginfo->date != NULL);
686         }
687         OPARENT quote_fmt CPARENT
688         {
689                 remove_visibility();
690         }
691         | QUERY_FROM
692         {
693                 add_visibility(msginfo->from != NULL);
694         }
695         OPARENT quote_fmt CPARENT
696         {
697                 remove_visibility();
698         }
699         | QUERY_FULLNAME
700         {
701                 add_visibility(msginfo->fromname != NULL);
702         }
703         OPARENT quote_fmt CPARENT
704         {
705                 remove_visibility();
706         }
707         | QUERY_SUBJECT
708         {
709                 add_visibility(msginfo->subject != NULL);
710         }
711         OPARENT quote_fmt CPARENT
712         {
713                 remove_visibility();
714         }
715         | QUERY_TO
716         {
717                 add_visibility(msginfo->to != NULL);
718         }
719         OPARENT quote_fmt CPARENT
720         {
721                 remove_visibility();
722         }
723         | QUERY_NEWSGROUPS
724         {
725                 add_visibility(msginfo->newsgroups != NULL);
726         }
727         OPARENT quote_fmt CPARENT
728         {
729                 remove_visibility();
730         }
731         | QUERY_MESSAGEID
732         {
733                 add_visibility(msginfo->msgid != NULL);
734         }
735         OPARENT quote_fmt CPARENT
736         {
737                 remove_visibility();
738         }
739         | QUERY_CC
740         {
741                 add_visibility(msginfo->cc != NULL);
742         }
743         OPARENT quote_fmt CPARENT
744         {
745                 remove_visibility();
746         }
747         | QUERY_REFERENCES
748         {
749                 gboolean found;
750                 GSList *item;
751
752                 found = (msginfo->inreplyto != NULL);
753                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
754                         if (item->data)
755                                 found = TRUE;
756                 add_visibility(found == TRUE);
757         }
758         OPARENT quote_fmt CPARENT
759         {
760                 remove_visibility();
761         };
762
763 query_not:
764         QUERY_NOT_DATE
765         {
766                 add_visibility(msginfo->date == NULL);
767         }
768         OPARENT quote_fmt CPARENT
769         {
770                 remove_visibility();
771         }
772         | QUERY_NOT_FROM
773         {
774                 add_visibility(msginfo->from == NULL);
775         }
776         OPARENT quote_fmt CPARENT
777         {
778                 remove_visibility();
779         }
780         | QUERY_NOT_FULLNAME
781         {
782                 add_visibility(msginfo->fromname == NULL);
783         }
784         OPARENT quote_fmt CPARENT
785         {
786                 remove_visibility();
787         }
788         | QUERY_NOT_SUBJECT
789         {
790                 add_visibility(msginfo->subject == NULL);
791         }
792         OPARENT quote_fmt CPARENT
793         {
794                 remove_visibility();
795         }
796         | QUERY_NOT_TO
797         {
798                 add_visibility(msginfo->to == NULL);
799         }
800         OPARENT quote_fmt CPARENT
801         {
802                 remove_visibility();
803         }
804         | QUERY_NOT_NEWSGROUPS
805         {
806                 add_visibility(msginfo->newsgroups == NULL);
807         }
808         OPARENT quote_fmt CPARENT
809         {
810                 remove_visibility();
811         }
812         | QUERY_NOT_MESSAGEID
813         {
814                 add_visibility(msginfo->msgid == NULL);
815         }
816         OPARENT quote_fmt CPARENT
817         {
818                 remove_visibility();
819         }
820         | QUERY_NOT_CC
821         {
822                 add_visibility(msginfo->cc == NULL);
823         }
824         OPARENT quote_fmt CPARENT
825         {
826                 remove_visibility();
827         }
828         | QUERY_NOT_REFERENCES
829         {
830                 gboolean found;
831                 GSList *item;
832
833                 found = (msginfo->inreplyto != NULL);
834                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
835                         if (item->data)
836                                 found = TRUE;
837                 add_visibility(found == FALSE);
838         }
839         OPARENT quote_fmt CPARENT
840         {
841                 remove_visibility();
842         };
843
844 insert:
845         INSERT_FILE
846         {
847                 current = &sub_expr;
848                 clear_buffer();
849         }
850         OPARENT sub_expr CPARENT
851         {
852                 current = &main_expr;
853                 if (!dry_run) {
854                         quote_fmt_insert_file(sub_expr.buffer);
855                 }
856         }
857         | INSERT_PROGRAMOUTPUT
858         {
859                 current = &sub_expr;
860                 clear_buffer();
861         }
862         OPARENT sub_expr CPARENT
863         {
864                 current = &main_expr;
865                 if (!dry_run) {
866                         quote_fmt_insert_program_output(sub_expr.buffer);
867                 }
868         }
869         | INSERT_USERINPUT
870         {
871                 current = &sub_expr;
872                 clear_buffer();
873         }
874         OPARENT sub_expr CPARENT
875         {
876                 current = &main_expr;
877                 if (!dry_run) {
878                         quote_fmt_insert_user_input(sub_expr.buffer);
879                 }
880         };