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