5d4681438094f6527a95b6e3999dd5ce927543c7
[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_init(MsgInfo *info, const gchar *my_quote_str,
150                     const gchar *my_body, gboolean my_dry_run)
151 {
152         quote_str = my_quote_str;
153         body = my_body;
154         msginfo = info;
155         dry_run = my_dry_run;
156         stacksize = 0;
157         add_visibility(TRUE);
158         main_expr.bufmax = 0;
159         sub_expr.bufmax = 0;
160         current = &main_expr;
161         clear_buffer();
162         error = 0;
163         if (var_table) {
164                 g_hash_table_destroy(var_table);
165                 var_table = NULL;
166         }
167         var_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
168
169         /*
170          * force LEX initialization
171          */
172         quote_fmt_firsttime = 1;
173         cursor_pos = -1;
174 }
175
176 void quote_fmterror(char *str)
177 {
178         g_warning("Error: %s\n", str);
179         error = 1;
180 }
181
182 int quote_fmtwrap(void)
183 {
184         return 1;
185 }
186
187 static int isseparator(int ch)
188 {
189         return g_ascii_isspace(ch) || ch == '.' || ch == '-';
190 }
191
192 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
193 {
194         char  result[100];
195         char *rptr;
196         char  zone[6];
197         struct tm lt;
198         const char *fptr;
199         const char *zptr;
200
201         if (!msginfo->date)
202                 return;
203         
204         /* 
205          * ALF - GNU C's strftime() has a nice format specifier 
206          * for time zone offset (%z). Non-standard however, so 
207          * emulate it.
208          */
209
210 #define RLEFT (sizeof result) - (rptr - result) 
211 #define STR_SIZE(x) (sizeof (x) - 1)
212
213         zone[0] = 0;
214
215         if (procheader_date_parse_to_tm(msginfo->date, &lt, zone)) {
216                 /*
217                  * break up format string in tiny bits delimited by valid %z's and 
218                  * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
219                  */
220                 for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
221                         int         perc;
222                         const char *p;
223                         char       *tmp;
224                         
225                         if (NULL != (zptr = strstr(fptr, "%z"))) {
226                                 /*
227                                  * count nr. of prepended percent chars
228                                  */
229                                 for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
230                                         ;
231                                 /*
232                                  * feed to strftime()
233                                  */
234                                 tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : STR_SIZE("%z")));
235                                 if (tmp) {
236                                         rptr += strftime(rptr, RLEFT, tmp, &lt);
237                                         g_free(tmp);
238                                 }
239                                 /*
240                                  * append time zone offset
241                                  */
242                                 if (zone[0] && perc % 2) 
243                                         rptr += g_snprintf(rptr, RLEFT, "%s", zone);
244                                 fptr = zptr + STR_SIZE("%z");
245                         } else {
246                                 rptr += strftime(rptr, RLEFT, fptr, &lt);
247                                 fptr  = NULL;
248                         }
249                 }
250                 
251                 if (g_utf8_validate(result, -1, NULL)) {
252                         INSERT(result);
253                 } else {
254                         gchar *utf = conv_codeset_strdup(result, 
255                                 conv_get_locale_charset_str_no_utf8(),
256                                 CS_INTERNAL);
257                         if (utf == NULL || 
258                             !g_utf8_validate(utf, -1, NULL)) {
259                                 g_free(utf);
260                                 utf = g_malloc(strlen(result)*2+1);
261                                 conv_localetodisp(utf, 
262                                         strlen(result)*2+1, result);
263                         }
264                         if (g_utf8_validate(utf, -1, NULL)) {
265                                 INSERT(utf);
266                         }
267                         g_free(utf);
268                 }
269         }
270 #undef STR_SIZE                 
271 #undef RLEFT                    
272 }               
273
274 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
275 {
276         guchar *p;
277         gchar *str;
278
279         if (!msginfo->fromname)
280                 return; 
281         
282         p = (guchar*)strchr(msginfo->fromname, ',');
283         if (p != NULL) {
284                 /* fromname is like "Duck, Donald" */
285                 p++;
286                 while (*p && isspace(*p)) p++;
287                 str = alloca(strlen((char *)p) + 1);
288                 if (str != NULL) {
289                         strcpy(str, (char *)p);
290                         INSERT(str);
291                 }
292         } else {
293                 /* fromname is like "Donald Duck" */
294                 str = alloca(strlen(msginfo->fromname) + 1);
295                 if (str != NULL) {
296                         strcpy(str, msginfo->fromname);
297                         p = (guchar *)str;
298                         while (*p && !isspace(*p)) p++;
299                         *p = '\0';
300                         INSERT(str);
301                 }
302         }
303 }
304
305 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
306 {
307         gchar *p;
308         gchar *str;
309
310         /* This probably won't work together very well with Middle
311            names and the like - thth */
312         if (!msginfo->fromname) 
313                 return;
314
315         str = alloca(strlen(msginfo->fromname) + 1);
316         if (str != NULL) {
317                 strcpy(str, msginfo->fromname);
318                 p = strchr(str, ',');
319                 if (p != NULL) {
320                         /* fromname is like "Duck, Donald" */
321                         *p = '\0';
322                         INSERT(str);
323                 } else {
324                         /* fromname is like "Donald Duck" */
325                         p = str;
326                         while (*p && !isspace(*p)) p++;
327                         if (*p) {
328                             /* We found a space. Get first 
329                              none-space char and insert
330                              rest of string from there. */
331                             while (*p && isspace(*p)) p++;
332                             if (*p) {
333                                 INSERT(p);
334                             } else {
335                                 /* If there is no none-space 
336                                  char, just insert whole 
337                                  fromname. */
338                                 INSERT(str);
339                             }
340                         } else {
341                             /* If there is no space, just 
342                              insert whole fromname. */
343                             INSERT(str);
344                         }
345                 }
346         }
347 }
348
349 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
350 {
351 #define MAX_SENDER_INITIAL 20
352         gchar tmp[MAX_SENDER_INITIAL];
353         guchar *p;
354         gchar *cur;
355         gint len = 0;
356
357         if (!msginfo->fromname) 
358                 return;
359
360         p = (guchar *)msginfo->fromname;
361         cur = tmp;
362         while (*p) {
363                 if (*p && g_utf8_validate((gchar *)p, 1, NULL)) {
364                         *cur = toupper(*p);
365                                 cur++;
366                         len++;
367                         if (len >= MAX_SENDER_INITIAL - 1)
368                                 break;
369                 } else
370                         break;
371                 while (*p && !isseparator(*p)) p++;
372                 while (*p && isseparator(*p)) p++;
373         }
374         *cur = '\0';
375         INSERT(tmp);
376 }
377
378 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
379                                gboolean quoted, gboolean signature,
380                                const gchar *quote_str)
381 {
382         gchar buf[BUFFSIZE];
383         FILE *fp;
384
385         if (!(msginfo->folder || body))
386                 return;
387
388         if (body)
389                 fp = str_open_as_stream(body);
390         else {
391                 if (procmime_msginfo_is_encrypted(msginfo))
392                         fp = procmime_get_first_encrypted_text_content(msginfo);
393                 else
394                         fp = procmime_get_first_text_content(msginfo);
395         }
396
397         if (fp == NULL)
398                 g_warning("Can't get text part\n");
399         else {
400                 while (fgets(buf, sizeof(buf), fp) != NULL) {
401                         strcrchomp(buf);
402                         
403                         if (!signature && strncmp(buf, "-- \n", 4) == 0)
404                                 break;
405                 
406                         if (quoted && quote_str)
407                                 INSERT(quote_str);
408                         
409                         INSERT(buf);
410                 }
411                 fclose(fp);
412         }
413 }
414
415 static void quote_fmt_insert_file(const gchar *filename)
416 {
417         FILE *file;
418         char buffer[256];
419         
420         if ((file = g_fopen(filename, "rb")) != NULL) {
421                 while (fgets(buffer, sizeof(buffer), file)) {
422                         INSERT(buffer);
423                 }
424                 fclose(file);
425         }
426
427 }
428
429 static void quote_fmt_insert_program_output(const gchar *progname)
430 {
431         FILE *file;
432         char buffer[256];
433
434         if ((file = popen(progname, "r")) != NULL) {
435                 while (fgets(buffer, sizeof(buffer), file)) {
436                         INSERT(buffer);
437                 }
438                 pclose(file);
439         }
440 }
441
442 static void quote_fmt_insert_user_input(const gchar *varname)
443 {
444         gchar *buf = NULL;
445         gchar *text = NULL;
446         
447         if (dry_run) 
448                 return;
449
450         if ((text = g_hash_table_lookup(var_table, varname)) == NULL) {
451                 buf = g_strdup_printf(_("Enter text to replace '%s'"), varname);
452                 text = input_dialog(_("Enter variable"), buf, "");
453                 g_free(buf);
454                 if (!text)
455                         return;
456                 g_hash_table_insert(var_table, g_strdup(varname), g_strdup(text));
457         } else {
458                 /* don't free the one in hashtable at the end */
459                 text = g_strdup(text);
460         }
461
462         if (!text)
463                 return;
464         INSERT(text);
465         g_free(text);
466 }
467
468 %}
469
470 %union {
471         char chr;
472         char str[256];
473 }
474
475 %token SHOW_NEWSGROUPS
476 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
477 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
478 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
479 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB
480 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
481 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
482 %token QUERY_DATE QUERY_FROM
483 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
484 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
485 %token QUERY_NOT_DATE QUERY_NOT_FROM
486 %token QUERY_NOT_FULLNAME QUERY_NOT_SUBJECT QUERY_NOT_TO QUERY_NOT_NEWSGROUPS
487 %token QUERY_NOT_MESSAGEID QUERY_NOT_CC QUERY_NOT_REFERENCES
488 %token INSERT_FILE INSERT_PROGRAMOUTPUT INSERT_USERINPUT
489 %token OPARENT CPARENT
490 %token CHARACTER
491 %token SHOW_DATE_EXPR
492 %token SET_CURSOR_POS
493
494 %start quote_fmt
495
496 %token <chr> CHARACTER
497 %type <chr> character
498 %type <str> string
499
500 %%
501
502 quote_fmt:
503         character_or_special_or_insert_or_query_list ;
504
505 sub_expr:
506         character_or_special_list ;
507
508 character_or_special_or_insert_or_query_list:
509         character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
510         | character_or_special_or_insert_or_query ;
511
512 character_or_special_list:
513         character_or_special character_or_special_list
514         | character_or_special ;
515
516 character_or_special_or_insert_or_query:
517         character_or_special
518         | query
519         | query_not
520         | insert ;
521
522 character_or_special:
523         special
524         | character
525         {
526                 INSERT_CHARACTER($1);
527         };
528
529 character:
530         CHARACTER
531         ;
532
533 string:
534         CHARACTER
535         {
536                 $$[0] = $1;
537                 $$[1] = '\0';
538         }
539         | string CHARACTER
540         {
541                 int len;
542                 
543                 strncpy($$, $1, sizeof($$));
544                 $$[sizeof($$) - 1] = '\0';
545                 len = strlen($$);
546                 if (len + 1 < sizeof($$)) {
547                         $$[len + 1] = '\0';
548                         $$[len] = $2;
549                 }
550         };
551
552 special:
553         SHOW_NEWSGROUPS
554         {
555                 if (msginfo->newsgroups)
556                         INSERT(msginfo->newsgroups);
557         }
558         | SHOW_DATE_EXPR OPARENT string CPARENT
559         {
560                 quote_fmt_show_date(msginfo, $3);
561         }
562         | SHOW_DATE
563         {
564                 if (msginfo->date)
565                         INSERT(msginfo->date);
566         }
567         | SHOW_FROM
568         {
569                 if (msginfo->from)
570                         INSERT(msginfo->from);
571         }
572         | SHOW_FULLNAME
573         {
574                 if (msginfo->fromname)
575                         INSERT(msginfo->fromname);
576         }
577         | SHOW_FIRST_NAME
578         {
579                 quote_fmt_show_first_name(msginfo);
580         }
581         | SHOW_LAST_NAME
582     {
583                 quote_fmt_show_last_name(msginfo);
584         }
585         | SHOW_SENDER_INITIAL
586         {
587                 quote_fmt_show_sender_initial(msginfo);
588         }
589         | SHOW_SUBJECT
590         {
591                 if (msginfo->subject)
592                         INSERT(msginfo->subject);
593         }
594         | SHOW_TO
595         {
596                 if (msginfo->to)
597                         INSERT(msginfo->to);
598         }
599         | SHOW_MESSAGEID
600         {
601                 if (msginfo->msgid)
602                         INSERT(msginfo->msgid);
603         }
604         | SHOW_PERCENT
605         {
606                 INSERT("%");
607         }
608         | SHOW_CC
609         {
610                 if (msginfo->cc)
611                         INSERT(msginfo->cc);
612         }
613         | SHOW_REFERENCES
614         {
615                 GSList *item;
616
617                 INSERT(msginfo->inreplyto);
618                 for (item = msginfo->references; item != NULL; item = g_slist_next(item))
619                         if (item->data)
620                                 INSERT(item->data);
621         }
622         | SHOW_MESSAGE
623         {
624                 quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
625         }
626         | SHOW_QUOTED_MESSAGE
627         {
628                 quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
629         }
630         | SHOW_MESSAGE_NO_SIGNATURE
631         {
632                 quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
633         }
634         | SHOW_QUOTED_MESSAGE_NO_SIGNATURE
635         {
636                 quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
637         }
638         | SHOW_BACKSLASH
639         {
640                 INSERT("\\");
641         }
642         | SHOW_TAB
643         {
644                 INSERT("\t");
645         }
646         | SHOW_EOL
647         {
648                 INSERT("\n");
649         }
650         | SHOW_QUESTION_MARK
651         {
652                 INSERT("?");
653         }
654         | SHOW_EXCLAMATION_MARK
655         {
656                 INSERT("!");
657         }
658         | SHOW_PIPE
659         {
660                 INSERT("|");
661         }
662         | SHOW_OPARENT
663         {
664                 INSERT("{");
665         }
666         | SHOW_CPARENT
667         {
668                 INSERT("}");
669         }
670         | SET_CURSOR_POS
671         {
672                 cursor_pos = current->bufsize;
673         };
674
675 query:
676         QUERY_DATE
677         {
678                 add_visibility(msginfo->date != NULL);
679         }
680         OPARENT quote_fmt CPARENT
681         {
682                 remove_visibility();
683         }
684         | QUERY_FROM
685         {
686                 add_visibility(msginfo->from != NULL);
687         }
688         OPARENT quote_fmt CPARENT
689         {
690                 remove_visibility();
691         }
692         | QUERY_FULLNAME
693         {
694                 add_visibility(msginfo->fromname != NULL);
695         }
696         OPARENT quote_fmt CPARENT
697         {
698                 remove_visibility();
699         }
700         | QUERY_SUBJECT
701         {
702                 add_visibility(msginfo->subject != NULL);
703         }
704         OPARENT quote_fmt CPARENT
705         {
706                 remove_visibility();
707         }
708         | QUERY_TO
709         {
710                 add_visibility(msginfo->to != NULL);
711         }
712         OPARENT quote_fmt CPARENT
713         {
714                 remove_visibility();
715         }
716         | QUERY_NEWSGROUPS
717         {
718                 add_visibility(msginfo->newsgroups != NULL);
719         }
720         OPARENT quote_fmt CPARENT
721         {
722                 remove_visibility();
723         }
724         | QUERY_MESSAGEID
725         {
726                 add_visibility(msginfo->msgid != NULL);
727         }
728         OPARENT quote_fmt CPARENT
729         {
730                 remove_visibility();
731         }
732         | QUERY_CC
733         {
734                 add_visibility(msginfo->cc != NULL);
735         }
736         OPARENT quote_fmt CPARENT
737         {
738                 remove_visibility();
739         }
740         | QUERY_REFERENCES
741         {
742                 gboolean found;
743                 GSList *item;
744
745                 found = (msginfo->inreplyto != NULL);
746                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
747                         if (item->data)
748                                 found = TRUE;
749                 add_visibility(found == TRUE);
750         }
751         OPARENT quote_fmt CPARENT
752         {
753                 remove_visibility();
754         };
755
756 query_not:
757         QUERY_NOT_DATE
758         {
759                 add_visibility(msginfo->date == NULL);
760         }
761         OPARENT quote_fmt CPARENT
762         {
763                 remove_visibility();
764         }
765         | QUERY_NOT_FROM
766         {
767                 add_visibility(msginfo->from == NULL);
768         }
769         OPARENT quote_fmt CPARENT
770         {
771                 remove_visibility();
772         }
773         | QUERY_NOT_FULLNAME
774         {
775                 add_visibility(msginfo->fromname == NULL);
776         }
777         OPARENT quote_fmt CPARENT
778         {
779                 remove_visibility();
780         }
781         | QUERY_NOT_SUBJECT
782         {
783                 add_visibility(msginfo->subject == NULL);
784         }
785         OPARENT quote_fmt CPARENT
786         {
787                 remove_visibility();
788         }
789         | QUERY_NOT_TO
790         {
791                 add_visibility(msginfo->to == NULL);
792         }
793         OPARENT quote_fmt CPARENT
794         {
795                 remove_visibility();
796         }
797         | QUERY_NOT_NEWSGROUPS
798         {
799                 add_visibility(msginfo->newsgroups == NULL);
800         }
801         OPARENT quote_fmt CPARENT
802         {
803                 remove_visibility();
804         }
805         | QUERY_NOT_MESSAGEID
806         {
807                 add_visibility(msginfo->msgid == NULL);
808         }
809         OPARENT quote_fmt CPARENT
810         {
811                 remove_visibility();
812         }
813         | QUERY_NOT_CC
814         {
815                 add_visibility(msginfo->cc == NULL);
816         }
817         OPARENT quote_fmt CPARENT
818         {
819                 remove_visibility();
820         }
821         | QUERY_NOT_REFERENCES
822         {
823                 gboolean found;
824                 GSList *item;
825
826                 found = (msginfo->inreplyto != NULL);
827                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
828                         if (item->data)
829                                 found = TRUE;
830                 add_visibility(found == FALSE);
831         }
832         OPARENT quote_fmt CPARENT
833         {
834                 remove_visibility();
835         };
836
837 insert:
838         INSERT_FILE
839         {
840                 current = &sub_expr;
841                 clear_buffer();
842         }
843         OPARENT sub_expr CPARENT
844         {
845                 current = &main_expr;
846                 if (!dry_run) {
847                         quote_fmt_insert_file(sub_expr.buffer);
848                 }
849         }
850         | INSERT_PROGRAMOUTPUT
851         {
852                 current = &sub_expr;
853                 clear_buffer();
854         }
855         OPARENT sub_expr CPARENT
856         {
857                 current = &main_expr;
858                 if (!dry_run) {
859                         quote_fmt_insert_program_output(sub_expr.buffer);
860                 }
861         }
862         | INSERT_USERINPUT
863         {
864                 current = &sub_expr;
865                 clear_buffer();
866         }
867         OPARENT sub_expr CPARENT
868         {
869                 current = &main_expr;
870                 if (!dry_run) {
871                         quote_fmt_insert_user_input(sub_expr.buffer);
872                 }
873         };