1 /* Writing C# satellite assemblies.
2 Copyright (C) 2003-2005 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2003.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
10 This program 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
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
25 #include "write-csharp.h"
34 #if STAT_MACROS_BROKEN
37 #if !defined S_ISDIR && defined S_IFDIR
38 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
40 #if !S_IRUSR && S_IREAD
41 # define S_IRUSR S_IREAD
44 # define S_IRUSR 00400
46 #if !S_IWUSR && S_IWRITE
47 # define S_IWUSR S_IWRITE
50 # define S_IWUSR 00200
52 #if !S_IXUSR && S_IEXEC
53 # define S_IXUSR S_IEXEC
56 # define S_IXUSR 00100
59 # define S_IRGRP (S_IRUSR >> 3)
62 # define S_IWGRP (S_IWUSR >> 3)
65 # define S_IXGRP (S_IXUSR >> 3)
68 # define S_IROTH (S_IRUSR >> 6)
71 # define S_IWOTH (S_IWUSR >> 6)
74 # define S_IXOTH (S_IXUSR >> 6)
82 /* mingw's mkdir() function has 1 argument, but we pass 2 arguments.
83 Therefore we have to disable the argument count checking. */
84 # define mkdir ((int (*)()) mkdir)
89 #include "relocatable.h"
90 #include "csharpcomp.h"
94 #include "msgl-iconv.h"
96 #include "plural-exp.h"
97 #include "po-charset.h"
100 #include "pathname.h"
101 #include "fatal-signal.h"
102 #include "fwriteerror.h"
104 #include "utf8-ucs4.h"
107 #define _(str) gettext (str)
110 /* Convert a resource name to a class name.
111 Return a nonempty string consisting of alphanumerics and underscores
112 and starting with a letter or underscore. */
114 construct_class_name (const char *resource_name
)
116 /* This code must be kept consistent with intl.cs, function
117 GettextResourceManager.ConstructClassName. */
118 /* We could just return an arbitrary fixed class name, like "Messages",
119 assuming that every assembly will only ever contain one
120 GettextResourceSet subclass, but this assumption would break the day
121 we want to support multi-domain PO files in the same format... */
125 /* Test for a valid ASCII identifier:
127 - first character is A..Za..z_ - see x-csharp.c:is_identifier_start.
128 - next characters are A..Za..z_0..9 - see x-csharp.c:is_identifier_part.
130 valid
= (resource_name
[0] != '\0');
131 for (p
= resource_name
; valid
&& *p
!= '\0'; p
++)
134 if (!((c
>= 'A' && c
<= 'Z') || (c
>= 'a' && c
<= 'z') || (c
== '_')
135 || (p
> resource_name
&& c
>= '0' && c
<= '9')))
139 return xstrdup (resource_name
);
142 static const char hexdigit
[] = "0123456789abcdef";
143 const char *str
= resource_name
;
144 const char *str_limit
= str
+ strlen (str
);
145 char *class_name
= (char *) xmalloc (12 + 6 * (str_limit
- str
) + 1);
149 memcpy (b
, "__UESCAPED__", 12); b
+= 12;
150 while (str
< str_limit
)
153 str
+= u8_mbtouc (&uc
, (const unsigned char *) str
, str_limit
- str
);
158 *b
++ = hexdigit
[(uc
>> 28) & 0x0f];
159 *b
++ = hexdigit
[(uc
>> 24) & 0x0f];
160 *b
++ = hexdigit
[(uc
>> 20) & 0x0f];
161 *b
++ = hexdigit
[(uc
>> 16) & 0x0f];
162 *b
++ = hexdigit
[(uc
>> 12) & 0x0f];
163 *b
++ = hexdigit
[(uc
>> 8) & 0x0f];
164 *b
++ = hexdigit
[(uc
>> 4) & 0x0f];
165 *b
++ = hexdigit
[uc
& 0x0f];
167 else if (!((uc
>= 'A' && uc
<= 'Z') || (uc
>= 'a' && uc
<= 'z')
168 || (uc
>= '0' && uc
<= '9')))
172 *b
++ = hexdigit
[(uc
>> 12) & 0x0f];
173 *b
++ = hexdigit
[(uc
>> 8) & 0x0f];
174 *b
++ = hexdigit
[(uc
>> 4) & 0x0f];
175 *b
++ = hexdigit
[uc
& 0x0f];
181 return (char *) xrealloc (class_name
, b
- class_name
);
186 /* Write a string in C# Unicode notation to the given stream. */
188 write_csharp_string (FILE *stream
, const char *str
)
190 static const char hexdigit
[] = "0123456789abcdef";
191 const char *str_limit
= str
+ strlen (str
);
193 fprintf (stream
, "\"");
194 while (str
< str_limit
)
197 str
+= u8_mbtouc (&uc
, (const unsigned char *) str
, str_limit
- str
);
199 fprintf (stream
, "\\0");
200 else if (uc
== 0x0007)
201 fprintf (stream
, "\\a");
202 else if (uc
== 0x0008)
203 fprintf (stream
, "\\b");
204 else if (uc
== 0x0009)
205 fprintf (stream
, "\\t");
206 else if (uc
== 0x000a)
207 fprintf (stream
, "\\n");
208 else if (uc
== 0x000b)
209 fprintf (stream
, "\\v");
210 else if (uc
== 0x000c)
211 fprintf (stream
, "\\f");
212 else if (uc
== 0x000d)
213 fprintf (stream
, "\\r");
214 else if (uc
== 0x0022)
215 fprintf (stream
, "\\\"");
216 else if (uc
== 0x005c)
217 fprintf (stream
, "\\\\");
218 else if (uc
>= 0x0020 && uc
< 0x007f)
219 fprintf (stream
, "%c", uc
);
220 else if (uc
< 0x10000)
221 fprintf (stream
, "\\u%c%c%c%c",
222 hexdigit
[(uc
>> 12) & 0x0f], hexdigit
[(uc
>> 8) & 0x0f],
223 hexdigit
[(uc
>> 4) & 0x0f], hexdigit
[uc
& 0x0f]);
225 fprintf (stream
, "\\U%c%c%c%c%c%c%c%c",
226 hexdigit
[(uc
>> 28) & 0x0f], hexdigit
[(uc
>> 24) & 0x0f],
227 hexdigit
[(uc
>> 20) & 0x0f], hexdigit
[(uc
>> 16) & 0x0f],
228 hexdigit
[(uc
>> 12) & 0x0f], hexdigit
[(uc
>> 8) & 0x0f],
229 hexdigit
[(uc
>> 4) & 0x0f], hexdigit
[uc
& 0x0f]);
231 fprintf (stream
, "\"");
235 /* Write C# code that returns the value for a message. If the message
236 has plural forms, it is an expression of type System.String[], otherwise it
237 is an expression of type System.String. */
239 write_csharp_msgstr (FILE *stream
, message_ty
*mp
)
241 if (mp
->msgid_plural
!= NULL
)
246 fprintf (stream
, "new System.String[] { ");
247 for (p
= mp
->msgstr
, first
= true;
248 p
< mp
->msgstr
+ mp
->msgstr_len
;
249 p
+= strlen (p
) + 1, first
= false)
252 fprintf (stream
, ", ");
253 write_csharp_string (stream
, p
);
255 fprintf (stream
, " }");
259 if (mp
->msgstr_len
!= strlen (mp
->msgstr
) + 1)
262 write_csharp_string (stream
, mp
->msgstr
);
267 /* Tests whether a plural expression, evaluated according to the C rules,
268 can only produce the values 0 and 1. */
270 is_expression_boolean (struct expression
*exp
)
272 switch (exp
->operation
)
285 case greater_or_equal
:
292 return (exp
->val
.num
== 0 || exp
->val
.num
== 1);
294 return is_expression_boolean (exp
->val
.args
[1])
295 && is_expression_boolean (exp
->val
.args
[2]);
302 /* Write C# code that evaluates a plural expression according to the C rules.
303 The variable is called 'n'. */
305 write_csharp_expression (FILE *stream
, struct expression
*exp
, bool as_boolean
)
307 /* We use parentheses everywhere. This frees us from tracking the priority
308 of arithmetic operators. */
311 /* Emit a C# expression of type 'bool'. */
312 switch (exp
->operation
)
315 fprintf (stream
, "%s", exp
->val
.num
? "true" : "false");
318 fprintf (stream
, "(!");
319 write_csharp_expression (stream
, exp
->val
.args
[0], true);
320 fprintf (stream
, ")");
323 fprintf (stream
, "(");
324 write_csharp_expression (stream
, exp
->val
.args
[0], false);
325 fprintf (stream
, " < ");
326 write_csharp_expression (stream
, exp
->val
.args
[1], false);
327 fprintf (stream
, ")");
330 fprintf (stream
, "(");
331 write_csharp_expression (stream
, exp
->val
.args
[0], false);
332 fprintf (stream
, " > ");
333 write_csharp_expression (stream
, exp
->val
.args
[1], false);
334 fprintf (stream
, ")");
337 fprintf (stream
, "(");
338 write_csharp_expression (stream
, exp
->val
.args
[0], false);
339 fprintf (stream
, " <= ");
340 write_csharp_expression (stream
, exp
->val
.args
[1], false);
341 fprintf (stream
, ")");
343 case greater_or_equal
:
344 fprintf (stream
, "(");
345 write_csharp_expression (stream
, exp
->val
.args
[0], false);
346 fprintf (stream
, " >= ");
347 write_csharp_expression (stream
, exp
->val
.args
[1], false);
348 fprintf (stream
, ")");
351 fprintf (stream
, "(");
352 write_csharp_expression (stream
, exp
->val
.args
[0], false);
353 fprintf (stream
, " == ");
354 write_csharp_expression (stream
, exp
->val
.args
[1], false);
355 fprintf (stream
, ")");
358 fprintf (stream
, "(");
359 write_csharp_expression (stream
, exp
->val
.args
[0], false);
360 fprintf (stream
, " != ");
361 write_csharp_expression (stream
, exp
->val
.args
[1], false);
362 fprintf (stream
, ")");
365 fprintf (stream
, "(");
366 write_csharp_expression (stream
, exp
->val
.args
[0], true);
367 fprintf (stream
, " && ");
368 write_csharp_expression (stream
, exp
->val
.args
[1], true);
369 fprintf (stream
, ")");
372 fprintf (stream
, "(");
373 write_csharp_expression (stream
, exp
->val
.args
[0], true);
374 fprintf (stream
, " || ");
375 write_csharp_expression (stream
, exp
->val
.args
[1], true);
376 fprintf (stream
, ")");
379 if (is_expression_boolean (exp
->val
.args
[1])
380 && is_expression_boolean (exp
->val
.args
[2]))
382 fprintf (stream
, "(");
383 write_csharp_expression (stream
, exp
->val
.args
[0], true);
384 fprintf (stream
, " ? ");
385 write_csharp_expression (stream
, exp
->val
.args
[1], true);
386 fprintf (stream
, " : ");
387 write_csharp_expression (stream
, exp
->val
.args
[2], true);
388 fprintf (stream
, ")");
398 fprintf (stream
, "(");
399 write_csharp_expression (stream
, exp
, false);
400 fprintf (stream
, " != 0)");
408 /* Emit a C# expression of type 'long'. */
409 switch (exp
->operation
)
412 fprintf (stream
, "n");
415 fprintf (stream
, "%lu", exp
->val
.num
);
418 fprintf (stream
, "(");
419 write_csharp_expression (stream
, exp
->val
.args
[0], false);
420 fprintf (stream
, " * ");
421 write_csharp_expression (stream
, exp
->val
.args
[1], false);
422 fprintf (stream
, ")");
425 fprintf (stream
, "(");
426 write_csharp_expression (stream
, exp
->val
.args
[0], false);
427 fprintf (stream
, " / ");
428 write_csharp_expression (stream
, exp
->val
.args
[1], false);
429 fprintf (stream
, ")");
432 fprintf (stream
, "(");
433 write_csharp_expression (stream
, exp
->val
.args
[0], false);
434 fprintf (stream
, " %% ");
435 write_csharp_expression (stream
, exp
->val
.args
[1], false);
436 fprintf (stream
, ")");
439 fprintf (stream
, "(");
440 write_csharp_expression (stream
, exp
->val
.args
[0], false);
441 fprintf (stream
, " + ");
442 write_csharp_expression (stream
, exp
->val
.args
[1], false);
443 fprintf (stream
, ")");
446 fprintf (stream
, "(");
447 write_csharp_expression (stream
, exp
->val
.args
[0], false);
448 fprintf (stream
, " - ");
449 write_csharp_expression (stream
, exp
->val
.args
[1], false);
450 fprintf (stream
, ")");
453 fprintf (stream
, "(");
454 write_csharp_expression (stream
, exp
->val
.args
[0], true);
455 fprintf (stream
, " ? ");
456 write_csharp_expression (stream
, exp
->val
.args
[1], false);
457 fprintf (stream
, " : ");
458 write_csharp_expression (stream
, exp
->val
.args
[2], false);
459 fprintf (stream
, ")");
465 case greater_or_equal
:
470 fprintf (stream
, "(");
471 write_csharp_expression (stream
, exp
, true);
472 fprintf (stream
, " ? 1 : 0)");
481 /* Write the C# code for the GettextResourceSet subclass to the given stream.
482 Note that we use fully qualified class names and no "using" statements,
483 because applications can have their own classes called X.Y.Hashtable or
486 write_csharp_code (FILE *stream
, const char *class_name
, message_list_ty
*mlp
)
488 const char *last_dot
;
489 const char *class_name_last_part
;
490 unsigned int plurals
;
494 "/* Automatically generated by GNU msgfmt. Do not modify! */\n");
495 /* We have to use a "using" statement here, to avoid a bug in the pnet-0.6.0
497 fprintf (stream
, "using GNU.Gettext;\n");
498 last_dot
= strrchr (class_name
, '.');
499 if (last_dot
!= NULL
)
501 fprintf (stream
, "namespace ");
502 fwrite (class_name
, 1, last_dot
- class_name
, stream
);
503 fprintf (stream
, " {\n");
504 class_name_last_part
= last_dot
+ 1;
507 class_name_last_part
= class_name
;
508 fprintf (stream
, "public class %s : GettextResourceSet {\n",
509 class_name_last_part
);
511 /* Determine whether there are plural messages. */
513 for (j
= 0; j
< mlp
->nitems
; j
++)
514 if (mlp
->item
[j
]->msgid_plural
!= NULL
)
517 /* Emit the constructor. */
518 fprintf (stream
, " public %s ()\n", class_name_last_part
);
519 fprintf (stream
, " : base () {\n");
520 fprintf (stream
, " }\n");
522 /* Emit the ReadResources method. */
523 fprintf (stream
, " protected override void ReadResources () {\n");
524 /* In some implementations, the ResourceSet constructor initializes Table
525 before calling ReadResources(). In other implementations, the
526 ReadResources() method is expected to initialize the Table. */
527 fprintf (stream
, " if (Table == null)\n");
528 fprintf (stream
, " Table = new System.Collections.Hashtable();\n");
529 fprintf (stream
, " System.Collections.Hashtable t = Table;\n");
530 for (j
= 0; j
< mlp
->nitems
; j
++)
532 fprintf (stream
, " t.Add(");
533 write_csharp_string (stream
, mlp
->item
[j
]->msgid
);
534 fprintf (stream
, ",");
535 write_csharp_msgstr (stream
, mlp
->item
[j
]);
536 fprintf (stream
, ");\n");
538 fprintf (stream
, " }\n");
540 /* Emit the msgid_plural strings. Only used by msgunfmt. */
543 fprintf (stream
, " public static System.Collections.Hashtable GetMsgidPluralTable () {\n");
544 fprintf (stream
, " System.Collections.Hashtable t = new System.Collections.Hashtable();\n");
545 for (j
= 0; j
< mlp
->nitems
; j
++)
546 if (mlp
->item
[j
]->msgid_plural
!= NULL
)
548 fprintf (stream
, " t.Add(");
549 write_csharp_string (stream
, mlp
->item
[j
]->msgid
);
550 fprintf (stream
, ",");
551 write_csharp_string (stream
, mlp
->item
[j
]->msgid_plural
);
552 fprintf (stream
, ");\n");
554 fprintf (stream
, " return t;\n");
555 fprintf (stream
, " }\n");
558 /* Emit the PluralEval function. It is a subroutine for GetPluralString. */
561 message_ty
*header_entry
;
562 struct expression
*plural
;
563 unsigned long int nplurals
;
565 header_entry
= message_list_search (mlp
, "");
566 extract_plural_expression (header_entry
? header_entry
->msgstr
: NULL
,
569 fprintf (stream
, " protected override long PluralEval (long n) {\n");
570 fprintf (stream
, " return ");
571 write_csharp_expression (stream
, plural
, false);
572 fprintf (stream
, ";\n");
573 fprintf (stream
, " }\n");
576 /* Terminate the class. */
577 fprintf (stream
, "}\n");
579 if (last_dot
!= NULL
)
580 /* Terminate the namespace. */
581 fprintf (stream
, "}\n");
585 /* Asynchronously cleaning up temporary files, when we receive any of the
586 usually occurring signals whose default action is to terminate the
592 const char *file_name
;
595 /* The signal handler. It gets called asynchronously. */
599 /* First cleanup the files in the subdirectory. */
601 const char *filename
= cleanup_list
.file_name
;
603 if (filename
!= NULL
)
607 /* Then cleanup the main temporary directory. */
609 const char *filename
= cleanup_list
.tmpdir
;
611 if (filename
!= NULL
)
618 msgdomain_write_csharp (message_list_ty
*mlp
, const char *canon_encoding
,
619 const char *resource_name
, const char *locale_name
,
620 const char *directory
)
628 char *csharp_file_name
;
630 const char *gettextlibdir
;
631 const char *csharp_sources
[1];
632 const char *libdirs
[1];
633 const char *libraries
[1];
635 /* If no entry for this resource/domain, don't even create the file. */
636 if (mlp
->nitems
== 0)
641 /* Convert the messages to Unicode. */
642 iconv_message_list (mlp
, canon_encoding
, po_charset_utf8
, NULL
);
644 cleanup_list
.tmpdir
= NULL
;
645 cleanup_list
.file_name
= NULL
;
647 static bool cleanup_already_registered
= false;
648 if (!cleanup_already_registered
)
650 at_fatal_signal (&cleanup
);
651 cleanup_already_registered
= true;
655 /* Create a temporary directory where we can put the C# file.
656 A simple temporary file would also be possible but would require us to
657 define our own variant of mkstemp(): On one hand the functions mktemp(),
658 tmpnam(), tempnam() present a security risk, and on the other hand the
659 function mkstemp() doesn't allow to specify a fixed suffix of the file.
660 It is simpler to create a temporary directory. */
661 template = (char *) xallocsa (PATH_MAX
);
662 if (path_search (template, PATH_MAX
, NULL
, "msg", 1))
665 _("cannot find a temporary directory, try setting $TMPDIR"));
668 block_fatal_signals ();
669 tmpdir
= mkdtemp (template);
670 cleanup_list
.tmpdir
= tmpdir
;
671 unblock_fatal_signals ();
675 _("cannot create a temporary directory using template \"%s\""),
680 /* Assign a default value to the resource name. */
681 if (resource_name
== NULL
)
682 resource_name
= "Messages";
684 /* Convert the locale name to a .NET specific culture name. */
685 culture_name
= xstrdup (locale_name
);
688 for (p
= culture_name
; *p
!= '\0'; p
++)
691 if (strncmp (culture_name
, "sr-CS", 5) == 0)
692 memcpy (culture_name
, "sr-SP", 5);
693 p
= strchr (culture_name
, '@');
696 if (strcmp (p
, "@latin") == 0)
698 else if (strcmp (p
, "@cyrillic") == 0)
701 if (strcmp (culture_name
, "sr-SP") == 0)
704 culture_name
= xstrdup ("sr-SP-Latn");
706 else if (strcmp (culture_name
, "uz-UZ") == 0)
709 culture_name
= xstrdup ("uz-UZ-Latn");
714 /* Compute the output file name. This code must be kept consistent with
715 intl.cs, function GetSatelliteAssembly(). */
717 char *output_dir
= concatenated_pathname (directory
, culture_name
, NULL
);
720 /* Try to create the output directory if it does not yet exist. */
721 if (stat (output_dir
, &statbuf
) < 0 && errno
== ENOENT
)
722 if (mkdir (output_dir
, S_IRUSR
| S_IWUSR
| S_IXUSR
723 | S_IRGRP
| S_IWGRP
| S_IXGRP
724 | S_IROTH
| S_IWOTH
| S_IXOTH
) < 0)
726 error (0, errno
, _("failed to create directory \"%s\""), output_dir
);
732 concatenated_pathname (output_dir
, resource_name
, ".resources.dll");
737 /* Compute the class name. This code must be kept consistent with intl.cs,
738 function InstantiateResourceSet(). */
740 char *class_name_part1
= construct_class_name (resource_name
);
744 (char *) xmalloc (strlen (class_name_part1
) + 1 + strlen (culture_name
) + 1);
745 sprintf (class_name
, "%s_%s", class_name_part1
, culture_name
);
746 for (p
= class_name
+ strlen (class_name_part1
) + 1; *p
!= '\0'; p
++)
749 free (class_name_part1
);
752 /* Compute the temporary C# file name. It must end in ".cs", so that
753 the C# compiler recognizes that it is C# source code. */
754 csharp_file_name
= concatenated_pathname (tmpdir
, "resset.cs", NULL
);
756 /* Create the C# file. */
757 cleanup_list
.file_name
= csharp_file_name
;
758 csharp_file
= fopen (csharp_file_name
, "w");
759 if (csharp_file
== NULL
)
761 error (0, errno
, _("failed to create \"%s\""), csharp_file_name
);
765 write_csharp_code (csharp_file
, class_name
, mlp
);
767 if (fwriteerror (csharp_file
))
769 error (0, errno
, _("error while writing \"%s\" file"), csharp_file_name
);
770 fclose (csharp_file
);
774 /* Make it possible to override the .dll location. This is
775 necessary for running the testsuite before "make install". */
776 gettextlibdir
= getenv ("GETTEXTCSHARPLIBDIR");
777 if (gettextlibdir
== NULL
|| gettextlibdir
[0] == '\0')
778 gettextlibdir
= relocate (LIBDIR
);
780 /* Compile the C# file to a .dll file. */
781 csharp_sources
[0] = csharp_file_name
;
782 libdirs
[0] = gettextlibdir
;
783 libraries
[0] = "GNU.Gettext";
784 if (compile_csharp_class (csharp_sources
, 1, libdirs
, 1, libraries
, 1,
785 output_file
, true, false, verbose
))
787 error (0, 0, _("compilation of C# class failed, please try --verbose"));
794 unlink (csharp_file_name
);
796 cleanup_list
.file_name
= NULL
;
797 free (csharp_file_name
);
804 cleanup_list
.tmpdir
= NULL
;
806 /* Here we could unregister the cleanup() handler. */