Starting fixing character set handling to handle multi-byte encodings.
[kopensolaris-gnu/glibc.git] / locale / programs / charmap.c
1 /* Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1996.
4
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.
9
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.
14
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 not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA.  */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <ctype.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <libintl.h>
28 #include <obstack.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "error.h"
34 #include "linereader.h"
35 #include "charset.h"
36 #include "locfile.h"
37 #include "repertoire.h"
38
39
40 /* Uncomment following line for production version.  */
41 /* define NDEBUG 1 */
42 #include <assert.h>
43
44
45 /* Define the lookup function.  */
46 #include "charmap-kw.h"
47
48
49 extern void *xmalloc (size_t __n);
50
51 /* Prototypes for local functions.  */
52 static struct charset_t *parse_charmap (const char *filename);
53 static void new_width (struct linereader *cmfile, struct charset_t *result,
54                        const char *from, const char *to,
55                        unsigned long int width);
56
57
58 struct charset_t *
59 charmap_read (const char *filename)
60 {
61   const char *pathnfile;
62   struct charset_t *result = NULL;
63
64   if (filename != NULL)
65     {
66       if (euidaccess (filename, R_OK) >= 0)
67         pathnfile = filename;
68       else if (filename[0] != '/')
69         {
70           char *cp = xmalloc (strlen (filename) + sizeof CHARMAP_PATH + 1);
71           stpcpy (stpcpy (stpcpy (cp, CHARMAP_PATH), "/"), filename);
72
73           pathnfile = (const char *) cp;
74         }
75       else
76         pathnfile = NULL;
77
78       if (pathnfile != NULL)
79         {
80           result = parse_charmap (pathnfile);
81
82           if (result == NULL && !be_quiet)
83             error (0, errno, _("character map file `%s' not found"), filename);
84         }
85     }
86
87   if (result == NULL)
88     {
89       /* OK, one more try.  We also accept the names given to the
90          character sets in the files.  Sometimes they differ from the
91          file name.  */
92       DIR *dir;
93       struct dirent *dirent;
94
95       dir = opendir (CHARMAP_PATH);
96       if (dir == NULL)
97         {
98           while ((dirent = readdir (dir)) != NULL)
99             if (strcmp (dirent->d_name, ".") != 0
100                 && strcmp (dirent->d_name, "..") != 0)
101               {
102                 char buf[sizeof (CHARMAP_PATH)
103                         + strlen (dirent->d_name) + 1];
104                 FILE *fp;
105 #ifdef _DIRENT_HAVE_D_TYPE
106                 if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_REG)
107                   continue;
108 #endif
109                 stpcpy (stpcpy (stpcpy (buf, CHARMAP_PATH), "/"),
110                         dirent->d_name);
111
112                 fp = fopen (buf, "r");
113                 if (fp != NULL)
114                   {
115                     char *name = NULL;
116
117                     while (!feof (fp))
118                       {
119                         char junk[BUFSIZ];
120
121                         if (fscanf (fp, " <code_set_name> %as", &name) == 1
122                             || fscanf (fp, "%% alias %as", &name) == 1)
123                           {
124                             if (strcasecmp (name, filename) == 0)
125                               break;
126
127                             free (name);
128                             name = NULL;
129                           }
130
131                         if (fgets (junk, sizeof junk, fp) != NULL)
132                           {
133                             if (strstr (junk, "CHARMAP") != NULL)
134                               /* We cannot expect more aliases from now on.  */
135                               break;
136
137                             while (strchr (junk, '\n') == NULL
138                                    && fgets (junk, sizeof junk, fp) != NULL)
139                               continue;
140                           }
141                       }
142
143                     fclose (fp);
144
145                     if (name != NULL)
146                       {
147                         result = parse_charmap (buf);
148
149                         free (buf);
150
151                         if (result)
152                           return result;
153
154                         break;
155                       }
156                   }
157               }
158
159           closedir (dir);
160         }
161     }
162
163   if (result == NULL)
164     {
165       pathnfile = CHARMAP_PATH "/" DEFAULT_CHARMAP;
166
167       result = parse_charmap (pathnfile);
168
169       if (result == NULL)
170         error (4, errno, _("default character map file `%s' not found"),
171                DEFAULT_CHARMAP);
172     }
173
174   return result;
175 }
176
177
178 static struct charset_t *
179 parse_charmap (const char *filename)
180 {
181   struct linereader *cmfile;
182   struct charset_t *result;
183   int state;
184   enum token_t expected_tok = tok_error;
185   const char *expected_str = NULL;
186   char *from_name = NULL;
187   char *to_name = NULL;
188
189   /* Determine path.  */
190   cmfile = lr_open (filename, charmap_hash);
191   if (cmfile == NULL)
192     {
193       if (strchr (filename, '/') == NULL)
194         {
195           /* Look in the systems charmap directory.  */
196           char *buf = xmalloc (strlen (filename) + 1 + sizeof (CHARMAP_PATH));
197
198           stpcpy (stpcpy (stpcpy (buf, CHARMAP_PATH), "/"), filename);
199           cmfile = lr_open (buf, charmap_hash);
200
201           if (cmfile == NULL)
202             free (buf);
203         }
204
205       if (cmfile == NULL)
206         return NULL;
207     }
208
209   /* Allocate room for result.  */
210   result = (struct charset_t *) xmalloc (sizeof (struct charset_t));
211   memset (result, '\0', sizeof (struct charset_t));
212   /* The default DEFAULT_WIDTH is 1.  */
213   result->width_default = 1;
214   /* Let the user overwrite the repertoire map we use.  */
215   result->repertoiremap = repertoiremap;
216
217 #define obstack_chunk_alloc malloc
218 #define obstack_chunk_free free
219   obstack_init (&result->mem_pool);
220
221   if (init_hash (&result->char_table, 256))
222     {
223       free (result);
224       return NULL;
225     }
226
227   /* We use a state machine to describe the charmap description file
228      format.  */
229   state = 1;
230   while (1)
231     {
232       /* What's on?  */
233       struct token *now = lr_token (cmfile, NULL);
234       enum token_t nowtok = now->tok;
235       struct token *arg;
236
237       if (nowtok == tok_eof)
238         break;
239
240       switch (state)
241         {
242         case 1:
243           /* The beginning.  We expect the special declarations, EOL or
244              `CHARMAP'.  */
245           if (nowtok == tok_eol)
246             /* Ignore empty lines.  */
247             continue;
248
249           if (nowtok == tok_charmap)
250             {
251               from_name = NULL;
252               to_name = NULL;
253
254               /* We have to set up the real work.  Fill in some
255                  default values.  */
256               if (result->mb_cur_max == 0)
257                 result->mb_cur_max = 1;
258               if (result->mb_cur_min == 0)
259                 result->mb_cur_min = result->mb_cur_max;
260               if (result->mb_cur_min > result->mb_cur_max)
261                 {
262                   if (!be_quiet)
263                     error (0, 0, _("\
264 %s: <mb_cur_max> must be greater than <mb_cur_min>\n"),
265                            cmfile->fname);
266
267                   result->mb_cur_min = result->mb_cur_max;
268                 }
269
270               lr_ignore_rest (cmfile, 1);
271
272               /* Read the repertoire map now.  */
273               if (result->repertoiremap == NULL)
274                 /* This is fatal.  */
275                 error (4, 0, _("no repertoire map specified: cannot proceed"));
276
277               result->repertoire = repertoire_read (result->repertoiremap);
278               if (result->repertoire == NULL)
279                 /* This is also fatal.  */
280                 error (4, errno, _("cannot read repertoire map `%s'"),
281                        result->repertoiremap);
282
283               state = 2;
284               continue;
285             }
286
287           if (nowtok != tok_code_set_name && nowtok != tok_mb_cur_max
288               && nowtok != tok_mb_cur_min && nowtok != tok_escape_char
289               && nowtok != tok_comment_char && nowtok != tok_g0esc
290               && nowtok != tok_g1esc && nowtok != tok_g2esc
291               && nowtok != tok_g3esc && nowtok != tok_repertoiremap)
292             {
293               lr_error (cmfile, _("syntax error in prolog: %s"),
294                         _("illegal definition"));
295
296               lr_ignore_rest (cmfile, 0);
297               continue;
298             }
299
300           /* We know that we need an argument.  */
301           arg = lr_token (cmfile, NULL);
302
303           switch (nowtok)
304             {
305             case tok_code_set_name:
306               if (arg->tok != tok_ident)
307                 {
308                 badarg:
309                   lr_error (cmfile, _("syntax error in prolog: %s"),
310                             _("bad argument"));
311
312                   lr_ignore_rest (cmfile, 0);
313                   continue;
314                 }
315
316               result->code_set_name = obstack_copy0 (&result->mem_pool,
317                                                      arg->val.str.start,
318                                                      arg->val.str.len);
319
320               lr_ignore_rest (cmfile, 1);
321               continue;
322
323             case tok_repertoiremap:
324               if (arg->tok != tok_ident)
325                 goto badarg;
326
327               if (result->repertoiremap == NULL)
328                 result->repertoiremap = obstack_copy0 (&result->mem_pool,
329                                                        arg->val.str.start,
330                                                        arg->val.str.len);
331
332               lr_ignore_rest (cmfile, 1);
333               continue;
334
335             case tok_mb_cur_max:
336             case tok_mb_cur_min:
337               if (arg->tok != tok_number)
338                 goto badarg;
339
340               if (arg->val.num < 1 || arg->val.num > 4)
341                 {
342                   lr_error (cmfile,
343                             _("value for <%s> must lie between 1 and 4"),
344                             nowtok == tok_mb_cur_min ? "mb_cur_min"
345                                                      : "mb_cur_max");
346
347                   lr_ignore_rest (cmfile, 0);
348                   continue;
349                 }
350               if ((nowtok == tok_mb_cur_max && result->mb_cur_min != 0
351                    && (int) arg->val.num < result->mb_cur_min)
352                   || (nowtok == tok_mb_cur_min && result->mb_cur_max != 0
353                       && (int) arg->val.num > result->mb_cur_max))
354                 {
355                   lr_error (cmfile, _("\
356 value of <mb_cur_max> must be greater than the value of <mb_cur_min>"));
357
358                   lr_ignore_rest (cmfile, 0);
359                   continue;
360                 }
361
362               if (nowtok == tok_mb_cur_max)
363                 result->mb_cur_max = arg->val.num;
364               else
365                 result->mb_cur_min = arg->val.num;
366
367               lr_ignore_rest (cmfile, 1);
368               continue;
369
370             case tok_escape_char:
371             case tok_comment_char:
372               if (arg->tok != tok_ident)
373                 goto badarg;
374
375               if (arg->val.str.len != 1)
376                 {
377                   lr_error (cmfile, _("\
378 argument to <%s> must be a single character"),
379                             nowtok == tok_escape_char ? "escape_char"
380                                                       : "comment_char");
381
382                   lr_ignore_rest (cmfile, 0);
383                   continue;
384                 }
385
386               if (nowtok == tok_escape_char)
387                 cmfile->escape_char = *arg->val.str.start;
388               else
389                 cmfile->comment_char = *arg->val.str.start;
390
391               lr_ignore_rest (cmfile, 1);
392               continue;
393
394             case tok_g0esc:
395             case tok_g1esc:
396             case tok_g2esc:
397             case tok_g3esc:
398               lr_ignore_rest (cmfile, 0); /* XXX */
399               continue;
400
401             default:
402               /* Cannot happen.  */
403               assert (! "Should not happen");
404             }
405           break;
406
407         case 2:
408           /* We have seen `CHARMAP' and now are in the body.  Each line
409              must have the format "%s %s %s\n" or "%s...%s %s %s\n".  */
410           if (nowtok == tok_eol)
411             /* Ignore empty lines.  */
412             continue;
413
414           if (nowtok == tok_end)
415             {
416               expected_tok = tok_charmap;
417               expected_str = "CHARMAP";
418               state = 90;
419               continue;
420             }
421
422           if (nowtok != tok_bsymbol)
423             {
424               lr_error (cmfile, _("syntax error in %s definition: %s"),
425                         "CHARMAP", _("no symbolic name given"));
426
427               lr_ignore_rest (cmfile, 0);
428               continue;
429             }
430
431           /* If the previous line was not completely correct free the
432              used memory.  */
433           if (from_name != NULL)
434             obstack_free (&result->mem_pool, from_name);
435
436           from_name = (char *) obstack_copy0 (&result->mem_pool,
437                                               now->val.str.start,
438                                               now->val.str.len);
439           to_name = NULL;
440
441           state = 3;
442           continue;
443
444         case 3:
445           /* We have two possibilities: We can see an ellipsis or an
446              encoding value.  */
447           if (nowtok == tok_ellipsis)
448             {
449               state = 4;
450               continue;
451             }
452           /* FALLTHROUGH */
453
454         case 5:
455           if (nowtok != tok_charcode && nowtok != tok_ucs2
456               && nowtok != tok_ucs4)
457             {
458               lr_error (cmfile, _("syntax error in %s definition: %s"),
459                         "CHARMAP", _("illegal encoding given"));
460
461               lr_ignore_rest (cmfile, 0);
462
463               state = 2;
464               continue;
465             }
466
467           if (now->val.charcode.nbytes < result->mb_cur_min)
468             lr_error (cmfile, _("too few bytes in character encoding"));
469           else if (now->val.charcode.nbytes > result->mb_cur_max)
470             lr_error (cmfile, _("too many bytes in character encoding"));
471           else
472             charset_new_char (cmfile, &result->char_table,
473                               now->val.charcode.nbytes,
474                               now->val.charcode.val, from_name, to_name);
475
476           /* Ignore trailing comment silently.  */
477           lr_ignore_rest (cmfile, 0);
478
479           from_name = NULL;
480           to_name = NULL;
481
482           state = 2;
483           continue;
484
485         case 4:
486           if (nowtok != tok_bsymbol)
487             {
488               lr_error (cmfile, _("syntax error in %s definition: %s"),
489                         "CHARMAP",
490                         _("no symbolic name given for end of range"));
491
492               lr_ignore_rest (cmfile, 0);
493               continue;
494             }
495
496           /* Copy the to-name in a safe place.  */
497           to_name = (char *) obstack_copy0 (&result->mem_pool,
498                                             cmfile->token.val.str.start,
499                                             cmfile->token.val.str.len);
500
501           state = 5;
502           continue;
503
504         case 90:
505           if (nowtok != expected_tok)
506             lr_error (cmfile, _("\
507 `%1$s' definition does not end with `END %1$s'"), expected_str);
508
509           lr_ignore_rest (cmfile, nowtok == expected_tok);
510           state = 91;
511           continue;
512
513         case 91:
514           /* Waiting for WIDTH... */
515           if (nowtok == tok_eol)
516             /* Ignore empty lines.  */
517             continue;
518
519           if (nowtok == tok_width_default)
520             {
521               state = 92;
522               continue;
523             }
524
525           if (nowtok == tok_width)
526             {
527               lr_ignore_rest (cmfile, 1);
528               state = 93;
529               continue;
530             }
531
532           if (nowtok == tok_width_variable)
533             {
534               lr_ignore_rest (cmfile, 1);
535               state = 98;
536               continue;
537             }
538
539           lr_error (cmfile, _("\
540 only WIDTH definitions are allowed to follow the CHARMAP definition"));
541
542           lr_ignore_rest (cmfile, 0);
543           continue;
544
545         case 92:
546           if (nowtok != tok_number)
547             lr_error (cmfile, _("value for %s must be an integer"),
548                       "WIDTH_DEFAULT");
549           else
550             result->width_default = now->val.num;
551
552           lr_ignore_rest (cmfile, nowtok == tok_number);
553
554           state = 91;
555           continue;
556
557         case 93:
558           /* We now expect `END WIDTH' or lines of the format "%s %d\n" or
559              "%s...%s %d\n".  */
560           if (nowtok == tok_eol)
561             /* ignore empty lines.  */
562             continue;
563
564           if (nowtok == tok_end)
565             {
566               expected_tok = tok_width;
567               expected_str = "WIDTH";
568               state = 90;
569               continue;
570             }
571
572           if (nowtok != tok_bsymbol)
573             {
574               lr_error (cmfile, _("syntax error in %s definition: %s"),
575                         "WIDTH", _("no symbolic name given"));
576
577               lr_ignore_rest (cmfile, 0);
578               continue;
579             }
580
581           if (from_name != NULL)
582             obstack_free (&result->mem_pool, from_name);
583
584           from_name = (char *) obstack_copy0 (&result->mem_pool,
585                                               now->val.str.start,
586                                               now->val.str.len);
587           to_name = NULL;
588
589           state = 94;
590           continue;
591
592         case 94:
593           if (nowtok == tok_ellipsis)
594             {
595               state = 95;
596               continue;
597             }
598
599         case 96:
600           if (nowtok != tok_number)
601             lr_error (cmfile, _("value for %s must be an integer"),
602                       "WIDTH");
603           else
604             {
605               /* Store width for chars.  */
606               new_width (cmfile, result, from_name, to_name, now->val.num);
607
608               from_name = NULL;
609               to_name = NULL;
610             }
611
612           lr_ignore_rest (cmfile, nowtok == tok_number);
613
614           state = 93;
615           continue;
616
617         case 95:
618           if (nowtok != tok_bsymbol)
619             {
620               lr_error (cmfile, _("syntax error in %s definition: %s"),
621                         "WIDTH", _("no symbolic name given for end of range"));
622
623               lr_ignore_rest (cmfile, 0);
624
625               state = 93;
626               continue;
627             }
628
629           to_name = (char *) obstack_copy0 (&result->mem_pool,
630                                             now->val.str.start,
631                                             now->val.str.len);
632
633           state = 96;
634           continue;
635
636         case 98:
637           /* We now expect `END WIDTH_VARIABLE' or lines of the format
638              "%s\n" or "%s...%s\n".  */
639           if (nowtok == tok_eol)
640             /* ignore empty lines.  */
641             continue;
642
643           if (nowtok == tok_end)
644             {
645               expected_tok = tok_width_variable;
646               expected_str = "WIDTH_VARIABLE";
647               state = 90;
648               continue;
649             }
650
651           if (nowtok != tok_bsymbol)
652             {
653               lr_error (cmfile, _("syntax error in %s definition: %s"),
654                         "WIDTH_VARIABLE", _("no symbolic name given"));
655
656               lr_ignore_rest (cmfile, 0);
657
658               continue;
659             }
660
661           if (from_name != NULL)
662             obstack_free (&result->mem_pool, from_name);
663
664           from_name = (char *) obstack_copy0 (&result->mem_pool,
665                                               now->val.str.start,
666                                               now->val.str.len);
667           to_name = NULL;
668
669           state = 99;
670           continue;
671
672         case 99:
673           if (nowtok == tok_ellipsis)
674             state = 100;
675
676           /* Store info.  */
677           from_name = NULL;
678
679           /* Warn */
680           state = 98;
681           continue;
682
683         case 100:
684           if (nowtok != tok_bsymbol)
685             lr_error (cmfile, _("syntax error in %s definition: %s"),
686                       "WIDTH_VARIABLE",
687                       _("no symbolic name given for end of range"));
688           else
689             {
690               to_name = (char *) obstack_copy0 (&result->mem_pool,
691                                                 now->val.str.start,
692                                                 now->val.str.len);
693               /* XXX Enter value into table.  */
694             }
695
696           lr_ignore_rest (cmfile, nowtok == tok_bsymbol);
697
698           state = 98;
699           continue;
700
701         default:
702           error (5, 0, _("%s: error in state machine"), __FILE__);
703           /* NOTREACHED */
704         }
705       break;
706     }
707
708   if (state != 91 && !be_quiet)
709     error (0, 0, _("%s: premature end of file"), cmfile->fname);
710
711   lr_close (cmfile);
712
713   return result;
714 }
715
716
717 static void
718 new_width (struct linereader *cmfile, struct charset_t *result,
719            const char *from, const char *to, unsigned long int width)
720 {
721   unsigned int from_val, to_val;
722
723   from_val = charset_find_value (&result->char_table, from, strlen (from));
724   if ((wchar_t) from_val == ILLEGAL_CHAR_VALUE)
725     {
726       lr_error (cmfile, _("unknown character `%s'"), from);
727       return;
728     }
729
730   if (to == NULL)
731     to_val = from_val;
732   else
733     {
734       to_val = charset_find_value (&result->char_table, to, strlen (to));
735       if ((wchar_t) to_val == ILLEGAL_CHAR_VALUE)
736         {
737           lr_error (cmfile, _("unknown character `%s'"), to);
738           return;
739         }
740     }
741
742   if (result->nwidth_rules >= result->nwidth_rules_max)
743     {
744       size_t new_size = result->nwidth_rules + 32;
745       struct width_rule *new_rules =
746         (struct width_rule *) obstack_alloc (&result->mem_pool,
747                                              (new_size
748                                               * sizeof (struct width_rule)));
749
750       memcpy (new_rules, result->width_rules,
751               result->nwidth_rules_max * sizeof (struct width_rule));
752
753       result->width_rules = new_rules;
754       result->nwidth_rules_max = new_size;
755     }
756
757   result->width_rules[result->nwidth_rules].from = from_val;
758   result->width_rules[result->nwidth_rules].to = to_val;
759   result->width_rules[result->nwidth_rules].width = (unsigned int) width;
760   ++result->nwidth_rules;
761 }