Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext/gettext-tools/src/format-lua.c
Line
Count
Source
1
/* Lua format strings.
2
   Copyright (C) 2012-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 Ľubomír Remák and Bruno Haible.  */
18
19
#include <config.h>
20
21
#include <stdbool.h>
22
#include <stdlib.h>
23
24
#include "format.h"
25
#include "attribute.h"
26
#include "gettext.h"
27
#include "xalloc.h"
28
#include "format-invalid.h"
29
#include "c-ctype.h"
30
#include "xvasprintf.h"
31
32
0
#define _(str) gettext (str)
33
34
/* The Lua format strings are described in the Lua manual,
35
   which can be found at:
36
   https://www.lua.org/manual/5.2/manual.html
37
   They are implemented in lua-5.2.4/src/lstrlib.c.
38
39
   A directive
40
   - starts with '%',
41
   - is optionally followed by any of the characters '0', '-', ' ', or
42
     each of which acts as a flag,
43
   - is optionally followed by a width specification: a nonempty digit
44
     sequence with at most 2 digits,
45
   - is optionally followed by '.' and a precision specification: an optional
46
     nonempty digit sequence with at most 2 digits,
47
   - is finished by a specifier
48
       - 's', 'q', that need a string argument,
49
       - 'd', 'i', 'o', 'u', 'X', 'x', that need an integer argument,
50
       - 'A', 'a', 'E', 'e', 'f', 'G', 'g', that need a floating-point argument,
51
       - 'c', that needs a character argument.
52
   Additionally there is the directive '%%', which takes no argument.
53
54
   Note: Lua does not distinguish between integer, floating-point
55
   and character arguments, since it has a number data type only.
56
   However, we should not allow users to use %d instead of %c.
57
   The same applies to %s and %q - we should not allow intermixing them.
58
 */
59
60
enum format_arg_type
61
{
62
  FAT_INTEGER,
63
  FAT_CHARACTER,
64
  FAT_FLOAT,
65
  FAT_STRING,
66
  FAT_ESCAPED_STRING
67
};
68
69
struct spec
70
{
71
  size_t directives;
72
  size_t format_args_count;
73
  enum format_arg_type *format_args
74
    COUNTED_BY (format_args_count);
75
};
76
77
static void *
78
format_parse (const char *format, bool translated, char *fdi,
79
              char **invalid_reason)
80
0
{
81
82
0
  const char *format_start = format;
83
0
  const char *fatstr = format;
84
85
0
  struct spec spec;
86
0
  spec.directives = 0;
87
0
  spec.format_args_count = 0;
88
0
  spec.format_args = NULL;
89
0
  size_t format_args_allocated = 0;
90
91
0
  for (; *fatstr != '\0';)
92
0
    {
93
0
      if (*fatstr++ == '%')
94
0
        {
95
0
          FDI_SET (fatstr - 1, FMTDIR_START);
96
0
          spec.directives++;
97
98
0
          if (*fatstr != '%')
99
0
            {
100
              /* Parse width. */
101
0
              if (c_isdigit (*fatstr))
102
0
                {
103
0
                  fatstr++;
104
0
                  if (c_isdigit (*fatstr))
105
0
                    fatstr++;
106
0
                }
107
108
0
              if (*fatstr == '.')
109
0
                {
110
0
                  fatstr++;
111
112
                  /* Parse precision. */
113
0
                  if (c_isdigit (*fatstr))
114
0
                    {
115
0
                      fatstr++;
116
0
                      if (c_isdigit (*fatstr))
117
0
                        fatstr++;
118
0
                    }
119
0
                }
120
121
0
              enum format_arg_type type;
122
0
              switch (*fatstr)
123
0
                {
124
0
                case 'c':
125
0
                  type = FAT_CHARACTER;
126
0
                  break;
127
0
                case 'd':
128
0
                case 'i':
129
0
                case 'o':
130
0
                case 'u':
131
0
                case 'X':
132
0
                case 'x':
133
0
                  type = FAT_INTEGER;
134
0
                  break;
135
0
                case 'a':
136
0
                case 'A':
137
0
                case 'E':
138
0
                case 'e':
139
0
                case 'f':
140
0
                case 'g':
141
0
                case 'G':
142
0
                  type = FAT_FLOAT;
143
0
                  break;
144
0
                case 's':
145
0
                  type = FAT_STRING;
146
0
                  break;
147
0
                case 'q':
148
0
                  type = FAT_ESCAPED_STRING;
149
0
                  break;
150
0
                default:
151
0
                  if (*fatstr == '\0')
152
0
                    {
153
0
                      *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
154
0
                      FDI_SET (fatstr - 1, FMTDIR_ERROR);
155
0
                    }
156
0
                  else
157
0
                    {
158
0
                      *invalid_reason =
159
0
                        INVALID_CONVERSION_SPECIFIER (spec.format_args_count + 1,
160
0
                                                      *fatstr);
161
0
                      FDI_SET (fatstr, FMTDIR_ERROR);
162
0
                    }
163
0
                  goto bad_format;
164
0
                }
165
166
0
              if (spec.format_args_count == format_args_allocated)
167
0
                {
168
0
                  format_args_allocated = 2 * format_args_allocated + 10;
169
0
                  spec.format_args =
170
0
                    xrealloc (spec.format_args,
171
0
                              format_args_allocated *
172
0
                              sizeof (enum format_arg_type));
173
0
                }
174
0
              size_t format_args_index = spec.format_args_count++;
175
0
              spec.format_args[format_args_index] = type;
176
0
            }
177
0
          FDI_SET (fatstr, FMTDIR_END);
178
0
          fatstr++;
179
0
        }
180
0
    }
181
182
0
  struct spec *result = XMALLOC (struct spec);
183
0
  *result = spec;
184
0
  return result;
185
186
0
 bad_format:
187
0
  if (spec.format_args != NULL)
188
0
    free (spec.format_args);
189
0
  return NULL;
190
0
}
191
192
static void
193
format_free (void *descr)
194
0
{
195
0
  struct spec *spec = (struct spec *) descr;
196
197
0
  if (spec->format_args != NULL)
198
0
    free (spec->format_args);
199
0
  free (spec);
200
0
}
201
202
static int
203
format_get_number_of_directives (void *descr)
204
0
{
205
0
  struct spec *spec = (struct spec *) descr;
206
207
0
  return spec->directives;
208
0
}
209
210
static bool
211
format_check (void *msgid_descr, void *msgstr_descr, bool equality,
212
              formatstring_error_logger_t error_logger, void *error_logger_data,
213
              const char *pretty_msgid, const char *pretty_msgstr)
214
0
{
215
0
  struct spec *spec1 = (struct spec *) msgid_descr;
216
0
  struct spec *spec2 = (struct spec *) msgstr_descr;
217
0
  bool err = false;
218
219
0
  if (spec1->format_args_count + spec2->format_args_count > 0)
220
0
    {
221
0
      size_t n1 = spec1->format_args_count;
222
0
      size_t n2 = spec2->format_args_count;
223
224
      /* Check that the argument counts are the same.  */
225
0
      if (n1 < n2)
226
0
        {
227
0
          if (error_logger)
228
0
            error_logger (error_logger_data,
229
0
                          _("a format specification for argument %zu, as in '%s', doesn't exist in '%s'"),
230
0
                          n1 + 1, pretty_msgstr, pretty_msgid);
231
0
          err = true;
232
0
        }
233
0
      else if (n1 > n2 && equality)
234
0
        {
235
0
          if (error_logger)
236
0
            error_logger (error_logger_data,
237
0
                          _("a format specification for argument %zu doesn't exist in '%s'"),
238
0
                          n2 + 1, pretty_msgstr);
239
0
          err = true;
240
0
        }
241
0
      else
242
0
        {
243
          /* Check that the argument types are the same.  */
244
0
          if (!err)
245
0
            for (size_t i = 0; i < n2; i++)
246
0
              {
247
0
                if (spec1->format_args[i] != spec2->format_args[i])
248
0
                  {
249
0
                    if (error_logger)
250
0
                      error_logger (error_logger_data,
251
0
                                    _("format specifications in '%s' and '%s' for argument %zu are not the same"),
252
0
                                    pretty_msgid, pretty_msgstr, i + 1);
253
0
                    err = true;
254
0
                    break;
255
0
                  }
256
0
              }
257
0
        }
258
0
    }
259
260
0
  return err;
261
0
}
262
263
struct formatstring_parser formatstring_lua =
264
{
265
  format_parse,
266
  format_free,
267
  format_get_number_of_directives,
268
  NULL,
269
  format_check
270
};
271
272
273
#ifdef TEST
274
275
/* Test program: Print the argument list specification returned by
276
   format_parse for strings read from standard input.  */
277
278
#include <stdio.h>
279
280
static void
281
format_print (void *descr)
282
{
283
  struct spec *spec = (struct spec *) descr;
284
285
  if (spec == NULL)
286
    {
287
      printf ("INVALID");
288
      return;
289
    }
290
291
  printf ("(");
292
  for (size_t i = 0; i < spec->format_args_count; i++)
293
    {
294
      if (i > 0)
295
        printf (" ");
296
      switch (spec->format_args[i])
297
        {
298
        case FAT_INTEGER:
299
          printf ("i");
300
          break;
301
        case FAT_FLOAT:
302
          printf ("f");
303
          break;
304
        case FAT_CHARACTER:
305
          printf ("c");
306
          break;
307
        case FAT_STRING:
308
          printf ("s");
309
          break;
310
        case FAT_ESCAPED_STRING:
311
          printf ("q");
312
          break;
313
        default:
314
          abort ();
315
        }
316
    }
317
  printf (")");
318
}
319
320
int
321
main ()
322
{
323
  for (;;)
324
    {
325
      char *line = NULL;
326
      size_t line_size = 0;
327
      int line_len = getline (&line, &line_size, stdin);
328
      if (line_len < 0)
329
        break;
330
      if (line_len > 0 && line[line_len - 1] == '\n')
331
        line[--line_len] = '\0';
332
333
      char *invalid_reason = NULL;
334
      void *descr = format_parse (line, false, NULL, &invalid_reason);
335
336
      format_print (descr);
337
      printf ("\n");
338
      if (descr == NULL)
339
        printf ("%s\n", invalid_reason);
340
341
      free (invalid_reason);
342
      free (line);
343
    }
344
345
  return 0;
346
}
347
348
/*
349
 * For Emacs M-x compile
350
 * Local Variables:
351
 * 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-lua.c ../gnulib-lib/libgettextlib.la"
352
 * End:
353
 */
354
355
#endif /* TEST */