1 /* Writing C# satellite assemblies.
2 Copyright (C) 2003-2010, 2015 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 3 of the License, or
8 (at your option) any later version.
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, see <http://www.gnu.org/licenses/>. */
24 #include "write-csharp.h"
33 #if !defined S_ISDIR && defined S_IFDIR
34 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
36 #if !S_IRUSR && S_IREAD
37 # define S_IRUSR S_IREAD
40 # define S_IRUSR 00400
42 #if !S_IWUSR && S_IWRITE
43 # define S_IWUSR S_IWRITE
46 # define S_IWUSR 00200
48 #if !S_IXUSR && S_IEXEC
49 # define S_IXUSR S_IEXEC
52 # define S_IXUSR 00100
55 # define S_IRGRP (S_IRUSR >> 3)
58 # define S_IWGRP (S_IWUSR >> 3)
61 # define S_IXGRP (S_IXUSR >> 3)
64 # define S_IROTH (S_IRUSR >> 6)
67 # define S_IWOTH (S_IWUSR >> 6)
70 # define S_IXOTH (S_IXUSR >> 6)
74 #include "relocatable.h"
77 #include "csharpcomp.h"
80 #include "msgl-iconv.h"
81 #include "plural-exp.h"
82 #include "po-charset.h"
85 #include "concat-filename.h"
86 #include "fwriteerror.h"
87 #include "clean-temp.h"
91 #define _(str) gettext (str)
94 /* Convert a resource name to a class name.
95 Return a nonempty string consisting of alphanumerics and underscores
96 and starting with a letter or underscore. */
98 construct_class_name (const char *resource_name)
100 /* This code must be kept consistent with intl.cs, function
101 GettextResourceManager.ConstructClassName. */
102 /* We could just return an arbitrary fixed class name, like "Messages",
103 assuming that every assembly will only ever contain one
104 GettextResourceSet subclass, but this assumption would break the day
105 we want to support multi-domain PO files in the same format... */
109 /* Test for a valid ASCII identifier:
111 - first character is A..Za..z_ - see x-csharp.c:is_identifier_start.
112 - next characters are A..Za..z_0..9 - see x-csharp.c:is_identifier_part.
114 valid = (resource_name[0] != '\0');
115 for (p = resource_name; valid && *p != '\0'; p++)
118 if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
119 || (p > resource_name && c >= '0' && c <= '9')))
123 return xstrdup (resource_name);
126 static const char hexdigit[] = "0123456789abcdef";
127 const char *str = resource_name;
128 const char *str_limit = str + strlen (str);
129 char *class_name = XNMALLOC (12 + 6 * (str_limit - str) + 1, char);
133 memcpy (b, "__UESCAPED__", 12); b += 12;
134 while (str < str_limit)
137 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
142 *b++ = hexdigit[(uc >> 28) & 0x0f];
143 *b++ = hexdigit[(uc >> 24) & 0x0f];
144 *b++ = hexdigit[(uc >> 20) & 0x0f];
145 *b++ = hexdigit[(uc >> 16) & 0x0f];
146 *b++ = hexdigit[(uc >> 12) & 0x0f];
147 *b++ = hexdigit[(uc >> 8) & 0x0f];
148 *b++ = hexdigit[(uc >> 4) & 0x0f];
149 *b++ = hexdigit[uc & 0x0f];
151 else if (!((uc >= 'A' && uc <= 'Z') || (uc >= 'a' && uc <= 'z')
152 || (uc >= '0' && uc <= '9')))
156 *b++ = hexdigit[(uc >> 12) & 0x0f];
157 *b++ = hexdigit[(uc >> 8) & 0x0f];
158 *b++ = hexdigit[(uc >> 4) & 0x0f];
159 *b++ = hexdigit[uc & 0x0f];
165 return (char *) xrealloc (class_name, b - class_name);
170 /* Write a string in C# Unicode notation to the given stream. */
172 write_csharp_string (FILE *stream, const char *str)
174 static const char hexdigit[] = "0123456789abcdef";
175 const char *str_limit = str + strlen (str);
177 fprintf (stream, "\"");
178 while (str < str_limit)
181 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
183 fprintf (stream, "\\0");
184 else if (uc == 0x0007)
185 fprintf (stream, "\\a");
186 else if (uc == 0x0008)
187 fprintf (stream, "\\b");
188 else if (uc == 0x0009)
189 fprintf (stream, "\\t");
190 else if (uc == 0x000a)
191 fprintf (stream, "\\n");
192 else if (uc == 0x000b)
193 fprintf (stream, "\\v");
194 else if (uc == 0x000c)
195 fprintf (stream, "\\f");
196 else if (uc == 0x000d)
197 fprintf (stream, "\\r");
198 else if (uc == 0x0022)
199 fprintf (stream, "\\\"");
200 else if (uc == 0x005c)
201 fprintf (stream, "\\\\");
202 else if (uc >= 0x0020 && uc < 0x007f)
203 fprintf (stream, "%c", (int) uc);
204 else if (uc < 0x10000)
205 fprintf (stream, "\\u%c%c%c%c",
206 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
207 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
209 fprintf (stream, "\\U%c%c%c%c%c%c%c%c",
210 hexdigit[(uc >> 28) & 0x0f], hexdigit[(uc >> 24) & 0x0f],
211 hexdigit[(uc >> 20) & 0x0f], hexdigit[(uc >> 16) & 0x0f],
212 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
213 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
215 fprintf (stream, "\"");
219 /* Write a (msgctxt, msgid) pair as a string in C# Unicode notation to the
222 write_csharp_msgid (FILE *stream, message_ty *mp)
224 const char *msgctxt = mp->msgctxt;
225 const char *msgid = mp->msgid;
228 write_csharp_string (stream, msgid);
231 size_t msgctxt_len = strlen (msgctxt);
232 size_t msgid_len = strlen (msgid);
233 size_t combined_len = msgctxt_len + 1 + msgid_len;
236 combined = (char *) xmalloca (combined_len);
237 memcpy (combined, msgctxt, msgctxt_len);
238 combined[msgctxt_len] = MSGCTXT_SEPARATOR;
239 memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1);
241 write_csharp_string (stream, combined);
248 /* Write C# code that returns the value for a message. If the message
249 has plural forms, it is an expression of type System.String[], otherwise it
250 is an expression of type System.String. */
252 write_csharp_msgstr (FILE *stream, message_ty *mp)
254 if (mp->msgid_plural != NULL)
259 fprintf (stream, "new System.String[] { ");
260 for (p = mp->msgstr, first = true;
261 p < mp->msgstr + mp->msgstr_len;
262 p += strlen (p) + 1, first = false)
265 fprintf (stream, ", ");
266 write_csharp_string (stream, p);
268 fprintf (stream, " }");
272 if (mp->msgstr_len != strlen (mp->msgstr) + 1)
275 write_csharp_string (stream, mp->msgstr);
280 /* Tests whether a plural expression, evaluated according to the C rules,
281 can only produce the values 0 and 1. */
283 is_expression_boolean (struct expression *exp)
285 switch (exp->operation)
298 case greater_or_equal:
305 return (exp->val.num == 0 || exp->val.num == 1);
307 return is_expression_boolean (exp->val.args[1])
308 && is_expression_boolean (exp->val.args[2]);
315 /* Write C# code that evaluates a plural expression according to the C rules.
316 The variable is called 'n'. */
318 write_csharp_expression (FILE *stream, const struct expression *exp, bool as_boolean)
320 /* We use parentheses everywhere. This frees us from tracking the priority
321 of arithmetic operators. */
324 /* Emit a C# expression of type 'bool'. */
325 switch (exp->operation)
328 fprintf (stream, "%s", exp->val.num ? "true" : "false");
331 fprintf (stream, "(!");
332 write_csharp_expression (stream, exp->val.args[0], true);
333 fprintf (stream, ")");
336 fprintf (stream, "(");
337 write_csharp_expression (stream, exp->val.args[0], false);
338 fprintf (stream, " < ");
339 write_csharp_expression (stream, exp->val.args[1], false);
340 fprintf (stream, ")");
343 fprintf (stream, "(");
344 write_csharp_expression (stream, exp->val.args[0], false);
345 fprintf (stream, " > ");
346 write_csharp_expression (stream, exp->val.args[1], false);
347 fprintf (stream, ")");
350 fprintf (stream, "(");
351 write_csharp_expression (stream, exp->val.args[0], false);
352 fprintf (stream, " <= ");
353 write_csharp_expression (stream, exp->val.args[1], false);
354 fprintf (stream, ")");
356 case greater_or_equal:
357 fprintf (stream, "(");
358 write_csharp_expression (stream, exp->val.args[0], false);
359 fprintf (stream, " >= ");
360 write_csharp_expression (stream, exp->val.args[1], false);
361 fprintf (stream, ")");
364 fprintf (stream, "(");
365 write_csharp_expression (stream, exp->val.args[0], false);
366 fprintf (stream, " == ");
367 write_csharp_expression (stream, exp->val.args[1], false);
368 fprintf (stream, ")");
371 fprintf (stream, "(");
372 write_csharp_expression (stream, exp->val.args[0], false);
373 fprintf (stream, " != ");
374 write_csharp_expression (stream, exp->val.args[1], false);
375 fprintf (stream, ")");
378 fprintf (stream, "(");
379 write_csharp_expression (stream, exp->val.args[0], true);
380 fprintf (stream, " && ");
381 write_csharp_expression (stream, exp->val.args[1], true);
382 fprintf (stream, ")");
385 fprintf (stream, "(");
386 write_csharp_expression (stream, exp->val.args[0], true);
387 fprintf (stream, " || ");
388 write_csharp_expression (stream, exp->val.args[1], true);
389 fprintf (stream, ")");
392 if (is_expression_boolean (exp->val.args[1])
393 && is_expression_boolean (exp->val.args[2]))
395 fprintf (stream, "(");
396 write_csharp_expression (stream, exp->val.args[0], true);
397 fprintf (stream, " ? ");
398 write_csharp_expression (stream, exp->val.args[1], true);
399 fprintf (stream, " : ");
400 write_csharp_expression (stream, exp->val.args[2], true);
401 fprintf (stream, ")");
411 fprintf (stream, "(");
412 write_csharp_expression (stream, exp, false);
413 fprintf (stream, " != 0)");
421 /* Emit a C# expression of type 'long'. */
422 switch (exp->operation)
425 fprintf (stream, "n");
428 fprintf (stream, "%lu", exp->val.num);
431 fprintf (stream, "(");
432 write_csharp_expression (stream, exp->val.args[0], false);
433 fprintf (stream, " * ");
434 write_csharp_expression (stream, exp->val.args[1], false);
435 fprintf (stream, ")");
438 fprintf (stream, "(");
439 write_csharp_expression (stream, exp->val.args[0], false);
440 fprintf (stream, " / ");
441 write_csharp_expression (stream, exp->val.args[1], false);
442 fprintf (stream, ")");
445 fprintf (stream, "(");
446 write_csharp_expression (stream, exp->val.args[0], false);
447 fprintf (stream, " %% ");
448 write_csharp_expression (stream, exp->val.args[1], false);
449 fprintf (stream, ")");
452 fprintf (stream, "(");
453 write_csharp_expression (stream, exp->val.args[0], false);
454 fprintf (stream, " + ");
455 write_csharp_expression (stream, exp->val.args[1], false);
456 fprintf (stream, ")");
459 fprintf (stream, "(");
460 write_csharp_expression (stream, exp->val.args[0], false);
461 fprintf (stream, " - ");
462 write_csharp_expression (stream, exp->val.args[1], false);
463 fprintf (stream, ")");
466 fprintf (stream, "(");
467 write_csharp_expression (stream, exp->val.args[0], true);
468 fprintf (stream, " ? ");
469 write_csharp_expression (stream, exp->val.args[1], false);
470 fprintf (stream, " : ");
471 write_csharp_expression (stream, exp->val.args[2], false);
472 fprintf (stream, ")");
478 case greater_or_equal:
483 fprintf (stream, "(");
484 write_csharp_expression (stream, exp, true);
485 fprintf (stream, " ? 1 : 0)");
494 /* Write the C# code for the GettextResourceSet subclass to the given stream.
495 Note that we use fully qualified class names and no "using" statements,
496 because applications can have their own classes called X.Y.Hashtable or
499 write_csharp_code (FILE *stream, const char *culture_name, const char *class_name, message_list_ty *mlp)
501 const char *last_dot;
502 const char *class_name_last_part;
503 unsigned int plurals;
507 "/* Automatically generated by GNU msgfmt. Do not modify! */\n");
509 /* We have to use a "using" statement here, to avoid a bug in the pnet-0.6.0
511 fprintf (stream, "using GNU.Gettext;\n");
513 /* Assign a strong name to the assembly, so that two different localizations
514 of the same domain can be loaded one after the other. This strong name
515 tells the Global Assembly Cache that they are meant to be different. */
516 fprintf (stream, "[assembly: System.Reflection.AssemblyCulture(");
517 write_csharp_string (stream, culture_name);
518 fprintf (stream, ")]\n");
520 last_dot = strrchr (class_name, '.');
521 if (last_dot != NULL)
523 fprintf (stream, "namespace ");
524 fwrite (class_name, 1, last_dot - class_name, stream);
525 fprintf (stream, " {\n");
526 class_name_last_part = last_dot + 1;
529 class_name_last_part = class_name;
530 fprintf (stream, "public class %s : GettextResourceSet {\n",
531 class_name_last_part);
533 /* Determine whether there are plural messages. */
535 for (j = 0; j < mlp->nitems; j++)
536 if (mlp->item[j]->msgid_plural != NULL)
539 /* Emit the constructor. */
540 fprintf (stream, " public %s ()\n", class_name_last_part);
541 fprintf (stream, " : base () {\n");
542 fprintf (stream, " }\n");
544 /* Emit the TableInitialized field. */
545 fprintf (stream, " private bool TableInitialized;\n");
547 /* Emit the ReadResources method. */
548 fprintf (stream, " protected override void ReadResources () {\n");
549 /* In some implementations, such as mono < 2009-02-27, the ReadResources
550 method is called just once, when Table == null. In other implementations,
551 such as mono >= 2009-02-27, it is called at every GetObject call, and it
552 is responsible for doing the initialization only once, even when called
553 simultaneously from multiple threads. */
554 fprintf (stream, " if (!TableInitialized) {\n");
555 fprintf (stream, " lock (this) {\n");
556 fprintf (stream, " if (!TableInitialized) {\n");
557 /* In some implementations, the ResourceSet constructor initializes Table
558 before calling ReadResources(). In other implementations, the
559 ReadResources() method is expected to initialize the Table. */
560 fprintf (stream, " if (Table == null)\n");
561 fprintf (stream, " Table = new System.Collections.Hashtable();\n");
562 fprintf (stream, " System.Collections.Hashtable t = Table;\n");
563 for (j = 0; j < mlp->nitems; j++)
565 fprintf (stream, " t.Add(");
566 write_csharp_msgid (stream, mlp->item[j]);
567 fprintf (stream, ",");
568 write_csharp_msgstr (stream, mlp->item[j]);
569 fprintf (stream, ");\n");
571 fprintf (stream, " TableInitialized = true;\n");
572 fprintf (stream, " }\n");
573 fprintf (stream, " }\n");
574 fprintf (stream, " }\n");
575 fprintf (stream, " }\n");
577 /* Emit the msgid_plural strings. Only used by msgunfmt. */
580 fprintf (stream, " public static System.Collections.Hashtable GetMsgidPluralTable () {\n");
581 fprintf (stream, " System.Collections.Hashtable t = new System.Collections.Hashtable();\n");
582 for (j = 0; j < mlp->nitems; j++)
583 if (mlp->item[j]->msgid_plural != NULL)
585 fprintf (stream, " t.Add(");
586 write_csharp_msgid (stream, mlp->item[j]);
587 fprintf (stream, ",");
588 write_csharp_string (stream, mlp->item[j]->msgid_plural);
589 fprintf (stream, ");\n");
591 fprintf (stream, " return t;\n");
592 fprintf (stream, " }\n");
595 /* Emit the PluralEval function. It is a subroutine for GetPluralString. */
598 message_ty *header_entry;
599 const struct expression *plural;
600 unsigned long int nplurals;
602 header_entry = message_list_search (mlp, NULL, "");
603 extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
606 fprintf (stream, " protected override long PluralEval (long n) {\n");
607 fprintf (stream, " return ");
608 write_csharp_expression (stream, plural, false);
609 fprintf (stream, ";\n");
610 fprintf (stream, " }\n");
613 /* Terminate the class. */
614 fprintf (stream, "}\n");
616 if (last_dot != NULL)
617 /* Terminate the namespace. */
618 fprintf (stream, "}\n");
623 msgdomain_write_csharp (message_list_ty *mlp, const char *canon_encoding,
624 const char *resource_name, const char *locale_name,
625 const char *directory)
628 struct temp_dir *tmpdir;
632 char *csharp_file_name;
634 const char *gettextlibdir;
635 const char *csharp_sources[1];
636 const char *libdirs[1];
637 const char *libraries[1];
639 /* If no entry for this resource/domain, don't even create the file. */
640 if (mlp->nitems == 0)
645 /* Convert the messages to Unicode. */
646 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
648 /* Create a temporary directory where we can put the C# file.
649 A simple temporary file would also be possible but would require us to
650 define our own variant of mkstemp(): On one hand the functions mktemp(),
651 tmpnam(), tempnam() present a security risk, and on the other hand the
652 function mkstemp() doesn't allow to specify a fixed suffix of the file.
653 It is simpler to create a temporary directory. */
654 tmpdir = create_temp_dir ("msg", NULL, false);
658 /* Assign a default value to the resource name. */
659 if (resource_name == NULL)
660 resource_name = "Messages";
662 /* Convert the locale name to a .NET specific culture name. */
663 culture_name = xstrdup (locale_name);
666 for (p = culture_name; *p != '\0'; p++)
669 if (strncmp (culture_name, "sr-CS", 5) == 0)
670 memcpy (culture_name, "sr-SP", 5);
671 p = strchr (culture_name, '@');
674 if (strcmp (p, "@latin") == 0)
676 else if (strcmp (p, "@cyrillic") == 0)
679 if (strcmp (culture_name, "sr-SP") == 0)
682 culture_name = xstrdup ("sr-SP-Latn");
684 else if (strcmp (culture_name, "uz-UZ") == 0)
687 culture_name = xstrdup ("uz-UZ-Latn");
691 /* Compute the output file name. This code must be kept consistent with
692 intl.cs, function GetSatelliteAssembly(). */
694 char *output_dir = xconcatenated_filename (directory, culture_name, NULL);
697 /* Try to create the output directory if it does not yet exist. */
698 if (stat (output_dir, &statbuf) < 0 && errno == ENOENT)
699 if (mkdir (output_dir, S_IRUSR | S_IWUSR | S_IXUSR
700 | S_IRGRP | S_IWGRP | S_IXGRP
701 | S_IROTH | S_IWOTH | S_IXOTH) < 0)
703 error (0, errno, _("failed to create directory \"%s\""), output_dir);
709 xconcatenated_filename (output_dir, resource_name, ".resources.dll");
714 /* Compute the class name. This code must be kept consistent with intl.cs,
715 function InstantiateResourceSet(). */
717 char *class_name_part1 = construct_class_name (resource_name);
721 XNMALLOC (strlen (class_name_part1) + 1 + strlen (culture_name) + 1, char);
722 sprintf (class_name, "%s_%s", class_name_part1, culture_name);
723 for (p = class_name + strlen (class_name_part1) + 1; *p != '\0'; p++)
726 free (class_name_part1);
729 /* Compute the temporary C# file name. It must end in ".cs", so that
730 the C# compiler recognizes that it is C# source code. */
732 xconcatenated_filename (tmpdir->dir_name, "resset.cs", NULL);
734 /* Create the C# file. */
735 register_temp_file (tmpdir, csharp_file_name);
736 csharp_file = fopen_temp (csharp_file_name, "w");
737 if (csharp_file == NULL)
739 error (0, errno, _("failed to create \"%s\""), csharp_file_name);
740 unregister_temp_file (tmpdir, csharp_file_name);
744 write_csharp_code (csharp_file, culture_name, class_name, mlp);
746 if (fwriteerror_temp (csharp_file))
748 error (0, errno, _("error while writing \"%s\" file"), csharp_file_name);
752 /* Make it possible to override the .dll location. This is
753 necessary for running the testsuite before "make install". */
754 gettextlibdir = getenv ("GETTEXTCSHARPLIBDIR");
755 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
756 gettextlibdir = relocate (LIBDIR);
758 /* Compile the C# file to a .dll file. */
759 csharp_sources[0] = csharp_file_name;
760 libdirs[0] = gettextlibdir;
761 libraries[0] = "GNU.Gettext";
762 if (compile_csharp_class (csharp_sources, 1, libdirs, 1, libraries, 1,
763 output_file, true, false, verbose > 0))
766 error (0, 0, _("compilation of C# class failed, please try --verbose"));
768 error (0, 0, _("compilation of C# class failed"));
775 free (csharp_file_name);
780 cleanup_temp_dir (tmpdir);