1 /* $NetBSD: plural_parser.c,v 1.2 2007/01/17 23:24:22 hubertf Exp $ */
4 * Copyright (c) 2005 Citrus Project,
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: plural_parser.c,v 1.2 2007/01/17 23:24:22 hubertf Exp $");
37 #include <citrus/citrus_namespace.h>
38 #include <citrus/citrus_region.h>
39 #include <citrus/citrus_memstream.h>
40 #include <citrus/citrus_bcs.h>
41 #include "plural_parser.h"
43 #if defined(TEST_TOKENIZER) || defined(TEST_PARSER)
45 #define ALLOW_ARBITRARY_IDENTIFIER
48 #define MAX_LEN_ATOM 10
49 #define MAX_NUM_OPERANDS 3
53 #define T_LAND 0x101 /* && */
54 #define T_LOR 0x102 /* || */
55 #define T_EQUALITY 0x103 /* == or != */
56 #define T_RELATIONAL 0x104 /* <, >, <= or >= */
57 #define T_ADDITIVE 0x105 /* + or - */
58 #define T_MULTIPLICATIVE 0x106 /* *, / or % */
59 #define T_IDENTIFIER 0x200
60 #define T_CONSTANT 0x201
61 #define T_ILCHAR 0x300
62 #define T_TOOLONG 0x301
63 #define T_ILTOKEN 0x302
66 #define T_NOTFOUND 0x305
67 #define T_ILPLURAL 0x306
68 #define T_IS_OPERATOR(t) ((t) < 0x200)
69 #define T_IS_ERROR(t) ((t) >= 0x300)
71 #define OP_EQ ('='+'=')
72 #define OP_NEQ ('!'+'=')
73 #define OP_LTEQ ('<'+'=')
74 #define OP_GTEQ ('>'+'=')
76 #define PLURAL_NUMBER_SYMBOL "n"
77 #define NPLURALS_SYMBOL "nplurals"
78 #define LEN_NPLURAL_SYMBOL (sizeof (NPLURALS_SYMBOL) -1)
79 #define PLURAL_SYMBOL "plural"
80 #define LEN_PLURAL_SYMBOL (sizeof (PLURAL_SYMBOL) -1)
81 #define PLURAL_FORMS "Plural-Forms:"
82 #define LEN_PLURAL_FORMS (sizeof (PLURAL_FORMS) -1)
84 /* ----------------------------------------------------------------------
90 unsigned long constant
;
91 #ifdef ALLOW_ARBITRARY_IDENTIFIER
92 char identifier
[MAX_LEN_ATOM
+1];
97 struct tokenizer_context
99 struct _memstream memstream
;
102 union token_data token_data
;
106 /* initialize a tokenizer context */
108 init_tokenizer_context(struct tokenizer_context
*tcx
)
110 tcx
->token0
.token
= T_NONE
;
113 /* get an atom (identifier or constant) */
115 tokenize_atom(struct tokenizer_context
*tcx
, union token_data
*token_data
)
118 char buf
[MAX_LEN_ATOM
+1];
121 while (/*CONSTCOND*/1) {
122 ch
= _memstream_getc(&tcx
->memstream
);
123 if (!(_bcs_isalnum(ch
) || ch
== '_')) {
124 _memstream_ungetc(&tcx
->memstream
, ch
);
127 if (len
== MAX_LEN_ATOM
)
135 if (_bcs_isdigit((int)(unsigned char)buf
[0])) {
138 ul
= strtoul(buf
, &post
, 0);
141 token_data
->constant
= ul
;
145 #ifdef ALLOW_ARBITRARY_IDENTIFIER
146 strcpy(token_data
->identifier
, buf
);
149 if (!strcmp(buf
, PLURAL_NUMBER_SYMBOL
))
155 /* tokenizer main routine */
157 tokenize(struct tokenizer_context
*tcx
, union token_data
*token_data
)
162 ch
= _memstream_getc(&tcx
->memstream
);
163 if (_bcs_isspace(ch
))
172 case '*': case '/': case '%':
174 return T_MULTIPLICATIVE
;
175 case '?': case ':': case '(': case ')':
180 ch
= _memstream_getc(&tcx
->memstream
);
182 _memstream_ungetc(&tcx
->memstream
, ch
);
193 case '=': case '!': case '<': case '>':
195 ch
= _memstream_getc(&tcx
->memstream
);
197 _memstream_ungetc(&tcx
->memstream
, ch
);
205 token_data
->op
= prevch
; /* OP_LT or OP_GT */
209 /* '==', '!=', '<=' or '>=' */
210 token_data
->op
= ch
+prevch
;
222 _memstream_ungetc(&tcx
->memstream
, ch
);
223 return tokenize_atom(tcx
, token_data
);
226 /* get the next token */
228 get_token(struct tokenizer_context
*tcx
, union token_data
*token_data
)
230 if (tcx
->token0
.token
!= T_NONE
) {
231 int token
= tcx
->token0
.token
;
232 tcx
->token0
.token
= T_NONE
;
233 *token_data
= tcx
->token0
.token_data
;
236 return tokenize(tcx
, token_data
);
239 /* push back the last token */
241 unget_token(struct tokenizer_context
*tcx
,
242 int token
, union token_data
*token_data
)
244 tcx
->token0
.token
= token
;
245 tcx
->token0
.token_data
= *token_data
;
248 #ifdef TEST_TOKENIZER
251 main(int argc
, char **argv
)
253 struct tokenizer_context tcx
;
254 union token_data token_data
;
258 fprintf(stderr
, "usage: %s <expression>\n", argv
[0]);
262 init_tokenizer_context(&tcx
);
263 _memstream_bind_ptr(&tcx
.memstream
, argv
[1], strlen(argv
[1]));
266 token
= get_token(&tcx
, &token_data
);
271 printf("illegal character.\n");
274 printf("too long atom.\n");
277 printf("constant: %lu\n", token_data
.constant
);
280 printf("symbol: %s\n", token_data
.identifier
);
283 printf("operator: ");
292 printf("%c=\n", token_data
.op
-'=');
295 switch(token_data
.op
) {
298 printf("%c=\n", token_data
.op
-'=');
301 printf("%c\n", token_data
.op
);
306 case T_MULTIPLICATIVE
:
307 printf("%c\n", token_data
.op
);
310 printf("operator: %c\n", token
);
317 #endif /* TEST_TOKENIZER */
320 /* ----------------------------------------------------------------------
325 * cond := lor | lor '?' cond ':' cond
327 * lor := land ( '||' land )*
329 * land := equality ( '&&' equality )*
331 * equality := relational ( equalityops relational )*
332 * equalityops := '==' | '!='
334 * relational := additive ( relationalops additive )*
335 * relationalops := '<' | '>' | '<=' | '>='
337 * additive := multiplicative ( additiveops multiplicative )*
338 * additiveops := '+' | '-'
340 * multiplicative := lnot ( multiplicativeops lnot )*
341 * multiplicativeops := '*' | '/' | '%'
343 * lnot := '!' lnot | term
345 * term := literal | identifier | '(' exp ')'
349 #define T_ENSURE_OK(token, label) \
351 if (T_IS_ERROR(token)) \
353 } while (/*CONSTCOND*/0)
354 #define T_ENSURE_SOMETHING(token, label) \
356 if ((token) == T_EOF) { \
359 } else if (T_IS_ERROR(token)) \
361 } while (/*CONSTCOND*/0)
363 #define parser_element plural_element
365 struct parser_element
;
369 struct parser_element
*operands
[MAX_NUM_OPERANDS
];
371 struct parser_element
376 struct parser_op parser_op
;
377 union token_data token_data
;
381 struct parser_op2_transition
384 const struct parser_op2_transition
*next
;
388 static int parse_cond(struct tokenizer_context
*, struct parser_element
*);
391 /* transition table for the 2-operand operators */
392 #define DEF_TR(t, k, n) \
393 static struct parser_op2_transition exp_tr_##t = { \
396 #define DEF_TR0(t, k) \
397 static struct parser_op2_transition exp_tr_##t = { \
398 k, NULL /* expect lnot */ \
401 DEF_TR0(multiplicative
, T_MULTIPLICATIVE
);
402 DEF_TR(additive
, T_ADDITIVE
, multiplicative
);
403 DEF_TR(relational
, T_RELATIONAL
, additive
);
404 DEF_TR(equality
, T_EQUALITY
, relational
);
405 DEF_TR(land
, T_LAND
, equality
);
406 DEF_TR(lor
, T_LOR
, land
);
408 /* init a parser element structure */
410 init_parser_element(struct parser_element
*pe
)
415 for (i
=0; i
<MAX_NUM_OPERANDS
; i
++)
416 pe
->u
.parser_op
.operands
[i
] = NULL
;
419 /* uninitialize a parser element structure with freeing children */
420 static void free_parser_element(struct parser_element
*);
422 uninit_parser_element(struct parser_element
*pe
)
426 if (T_IS_OPERATOR(pe
->kind
))
427 for (i
=0; i
<MAX_NUM_OPERANDS
; i
++)
428 if (pe
->u
.parser_op
.operands
[i
])
430 pe
->u
.parser_op
.operands
[i
]);
433 /* free a parser element structure with freeing children */
435 free_parser_element(struct parser_element
*pe
)
438 uninit_parser_element(pe
);
444 /* copy a parser element structure shallowly */
446 copy_parser_element(struct parser_element
*dpe
,
447 const struct parser_element
*spe
)
449 memcpy(dpe
, spe
, sizeof *dpe
);
452 /* duplicate a parser element structure shallowly */
453 static struct parser_element
*
454 dup_parser_element(const struct parser_element
*pe
)
456 struct parser_element
*dpe
= malloc(sizeof *dpe
);
458 copy_parser_element(dpe
, pe
);
462 /* term := identifier | constant | '(' exp ')' */
464 parse_term(struct tokenizer_context
*tcx
, struct parser_element
*pelem
)
466 struct parser_element pe0
;
468 union token_data token_data
;
470 token
= get_token(tcx
, &token_data
);
474 init_parser_element(&pe0
);
476 token
= parse_cond(tcx
, &pe0
);
477 T_ENSURE_OK(token
, err
);
479 token
= get_token(tcx
, &token_data
);
480 T_ENSURE_SOMETHING(token
, err
);
482 unget_token(tcx
, token
, &token_data
);
486 copy_parser_element(pelem
, &pe0
);
489 uninit_parser_element(&pe0
);
494 pelem
->u
.token_data
= token_data
;
503 /* lnot := '!' lnot | term */
505 parse_lnot(struct tokenizer_context
*tcx
, struct parser_element
*pelem
)
507 struct parser_element pe0
;
509 union token_data token_data
;
511 init_parser_element(&pe0
);
514 token
= get_token(tcx
, &token_data
);
517 unget_token(tcx
, token
, &token_data
);
518 return parse_term(tcx
, pelem
);
522 token
= parse_lnot(tcx
, &pe0
);
523 T_ENSURE_OK(token
, err
);
526 pelem
->u
.parser_op
.operands
[0] = dup_parser_element(&pe0
);
529 uninit_parser_element(&pe0
);
533 /* ext_op := ext_next ( op ext_next )* */
535 parse_op2(struct tokenizer_context
*tcx
, struct parser_element
*pelem
,
536 const struct parser_op2_transition
*tr
)
538 struct parser_element pe0
, pe1
, peop
;
540 union token_data token_data
;
543 /* special case: expect lnot */
545 return parse_lnot(tcx
, pelem
);
547 init_parser_element(&pe0
);
548 init_parser_element(&pe1
);
549 token
= parse_op2(tcx
, &pe0
, tr
->next
);
550 T_ENSURE_OK(token
, err
);
552 while (/*CONSTCOND*/1) {
553 /* expect op or empty */
554 token
= get_token(tcx
, &token_data
);
555 if (token
!= tr
->kind
) {
557 unget_token(tcx
, token
, &token_data
);
558 copy_parser_element(pelem
, &pe0
);
563 token
= parse_op2(tcx
, &pe1
, tr
->next
);
564 T_ENSURE_OK(token
, err
);
566 init_parser_element(&peop
);
567 peop
.kind
= tr
->kind
;
568 peop
.u
.parser_op
.op
= op
;
569 peop
.u
.parser_op
.operands
[0] = dup_parser_element(&pe0
);
570 init_parser_element(&pe0
);
571 peop
.u
.parser_op
.operands
[1] = dup_parser_element(&pe1
);
572 init_parser_element(&pe1
);
573 copy_parser_element(&pe0
, &peop
);
577 uninit_parser_element(&pe1
);
578 uninit_parser_element(&pe0
);
582 /* cond := lor | lor '?' cond ':' cond */
584 parse_cond(struct tokenizer_context
*tcx
, struct parser_element
*pelem
)
586 struct parser_element pe0
, pe1
, pe2
;
588 union token_data token_data
;
590 init_parser_element(&pe0
);
591 init_parser_element(&pe1
);
592 init_parser_element(&pe2
);
594 /* expect lor or empty */
595 token
= parse_op2(tcx
, &pe0
, &exp_tr_lor
);
596 T_ENSURE_OK(token
, err
);
599 token
= get_token(tcx
, &token_data
);
602 unget_token(tcx
, token
, &token_data
);
603 copy_parser_element(pelem
, &pe0
);
607 /* lor '?' cond ':' cond */
609 token
= parse_cond(tcx
, &pe1
);
610 T_ENSURE_OK(token
, err
);
613 token
= get_token(tcx
, &token_data
);
614 T_ENSURE_OK(token
, err
);
616 unget_token(tcx
, token
, &token_data
);
622 token
= parse_cond(tcx
, &pe2
);
623 T_ENSURE_OK(token
, err
);
626 pelem
->u
.parser_op
.operands
[0] = dup_parser_element(&pe0
);
627 pelem
->u
.parser_op
.operands
[1] = dup_parser_element(&pe1
);
628 pelem
->u
.parser_op
.operands
[2] = dup_parser_element(&pe2
);
631 uninit_parser_element(&pe2
);
632 uninit_parser_element(&pe1
);
633 uninit_parser_element(&pe0
);
638 parse_exp(struct tokenizer_context
*tcx
, struct parser_element
*pelem
)
641 union token_data token_data
;
645 token
= get_token(tcx
, &token_data
);
648 unget_token(tcx
, token
, &token_data
);
651 token
= parse_cond(tcx
, pelem
);
652 if (!T_IS_ERROR(token
)) {
653 /* termination check */
654 token1
= get_token(tcx
, &token_data
);
657 else if (!T_IS_ERROR(token
))
658 unget_token(tcx
, token1
, &token_data
);
665 #if defined(TEST_PARSER) || defined(TEST_PARSE_PLURAL)
668 static void dump_elem(struct parser_element
*);
671 dump_op2(struct parser_element
*pelem
)
673 dump_elem(pelem
->u
.parser_op
.operands
[0]);
675 dump_elem(pelem
->u
.parser_op
.operands
[1]);
680 dump_op3(struct parser_element
*pelem
)
682 dump_elem(pelem
->u
.parser_op
.operands
[0]);
684 dump_elem(pelem
->u
.parser_op
.operands
[1]);
686 dump_elem(pelem
->u
.parser_op
.operands
[2]);
691 dump_elem(struct parser_element
*pelem
)
693 switch (pelem
->kind
) {
703 switch (pelem
->u
.parser_op
.op
) {
714 switch (pelem
->u
.parser_op
.op
) {
717 printf("(%c ", pelem
->u
.parser_op
.op
);
721 printf("(%c= ", pelem
->u
.parser_op
.op
-'=');
727 case T_MULTIPLICATIVE
:
728 printf("(%c ", pelem
->u
.parser_op
.op
);
733 dump_elem(pelem
->u
.parser_op
.operands
[0]);
741 printf("%d", pelem
->u
.token_data
.constant
);
744 #ifdef ALLOW_ARBITRARY_IDENTIFIER
745 printf("%s", pelem
->u
.token_data
.identifier
);
747 printf(PLURAL_NUMBER_SYMBOL
);
755 main(int argc
, char **argv
)
757 struct tokenizer_context tcx
;
758 struct parser_element pelem
;
762 fprintf(stderr
, "usage: %s <expression>\n", argv
[0]);
766 init_tokenizer_context(&tcx
);
767 _memstream_bind_ptr(&tcx
.memstream
, argv
[1], strlen(argv
[1]));
769 init_parser_element(&pelem
);
770 token
= parse_exp(&tcx
, &pelem
);
774 else if (T_IS_ERROR(token
))
775 printf("error: 0x%X", token
);
780 uninit_parser_element(&pelem
);
784 #endif /* TEST_PARSER */
786 /* ----------------------------------------------------------------------
787 * calcurate plural number
790 calculate_plural(const struct parser_element
*pe
, unsigned long n
)
792 unsigned long val0
, val1
;
797 return pe
->u
.token_data
.constant
;
799 val0
= calculate_plural(pe
->u
.parser_op
.operands
[0], n
);
801 val1
=calculate_plural(pe
->u
.parser_op
.operands
[1], n
);
803 val1
=calculate_plural(pe
->u
.parser_op
.operands
[2], n
);
806 return !calculate_plural(pe
->u
.parser_op
.operands
[0], n
);
807 case T_MULTIPLICATIVE
:
813 val0
= calculate_plural(pe
->u
.parser_op
.operands
[0], n
);
814 val1
= calculate_plural(pe
->u
.parser_op
.operands
[1], n
);
815 switch (pe
->u
.parser_op
.op
) {
847 #ifdef TEST_CALC_PLURAL
851 main(int argc
, char **argv
)
853 struct tokenizer_context tcx
;
854 struct parser_element pelem
;
858 fprintf(stderr
, "usage: %s <expression> <n>\n", argv
[0]);
862 init_tokenizer_context(&tcx
);
863 _memstream_bind_ptr(&tcx
.memstream
, argv
[1], strlen(argv
[1]));
865 init_parser_element(&pelem
);
866 token
= parse_exp(&tcx
, &pelem
);
870 else if (T_IS_ERROR(token
))
871 printf("error: 0x%X", token
);
873 printf("plural = %lu",
874 calculate_plural(&pelem
, atoi(argv
[2])));
878 uninit_parser_element(&pelem
);
882 #endif /* TEST_CALC_PLURAL */
885 /* ----------------------------------------------------------------------
890 region_skip_ws(struct _region
*r
)
892 const char *str
= _region_head(r
);
893 size_t len
= _region_size(r
);
895 str
= _bcs_skip_ws_len(str
, &len
);
896 _region_init(r
, __UNCONST(str
), len
);
900 region_trunc_rws(struct _region
*r
)
902 const char *str
= _region_head(r
);
903 size_t len
= _region_size(r
);
905 _bcs_trunc_rws_len(str
, &len
);
906 _region_init(r
, __UNCONST(str
), len
);
910 region_check_prefix(struct _region
*r
, const char *pre
, size_t prelen
,
913 if (_region_size(r
) < prelen
)
917 if (_bcs_strncasecmp(_region_head(r
), pre
, prelen
))
920 if (memcmp(_region_head(r
), pre
, prelen
))
927 cut_trailing_semicolon(struct _region
*r
)
931 if (_region_size(r
) == 0 || _region_peek8(r
, _region_size(r
)-1) != ';')
933 _region_get_subregion(r
, r
, 0, _region_size(r
)-1);
938 find_plural_forms(struct _region
*r
)
940 struct _memstream ms
;
943 _memstream_bind(&ms
, r
);
945 while (!_memstream_getln_region(&ms
, &rr
)) {
946 if (!region_check_prefix(&rr
,
947 PLURAL_FORMS
, LEN_PLURAL_FORMS
, 1)) {
948 _region_get_subregion(
949 r
, &rr
, LEN_PLURAL_FORMS
,
950 _region_size(&rr
)-LEN_PLURAL_FORMS
);
960 skip_assignment(struct _region
*r
, const char *sym
, size_t symlen
)
963 if (region_check_prefix(r
, sym
, symlen
, 0))
965 _region_get_subregion(r
, r
, symlen
, _region_size(r
)-symlen
);
967 if (_region_size(r
) == 0 || _region_peek8(r
, 0) != '=')
969 _region_get_subregion(r
, r
, 1, _region_size(r
)-1);
975 skip_nplurals(struct _region
*r
, unsigned long *rnp
)
978 char buf
[MAX_LEN_ATOM
+2], *endptr
;
979 const char *endptrconst
;
982 if (skip_assignment(r
, NPLURALS_SYMBOL
, LEN_NPLURAL_SYMBOL
))
984 if (_region_size(r
) == 0 || !_bcs_isdigit(_region_peek8(r
, 0)))
986 strlcpy(buf
, _region_head(r
), sizeof (buf
));
987 np
= strtoul(buf
, &endptr
, 0);
988 endptrconst
= _bcs_skip_ws(endptr
);
989 if (*endptrconst
!= ';')
991 ofs
= endptrconst
+1-buf
;
992 if (_region_get_subregion(r
, r
, ofs
, _region_size(r
)-ofs
))
1000 parse_plural_body(struct _region
*r
, struct parser_element
**rpe
)
1003 struct tokenizer_context tcx
;
1004 struct parser_element pelem
, *ppe
;
1006 init_tokenizer_context(&tcx
);
1007 _memstream_bind(&tcx
.memstream
, r
);
1009 init_parser_element(&pelem
);
1010 token
= parse_exp(&tcx
, &pelem
);
1011 if (T_IS_ERROR(token
))
1014 ppe
= dup_parser_element(&pelem
);
1016 uninit_parser_element(&pelem
);
1026 parse_plural(struct parser_element
**rpe
, unsigned long *rnp
,
1027 const char *str
, size_t len
)
1031 _region_init(&r
, __UNCONST(str
), len
);
1033 if (find_plural_forms(&r
))
1035 if (skip_nplurals(&r
, rnp
))
1037 if (skip_assignment(&r
, PLURAL_SYMBOL
, LEN_PLURAL_SYMBOL
))
1039 if (cut_trailing_semicolon(&r
))
1041 return parse_plural_body(&r
, rpe
);
1044 #ifdef TEST_PARSE_PLURAL
1046 main(int argc
, char **argv
)
1049 struct parser_element
*pelem
;
1052 if (argc
!= 2 && argc
!= 3) {
1053 fprintf(stderr
, "usage: %s <mime-header> [n]\n", argv
[0]);
1054 return EXIT_FAILURE
;
1057 ret
= parse_plural(&pelem
, &np
, argv
[1], strlen(argv
[1]));
1061 else if (T_IS_ERROR(ret
))
1062 printf("error: 0x%X", ret
);
1064 printf("syntax tree: ");
1066 printf("\nnplurals = %lu", np
);
1068 printf(", plural = %lu",
1069 calculate_plural(pelem
, atoi(argv
[2])));
1070 free_parser_element(pelem
);
1075 return EXIT_SUCCESS
;
1077 #endif /* TEST_PARSE_PLURAL */
1080 * external interface
1084 _gettext_parse_plural(struct gettext_plural
**rpe
, unsigned long *rnp
,
1085 const char *str
, size_t len
)
1087 return parse_plural((struct parser_element
**)rpe
, rnp
, str
, len
);
1091 _gettext_calculate_plural(const struct gettext_plural
*pe
, unsigned long n
)
1093 return calculate_plural((void *)__UNCONST(pe
), n
);
1097 _gettext_free_plural(struct gettext_plural
*pe
)
1099 free_parser_element((void *)pe
);
1103 #include <libintl.h>
1106 #define PR(n) printf("n=%d: \"%s\"\n", n, dngettext("test", "1", "2", n))
1111 bindtextdomain("test", "."); /* ./LANG/LC_MESSAGES/test.mo */