1 /* GNU gettext - internationalization aids
2    Copyright (C) 1995-1996, 1998, 2000-2001, 2003, 2005-2006, 2012-2013, 2016, 2020
3    Free Software Foundation, Inc.
4 
5    This file was written by Peter Miller <pmiller@agso.gov.au>
6 
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
19 
20 %{
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24 
25 /* Specification.  */
26 #include "po-gram.h"
27 
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #include "str-list.h"
34 #include "po-lex.h"
35 #include "po-charset.h"
36 #include "error.h"
37 #include "xalloc.h"
38 #include "gettext.h"
39 #include "read-catalog-abstract.h"
40 
41 #define _(str) gettext (str)
42 
43 static long plural_counter;
44 
45 #define check_obsolete(value1,value2) \
46   if ((value1).obsolete != (value2).obsolete) \
47     po_gram_error_at_line (&(value2).pos, _("inconsistent use of #~"));
48 
49 static inline void
do_callback_message(char * msgctxt,char * msgid,lex_pos_ty * msgid_pos,char * msgid_plural,char * msgstr,size_t msgstr_len,lex_pos_ty * msgstr_pos,char * prev_msgctxt,char * prev_msgid,char * prev_msgid_plural,bool obsolete)50 do_callback_message (char *msgctxt,
51                      char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
52                      char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
53                      char *prev_msgctxt,
54                      char *prev_msgid, char *prev_msgid_plural,
55                      bool obsolete)
56 {
57   /* Test for header entry.  Ignore fuzziness of the header entry.  */
58   if (msgctxt == NULL && msgid[0] == '\0' && !obsolete)
59     po_lex_charset_set (msgstr, gram_pos.file_name);
60 
61   po_callback_message (msgctxt,
62                        msgid, msgid_pos, msgid_plural,
63                        msgstr, msgstr_len, msgstr_pos,
64                        prev_msgctxt, prev_msgid, prev_msgid_plural,
65                        false, obsolete);
66 }
67 
68 #define free_message_intro(value) \
69   if ((value).prev_ctxt != NULL)        \
70     free ((value).prev_ctxt);           \
71   if ((value).prev_id != NULL)          \
72     free ((value).prev_id);             \
73   if ((value).prev_id_plural != NULL)   \
74     free ((value).prev_id_plural);      \
75   if ((value).ctxt != NULL)             \
76     free ((value).ctxt);
77 
78 %}
79 
80 %require "3.0"
81 
82 /* Remap parser interface names, so we can have multiple Bison
83    generated parsers in the same program.  */
84 %define api.prefix {po_gram_}
85 
86 %token COMMENT
87 %token DOMAIN
88 %token JUNK
89 %token PREV_MSGCTXT
90 %token PREV_MSGID
91 %token PREV_MSGID_PLURAL
92 %token PREV_STRING
93 %token MSGCTXT
94 %token MSGID
95 %token MSGID_PLURAL
96 %token MSGSTR
97 %token NAME
98 %token '[' ']'
99 %token NUMBER
100 %token STRING
101 
102 %union
103 {
104   struct { char *string; lex_pos_ty pos; bool obsolete; } string;
105   struct { string_list_ty stringlist; lex_pos_ty pos; bool obsolete; } stringlist;
106   struct { long number; lex_pos_ty pos; bool obsolete; } number;
107   struct { lex_pos_ty pos; bool obsolete; } pos;
108   struct { char *ctxt; char *id; char *id_plural; lex_pos_ty pos; bool obsolete; } prev;
109   struct { char *prev_ctxt; char *prev_id; char *prev_id_plural; char *ctxt; lex_pos_ty pos; bool obsolete; } message_intro;
110   struct { struct msgstr_def rhs; lex_pos_ty pos; bool obsolete; } rhs;
111 }
112 
113 %type <string> STRING PREV_STRING COMMENT NAME
114                msg_intro prev_msg_intro msgid_pluralform prev_msgid_pluralform
115 %type <stringlist> string_list prev_string_list
116 %type <number> NUMBER
117 %type <pos> DOMAIN
118             PREV_MSGCTXT PREV_MSGID PREV_MSGID_PLURAL
119             MSGCTXT MSGID MSGID_PLURAL MSGSTR '[' ']'
120 %type <prev> prev
121 %type <message_intro> message_intro
122 %type <rhs> pluralform pluralform_list
123 
124 %right MSGSTR
125 
126 %%
127 
128 po_file
129         : /* empty */
130         | po_file comment
131         | po_file domain
132         | po_file message
133         | po_file error
134         ;
135 
136 
137 comment
138         : COMMENT
139                 {
140                   po_callback_comment_dispatcher ($1.string);
141                 }
142         ;
143 
144 
145 domain
146         : DOMAIN STRING
147                 {
148                    po_callback_domain ($2.string);
149                 }
150         ;
151 
152 
153 message
154         : message_intro string_list MSGSTR string_list
155                 {
156                   char *string2 = string_list_concat_destroy (&$2.stringlist);
157                   char *string4 = string_list_concat_destroy (&$4.stringlist);
158 
159                   check_obsolete ($1, $2);
160                   check_obsolete ($1, $3);
161                   check_obsolete ($1, $4);
162                   if (!$1.obsolete || pass_obsolete_entries)
163                     do_callback_message ($1.ctxt, string2, &$1.pos, NULL,
164                                          string4, strlen (string4) + 1, &$3.pos,
165                                          $1.prev_ctxt,
166                                          $1.prev_id, $1.prev_id_plural,
167                                          $1.obsolete);
168                   else
169                     {
170                       free_message_intro ($1);
171                       free (string2);
172                       free (string4);
173                     }
174                 }
175         | message_intro string_list msgid_pluralform pluralform_list
176                 {
177                   char *string2 = string_list_concat_destroy (&$2.stringlist);
178 
179                   check_obsolete ($1, $2);
180                   check_obsolete ($1, $3);
181                   check_obsolete ($1, $4);
182                   if (!$1.obsolete || pass_obsolete_entries)
183                     do_callback_message ($1.ctxt, string2, &$1.pos, $3.string,
184                                          $4.rhs.msgstr, $4.rhs.msgstr_len, &$4.pos,
185                                          $1.prev_ctxt,
186                                          $1.prev_id, $1.prev_id_plural,
187                                          $1.obsolete);
188                   else
189                     {
190                       free_message_intro ($1);
191                       free (string2);
192                       free ($3.string);
193                       free ($4.rhs.msgstr);
194                     }
195                 }
196         | message_intro string_list msgid_pluralform
197                 {
198                   check_obsolete ($1, $2);
199                   check_obsolete ($1, $3);
200                   po_gram_error_at_line (&$1.pos, _("missing 'msgstr[]' section"));
201                   free_message_intro ($1);
202                   string_list_destroy (&$2.stringlist);
203                   free ($3.string);
204                 }
205         | message_intro string_list pluralform_list
206                 {
207                   check_obsolete ($1, $2);
208                   check_obsolete ($1, $3);
209                   po_gram_error_at_line (&$1.pos, _("missing 'msgid_plural' section"));
210                   free_message_intro ($1);
211                   string_list_destroy (&$2.stringlist);
212                   free ($3.rhs.msgstr);
213                 }
214         | message_intro string_list
215                 {
216                   check_obsolete ($1, $2);
217                   po_gram_error_at_line (&$1.pos, _("missing 'msgstr' section"));
218                   free_message_intro ($1);
219                   string_list_destroy (&$2.stringlist);
220                 }
221         ;
222 
223 
224 message_intro
225         : msg_intro
226                 {
227                   $$.prev_ctxt = NULL;
228                   $$.prev_id = NULL;
229                   $$.prev_id_plural = NULL;
230                   $$.ctxt = $1.string;
231                   $$.pos = $1.pos;
232                   $$.obsolete = $1.obsolete;
233                 }
234         | prev msg_intro
235                 {
236                   check_obsolete ($1, $2);
237                   $$.prev_ctxt = $1.ctxt;
238                   $$.prev_id = $1.id;
239                   $$.prev_id_plural = $1.id_plural;
240                   $$.ctxt = $2.string;
241                   $$.pos = $2.pos;
242                   $$.obsolete = $2.obsolete;
243                 }
244         ;
245 
246 
247 prev
248         : prev_msg_intro prev_string_list
249                 {
250                   check_obsolete ($1, $2);
251                   $$.ctxt = $1.string;
252                   $$.id = string_list_concat_destroy (&$2.stringlist);
253                   $$.id_plural = NULL;
254                   $$.pos = $1.pos;
255                   $$.obsolete = $1.obsolete;
256                 }
257         | prev_msg_intro prev_string_list prev_msgid_pluralform
258                 {
259                   check_obsolete ($1, $2);
260                   check_obsolete ($1, $3);
261                   $$.ctxt = $1.string;
262                   $$.id = string_list_concat_destroy (&$2.stringlist);
263                   $$.id_plural = $3.string;
264                   $$.pos = $1.pos;
265                   $$.obsolete = $1.obsolete;
266                 }
267         ;
268 
269 
270 msg_intro
271         : MSGID
272                 {
273                   $$.string = NULL;
274                   $$.pos = $1.pos;
275                   $$.obsolete = $1.obsolete;
276                 }
277         | MSGCTXT string_list MSGID
278                 {
279                   check_obsolete ($1, $2);
280                   check_obsolete ($1, $3);
281                   $$.string = string_list_concat_destroy (&$2.stringlist);
282                   $$.pos = $3.pos;
283                   $$.obsolete = $3.obsolete;
284                 }
285         ;
286 
287 prev_msg_intro
288         : PREV_MSGID
289                 {
290                   $$.string = NULL;
291                   $$.pos = $1.pos;
292                   $$.obsolete = $1.obsolete;
293                 }
294         | PREV_MSGCTXT prev_string_list PREV_MSGID
295                 {
296                   check_obsolete ($1, $2);
297                   check_obsolete ($1, $3);
298                   $$.string = string_list_concat_destroy (&$2.stringlist);
299                   $$.pos = $3.pos;
300                   $$.obsolete = $3.obsolete;
301                 }
302         ;
303 
304 
305 msgid_pluralform
306         : MSGID_PLURAL string_list
307                 {
308                   check_obsolete ($1, $2);
309                   plural_counter = 0;
310                   $$.string = string_list_concat_destroy (&$2.stringlist);
311                   $$.pos = $1.pos;
312                   $$.obsolete = $1.obsolete;
313                 }
314         ;
315 
316 prev_msgid_pluralform
317         : PREV_MSGID_PLURAL prev_string_list
318                 {
319                   check_obsolete ($1, $2);
320                   $$.string = string_list_concat_destroy (&$2.stringlist);
321                   $$.pos = $1.pos;
322                   $$.obsolete = $1.obsolete;
323                 }
324         ;
325 
326 
327 pluralform_list
328         : pluralform
329                 {
330                   $$ = $1;
331                 }
332         | pluralform_list pluralform
333                 {
334                   check_obsolete ($1, $2);
335                   $$.rhs.msgstr = XNMALLOC ($1.rhs.msgstr_len + $2.rhs.msgstr_len, char);
336                   memcpy ($$.rhs.msgstr, $1.rhs.msgstr, $1.rhs.msgstr_len);
337                   memcpy ($$.rhs.msgstr + $1.rhs.msgstr_len, $2.rhs.msgstr, $2.rhs.msgstr_len);
338                   $$.rhs.msgstr_len = $1.rhs.msgstr_len + $2.rhs.msgstr_len;
339                   free ($1.rhs.msgstr);
340                   free ($2.rhs.msgstr);
341                   $$.pos = $1.pos;
342                   $$.obsolete = $1.obsolete;
343                 }
344         ;
345 
346 pluralform
347         : MSGSTR '[' NUMBER ']' string_list
348                 {
349                   check_obsolete ($1, $2);
350                   check_obsolete ($1, $3);
351                   check_obsolete ($1, $4);
352                   check_obsolete ($1, $5);
353                   if ($3.number != plural_counter)
354                     {
355                       if (plural_counter == 0)
356                         po_gram_error_at_line (&$1.pos, _("first plural form has nonzero index"));
357                       else
358                         po_gram_error_at_line (&$1.pos, _("plural form has wrong index"));
359                     }
360                   plural_counter++;
361                   $$.rhs.msgstr = string_list_concat_destroy (&$5.stringlist);
362                   $$.rhs.msgstr_len = strlen ($$.rhs.msgstr) + 1;
363                   $$.pos = $1.pos;
364                   $$.obsolete = $1.obsolete;
365                 }
366         ;
367 
368 
369 string_list
370         : STRING
371                 {
372                   string_list_init (&$$.stringlist);
373                   string_list_append (&$$.stringlist, $1.string);
374                   free ($1.string);
375                   $$.pos = $1.pos;
376                   $$.obsolete = $1.obsolete;
377                 }
378         | string_list STRING
379                 {
380                   check_obsolete ($1, $2);
381                   $$.stringlist = $1.stringlist;
382                   string_list_append (&$$.stringlist, $2.string);
383                   free ($2.string);
384                   $$.pos = $1.pos;
385                   $$.obsolete = $1.obsolete;
386                 }
387         ;
388 
389 prev_string_list
390         : PREV_STRING
391                 {
392                   string_list_init (&$$.stringlist);
393                   string_list_append (&$$.stringlist, $1.string);
394                   free ($1.string);
395                   $$.pos = $1.pos;
396                   $$.obsolete = $1.obsolete;
397                 }
398         | prev_string_list PREV_STRING
399                 {
400                   check_obsolete ($1, $2);
401                   $$.stringlist = $1.stringlist;
402                   string_list_append (&$$.stringlist, $2.string);
403                   free ($2.string);
404                   $$.pos = $1.pos;
405                   $$.obsolete = $1.obsolete;
406                 }
407         ;
408