/src/gettext/gettext-tools/src/format-ocaml.c
Line | Count | Source |
1 | | /* OCaml format strings. |
2 | | Copyright (C) 2001-2026 Free Software Foundation, Inc. |
3 | | |
4 | | This program is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation; either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | This program is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
16 | | |
17 | | /* Written by Bruno Haible. */ |
18 | | |
19 | | #include <config.h> |
20 | | |
21 | | #include <stdbool.h> |
22 | | #include <stdlib.h> |
23 | | #include <string.h> |
24 | | |
25 | | #include "format.h" |
26 | | #include "attribute.h" |
27 | | #include "gettext.h" |
28 | | #include "xalloc.h" |
29 | | #include "format-invalid.h" |
30 | | #include "c-ctype.h" |
31 | | #include "xvasprintf.h" |
32 | | |
33 | 0 | #define _(str) gettext (str) |
34 | | |
35 | | /* The OCaml format strings are described in the OCaml reference manual, |
36 | | at https://ocaml.org/manual/5.3/api/Printf.html#VALfprintf . |
37 | | They are implemented in ocaml-5.3.0/stdlib/scanf.ml. |
38 | | |
39 | | A directive |
40 | | - starts with '%', |
41 | | - [in msgstr only] is optionally followed by |
42 | | a positive integer m, then '$' |
43 | | - is optionally followed by a sequence of flags, each being one of |
44 | | '+', '-', ' ', '0', '#', |
45 | | - is optionally followed by a width specification: |
46 | | a positive integer, or |
47 | | '*', or |
48 | | [in msgstr only] '*', then a positive integer, then '$', |
49 | | - is optionally followed by a precision specification: |
50 | | '.' then optionally: |
51 | | a positive integer, or |
52 | | '*', or |
53 | | [in msgstr only] '*', then a positive integer, then '$', |
54 | | - is finished by a specifier |
55 | | - 'd', 'i', 'u', 'x', 'X', 'o', that need an integer argument, |
56 | | - 'l' then 'd', 'i', 'u', 'x', 'X', 'o', that need an int32 argument, |
57 | | - 'n' then 'd', 'i', 'u', 'x', 'X', 'o', that need an nativeint argument, |
58 | | - 'L' then 'd', 'i', 'u', 'x', 'X', 'o', that need an int64 argument, |
59 | | - 's', that needs a string argument, |
60 | | - 'S', that needs a string argument and outputs it in OCaml syntax, |
61 | | - 'c', that needs a character argument, |
62 | | - 'C', that needs a character argument and outputs it in OCaml syntax, |
63 | | - 'f', 'e', 'E', 'g', 'G', 'h', 'H', that need a floating-point argument, |
64 | | - 'F', that needs a floating-point argument and outputs it in OCaml syntax, |
65 | | - 'B', that needs a boolean argument, |
66 | | - 'a', that takes a function (of type : out_channel -> unit) argument, |
67 | | - 't', that takes two arguments: a function (of type : out_channel -> <T> -> unit) |
68 | | and a <T>, |
69 | | - '{' FMT '%}', that takes a format string argument without msgstr |
70 | | extensions, expected to have the same signature as FMT, effectively |
71 | | ignores it, and instead outputs the minimal format string with the |
72 | | same signature as FMT: a concatenation of |
73 | | - "%i" for an integer argument, |
74 | | - "%li" for an int32 argument, |
75 | | - "%ni" for a nativeint argument, |
76 | | - "%Li" for an int64 argument, |
77 | | - "%s" for a string argument, |
78 | | - "%c" for a character argument, |
79 | | - "%f" for a floating-point argument, |
80 | | - "%B" for a boolean argument, |
81 | | - "%a" for a function argument, |
82 | | - "%t" for two arguments, as described above, |
83 | | - '(' FMT '%)', that takes a format string argument without msgstr |
84 | | extensions, expected to have the same signature as FMT, and a set |
85 | | of arguments suitable for FMT, |
86 | | - '!', '%', '@', ',', that take no argument. |
87 | | Numbered ('%m$' or '*m$') and unnumbered argument specifications cannot |
88 | | be used in the same string. |
89 | | */ |
90 | | |
91 | | enum format_arg_type |
92 | | { |
93 | | FAT_NONE = 0, |
94 | | /* Basic types */ |
95 | | FAT_INTEGER = 1, |
96 | | FAT_INT32 = 2, |
97 | | FAT_NATIVEINT = 3, |
98 | | FAT_INT64 = 4, |
99 | | FAT_STRING = 5, |
100 | | FAT_CHARACTER = 6, |
101 | | FAT_FLOATINGPOINT = 7, |
102 | | FAT_BOOLEAN = 8, |
103 | | FAT_FUNCTION_A = 9, |
104 | | FAT_FUNCTION_T = 10, /* first argument for %t */ |
105 | | FAT_FUNCTION_T2 = 11, /* second argument for %t */ |
106 | | FAT_FORMAT_STRING = 12, |
107 | | /* Flags */ |
108 | | FAT_OCAML_SYNTAX = 1 << 4, |
109 | | FAT_OPTIONAL_OCAML_SYNTAX = 1 << 5, |
110 | | /* Bitmasks */ |
111 | | FAT_BASIC_MASK = (FAT_INTEGER | FAT_INT32 | FAT_NATIVEINT | FAT_INT64 |
112 | | | FAT_STRING | FAT_CHARACTER | FAT_FLOATINGPOINT |
113 | | | FAT_BOOLEAN | FAT_FUNCTION_A | FAT_FUNCTION_T |
114 | | | FAT_FUNCTION_T2 | FAT_FORMAT_STRING) |
115 | | }; |
116 | | #ifdef __cplusplus |
117 | | typedef int format_arg_type_t; |
118 | | #else |
119 | | typedef enum format_arg_type format_arg_type_t; |
120 | | #endif |
121 | | |
122 | | struct numbered_arg |
123 | | { |
124 | | size_t number; |
125 | | format_arg_type_t type; |
126 | | char *signature; /* for type == FAT_FORMAT_STRING */ |
127 | | }; |
128 | | |
129 | | struct spec |
130 | | { |
131 | | size_t directives; |
132 | | size_t numbered_arg_count; |
133 | | struct numbered_arg *numbered |
134 | | COUNTED_BY (numbered_arg_count); |
135 | | }; |
136 | | |
137 | | |
138 | | static int |
139 | | numbered_arg_compare (const void *p1, const void *p2) |
140 | 0 | { |
141 | 0 | size_t n1 = ((const struct numbered_arg *) p1)->number; |
142 | 0 | size_t n2 = ((const struct numbered_arg *) p2)->number; |
143 | |
|
144 | 0 | return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); |
145 | 0 | } |
146 | | |
147 | | /* Frees the memory held by *spec. */ |
148 | | static void |
149 | | destroy_spec (struct spec *spec) |
150 | 0 | { |
151 | 0 | if (spec->numbered != NULL) |
152 | 0 | { |
153 | 0 | for (size_t i = spec->numbered_arg_count; i > 0; ) |
154 | 0 | { |
155 | 0 | --i; |
156 | 0 | if (spec->numbered[i].type == FAT_FORMAT_STRING) |
157 | 0 | free (spec->numbered[i].signature); |
158 | 0 | } |
159 | 0 | free (spec->numbered); |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | /* Returns the signature of a format string |
164 | | as a freshly allocated string. */ |
165 | | static char * |
166 | | format_string_signature (const struct spec *spec) |
167 | 0 | { |
168 | 0 | size_t len; |
169 | 0 | { |
170 | 0 | size_t i; |
171 | 0 | const struct numbered_arg *p; |
172 | 0 | len = spec->numbered_arg_count; |
173 | 0 | for (i = 0, p = spec->numbered; i < spec->numbered_arg_count; i++, p++) |
174 | 0 | if ((p->type & FAT_BASIC_MASK) == FAT_FORMAT_STRING) |
175 | 0 | len += strlen (p->signature) + 1; |
176 | 0 | } |
177 | 0 | char *signature = (char *) xmalloc (len + 1); |
178 | 0 | { |
179 | 0 | size_t i; |
180 | 0 | const struct numbered_arg *p; |
181 | 0 | char *s; |
182 | 0 | for (i = 0, p = spec->numbered, s = signature; |
183 | 0 | i < spec->numbered_arg_count; |
184 | 0 | i++, p++) |
185 | 0 | switch (p->type & FAT_BASIC_MASK) |
186 | 0 | { |
187 | 0 | case FAT_INTEGER: |
188 | 0 | *s++ = 'i'; |
189 | 0 | break; |
190 | 0 | case FAT_INT32: |
191 | 0 | *s++ = 'l'; |
192 | 0 | break; |
193 | 0 | case FAT_NATIVEINT: |
194 | 0 | *s++ = 'n'; |
195 | 0 | break; |
196 | 0 | case FAT_INT64: |
197 | 0 | *s++ = 'L'; |
198 | 0 | break; |
199 | 0 | case FAT_STRING: |
200 | 0 | *s++ = 's'; |
201 | 0 | break; |
202 | 0 | case FAT_CHARACTER: |
203 | 0 | *s++ = 'c'; |
204 | 0 | break; |
205 | 0 | case FAT_FLOATINGPOINT: |
206 | 0 | *s++ = 'f'; |
207 | 0 | break; |
208 | 0 | case FAT_BOOLEAN: |
209 | 0 | *s++ = 'B'; |
210 | 0 | break; |
211 | 0 | case FAT_FUNCTION_A: |
212 | 0 | *s++ = 'a'; |
213 | 0 | break; |
214 | 0 | case FAT_FUNCTION_T: |
215 | 0 | *s++ = 't'; |
216 | 0 | break; |
217 | 0 | case FAT_FUNCTION_T2: |
218 | 0 | break; |
219 | 0 | case FAT_FORMAT_STRING: |
220 | 0 | *s++ = '('; |
221 | 0 | memcpy (s, p->signature, strlen (p->signature)); |
222 | 0 | s += strlen (p->signature); |
223 | 0 | *s++ = ')'; |
224 | 0 | break; |
225 | 0 | default: |
226 | 0 | abort (); |
227 | 0 | } |
228 | 0 | *s = '\0'; |
229 | 0 | } |
230 | 0 | return signature; |
231 | 0 | } |
232 | | |
233 | | /* When a type is specified via format string substitution, e.g. "%(%s%)", both |
234 | | the variant without OCaml syntax "%s" and the variant with OCaml syntax "%S" |
235 | | are allowed. */ |
236 | | static format_arg_type_t |
237 | | type_without_translator_constraint (format_arg_type_t type) |
238 | 0 | { |
239 | 0 | switch (type & FAT_BASIC_MASK) |
240 | 0 | { |
241 | 0 | case FAT_STRING: |
242 | 0 | case FAT_CHARACTER: |
243 | 0 | case FAT_FLOATINGPOINT: |
244 | 0 | return (type & FAT_BASIC_MASK) | FAT_OPTIONAL_OCAML_SYNTAX; |
245 | 0 | default: |
246 | 0 | return type; |
247 | 0 | } |
248 | 0 | } |
249 | | |
250 | | /* Parse a piece of format string, until the matching terminating format |
251 | | directive is encountered. |
252 | | spec is the global struct spec. |
253 | | format is the remainder of the format string. |
254 | | It is updated upon valid return. |
255 | | terminator is '\0' at the top-level, otherwise '}' or ')'. |
256 | | translated is true when msgstr extensions should be accepted. |
257 | | fdi is an array to be filled with format directive indicators, or NULL. |
258 | | If the format string is invalid, false is returned and *invalid_reason is |
259 | | set to an error message explaining why. */ |
260 | | static bool |
261 | | parse_upto (struct spec *spec, |
262 | | const char **formatp, |
263 | | char terminator, bool translated, |
264 | | char *fdi, char **invalid_reason) |
265 | 0 | { |
266 | 0 | const char *format = *formatp; |
267 | 0 | const char *const format_start = format; |
268 | |
|
269 | 0 | spec->directives = 0; |
270 | 0 | spec->numbered_arg_count = 0; |
271 | 0 | spec->numbered = NULL; |
272 | 0 | size_t numbered_allocated = 0; |
273 | 0 | size_t unnumbered_arg_count = 0; |
274 | 0 | struct numbered_arg *unnumbered = NULL; |
275 | |
|
276 | 0 | for (; *format != '\0';) |
277 | | /* Invariant: spec->numbered_arg_count == 0 || unnumbered_arg_count == 0. */ |
278 | 0 | if (*format++ == '%') |
279 | 0 | { |
280 | | /* A directive. */ |
281 | 0 | FDI_SET (format - 1, FMTDIR_START); |
282 | 0 | spec->directives++; |
283 | |
|
284 | 0 | size_t number = 0; |
285 | 0 | if (translated && c_isdigit (*format)) |
286 | 0 | { |
287 | 0 | const char *f = format; |
288 | 0 | size_t m = 0; |
289 | |
|
290 | 0 | do |
291 | 0 | { |
292 | 0 | m = 10 * m + (*f - '0'); |
293 | 0 | f++; |
294 | 0 | } |
295 | 0 | while (c_isdigit (*f)); |
296 | |
|
297 | 0 | if (*f == '$') |
298 | 0 | { |
299 | 0 | if (m == 0) |
300 | 0 | { |
301 | 0 | *invalid_reason = INVALID_ARGNO_0 (spec->directives); |
302 | 0 | FDI_SET (f, FMTDIR_ERROR); |
303 | 0 | goto bad_format; |
304 | 0 | } |
305 | 0 | number = m; |
306 | 0 | format = ++f; |
307 | 0 | } |
308 | 0 | } |
309 | | |
310 | | /* Parse flags. */ |
311 | 0 | while (*format == ' ' || *format == '+' || *format == '-' |
312 | 0 | || *format == '#' || *format == '0') |
313 | 0 | format++; |
314 | | |
315 | | /* Parse width. */ |
316 | 0 | if (*format == '*') |
317 | 0 | { |
318 | 0 | format++; |
319 | |
|
320 | 0 | size_t width_number = 0; |
321 | 0 | if (translated && c_isdigit (*format)) |
322 | 0 | { |
323 | 0 | const char *f = format; |
324 | 0 | size_t m = 0; |
325 | |
|
326 | 0 | do |
327 | 0 | { |
328 | 0 | m = 10 * m + (*f - '0'); |
329 | 0 | f++; |
330 | 0 | } |
331 | 0 | while (c_isdigit (*f)); |
332 | |
|
333 | 0 | if (*f == '$') |
334 | 0 | { |
335 | 0 | if (m == 0) |
336 | 0 | { |
337 | 0 | *invalid_reason = |
338 | 0 | INVALID_WIDTH_ARGNO_0 (spec->directives); |
339 | 0 | FDI_SET (f, FMTDIR_ERROR); |
340 | 0 | goto bad_format; |
341 | 0 | } |
342 | 0 | width_number = m; |
343 | 0 | format = ++f; |
344 | 0 | } |
345 | 0 | } |
346 | | |
347 | 0 | if (width_number) |
348 | 0 | { |
349 | | /* Numbered argument. */ |
350 | | |
351 | | /* Numbered and unnumbered specifications are exclusive. */ |
352 | 0 | if (unnumbered_arg_count > 0) |
353 | 0 | { |
354 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
355 | 0 | FDI_SET (format - 1, FMTDIR_ERROR); |
356 | 0 | goto bad_format; |
357 | 0 | } |
358 | | |
359 | 0 | if (numbered_allocated == spec->numbered_arg_count) |
360 | 0 | { |
361 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
362 | 0 | spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, numbered_allocated * sizeof (struct numbered_arg)); |
363 | 0 | } |
364 | 0 | size_t numbered_index = spec->numbered_arg_count++; |
365 | 0 | spec->numbered[numbered_index].number = width_number; |
366 | 0 | spec->numbered[numbered_index].type = FAT_INTEGER; |
367 | 0 | } |
368 | 0 | else |
369 | 0 | { |
370 | | /* Unnumbered argument. */ |
371 | | |
372 | | /* Numbered and unnumbered specifications are exclusive. */ |
373 | 0 | if (spec->numbered_arg_count > 0) |
374 | 0 | { |
375 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
376 | 0 | FDI_SET (format - 1, FMTDIR_ERROR); |
377 | 0 | goto bad_format; |
378 | 0 | } |
379 | | |
380 | 0 | if (numbered_allocated == unnumbered_arg_count) |
381 | 0 | { |
382 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
383 | 0 | unnumbered = (struct numbered_arg *) xrealloc (unnumbered, numbered_allocated * sizeof (struct numbered_arg)); |
384 | 0 | } |
385 | 0 | size_t unnumbered_index = unnumbered_arg_count++; |
386 | 0 | unnumbered[unnumbered_index].number = unnumbered_index + 1; |
387 | 0 | unnumbered[unnumbered_index].type = FAT_INTEGER; |
388 | 0 | } |
389 | 0 | } |
390 | 0 | else if (c_isdigit (*format)) |
391 | 0 | { |
392 | 0 | do format++; while (c_isdigit (*format)); |
393 | 0 | } |
394 | | |
395 | | /* Parse precision. */ |
396 | 0 | if (*format == '.') |
397 | 0 | { |
398 | 0 | format++; |
399 | |
|
400 | 0 | if (*format == '*') |
401 | 0 | { |
402 | 0 | format++; |
403 | |
|
404 | 0 | size_t precision_number = 0; |
405 | 0 | if (translated && c_isdigit (*format)) |
406 | 0 | { |
407 | 0 | const char *f = format; |
408 | 0 | size_t m = 0; |
409 | |
|
410 | 0 | do |
411 | 0 | { |
412 | 0 | m = 10 * m + (*f - '0'); |
413 | 0 | f++; |
414 | 0 | } |
415 | 0 | while (c_isdigit (*f)); |
416 | |
|
417 | 0 | if (*f == '$') |
418 | 0 | { |
419 | 0 | if (m == 0) |
420 | 0 | { |
421 | 0 | *invalid_reason = |
422 | 0 | INVALID_PRECISION_ARGNO_0 (spec->directives); |
423 | 0 | FDI_SET (f, FMTDIR_ERROR); |
424 | 0 | goto bad_format; |
425 | 0 | } |
426 | 0 | precision_number = m; |
427 | 0 | format = ++f; |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | 0 | if (precision_number) |
432 | 0 | { |
433 | | /* Numbered argument. */ |
434 | | |
435 | | /* Numbered and unnumbered specifications are exclusive. */ |
436 | 0 | if (unnumbered_arg_count > 0) |
437 | 0 | { |
438 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
439 | 0 | FDI_SET (format - 1, FMTDIR_ERROR); |
440 | 0 | goto bad_format; |
441 | 0 | } |
442 | | |
443 | 0 | if (numbered_allocated == spec->numbered_arg_count) |
444 | 0 | { |
445 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
446 | 0 | spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, numbered_allocated * sizeof (struct numbered_arg)); |
447 | 0 | } |
448 | 0 | size_t numbered_index = spec->numbered_arg_count++; |
449 | 0 | spec->numbered[numbered_index].number = precision_number; |
450 | 0 | spec->numbered[numbered_index].type = FAT_INTEGER; |
451 | 0 | } |
452 | 0 | else |
453 | 0 | { |
454 | | /* Unnumbered argument. */ |
455 | | |
456 | | /* Numbered and unnumbered specifications are exclusive. */ |
457 | 0 | if (spec->numbered_arg_count > 0) |
458 | 0 | { |
459 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
460 | 0 | FDI_SET (format - 1, FMTDIR_ERROR); |
461 | 0 | goto bad_format; |
462 | 0 | } |
463 | | |
464 | 0 | if (numbered_allocated == unnumbered_arg_count) |
465 | 0 | { |
466 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
467 | 0 | unnumbered = (struct numbered_arg *) xrealloc (unnumbered, numbered_allocated * sizeof (struct numbered_arg)); |
468 | 0 | } |
469 | 0 | size_t unnumbered_index = unnumbered_arg_count++; |
470 | 0 | unnumbered[unnumbered_index].number = unnumbered_index + 1; |
471 | 0 | unnumbered[unnumbered_index].type = FAT_INTEGER; |
472 | 0 | } |
473 | 0 | } |
474 | 0 | else if (c_isdigit (*format)) |
475 | 0 | { |
476 | 0 | do format++; while (c_isdigit (*format)); |
477 | 0 | } |
478 | 0 | } |
479 | | |
480 | | /* Parse the specifier. */ |
481 | 0 | enum format_arg_type integer_type = FAT_INTEGER; |
482 | 0 | if (*format == 'l') |
483 | 0 | { |
484 | 0 | integer_type = FAT_INT32; |
485 | 0 | format++; |
486 | 0 | } |
487 | 0 | else if (*format == 'n') |
488 | 0 | { |
489 | 0 | integer_type = FAT_NATIVEINT; |
490 | 0 | format++; |
491 | 0 | } |
492 | 0 | else if (*format == 'L') |
493 | 0 | { |
494 | 0 | integer_type = FAT_INT64; |
495 | 0 | format++; |
496 | 0 | } |
497 | |
|
498 | 0 | format_arg_type_t type; |
499 | 0 | char *signature = NULL; |
500 | 0 | switch (*format) |
501 | 0 | { |
502 | 0 | case 'd': |
503 | 0 | case 'i': |
504 | 0 | case 'u': |
505 | 0 | case 'x': case 'X': |
506 | 0 | case 'o': |
507 | 0 | type = integer_type; |
508 | 0 | break; |
509 | 0 | default: |
510 | 0 | if (integer_type != FAT_INTEGER) |
511 | 0 | --format; |
512 | 0 | switch (*format) |
513 | 0 | { |
514 | 0 | case 's': |
515 | 0 | type = FAT_STRING; |
516 | 0 | break; |
517 | 0 | case 'S': |
518 | 0 | type = FAT_STRING | FAT_OCAML_SYNTAX; |
519 | 0 | break; |
520 | 0 | case 'c': |
521 | 0 | type = FAT_CHARACTER; |
522 | 0 | break; |
523 | 0 | case 'C': |
524 | 0 | type = FAT_CHARACTER | FAT_OCAML_SYNTAX; |
525 | 0 | break; |
526 | 0 | case 'f': |
527 | 0 | case 'e': case 'E': |
528 | 0 | case 'g': case 'G': |
529 | 0 | case 'h': case 'H': |
530 | 0 | type = FAT_FLOATINGPOINT; |
531 | 0 | break; |
532 | 0 | case 'F': |
533 | 0 | type = FAT_FLOATINGPOINT | FAT_OCAML_SYNTAX; |
534 | 0 | break; |
535 | 0 | case 'B': |
536 | 0 | type = FAT_BOOLEAN; |
537 | 0 | break; |
538 | 0 | case 'a': |
539 | 0 | type = FAT_FUNCTION_A; |
540 | 0 | break; |
541 | 0 | case 't': |
542 | 0 | type = FAT_FUNCTION_T; |
543 | 0 | break; |
544 | 0 | case '{': |
545 | 0 | { |
546 | 0 | struct spec sub_spec; |
547 | 0 | *formatp = format; |
548 | 0 | if (!parse_upto (&sub_spec, formatp, '}', false, |
549 | 0 | fdi, invalid_reason)) |
550 | 0 | { |
551 | 0 | FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, |
552 | 0 | FMTDIR_ERROR); |
553 | 0 | goto bad_format; |
554 | 0 | } |
555 | 0 | format = *formatp; |
556 | 0 | type = FAT_FORMAT_STRING; |
557 | 0 | signature = format_string_signature (&sub_spec); |
558 | 0 | destroy_spec (&sub_spec); |
559 | 0 | } |
560 | 0 | break; |
561 | 0 | case '}': |
562 | 0 | if (terminator != '}') |
563 | 0 | { |
564 | 0 | *invalid_reason = |
565 | 0 | xasprintf (_("Found '%s' without matching '%s'."), "%}", "%{"); |
566 | 0 | FDI_SET (format - 1, FMTDIR_ERROR); |
567 | 0 | goto bad_format; |
568 | 0 | } |
569 | 0 | spec->directives--; |
570 | 0 | goto done; |
571 | 0 | case '(': |
572 | 0 | { |
573 | 0 | struct spec sub_spec; |
574 | 0 | *formatp = format; |
575 | 0 | if (!parse_upto (&sub_spec, formatp, ')', false, |
576 | 0 | fdi, invalid_reason)) |
577 | 0 | { |
578 | 0 | FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, |
579 | 0 | FMTDIR_ERROR); |
580 | 0 | goto bad_format; |
581 | 0 | } |
582 | 0 | format = *formatp; |
583 | 0 | type = FAT_FORMAT_STRING; |
584 | 0 | signature = format_string_signature (&sub_spec); |
585 | |
|
586 | 0 | if (number) |
587 | 0 | { |
588 | | /* Numbered argument. */ |
589 | | |
590 | | /* Numbered and unnumbered specifications are exclusive. */ |
591 | 0 | if (unnumbered_arg_count > 0) |
592 | 0 | { |
593 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
594 | 0 | FDI_SET (format, FMTDIR_ERROR); |
595 | 0 | goto bad_format; |
596 | 0 | } |
597 | | |
598 | 0 | size_t new_numbered_arg_count = |
599 | 0 | spec->numbered_arg_count + 1 + sub_spec.numbered_arg_count; |
600 | 0 | if (numbered_allocated < new_numbered_arg_count) |
601 | 0 | { |
602 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
603 | 0 | if (numbered_allocated < new_numbered_arg_count) |
604 | 0 | numbered_allocated = new_numbered_arg_count; |
605 | 0 | spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, numbered_allocated * sizeof (struct numbered_arg)); |
606 | 0 | } |
607 | 0 | { |
608 | 0 | size_t numbered_index = spec->numbered_arg_count++; |
609 | 0 | spec->numbered[numbered_index].number = number; |
610 | 0 | spec->numbered[numbered_index].type = type; |
611 | 0 | spec->numbered[numbered_index].signature = signature; |
612 | 0 | } |
613 | 0 | for (size_t i = 0; i < sub_spec.numbered_arg_count; i++) |
614 | 0 | { |
615 | 0 | size_t numbered_index = spec->numbered_arg_count++; |
616 | 0 | spec->numbered[numbered_index].number = number + sub_spec.numbered[i].number; |
617 | 0 | spec->numbered[numbered_index].type = |
618 | 0 | type_without_translator_constraint (sub_spec.numbered[i].type); |
619 | 0 | if (sub_spec.numbered[i].type == FAT_FORMAT_STRING) |
620 | 0 | spec->numbered[numbered_index].signature = sub_spec.numbered[i].signature; |
621 | 0 | } |
622 | 0 | } |
623 | 0 | else |
624 | 0 | { |
625 | | /* Unnumbered argument. */ |
626 | | |
627 | | /* Numbered and unnumbered specifications are exclusive. */ |
628 | 0 | if (spec->numbered_arg_count > 0) |
629 | 0 | { |
630 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
631 | 0 | FDI_SET (format, FMTDIR_ERROR); |
632 | 0 | goto bad_format; |
633 | 0 | } |
634 | | |
635 | 0 | size_t new_unnumbered_arg_count = |
636 | 0 | unnumbered_arg_count + 1 + sub_spec.numbered_arg_count; |
637 | 0 | if (numbered_allocated < new_unnumbered_arg_count) |
638 | 0 | { |
639 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
640 | 0 | if (numbered_allocated < new_unnumbered_arg_count) |
641 | 0 | numbered_allocated = new_unnumbered_arg_count; |
642 | 0 | unnumbered = (struct numbered_arg *) xrealloc (unnumbered, numbered_allocated * sizeof (struct numbered_arg)); |
643 | 0 | } |
644 | 0 | { |
645 | 0 | size_t unnumbered_index = unnumbered_arg_count++; |
646 | 0 | unnumbered[unnumbered_index].number = unnumbered_index + 1; |
647 | 0 | unnumbered[unnumbered_index].type = type; |
648 | 0 | unnumbered[unnumbered_index].signature = signature; |
649 | 0 | } |
650 | 0 | for (size_t i = 0; i < sub_spec.numbered_arg_count; i++) |
651 | 0 | { |
652 | 0 | size_t unnumbered_index = unnumbered_arg_count++; |
653 | 0 | unnumbered[unnumbered_index].number = unnumbered_index + 1; |
654 | 0 | unnumbered[unnumbered_index].type = |
655 | 0 | type_without_translator_constraint (sub_spec.numbered[i].type); |
656 | 0 | if (sub_spec.numbered[i].type == FAT_FORMAT_STRING) |
657 | 0 | unnumbered[unnumbered_index].signature = sub_spec.numbered[i].signature; |
658 | 0 | } |
659 | 0 | } |
660 | | |
661 | 0 | free (sub_spec.numbered); |
662 | 0 | } |
663 | 0 | goto done_specifier; |
664 | 0 | case ')': |
665 | 0 | if (terminator != ')') |
666 | 0 | { |
667 | 0 | *invalid_reason = |
668 | 0 | xasprintf (_("Found '%s' without matching '%s'."), "%)", "%("); |
669 | 0 | FDI_SET (format - 1, FMTDIR_ERROR); |
670 | 0 | goto bad_format; |
671 | 0 | } |
672 | 0 | spec->directives--; |
673 | 0 | goto done; |
674 | 0 | case '!': |
675 | 0 | case '%': |
676 | 0 | case '@': |
677 | 0 | case ',': |
678 | 0 | type = FAT_NONE; |
679 | 0 | break; |
680 | 0 | default: |
681 | 0 | if (*format == '\0') |
682 | 0 | { |
683 | 0 | *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); |
684 | 0 | FDI_SET (format - 1, FMTDIR_ERROR); |
685 | 0 | } |
686 | 0 | else |
687 | 0 | { |
688 | 0 | *invalid_reason = |
689 | 0 | INVALID_CONVERSION_SPECIFIER (spec->directives, *format); |
690 | 0 | FDI_SET (format, FMTDIR_ERROR); |
691 | 0 | } |
692 | 0 | goto bad_format; |
693 | 0 | } |
694 | 0 | break; |
695 | 0 | } |
696 | | |
697 | 0 | if (type != FAT_NONE) |
698 | 0 | { |
699 | 0 | if (number) |
700 | 0 | { |
701 | | /* Numbered argument. */ |
702 | | |
703 | | /* Numbered and unnumbered specifications are exclusive. */ |
704 | 0 | if (unnumbered_arg_count > 0) |
705 | 0 | { |
706 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
707 | 0 | FDI_SET (format, FMTDIR_ERROR); |
708 | 0 | goto bad_format; |
709 | 0 | } |
710 | | |
711 | 0 | size_t new_numbered_arg_count = |
712 | 0 | spec->numbered_arg_count + 1 + (type == FAT_FUNCTION_T); |
713 | 0 | if (numbered_allocated < new_numbered_arg_count) |
714 | 0 | { |
715 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
716 | 0 | if (numbered_allocated < new_numbered_arg_count) |
717 | 0 | numbered_allocated = new_numbered_arg_count; |
718 | 0 | spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, numbered_allocated * sizeof (struct numbered_arg)); |
719 | 0 | } |
720 | 0 | { |
721 | 0 | size_t numbered_index = spec->numbered_arg_count++; |
722 | 0 | spec->numbered[numbered_index].number = number; |
723 | 0 | spec->numbered[numbered_index].type = type; |
724 | 0 | if (type == FAT_FORMAT_STRING) |
725 | 0 | spec->numbered[numbered_index].signature = signature; |
726 | 0 | } |
727 | 0 | if (type == FAT_FUNCTION_T) |
728 | 0 | { |
729 | 0 | size_t numbered_index = spec->numbered_arg_count++; |
730 | 0 | spec->numbered[numbered_index].number = number + 1; |
731 | 0 | spec->numbered[numbered_index].type = FAT_FUNCTION_T2; |
732 | 0 | } |
733 | 0 | } |
734 | 0 | else |
735 | 0 | { |
736 | | /* Unnumbered argument. */ |
737 | | |
738 | | /* Numbered and unnumbered specifications are exclusive. */ |
739 | 0 | if (spec->numbered_arg_count > 0) |
740 | 0 | { |
741 | 0 | *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); |
742 | 0 | FDI_SET (format, FMTDIR_ERROR); |
743 | 0 | goto bad_format; |
744 | 0 | } |
745 | | |
746 | 0 | size_t new_unnumbered_arg_count = |
747 | 0 | unnumbered_arg_count + 1 + (type == FAT_FUNCTION_T); |
748 | 0 | if (numbered_allocated < new_unnumbered_arg_count) |
749 | 0 | { |
750 | 0 | numbered_allocated = 2 * numbered_allocated + 1; |
751 | 0 | if (numbered_allocated < new_unnumbered_arg_count) |
752 | 0 | numbered_allocated = new_unnumbered_arg_count; |
753 | 0 | unnumbered = (struct numbered_arg *) xrealloc (unnumbered, numbered_allocated * sizeof (struct numbered_arg)); |
754 | 0 | } |
755 | 0 | { |
756 | 0 | size_t unnumbered_index = unnumbered_arg_count++; |
757 | 0 | unnumbered[unnumbered_index].number = unnumbered_index + 1; |
758 | 0 | unnumbered[unnumbered_index].type = type; |
759 | 0 | if (type == FAT_FORMAT_STRING) |
760 | 0 | unnumbered[unnumbered_index].signature = signature; |
761 | 0 | } |
762 | 0 | if (type == FAT_FUNCTION_T) |
763 | 0 | { |
764 | 0 | size_t unnumbered_index = unnumbered_arg_count++; |
765 | 0 | unnumbered[unnumbered_index].number = unnumbered_index + 1; |
766 | 0 | unnumbered[unnumbered_index].type = FAT_FUNCTION_T2; |
767 | 0 | } |
768 | 0 | } |
769 | 0 | } |
770 | | |
771 | 0 | done_specifier: |
772 | 0 | FDI_SET (format, FMTDIR_END); |
773 | |
|
774 | 0 | format++; |
775 | 0 | } |
776 | | |
777 | 0 | if (terminator != '\0') |
778 | 0 | { |
779 | 0 | *invalid_reason = xasprintf (_("Found '%%%c' without matching '%%%c'."), |
780 | 0 | terminator == '}' ? '{' : '(', terminator); |
781 | 0 | goto bad_format; |
782 | 0 | } |
783 | | |
784 | 0 | done: |
785 | | /* Convert the unnumbered argument array to numbered arguments. */ |
786 | 0 | if (unnumbered_arg_count > 0) |
787 | 0 | { |
788 | 0 | spec->numbered = unnumbered; |
789 | 0 | spec->numbered_arg_count = unnumbered_arg_count; |
790 | 0 | } |
791 | | /* Sort the numbered argument array, and eliminate duplicates. */ |
792 | 0 | else if (spec->numbered_arg_count > 1) |
793 | 0 | { |
794 | 0 | qsort (spec->numbered, spec->numbered_arg_count, |
795 | 0 | sizeof (struct numbered_arg), numbered_arg_compare); |
796 | | |
797 | | /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ |
798 | 0 | bool err = false; |
799 | 0 | size_t i, j; |
800 | 0 | for (i = j = 0; i < spec->numbered_arg_count; i++) |
801 | 0 | if (j > 0 && spec->numbered[i].number == spec->numbered[j-1].number) |
802 | 0 | { |
803 | 0 | format_arg_type_t type1 = spec->numbered[i].type; |
804 | 0 | format_arg_type_t type2 = spec->numbered[j-1].type; |
805 | |
|
806 | 0 | format_arg_type_t type_both; |
807 | 0 | if (((type1 == type2) |
808 | 0 | && (type1 != FAT_FORMAT_STRING |
809 | 0 | || streq (spec->numbered[i].signature, |
810 | 0 | spec->numbered[j-1].signature))) |
811 | 0 | || (((type1 | type2) & FAT_OPTIONAL_OCAML_SYNTAX) != 0 |
812 | 0 | && (((type1 & ~FAT_OPTIONAL_OCAML_SYNTAX) | FAT_OCAML_SYNTAX) |
813 | 0 | == ((type2 & ~FAT_OPTIONAL_OCAML_SYNTAX) | FAT_OCAML_SYNTAX)))) |
814 | 0 | type_both = (type1 | type2) & ~FAT_OPTIONAL_OCAML_SYNTAX; |
815 | 0 | else |
816 | 0 | { |
817 | | /* Incompatible types. */ |
818 | 0 | type_both = FAT_NONE; |
819 | 0 | if (!err) |
820 | 0 | *invalid_reason = |
821 | 0 | INVALID_INCOMPATIBLE_ARG_TYPES (spec->numbered[i].number); |
822 | 0 | err = true; |
823 | 0 | } |
824 | |
|
825 | 0 | spec->numbered[j-1].type = type_both; |
826 | 0 | if (type_both == FAT_FORMAT_STRING) |
827 | 0 | free (spec->numbered[i].signature); |
828 | 0 | } |
829 | 0 | else |
830 | 0 | { |
831 | 0 | if (j < i) |
832 | 0 | { |
833 | 0 | spec->numbered[j].number = spec->numbered[i].number; |
834 | 0 | spec->numbered[j].type = spec->numbered[i].type; |
835 | 0 | if (spec->numbered[j].type == FAT_FORMAT_STRING) |
836 | 0 | spec->numbered[j].signature = spec->numbered[i].signature; |
837 | 0 | } |
838 | 0 | j++; |
839 | 0 | } |
840 | 0 | spec->numbered_arg_count = j; |
841 | 0 | if (err) |
842 | | /* *invalid_reason has already been set above. */ |
843 | 0 | goto bad_format; |
844 | 0 | } |
845 | | |
846 | 0 | *formatp = format; |
847 | 0 | return true; |
848 | | |
849 | 0 | bad_format: |
850 | 0 | if (unnumbered != NULL) |
851 | 0 | free (unnumbered); |
852 | 0 | destroy_spec (spec); |
853 | 0 | return false; |
854 | 0 | } |
855 | | |
856 | | static void * |
857 | | format_parse (const char *format, bool translated, char *fdi, |
858 | | char **invalid_reason) |
859 | 0 | { |
860 | 0 | struct spec spec; |
861 | |
|
862 | 0 | if (!parse_upto (&spec, &format, '\0', translated, fdi, invalid_reason)) |
863 | 0 | return NULL; |
864 | | |
865 | 0 | struct spec *result = XMALLOC (struct spec); |
866 | 0 | *result = spec; |
867 | 0 | return result; |
868 | 0 | } |
869 | | |
870 | | static void |
871 | | format_free (void *descr) |
872 | 0 | { |
873 | 0 | struct spec *spec = (struct spec *) descr; |
874 | |
|
875 | 0 | destroy_spec (spec); |
876 | 0 | free (spec); |
877 | 0 | } |
878 | | |
879 | | static int |
880 | | format_get_number_of_directives (void *descr) |
881 | 0 | { |
882 | 0 | struct spec *spec = (struct spec *) descr; |
883 | |
|
884 | 0 | return spec->directives; |
885 | 0 | } |
886 | | |
887 | | static bool |
888 | | format_check (void *msgid_descr, void *msgstr_descr, bool equality, |
889 | | formatstring_error_logger_t error_logger, void *error_logger_data, |
890 | | const char *pretty_msgid, const char *pretty_msgstr) |
891 | 0 | { |
892 | 0 | struct spec *spec1 = (struct spec *) msgid_descr; |
893 | 0 | struct spec *spec2 = (struct spec *) msgstr_descr; |
894 | 0 | bool err = false; |
895 | |
|
896 | 0 | if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) |
897 | 0 | { |
898 | 0 | size_t n1 = spec1->numbered_arg_count; |
899 | 0 | size_t n2 = spec2->numbered_arg_count; |
900 | | |
901 | | /* Check that the argument numbers are the same. |
902 | | Both arrays are sorted. We search for the first difference. */ |
903 | 0 | { |
904 | 0 | size_t i, j; |
905 | 0 | for (i = 0, j = 0; i < n1 || j < n2; ) |
906 | 0 | { |
907 | 0 | int cmp = (i >= n1 ? 1 : |
908 | 0 | j >= n2 ? -1 : |
909 | 0 | spec1->numbered[i].number > spec2->numbered[j].number ? 1 : |
910 | 0 | spec1->numbered[i].number < spec2->numbered[j].number ? -1 : |
911 | 0 | 0); |
912 | |
|
913 | 0 | if (cmp > 0) |
914 | 0 | { |
915 | 0 | if (error_logger) |
916 | 0 | error_logger (error_logger_data, |
917 | 0 | _("a format specification for argument %zu, as in '%s', doesn't exist in '%s'"), |
918 | 0 | spec2->numbered[j].number, pretty_msgstr, |
919 | 0 | pretty_msgid); |
920 | 0 | err = true; |
921 | 0 | break; |
922 | 0 | } |
923 | 0 | else if (cmp < 0) |
924 | 0 | { |
925 | 0 | if (equality) |
926 | 0 | { |
927 | 0 | if (error_logger) |
928 | 0 | error_logger (error_logger_data, |
929 | 0 | _("a format specification for argument %zu doesn't exist in '%s'"), |
930 | 0 | spec1->numbered[i].number, pretty_msgstr); |
931 | 0 | err = true; |
932 | 0 | break; |
933 | 0 | } |
934 | 0 | else |
935 | 0 | i++; |
936 | 0 | } |
937 | 0 | else |
938 | 0 | j++, i++; |
939 | 0 | } |
940 | 0 | } |
941 | | /* Check that the argument types are essentially the same. */ |
942 | 0 | if (!err) |
943 | 0 | { |
944 | 0 | size_t i, j; |
945 | 0 | for (i = 0, j = 0; j < n2; ) |
946 | 0 | { |
947 | 0 | if (spec1->numbered[i].number == spec2->numbered[j].number) |
948 | 0 | { |
949 | 0 | format_arg_type_t type1 = spec1->numbered[i].type; |
950 | 0 | format_arg_type_t type2 = spec2->numbered[j].type; |
951 | |
|
952 | 0 | if (!(((type1 == type2) |
953 | 0 | && (type1 != FAT_FORMAT_STRING |
954 | 0 | || streq (spec1->numbered[i].signature, |
955 | 0 | spec2->numbered[j].signature))) |
956 | 0 | || ((type2 & FAT_OPTIONAL_OCAML_SYNTAX) != 0 |
957 | 0 | && (type2 & ~FAT_OPTIONAL_OCAML_SYNTAX) |
958 | 0 | == (type1 & ~FAT_OCAML_SYNTAX)))) |
959 | 0 | { |
960 | 0 | if (error_logger) |
961 | 0 | error_logger (error_logger_data, |
962 | 0 | _("format specifications in '%s' and '%s' for argument %zu are not the same"), |
963 | 0 | pretty_msgid, pretty_msgstr, |
964 | 0 | spec2->numbered[j].number); |
965 | 0 | err = true; |
966 | 0 | break; |
967 | 0 | } |
968 | 0 | j++, i++; |
969 | 0 | } |
970 | 0 | else |
971 | 0 | i++; |
972 | 0 | } |
973 | 0 | } |
974 | 0 | } |
975 | |
|
976 | 0 | return err; |
977 | 0 | } |
978 | | |
979 | | |
980 | | struct formatstring_parser formatstring_ocaml = |
981 | | { |
982 | | format_parse, |
983 | | format_free, |
984 | | format_get_number_of_directives, |
985 | | NULL, |
986 | | format_check |
987 | | }; |
988 | | |
989 | | |
990 | | #ifdef TEST |
991 | | |
992 | | /* Test program: Print the argument list specification returned by |
993 | | format_parse for strings read from standard input. */ |
994 | | |
995 | | #include <stdio.h> |
996 | | |
997 | | static void |
998 | | format_print (void *descr) |
999 | | { |
1000 | | struct spec *spec = (struct spec *) descr; |
1001 | | |
1002 | | if (spec == NULL) |
1003 | | { |
1004 | | printf ("INVALID"); |
1005 | | return; |
1006 | | } |
1007 | | |
1008 | | printf ("("); |
1009 | | size_t last = 1; |
1010 | | for (size_t i = 0; i < spec->numbered_arg_count; i++) |
1011 | | { |
1012 | | size_t number = spec->numbered[i].number; |
1013 | | |
1014 | | if (i > 0) |
1015 | | printf (" "); |
1016 | | if (number < last) |
1017 | | abort (); |
1018 | | for (; last < number; last++) |
1019 | | printf ("_ "); |
1020 | | switch (spec->numbered[i].type & FAT_BASIC_MASK) |
1021 | | { |
1022 | | case FAT_INTEGER: |
1023 | | printf ("i"); |
1024 | | break; |
1025 | | case FAT_INT32: |
1026 | | printf ("l"); |
1027 | | break; |
1028 | | case FAT_NATIVEINT: |
1029 | | printf ("n"); |
1030 | | break; |
1031 | | case FAT_INT64: |
1032 | | printf ("L"); |
1033 | | break; |
1034 | | case FAT_STRING: |
1035 | | printf ("s"); |
1036 | | break; |
1037 | | case FAT_CHARACTER: |
1038 | | printf ("c"); |
1039 | | break; |
1040 | | case FAT_FLOATINGPOINT: |
1041 | | printf ("f"); |
1042 | | break; |
1043 | | case FAT_BOOLEAN: |
1044 | | printf ("B"); |
1045 | | break; |
1046 | | case FAT_FUNCTION_A: |
1047 | | printf ("a"); |
1048 | | break; |
1049 | | case FAT_FUNCTION_T: |
1050 | | printf ("t1"); |
1051 | | break; |
1052 | | case FAT_FUNCTION_T2: |
1053 | | printf ("t2"); |
1054 | | break; |
1055 | | case FAT_FORMAT_STRING: |
1056 | | printf ("\"%s\"", spec->numbered[i].signature); |
1057 | | break; |
1058 | | default: |
1059 | | abort (); |
1060 | | } |
1061 | | if (spec->numbered[i].type & FAT_OCAML_SYNTAX) |
1062 | | printf ("!"); |
1063 | | if (spec->numbered[i].type & FAT_OPTIONAL_OCAML_SYNTAX) |
1064 | | printf ("?"); |
1065 | | last = number + 1; |
1066 | | } |
1067 | | printf (")"); |
1068 | | } |
1069 | | |
1070 | | int |
1071 | | main () |
1072 | | { |
1073 | | for (;;) |
1074 | | { |
1075 | | char *line = NULL; |
1076 | | size_t line_size = 0; |
1077 | | int line_len = getline (&line, &line_size, stdin); |
1078 | | if (line_len < 0) |
1079 | | break; |
1080 | | if (line_len > 0 && line[line_len - 1] == '\n') |
1081 | | line[--line_len] = '\0'; |
1082 | | |
1083 | | char *invalid_reason = NULL; |
1084 | | void *descr = format_parse (line, true, NULL, &invalid_reason); |
1085 | | |
1086 | | format_print (descr); |
1087 | | printf ("\n"); |
1088 | | if (descr == NULL) |
1089 | | printf ("%s\n", invalid_reason); |
1090 | | |
1091 | | free (invalid_reason); |
1092 | | free (line); |
1093 | | } |
1094 | | |
1095 | | return 0; |
1096 | | } |
1097 | | |
1098 | | /* |
1099 | | * For Emacs M-x compile |
1100 | | * Local Variables: |
1101 | | * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../../gettext-runtime/intl -DTEST format-ocaml.c ../gnulib-lib/libgettextlib.la" |
1102 | | * End: |
1103 | | */ |
1104 | | |
1105 | | #endif /* TEST */ |