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