587db4ab5be8ee262c4bf5d8f0826e9dee5439de
[colloquium.git] / libstorycode / storycode.y
1 /*
2  * storycode.y
3  *
4  * Copyright © 2019 Thomas White <taw@bitwiz.org.uk>
5  *
6  * This file is part of Colloquium.
7  *
8  * Colloquium is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 %define api.token.prefix {SC_}
24 %define api.prefix {sc}
25 %locations
26
27 %code requires {
28
29   #include "narrative.h"
30   #include "narrative_priv.h"
31   #include "slide.h"
32   #include "stylesheet.h"
33
34   /* NB These structures look very similar to ones in stylesheet.c.
35    * However, those structures are not exposed in the API, since there's no
36    * need other than re-using them for a slightly different purpose here. */
37   enum parse_style_mask
38   {
39     STYMASK_GEOM = 1<<0,
40     STYMASK_FONT = 1<<1,
41     STYMASK_ALIGNMENT = 1<<2,
42     STYMASK_PADDING = 1<<3,
43     STYMASK_PARASPACE = 1<<4,
44     STYMASK_FGCOL = 1<<5,
45     STYMASK_BGCOL = 1<<6,
46   };
47
48   struct parse_style
49   {
50     enum parse_style_mask mask;
51     struct frame_geom geom;
52     char *font;
53     enum alignment alignment;
54     struct length padding[4];
55     struct length paraspace[4];
56     struct colour fgcol;
57     enum gradient bggrad;
58     struct colour bgcol;
59     struct colour bgcol2;
60   };
61
62   struct parse_paragraph {
63     struct text_run *runs;
64     int n_runs;
65     int max_runs;
66   };
67
68   struct parse_many_paragraphs {
69     struct parse_paragraph *paras;
70     int n_paras;
71     int max_paras;
72   };
73
74 }
75
76 %union {
77
78   Stylesheet *ss;
79   Narrative *n;
80   Slide *slide;
81   SlideItem *slide_item;
82
83   char *str;
84   struct text_run run;
85   struct parse_paragraph para;
86   struct parse_many_paragraphs many_paragraphs;
87
88   struct parse_style style;
89
90   struct length len;
91   struct length lenquad[4];
92   struct frame_geom geom;
93   char character;
94   double val;
95   struct colour col;
96   enum alignment align;
97   enum gradient grad;
98
99 }
100
101 %{
102   #include <stdio.h>
103   #include <stdlib.h>
104   #include <string.h>
105
106   extern int sclex();
107   extern int scparse();
108   void scerror(Narrative *n, const char *s);
109   extern int lineno;
110 %}
111
112 %define parse.trace
113
114 %token STYLES
115 %token SLIDE
116 %token EOP
117 %token NARRATIVE
118 %token PRESTITLE
119 %token SLIDETITLE
120 %token FOOTER
121 %token TEXTFRAME
122 %token IMAGEFRAME
123 %token FILENAME
124 %token BP
125 %token FONT GEOMETRY PAD ALIGN FGCOL BGCOL PARASPACE
126 %token VERT HORIZ
127 %token LEFT CENTER RIGHT
128 %token FONTNAME RUN_TEXT
129 %token UNIT VALUE HEXCOL
130 %token TEXT_START
131
132 %type <n> narrative
133 %type <slide> slide
134 %type <slide> slide_parts
135 %type <slide_item> slide_part
136 %type <slide_item> imageframe
137 %type <slide_item> slidetitle
138 %type <slide_item> textframe
139 %type <slide_item> slide_prestitle
140 %type <many_paragraphs> multi_line_string
141 %type <para> text_line
142 %type <para> slide_bulletpoint
143 %type <para> text_line_with_start
144 %type <run> text_run
145 %type <str> RUN_TEXT
146 %type <str> one_or_more_runs
147
148 %type <ss> stylesheet
149 %type <style> style_narrative_bp
150 %type <style> style_narrative_prestitle
151 %type <style> styledefs
152 %type <style> styledef
153 %type <style> slide_geom
154 %type <style> background
155 %type <style> frame_options
156 %type <style> frame_option
157 %type <style> frameopt
158 %type <style> style_slide_prestitle
159 %type <style> style_slide_text
160 %type <style> style_slide_title
161 %type <style> style_slide_footer
162
163 %type <str> FONTNAME
164 %type <str> FILENAME
165
166 %type <geom> geometry
167 %type <lenquad> lenquad
168 %type <col> colour
169 %type <str> HEXCOL
170 %type <len> length
171 %type <align> alignment
172 %type <character> UNIT
173 %type <val> VALUE
174 %type <grad> gradtype
175
176 %parse-param { Narrative *n };
177
178 %{
179
180 static void merge_style(struct parse_style *combined, struct parse_style inp)
181 {
182     int i;
183
184     switch ( inp.mask ) {
185
186         case STYMASK_GEOM :
187         combined->geom = inp.geom;
188         break;
189
190         case STYMASK_FONT :
191         combined->font = inp.font;
192         break;
193
194         case STYMASK_ALIGNMENT :
195         combined->alignment = inp.alignment;
196         break;
197
198         case STYMASK_PADDING :
199         for ( i=0; i<4; i++ ) combined->padding[i] = inp.padding[i];
200         break;
201
202         case STYMASK_PARASPACE :
203         for ( i=0; i<4; i++ ) combined->paraspace[i] = inp.paraspace[i];
204         break;
205
206         case STYMASK_FGCOL :
207         copy_col(&combined->fgcol, inp.fgcol);
208         break;
209
210         case STYMASK_BGCOL :
211         copy_col(&combined->bgcol, inp.bgcol);
212         copy_col(&combined->bgcol2, inp.bgcol2);
213         combined->bggrad = inp.bggrad;
214         break;
215
216         default :
217         printf("Can't merge style %i\n", inp.mask);
218         return;
219
220     }
221
222     combined->mask |= inp.mask;
223 }
224
225
226 static int hex_to_double(const char *v, double *r)
227 {
228     char c[5];
229
230     if ( strlen(v) != 7 ) return 0;
231     if ( v[0] != '#' ) return 0;  /* should've already been blocked by lexxer */
232
233     c[0] = '0';  c[1] = 'x';  c[4] = '\0';
234
235     c[2] = v[1]; c[3] = v[2];
236     r[0] = strtod(c, NULL) / 255.0;
237
238     c[2] = v[3]; c[3] = v[4];
239     r[1] = strtod(c, NULL) / 255.0;
240
241     c[2] = v[5]; c[3] = v[6];
242     r[2] = strtod(c, NULL) / 255.0;
243
244     return 1;
245 }
246
247
248 void push_paragraph(struct parse_many_paragraphs *mp, struct parse_paragraph p)
249 {
250     if ( mp->n_paras == mp->max_paras ) {
251         struct parse_paragraph *nparas;
252         nparas = realloc(mp->paras, (mp->max_paras+8)*sizeof(struct parse_paragraph));
253         if ( nparas == NULL ) return;
254         mp->max_paras += 8;
255         mp->paras = nparas;
256     }
257
258     mp->paras[mp->n_paras++] = p;
259 }
260
261
262 struct text_run **combine_paras(struct parse_many_paragraphs mp, int **pn_runs)
263 {
264     struct text_run **combined_paras;
265     int *n_runs;
266     int i;
267
268     combined_paras = malloc(mp.n_paras * sizeof(struct text_run *));
269     n_runs = malloc(mp.n_paras * sizeof(int));
270     for ( i=0; i<mp.n_paras; i++ ) {
271         for ( int j=0; j<mp.paras[i].n_runs; j++ ) {
272         }
273         combined_paras[i] = mp.paras[i].runs;
274         n_runs[i] = mp.paras[i].n_runs;
275     }
276
277     *pn_runs = n_runs;
278     return combined_paras;
279 }
280
281
282 void set_stylesheet(Narrative *n, struct parse_style *style, const char *element)
283 {
284     Stylesheet *ss = narrative_get_stylesheet(n);
285     if ( style->mask & STYMASK_GEOM ) stylesheet_set_geometry(ss, element, style->geom);
286     if ( style->mask & STYMASK_FONT ) stylesheet_set_font(ss, element, style->font);
287     if ( style->mask & STYMASK_ALIGNMENT ) stylesheet_set_alignment(ss, element, style->alignment);
288     if ( style->mask & STYMASK_PADDING ) stylesheet_set_padding(ss, element, style->padding);
289     if ( style->mask & STYMASK_PARASPACE ) stylesheet_set_paraspace(ss, element, style->paraspace);
290     if ( style->mask & STYMASK_FGCOL ) stylesheet_set_fgcol(ss, element, style->fgcol);
291     if ( style->mask & STYMASK_BGCOL ) stylesheet_set_background(ss, element, style->bggrad,
292                                                                style->bgcol, style->bgcol2);
293 }
294
295 %}
296
297 %%
298
299 /* The only thing a "presentation" really needs is narrative */
300 presentation:
301   stylesheet narrative
302 | stylesheet
303 | narrative
304 ;
305
306
307 /* ------ Narrative ------ */
308
309 narrative:
310   narrative_el { }
311 | narrative narrative_el  { }
312 ;
313
314 narrative_el:
315   PRESTITLE TEXT_START text_line  { narrative_add_prestitle(n, $3.runs, $3.n_runs); }
316 | BP TEXT_START text_line         { narrative_add_bp(n, $3.runs, $3.n_runs); }
317 | TEXT_START text_line            { narrative_add_text(n, $2.runs, $2.n_runs); }
318 | slide                           { narrative_add_slide(n, $1); }
319 | EOP                             { narrative_add_eop(n); }
320 ;
321
322 text_line: { $<para>$.n_runs = 0;
323              $<para>$.max_runs = 0;
324              $<para>$.runs = NULL;
325            }
326   %empty
327 | text_line text_run   {
328                          if ( $<para>$.n_runs == $<para>$.max_runs ) {
329                              struct text_run *nruns;
330                              nruns = realloc($<para>$.runs,
331                                              ($<para>$.max_runs+8)*sizeof(struct text_run));
332                              if ( nruns != NULL ) {
333                                 $<para>$.max_runs += 8;
334                                 $<para>$.runs = nruns;
335                             }
336                          }
337                          if ( $<para>$.n_runs < $<para>$.max_runs ) {
338                             $<para>$.runs[$<para>$.n_runs++] = $2;
339                          }
340                        }
341
342 ;
343
344 /* FIXME: Modifiers might be nested or overlap, e.g.
345  *      _hello *there*, world_
346  *      _hello *there_, world*
347  */
348
349 text_run:
350   RUN_TEXT         { $$.text = $1;  $$.type = TEXT_RUN_NORMAL; }
351 | '*' one_or_more_runs '*' { $$.text = $2;  $$.type = TEXT_RUN_BOLD; }
352 | '/' one_or_more_runs '/' { $$.text = $2;  $$.type = TEXT_RUN_ITALIC; }
353 | '_' one_or_more_runs '_' { $$.text = $2;  $$.type = TEXT_RUN_UNDERLINE; }
354
355 one_or_more_runs:
356   RUN_TEXT { $$ = $1; }
357 | one_or_more_runs RUN_TEXT { char *nt;
358                               size_t len;
359                               len = strlen($1) + strlen($2) + 1;
360                               nt = malloc(len);
361                               if ( nt != NULL ) {
362                                   nt[0] = '\0';
363                                   strcat(nt, $1);
364                                   strcat(nt, $2);
365                                   free($1);
366                                   $$ = nt;
367                               } else {
368                                   $$ = strdup("ERROR");
369                               }
370                             }
371 ;
372
373 /* -------- Slide -------- */
374
375 slide:
376   SLIDE '{' slide_parts '}'  { $$ = $3; }
377 ;
378
379 slide_parts: { $<slide>$ = slide_new(); }
380   %empty
381 | slide_parts slide_part { slide_add_item($<slide>$, $2); }
382 ;
383
384 slide_part:
385   slide_prestitle { $$ = $1; }
386 | textframe       { $$ = $1; }
387 | slidetitle      { $$ = $1; }
388 | imageframe      { $$ = $1; }
389 | FOOTER          { $$ = slide_item_footer(); }
390 ;
391
392 slide_prestitle:
393   PRESTITLE multi_line_string { struct text_run **cp;
394                                 int *n_runs;
395                                 cp = combine_paras($2, &n_runs);
396                                 $$ = slide_item_prestitle(cp, n_runs, $2.n_paras);
397                               }
398 | PRESTITLE '{' multi_line_string '}' { struct text_run **cp;
399                                         int *n_runs;
400                                         cp = combine_paras($3, &n_runs);
401                                         $$ = slide_item_prestitle(cp, n_runs, $3.n_paras);
402                                       }
403 ;
404
405 slidetitle:
406   SLIDETITLE multi_line_string { struct text_run **cp;
407                                    int *n_runs;
408                                    cp = combine_paras($2, &n_runs);
409                                    $$ = slide_item_slidetitle(cp, n_runs, $2.n_paras);
410                                  }
411 | SLIDETITLE '{' multi_line_string '}' { struct text_run **cp;
412                                          int *n_runs;
413                                          cp = combine_paras($3, &n_runs);
414                                          $$ = slide_item_slidetitle(cp, n_runs, $3.n_paras);
415                                        }
416 ;
417
418 imageframe:
419   IMAGEFRAME frame_options TEXT_START FILENAME { if ( $2.mask & STYMASK_GEOM ) {
420                                                      $$ = slide_item_image($4, $2.geom);
421                                                  } else {
422                                                      printf("Image frame must have geometry.\n");
423                                                  }
424                                                }
425 ;
426
427 textframe:
428   TEXTFRAME frame_options multi_line_string { struct text_run **cp;
429                                               int *n_runs;
430                                               cp = combine_paras($3, &n_runs);
431                                               $$ = slide_item_text(cp, n_runs, $3.n_paras,
432                                                                    $2.geom, $2.alignment);
433                                             }
434 | TEXTFRAME frame_options '{' multi_line_string '}' { struct text_run **cp;
435                                                       int *n_runs;
436                                                       cp = combine_paras($4, &n_runs);
437                                                       $$ = slide_item_text(cp, n_runs, $4.n_paras,
438                                                                            $2.geom, $2.alignment);
439                                                     }
440 ;
441
442 text_line_with_start:
443   TEXT_START text_line { $$ = $2; }
444 ;
445
446 slide_bulletpoint:
447   BP TEXT_START text_line { $$ = $3; }
448 ;
449
450 multi_line_string:   { $<many_paragraphs>$.n_paras = 0;
451                        $<many_paragraphs>$.paras = NULL;
452                        $<many_paragraphs>$.max_paras = 0;  }
453   text_line_with_start                   { push_paragraph(&$<many_paragraphs>$, $2); }
454 | multi_line_string text_line_with_start { push_paragraph(&$<many_paragraphs>$, $2); }
455 | slide_bulletpoint                      { push_paragraph(&$<many_paragraphs>$, $1); }
456 | multi_line_string slide_bulletpoint    { push_paragraph(&$<many_paragraphs>$, $2); }
457 ;
458
459 /* There can be any number of options */
460 frame_options: { $<style>$.mask = 0;  $<style>$.alignment = ALIGN_INHERIT; }
461   %empty
462 | frame_options frame_option { merge_style(&$<style>$, $2); }
463 ;
464
465 /* Each option is enclosed in square brackets */
466 frame_option:
467   '[' frameopt ']' { $$ = $2; }
468 ;
469
470 frameopt:
471   geometry   { $$.geom = $1;  $$.mask = STYMASK_GEOM; }
472 | alignment  { $$.alignment = $1; $$.mask = STYMASK_ALIGNMENT; }
473 ;
474
475 /* Primitives for describing styles (used in frame options and stylesheets) */
476 geometry:
477   length 'x' length '+' length '+' length { $$.w = $1;  $$.h = $3;
478                                             $$.x = $5;  $$.y = $7; }
479 ;
480
481 lenquad:
482   length ',' length ',' length ',' length { $$[0] = $1;  $$[1] = $3;
483                                             $$[2] = $5;  $$[3] = $7; }
484 ;
485
486 colour:
487   VALUE ',' VALUE ',' VALUE ',' VALUE { $$.rgba[0] = $1;  $$.rgba[1] = $3;
488                                         $$.rgba[2] = $5;  $$.rgba[3] = $7;
489                                         $$.hexcode = 0;  }
490 | HEXCOL { double col[3];
491            if ( hex_to_double($1, col) ) {
492                $$.rgba[0] = col[0];  $$.rgba[1] = col[1];
493                $$.rgba[2] = col[2];  $$.rgba[3] = 1.0;
494                $$.hexcode = 1;
495            }
496          }
497 ;
498
499 alignment:
500   LEFT     { $$ = ALIGN_LEFT; }
501 | CENTER   { $$ = ALIGN_CENTER; }
502 | RIGHT    { $$ = ALIGN_RIGHT; }
503 ;
504
505 length:
506   VALUE UNIT  { $$.len = $VALUE;
507                 if ( $UNIT == 'u' ) $$.unit = LENGTH_UNIT;
508                 if ( $UNIT == 'f' ) $$.unit = LENGTH_FRAC; }
509 ;
510
511 gradtype:
512   VERT { $$ = GRAD_VERT; }
513 | HORIZ { $$ = GRAD_HORIZ; }
514 ;
515
516
517 /* ------ Stylesheet ------ */
518
519 stylesheet:
520   STYLES '{' stylesheet_blocks '}' { }
521 ;
522
523 stylesheet_blocks: { }
524   %empty
525 | stylesheet_blocks stylesheet_block { }
526 ;
527
528 stylesheet_block:
529   NARRATIVE '{' style_narrative_defs '}' { }
530 | SLIDE '{' style_slide_defs '}' { }
531 ;
532
533 style_narrative_defs:
534   %empty
535 | style_narrative_defs style_narrative_def { }
536 ;
537
538 style_slide_defs:
539   %empty
540 | style_slide_defs style_slide_def { }
541 ;
542
543 style_narrative_def:
544   style_narrative_prestitle { set_stylesheet(n, &$1, "NARRATIVE.PRESTITLE"); }
545 | style_narrative_bp        { set_stylesheet(n, &$1, "NARRATIVE.BP"); }
546 | styledef                  { set_stylesheet(n, &$1, "NARRATIVE"); }
547 ;
548
549 style_slide_def:
550   background            { set_stylesheet(n, &$1, "SLIDE"); }
551 | slide_geom            { set_stylesheet(n, &$1, "SLIDE"); }
552 | style_slide_prestitle { set_stylesheet(n, &$1, "SLIDE.PRESTITLE"); }
553 | style_slide_text      { set_stylesheet(n, &$1, "SLIDE.TEXT"); }
554 | style_slide_title     { set_stylesheet(n, &$1, "SLIDE.SLIDETITLE"); }
555 | style_slide_footer    { set_stylesheet(n, &$1, "SLIDE.FOOTER"); }
556 ;
557
558 style_narrative_prestitle:
559   PRESTITLE '{' styledefs '}' { $$ = $3; }
560 ;
561
562 style_narrative_bp:
563   BP '{' styledefs '}' { $$ = $3; }
564 ;
565
566 background:
567   BGCOL colour       { copy_col(&$$.bgcol, $2);
568                        $$.bggrad = GRAD_NONE;
569                        $$.mask = STYMASK_BGCOL; }
570 | BGCOL gradtype colour colour  { copy_col(&$$.bgcol, $3);
571                                   copy_col(&$$.bgcol2, $4);
572                                   $$.bggrad = $2;
573                                   $$.mask = STYMASK_BGCOL; }
574 ;
575
576 slide_geom:
577   GEOMETRY geometry  { $$.geom = $2; $$.mask = STYMASK_GEOM; }
578 ;
579
580 style_slide_prestitle:
581   PRESTITLE '{' styledefs '}' { $$ = $3; }
582 ;
583
584 style_slide_title:
585   SLIDETITLE '{' styledefs '}' { $$ = $3; }
586 ;
587
588 style_slide_text:
589   TEXTFRAME '{' styledefs '}' { $$ = $3; }
590 ;
591
592 style_slide_footer:
593   FOOTER '{' styledefs '}' { $$ = $3; }
594 ;
595
596 styledefs: { $<style>$.mask = 0;  $<style>$.alignment = ALIGN_INHERIT; }
597   %empty
598 | styledefs styledef { merge_style(&$$, $2); }
599 ;
600
601 styledef:
602   FONT FONTNAME      { $$.font = $2; $$.mask = STYMASK_FONT; }
603 | GEOMETRY geometry  { $$.geom = $2; $$.mask = STYMASK_GEOM; }
604 | PAD lenquad        { for ( int i=0; i<4; i++ ) $$.padding[i] = $2[i];
605                        $$.mask = STYMASK_PADDING; }
606 | PARASPACE lenquad  { for ( int i=0; i<4; i++ ) $$.paraspace[i] = $2[i];
607                        $$.mask = STYMASK_PARASPACE; }
608 | FGCOL colour       { copy_col(&$$.fgcol, $2); $$.mask = STYMASK_FGCOL; }
609 | background         { $$ = $1; }
610 | ALIGN alignment    { $$.alignment = $2; $$.mask = STYMASK_ALIGNMENT; }
611 ;
612
613 %%
614
615 void scerror(Narrative *n, const char *s) {
616         printf("Storycode parse error at line %i\n", lineno);
617 }