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