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