]>
git.ipfire.org Git - thirdparty/glibc.git/blob - catgets/gencat.c
1 /* Copyright (C) 1996 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper, <drepper@gnu.ai.mit.edu>.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
15 You should have received a copy of the GNU Library General Public
16 License along with the GNU C Library; see the file COPYING.LIB. If
17 not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA. */
42 #include "catgetsinfo.h"
46 (((w) << 24) | (((w) & 0xff00) << 8) | (((w) >> 8) & 0xff00) | ((w) >> 24))
57 struct message_list
*next
;
65 struct message_list
*messages
;
72 struct set_list
*next
;
78 struct set_list
*all_sets
;
79 struct set_list
*current_set
;
80 size_t total_messages
;
84 struct obstack mem_pool
;
88 /* If non-zero force creation of new file, not using existing one. */
92 static const struct option long_options
[] =
94 { "header", required_argument
, NULL
, 'H' },
95 { "help", no_argument
, NULL
, 'h' },
96 { "new", no_argument
, &force_new
, 1 },
97 { "output", required_argument
, NULL
, 'o' },
98 { "version", no_argument
, NULL
, 'V' },
102 /* Wrapper functions with error checking for standard functions. */
103 extern void *xmalloc (size_t n
);
105 /* Prototypes for local functions. */
106 static void usage (int status
) __attribute__ ((noreturn
));
107 static void error_print (void);
108 static struct catalog
*read_input_file (struct catalog
*current
,
110 static void write_out (struct catalog
*result
, const char *output_name
,
111 const char *header_name
);
112 static struct set_list
*find_set (struct catalog
*current
, int number
);
113 static void normalize_line (const char *fname
, size_t line
, char *string
,
115 static void read_old (struct catalog
*catalog
, const char *file_name
);
119 main (int argc
, char *argv
[])
121 struct catalog
*result
;
122 const char *output_name
;
123 const char *header_name
;
128 /* Set program name for messages. */
129 error_print_progname
= error_print
;
131 /* Set locale via LC_ALL. */
132 setlocale (LC_ALL
, "");
134 /* Set the text message domain. */
135 textdomain (PACKAGE
);
137 /* Initialize local variables. */
144 while ((opt
= getopt_long (argc
, argv
, "hH:o:V", long_options
, NULL
)) != EOF
)
147 case '\0': /* Long option. */
153 header_name
= optarg
;
156 output_name
= optarg
;
162 usage (EXIT_FAILURE
);
165 /* Version information is requested. */
168 fprintf (stderr
, "%s - GNU %s %s\n", program_invocation_name
,
173 /* Help is requested. */
175 usage (EXIT_SUCCESS
);
177 /* Determine output file. */
178 if (output_name
== NULL
)
179 output_name
= optind
< argc
? argv
[optind
++] : "-";
181 /* Process all input files. */
182 setlocale (LC_CTYPE
, "C");
185 result
= read_input_file (result
, argv
[optind
]);
186 while (++optind
< argc
);
188 result
= read_input_file (NULL
, "-");
190 /* Write out the result. */
192 write_out (result
, output_name
, header_name
);
201 if (status
!= EXIT_SUCCESS
)
202 fprintf (stderr
, gettext ("Try `%s --help' for more information.\n"),
203 program_invocation_name
);
206 Usage: %s [OPTION]... -o OUTPUT-FILE [INPUT-FILE]...\n\
207 %s [OPTION]... [OUTPUT-FILE [INPUT-FILE]...]\n\
208 Mandatory arguments to long options are mandatory for short options too.\n\
209 -H, --header create C header file containing symbol definitions\n\
210 -h, --help display this help and exit\n\
211 --new do not use existing catalog, force new output file\n\
212 -o, --output=NAME write output to file NAME\n\
213 -V, --version output version information and exit\n\
214 If INPUT-FILE is -, input is read from standard input. If OUTPUT-FILE\n\
215 is -, output is written to standard output.\n"),
216 program_invocation_name
, program_invocation_name
);
222 /* The address of this function will be assigned to the hook in the
227 /* We don't want the program name to be printed in messages. Emacs'
228 compile.el does not like this. */
232 static struct catalog
*
233 read_input_file (struct catalog
*current
, const char *fname
)
240 if (strcmp (fname
, "-") == 0 || strcmp (fname
, "/dev/stdin") == 0)
243 fname
= gettext ("*standard input*");
246 fp
= fopen (fname
, "r");
249 error (0, errno
, gettext ("cannot open input file `%s'"), fname
);
253 /* If we haven't seen anything yet, allocate result structure. */
256 current
= (struct catalog
*) xmalloc (sizeof (*current
));
258 current
->all_sets
= NULL
;
259 current
->total_messages
= 0;
260 current
->last_set
= 0;
261 current
->current_set
= find_set (current
, NL_SETD
);
263 #define obstack_chunk_alloc xmalloc
264 #define obstack_chunk_free free
265 obstack_init (¤t
->mem_pool
);
275 size_t start_line
= line_number
+ 1;
282 act_len
= getline (&buf
, &len
, fp
);
287 /* It the line continued? */
288 if (buf
[act_len
- 1] == '\n')
291 continued
= buf
[act_len
- 1] == '\\';
298 /* Append to currently selected line. */
299 obstack_grow (¤t
->mem_pool
, buf
, act_len
);
303 obstack_1grow (¤t
->mem_pool
, '\0');
304 this_line
= (char *) obstack_finish (¤t
->mem_pool
);
307 if (this_line
[0] == '$')
309 if (isspace (this_line
[1]))
310 /* This is a comment line. Do nothing. */;
311 else if (strncmp (&this_line
[1], "set", 3) == 0)
313 int cnt
= sizeof ("cnt");
315 const char *symbol
= NULL
;
316 while (isspace (this_line
[cnt
]))
319 if (isdigit (this_line
[cnt
]))
321 set_number
= atol (&this_line
[cnt
]);
323 /* If the given number for the character set is
324 higher than any we used for symbolic set names
325 avoid clashing by using only higher numbers for
326 the following symbolic definitions. */
327 if (set_number
> current
->last_set
)
328 current
->last_set
= set_number
;
332 /* See whether it is a reasonable identifier. */
334 while (isalnum (this_line
[cnt
]) || this_line
[cnt
] == '_')
339 /* No correct character found. */
340 error_at_line (0, 0, fname
, start_line
,
341 gettext ("illegal set number"));
346 /* We have found seomthing which looks like a
347 correct identifier. */
348 struct set_list
*runp
;
350 this_line
[cnt
] = '\0';
352 symbol
= &this_line
[start
];
354 /* Test whether the identifier was already used. */
355 runp
= current
->all_sets
;
357 if (runp
->symbol
!= NULL
358 && strcmp (runp
->symbol
, symbol
) == 0)
365 /* We cannot allow duplicate identifiers for
367 error_at_line (0, 0, fname
, start_line
,
368 gettext ("duplicate set definition"));
369 error_at_line (0, 0, runp
->fname
, runp
->line
,
371 this is the first definition"));
375 /* Allocate next free message set for identifier. */
376 set_number
= ++current
->last_set
;
382 /* We found a legal set number. */
383 current
->current_set
= find_set (current
, set_number
);
386 current
->current_set
->symbol
= symbol
;
387 current
->current_set
->fname
= fname
;
388 current
->current_set
->line
= start_line
;
391 else if (strncmp (&this_line
[1], "delset", 6) == 0)
393 int cnt
= sizeof ("delset");
395 while (isspace (this_line
[cnt
]))
398 if (isdigit (this_line
[cnt
]))
400 size_t set_number
= atol (&this_line
[cnt
]);
401 struct set_list
*set
;
403 /* Mark the message set with the given number as
405 set
= find_set (current
, set_number
);
410 /* See whether it is a reasonable identifier. */
412 while (isalnum (this_line
[cnt
]) || this_line
[cnt
] == '_')
417 error_at_line (0, 0, fname
, start_line
,
418 gettext ("illegal set number"));
424 struct set_list
*runp
;
426 this_line
[cnt
] = '\0';
428 symbol
= &this_line
[start
];
430 /* We have a symbolic set name. This name must
431 appear somewhere else in the catalogs read so
434 for (runp
= current
->all_sets
; runp
!= NULL
;
437 if (strcmp (runp
->symbol
, symbol
) == 0)
444 /* Name does not exist before. */
445 error_at_line (0, 0, fname
, start_line
,
446 gettext ("unknown set `%s'"), symbol
);
450 else if (strncmp (&this_line
[1], "quote", 5) == 0)
452 int cnt
= sizeof ("quote");
453 while (isspace (this_line
[cnt
]))
455 /* Yes, the quote char can be '\0'; this means no quote
457 current
->quote_char
= this_line
[cnt
];
463 while (this_line
[cnt
] != '\0' && !isspace (this_line
[cnt
]))
465 this_line
[cnt
] = '\0';
466 error_at_line (0, 0, fname
, start_line
,
467 gettext ("unknown directive `%s': line ignored"),
471 else if (isalnum (this_line
[0]) || this_line
[0] == '_')
473 const char *ident
= this_line
;
478 while (this_line
[0] != '\0' && !isspace (this_line
[0]));;
479 this_line
[0] = '\0'; /* Terminate the identifier. */
483 while (isspace (this_line
[0]));
484 /* Now we found the beginning of the message itself. */
486 if (isdigit (ident
[0]))
488 struct message_list
*runp
;
490 message_number
= atoi (ident
);
492 /* Find location to insert the new message. */
493 runp
= current
->current_set
->messages
;
495 if (runp
->number
== message_number
)
501 /* Oh, oh. There is already a message with this
502 number is the message set. */
503 error_at_line (0, 0, fname
, start_line
,
504 gettext ("duplicated message number"));
505 error_at_line (0, 0, runp
->fname
, runp
->line
,
506 gettext ("this is the first definition"));
509 ident
= NULL
; /* We don't have a symbol. */
511 if (message_number
!= 0
512 && message_number
> current
->current_set
->last_message
)
513 current
->current_set
->last_message
= message_number
;
515 else if (ident
[0] != '\0')
517 struct message_list
*runp
;
518 runp
= current
->current_set
->messages
;
520 /* Test whether the symbolic name was not used for
521 another message in this message set. */
523 if (runp
->symbol
!= NULL
&& strcmp (ident
, runp
->symbol
) == 0)
529 /* The name is already used. */
530 error_at_line (0, 0, fname
, start_line
,
531 gettext ("duplicated message identifier"));
532 error_at_line (0, 0, runp
->fname
, runp
->line
,
533 gettext ("this is the first definition"));
537 /* Give the message the next unused number. */
538 message_number
= ++current
->current_set
->last_message
;
543 if (message_number
!= 0)
545 struct message_list
*newp
;
547 used
= 1; /* Yes, we use the line. */
549 /* Strip quote characters, change escape sequences into
550 correct characters etc. */
551 normalize_line (fname
, start_line
, this_line
,
552 current
->quote_char
);
554 newp
= (struct message_list
*) xmalloc (sizeof (*newp
));
555 newp
->number
= message_number
;
556 newp
->message
= this_line
;
557 /* Remember symbolic name; is NULL if no is given. */
558 newp
->symbol
= ident
;
559 /* Remember where we found the character. */
561 newp
->line
= start_line
;
563 /* Find place to insert to message. We keep them in a
564 sorted single linked list. */
565 if (current
->current_set
->messages
== NULL
566 || current
->current_set
->messages
->number
> message_number
)
568 newp
->next
= current
->current_set
->messages
;
569 current
->current_set
->messages
= newp
;
573 struct message_list
*runp
;
574 runp
= current
->current_set
->messages
;
575 while (runp
->next
!= NULL
)
576 if (runp
->next
->number
> message_number
)
580 newp
->next
= runp
->next
;
584 ++current
->total_messages
;
591 /* See whether we have any non-white space character in this
593 while (this_line
[cnt
] != '\0' && isspace (this_line
[cnt
]))
596 if (this_line
[cnt
] != '\0')
597 /* Yes, some unknown characters found. */
598 error_at_line (0, 0, fname
, start_line
,
599 gettext ("malformed line ignored"));
602 /* We can save the memory for the line if it was not used. */
604 obstack_free (¤t
->mem_pool
, this_line
);
614 write_out (struct catalog
*catalog
, const char *output_name
,
615 const char *header_name
)
617 /* Computing the "optimal" size. */
618 struct set_list
*set_run
;
619 size_t best_total
, best_size
, best_depth
;
620 size_t act_size
, act_depth
;
621 struct catalog_obj obj
;
622 struct obstack string_pool
;
625 u_int32_t
*array1
, *array2
;
629 /* If not otherwise told try to read file with existing
632 read_old (catalog
, output_name
);
634 /* Initialize best_size with a very high value. */
635 best_total
= best_size
= best_depth
= UINT_MAX
;
637 /* We need some start size for testing. Let's start with
638 TOTAL_MESSAGES / 5, which theoretically provides a mean depth of
640 act_size
= 1 + catalog
->total_messages
/ 5;
642 /* We determine the size of a hash table here. Because the message
643 numbers can be chosen arbitrary by the programmer we cannot use
644 the simple method of accessing the array using the message
645 number. The algorithm is based on the trivial hash function
646 NUMBER % TABLE_SIZE, where collisions are stored in a second
647 dimension up to TABLE_DEPTH. We here compute TABLE_SIZE so that
648 the needed space (= TABLE_SIZE * TABLE_DEPTH) is minimal. */
649 while (act_size
<= best_total
)
651 size_t deep
[act_size
];
654 memset (deep
, '\0', act_size
* sizeof (size_t));
655 set_run
= catalog
->all_sets
;
656 while (set_run
!= NULL
)
658 struct message_list
*message_run
;
660 message_run
= set_run
->messages
;
661 while (message_run
!= NULL
)
663 size_t idx
= (message_run
->number
* set_run
->number
) % act_size
;
666 if (deep
[idx
] > act_depth
)
668 act_depth
= deep
[idx
];
669 if (act_depth
* act_size
> best_total
)
672 message_run
= message_run
->next
;
674 set_run
= set_run
->next
;
677 if (act_depth
* act_size
<= best_total
)
679 /* We have found a better solution. */
680 best_total
= act_depth
* act_size
;
681 best_size
= act_size
;
682 best_depth
= act_depth
;
688 /* let's be prepared for an empty message file. */
689 if (best_size
== UINT_MAX
)
695 /* OK, now we have the size we will use. Fill in the header, build
696 the table and the second one with swapped byte order. */
697 obj
.magic
= CATGETS_MAGIC
;
698 obj
.plane_size
= best_size
;
699 obj
.plane_depth
= best_depth
;
701 /* Allocate room for all needed arrays. */
703 (u_int32_t
*) alloca (best_size
* best_depth
* sizeof (u_int32_t
) * 3);
704 memset (array1
, '\0', best_size
* best_depth
* sizeof (u_int32_t
) * 3);
706 = (u_int32_t
*) alloca (best_size
* best_depth
* sizeof (u_int32_t
) * 3);
707 obstack_init (&string_pool
);
709 set_run
= catalog
->all_sets
;
710 while (set_run
!= NULL
)
712 struct message_list
*message_run
;
714 message_run
= set_run
->messages
;
715 while (message_run
!= NULL
)
717 size_t idx
= (((message_run
->number
* set_run
->number
) % best_size
)
719 /* Determine collision depth. */
720 while (array1
[idx
] != 0)
721 idx
+= best_size
* 3;
723 /* Store set number, message number and pointer into string
724 space, relative to the first string. */
725 array1
[idx
+ 0] = set_run
->number
;
726 array1
[idx
+ 1] = message_run
->number
;
727 array1
[idx
+ 2] = obstack_object_size (&string_pool
);
729 /* Add current string to the continuous space containing all
731 obstack_grow0 (&string_pool
, message_run
->message
,
732 strlen (message_run
->message
));
734 message_run
= message_run
->next
;
737 set_run
= set_run
->next
;
739 strings_size
= obstack_object_size (&string_pool
);
740 strings
= obstack_finish (&string_pool
);
742 /* Compute ARRAY2 by changing the byte order. */
743 for (cnt
= 0; cnt
< best_size
* best_depth
* 3; ++cnt
)
744 array2
[cnt
] = SWAPU32 (array1
[cnt
]);
746 /* Now we can write out the whole data. */
747 if (strcmp (output_name
, "-") == 0
748 || strcmp (output_name
, "/dev/stdout") == 0)
752 fd
= creat (output_name
, 0666);
754 error (EXIT_FAILURE
, errno
, gettext ("cannot open output file `%s'"),
758 /* Write out header. */
759 write (fd
, &obj
, sizeof (obj
));
761 /* We always write out the little endian version of the index
763 #if __BYTE_ORDER == __LITTLE_ENDIAN
764 write (fd
, array1
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
765 write (fd
, array2
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
766 #elif __BYTE_ORDER == __BIG_ENDIAN
767 write (fd
, array2
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
768 write (fd
, array1
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
770 # error Cannot handle __BYTE_ORDER byte order
773 /* Finally write the strings. */
774 write (fd
, strings
, strings_size
);
776 if (fd
!= STDOUT_FILENO
)
779 /* If requested now write out the header file. */
780 if (header_name
!= NULL
)
785 /* Open output file. "-" or "/dev/stdout" means write to
787 if (strcmp (header_name
, "-") == 0
788 || strcmp (header_name
, "/dev/stdout") == 0)
792 fp
= fopen (header_name
, "w");
794 error (EXIT_FAILURE
, errno
,
795 gettext ("cannot open output file `%s'"), header_name
);
798 /* Iterate over all sets and all messages. */
799 set_run
= catalog
->all_sets
;
800 while (set_run
!= NULL
)
802 struct message_list
*message_run
;
804 /* If the current message set has a symbolic name write this
806 if (set_run
->symbol
!= NULL
)
807 fprintf (fp
, "%s#define %sSet %#x\t/* %s:%Zu */\n",
808 first
? "" : "\n", set_run
->symbol
, set_run
->number
- 1,
809 set_run
->fname
, set_run
->line
);
812 message_run
= set_run
->messages
;
813 while (message_run
!= NULL
)
815 /* If the current message has a symbolic name write
816 #define out. But we have to take care for the set
817 not having a symbolic name. */
818 if (message_run
->symbol
!= NULL
)
819 if (set_run
->symbol
== NULL
)
820 fprintf (fp
, "#define AutomaticSet%d%s %#x\t/* %s:%Zu */\n",
821 set_run
->number
, message_run
->symbol
,
822 message_run
->number
, message_run
->fname
,
825 fprintf (fp
, "#define %s%s %#x\t/* %s:%Zu */\n",
826 set_run
->symbol
, message_run
->symbol
,
827 message_run
->number
, message_run
->fname
,
830 message_run
= message_run
->next
;
833 set_run
= set_run
->next
;
842 static struct set_list
*
843 find_set (struct catalog
*current
, int number
)
845 struct set_list
*result
= current
->all_sets
;
847 /* We must avoid set number 0 because a set of this number signals
848 in the tables that the entry is not occupied. */
851 while (result
!= NULL
)
852 if (result
->number
== number
)
855 result
= result
->next
;
857 /* Prepare new message set. */
858 result
= (struct set_list
*) xmalloc (sizeof (*result
));
859 result
->number
= number
;
861 result
->messages
= NULL
;
862 result
->next
= current
->all_sets
;
863 current
->all_sets
= result
;
869 /* Normalize given string *in*place* by processing escape sequences
870 and quote characters. */
872 normalize_line (const char *fname
, size_t line
, char *string
, char quote_char
)
878 if (quote_char
!= '\0' && *rp
== quote_char
)
887 if (*rp
== quote_char
)
888 /* We simply end the string when we find the first time an
889 not-escaped quote character. */
891 else if (*rp
== '\\')
894 if (quote_char
!= '\0' && *rp
== quote_char
)
895 /* This is an extension to XPG. */
898 /* Recognize escape sequences. */
931 int number
= *rp
++ - '0';
932 while (number
<= (255 / 8) && *rp
>= '0' && *rp
<= '7')
935 number
+= *rp
++ - '0';
937 *wp
++ = (char) number
;
941 /* Simply ignore the backslash character. */
948 /* If we saw a quote character at the beginning we expect another
950 if (is_quoted
&& *rp
!= quote_char
)
951 error (0, 0, fname
, line
, gettext ("unterminated message"));
953 /* Terminate string. */
960 read_old (struct catalog
*catalog
, const char *file_name
)
962 struct catalog_info old_cat_obj
;
963 struct set_list
*set
= NULL
;
967 old_cat_obj
.status
= closed
;
968 old_cat_obj
.cat_name
= file_name
;
970 /* Try to open catalog, but don't look through the NLSPATH. */
971 __open_catalog (&old_cat_obj
, 0);
973 if (old_cat_obj
.status
!= mmaped
&& old_cat_obj
.status
!= malloced
)
975 /* No problem, the catalog simply does not exist. */
978 error (EXIT_FAILURE
, errno
, gettext ("while opening old catalog file"));
980 /* OK, we have the catalog loaded. Now read all messages and merge
981 them. When set and message number clash for any message the new
983 for (cnt
= 0; cnt
< old_cat_obj
.plane_size
* old_cat_obj
.plane_depth
; ++cnt
)
985 struct message_list
*message
, *last
;
987 if (old_cat_obj
.name_ptr
[cnt
* 3 + 0] == 0)
988 /* No message in this slot. */
991 if (old_cat_obj
.name_ptr
[cnt
* 3 + 0] - 1 != (u_int32_t
) last_set
)
993 last_set
= old_cat_obj
.name_ptr
[cnt
* 3 + 0] - 1;
994 set
= find_set (catalog
, old_cat_obj
.name_ptr
[cnt
* 3 + 0] - 1);
998 message
= set
->messages
;
999 while (message
!= NULL
)
1001 if ((u_int32_t
) message
->number
>= old_cat_obj
.name_ptr
[cnt
* 3 + 1])
1004 message
= message
->next
;
1008 || (u_int32_t
) message
->number
> old_cat_obj
.name_ptr
[cnt
* 3 + 1])
1010 /* We have found a message which is not yet in the catalog.
1011 Insert it at the right position. */
1012 struct message_list
*newp
;
1014 newp
= (struct message_list
*) xmalloc (sizeof(*newp
));
1015 newp
->number
= old_cat_obj
.name_ptr
[cnt
* 3 + 1];
1017 &old_cat_obj
.strings
[old_cat_obj
.name_ptr
[cnt
* 3 + 2]];
1020 newp
->symbol
= NULL
;
1021 newp
->next
= message
;
1024 set
->messages
= newp
;
1028 ++catalog
->total_messages
;