summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authortshiozak <tshiozak@NetBSD.org>2005-05-14 17:58:56 +0000
committertshiozak <tshiozak@NetBSD.org>2005-05-14 17:58:56 +0000
commitdd416aa8683822d3dd271095da0be90cdfd3efde (patch)
tree19b69ea6a4acc7cb2548d3a0e8a6fca41cd21252 /lib
parenta023aa0fb7fc6c6d1c980210b8bd19cedc8e70b5 (diff)
add plural support.
Diffstat (limited to 'lib')
-rw-r--r--lib/libintl/Makefile6
-rw-r--r--lib/libintl/gettext.c85
-rw-r--r--lib/libintl/libintl_local.h5
-rw-r--r--lib/libintl/plural_parser.c1120
-rw-r--r--lib/libintl/plural_parser.h43
5 files changed, 1237 insertions, 22 deletions
diff --git a/lib/libintl/Makefile b/lib/libintl/Makefile
index 1305d92beac..8f76d988430 100644
--- a/lib/libintl/Makefile
+++ b/lib/libintl/Makefile
@@ -1,15 +1,15 @@
-# $NetBSD: Makefile,v 1.5 2004/09/23 21:35:27 tshiozak Exp $
+# $NetBSD: Makefile,v 1.6 2005/05/14 17:58:56 tshiozak Exp $
.include <bsd.own.mk>
LIB= intl
SRCS= gettext.c textdomain.c gettext_iconv.c gettext_dummy.c strhash.c \
- sysdep.c
+ sysdep.c plural_parser.c
INCS= libintl.h
INCSDIR=/usr/include
#CFLAGS+=-g
-CPPFLAGS+=-I${.CURDIR}
+CPPFLAGS+=-I${.CURDIR} -I${.CURDIR}/../libc
MAN= gettext.3
MLINKS= gettext.3 dgettext.3 gettext.3 dcgettext.3 \
diff --git a/lib/libintl/gettext.c b/lib/libintl/gettext.c
index b6d0e958487..d5fa3c7f825 100644
--- a/lib/libintl/gettext.c
+++ b/lib/libintl/gettext.c
@@ -1,4 +1,4 @@
-/* $NetBSD: gettext.c,v 1.21 2005/04/27 09:51:52 yamt Exp $ */
+/* $NetBSD: gettext.c,v 1.22 2005/05/14 17:58:56 tshiozak Exp $ */
/*-
* Copyright (c) 2000, 2001 Citrus Project,
@@ -29,7 +29,7 @@
*/
#include <sys/cdefs.h>
-__RCSID("$NetBSD: gettext.c,v 1.21 2005/04/27 09:51:52 yamt Exp $");
+__RCSID("$NetBSD: gettext.c,v 1.22 2005/05/14 17:58:56 tshiozak Exp $");
#include <sys/param.h>
#include <sys/stat.h>
@@ -48,6 +48,7 @@ __RCSID("$NetBSD: gettext.c,v 1.21 2005/04/27 09:51:52 yamt Exp $");
#include <libintl.h>
#include <locale.h>
#include "libintl_local.h"
+#include "plural_parser.h"
#include "pathnames.h"
static const char *lookup_category __P((int));
@@ -58,9 +59,12 @@ static u_int32_t flip __P((u_int32_t, u_int32_t));
static int validate __P((void *, struct mohandle *));
static int mapit __P((const char *, struct domainbinding *));
static int unmapit __P((struct domainbinding *));
-static const char *lookup_hash __P((const char *, struct domainbinding *));
-static const char *lookup_bsearch __P((const char *, struct domainbinding *));
-static const char *lookup __P((const char *, struct domainbinding *));
+static const char *lookup_hash __P((const char *, struct domainbinding *,
+ size_t *));
+static const char *lookup_bsearch __P((const char *, struct domainbinding *,
+ size_t *));
+static const char *lookup __P((const char *, struct domainbinding *,
+ size_t *));
static const char *get_lang_env __P((const char *));
/*
@@ -503,7 +507,7 @@ mapit(path, db)
const u_int32_t *htable;
struct moentry_h *p;
struct mo *mo;
- size_t l;
+ size_t l, headerlen;
int i;
char *v;
struct mohandle *mohandle = &db->mohandle;
@@ -635,7 +639,7 @@ mapit(path, db)
}
}
/* grab MIME-header and charset field */
- mohandle->mo.mo_header = lookup("", db);
+ mohandle->mo.mo_header = lookup("", db, &headerlen);
if (mohandle->mo.mo_header)
v = strstr(mohandle->mo.mo_header, "charset=");
else
@@ -648,6 +652,10 @@ mapit(path, db)
if (v)
*v = '\0';
}
+ if (_gettext_parse_plural(&mohandle->mo.mo_plural,
+ &mohandle->mo.mo_nplurals,
+ mohandle->mo.mo_header, headerlen))
+ mohandle->mo.mo_plural = NULL;
/*
* XXX check charset, reject it if we are unable to support the charset
@@ -714,15 +722,18 @@ unmapit(db)
free_sysdep_table(mohandle->mo.mo_sysdep_ttable,
mohandle->mo.mo_sysdep_nstring);
}
+ if (mohandle->mo.mo_plural)
+ _gettext_free_plural(mohandle->mo.mo_plural);
memset(&mohandle->mo, 0, sizeof(mohandle->mo));
return 0;
}
/* ARGSUSED */
static const char *
-lookup_hash(msgid, db)
+lookup_hash(msgid, db, rlen)
const char *msgid;
struct domainbinding *db;
+ size_t *rlen;
{
struct mohandle *mohandle = &db->mohandle;
u_int32_t idx, hashval, step, strno;
@@ -748,6 +759,9 @@ lookup_hash(msgid, db)
if (len <= mohandle->mo.mo_otable[strno].len &&
!strcmp(msgid, mohandle->mo.mo_otable[strno].off)) {
/* hit */
+ if (rlen)
+ *rlen =
+ mohandle->mo.mo_ttable[strno].len;
return mohandle->mo.mo_ttable[strno].off;
}
} else {
@@ -761,6 +775,8 @@ lookup_hash(msgid, db)
if (expand_sysdep(mohandle, sysdep_ttable))
/* memory exhausted */
return NULL;
+ if (rlen)
+ *rlen = sysdep_ttable->expanded_len;
return sysdep_ttable->expanded;
}
}
@@ -770,9 +786,10 @@ lookup_hash(msgid, db)
}
static const char *
-lookup_bsearch(msgid, db)
+lookup_bsearch(msgid, db, rlen)
const char *msgid;
struct domainbinding *db;
+ size_t *rlen;
{
int top, bottom, middle, omiddle;
int n;
@@ -793,8 +810,11 @@ lookup_bsearch(msgid, db)
break;
n = strcmp(msgid, mohandle->mo.mo_otable[middle].off);
- if (n == 0)
+ if (n == 0) {
+ if (rlen)
+ *rlen = mohandle->mo.mo_ttable[middle].len;
return (const char *)mohandle->mo.mo_ttable[middle].off;
+ }
else if (n < 0)
bottom = middle;
else
@@ -806,17 +826,18 @@ lookup_bsearch(msgid, db)
}
static const char *
-lookup(msgid, db)
+lookup(msgid, db, rlen)
const char *msgid;
struct domainbinding *db;
+ size_t *rlen;
{
const char *v;
- v = lookup_hash(msgid, db);
+ v = lookup_hash(msgid, db, rlen);
if (v)
return v;
- return lookup_bsearch(msgid, db);
+ return lookup_bsearch(msgid, db, rlen);
}
static const char *
@@ -842,6 +863,22 @@ get_lang_env(const char *category_name)
return split_locale(lang);
}
+static const char *
+get_indexed_string(const char *str, size_t len, unsigned long idx)
+{
+ while (idx > 0) {
+ if (len <= 1)
+ return str;
+ if (*str == '\0')
+ idx--;
+ if (len > 0) {
+ str++;
+ len--;
+ }
+ }
+ return str;
+}
+
char *
dcngettext(domainname, msgid1, msgid2, n, category)
const char *domainname;
@@ -859,10 +896,8 @@ dcngettext(domainname, msgid1, msgid2, n, category)
static char *ocname = NULL;
static char *odomainname = NULL;
struct domainbinding *db;
-
- msgid = (n == 1) ? msgid1 : msgid2;
- if (msgid == NULL)
- return NULL;
+ unsigned long plural_index;
+ size_t len;
if (!domainname)
domainname = __current_domainname;
@@ -925,8 +960,22 @@ dcngettext(domainname, msgid1, msgid2, n, category)
strlcpy(olpath, lpath, sizeof(olpath));
found:
- v = lookup(msgid, db);
+ if (db->mohandle.mo.mo_plural) {
+ plural_index =
+ _gettext_calculate_plural(db->mohandle.mo.mo_plural, n);
+ if (plural_index >= db->mohandle.mo.mo_nplurals)
+ plural_index = 0;
+ msgid = msgid1;
+ } else
+ msgid = (n == 1) ? msgid1 : msgid2;
+
+ if (msgid == NULL)
+ return NULL;
+
+ v = lookup(msgid, db, &len);
if (v) {
+ if (db->mohandle.mo.mo_plural)
+ v = get_indexed_string(v, len, plural_index);
/*
* convert the translated message's encoding.
*
diff --git a/lib/libintl/libintl_local.h b/lib/libintl/libintl_local.h
index f18a0965f66..94fa2aad30c 100644
--- a/lib/libintl/libintl_local.h
+++ b/lib/libintl/libintl_local.h
@@ -1,4 +1,4 @@
-/* $NetBSD: libintl_local.h,v 1.9 2004/09/23 21:35:27 tshiozak Exp $ */
+/* $NetBSD: libintl_local.h,v 1.10 2005/05/14 17:58:56 tshiozak Exp $ */
/*-
* Copyright (c) 2000, 2001 Citrus Project,
@@ -97,6 +97,7 @@ struct mosysdepstr_h {
struct mosysdepsegentry_h segs[1]; /* text segments */
};
+struct gettext_plural;
struct mo_h {
u_int32_t mo_magic; /* determines endian */
u_int32_t mo_revision; /* file format revision: 0 */
@@ -104,6 +105,8 @@ struct mo_h {
struct moentry_h *mo_otable; /* O: original text table offset */
struct moentry_h *mo_ttable; /* T: translated text table offset */
const char *mo_header;
+ struct gettext_plural *mo_plural;
+ unsigned long mo_nplurals;
char *mo_charset;
u_int32_t mo_hsize; /* S: size of hashing table */
u_int32_t *mo_htable; /* H: hashing table */
diff --git a/lib/libintl/plural_parser.c b/lib/libintl/plural_parser.c
new file mode 100644
index 00000000000..bcf1c8dbed0
--- /dev/null
+++ b/lib/libintl/plural_parser.c
@@ -0,0 +1,1120 @@
+/* $NetBSD: plural_parser.c,v 1.1 2005/05/14 17:58:57 tshiozak Exp $ */
+
+/*-
+ * Copyright (c) 2005 Citrus Project,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: plural_parser.c,v 1.1 2005/05/14 17:58:57 tshiozak Exp $");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <citrus/citrus_namespace.h>
+#include <citrus/citrus_region.h>
+#include <citrus/citrus_memstream.h>
+#include <citrus/citrus_bcs.h>
+#include "plural_parser.h"
+
+#if defined(TEST_TOKENIZER) || defined(TEST_PARSER)
+#define ALLOW_EMPTY
+#define ALLOW_ARBITRARY_IDENTIFIER
+#endif
+
+#define MAX_LEN_ATOM 10
+#define MAX_NUM_OPERANDS 3
+
+#define T_EOF EOF
+#define T_NONE 0x100
+#define T_LAND 0x101 /* && */
+#define T_LOR 0x102 /* || */
+#define T_EQUALITY 0x103 /* == or != */
+#define T_RELATIONAL 0x104 /* <, >, <= or >= */
+#define T_ADDITIVE 0x105 /* + or - */
+#define T_MULTIPLICATIVE 0x106 /* *, / or % */
+#define T_IDENTIFIER 0x200
+#define T_CONSTANT 0x201
+#define T_ILCHAR 0x300
+#define T_TOOLONG 0x301
+#define T_ILTOKEN 0x302
+#define T_ILEND 0x303
+#define T_NOMEM 0x304
+#define T_NOTFOUND 0x305
+#define T_ILPLURAL 0x306
+#define T_IS_OPERATOR(t) ((t) < 0x200)
+#define T_IS_ERROR(t) ((t) >= 0x300)
+
+#define OP_EQ ('='+'=')
+#define OP_NEQ ('!'+'=')
+#define OP_LTEQ ('<'+'=')
+#define OP_GTEQ ('>'+'=')
+
+#define PLURAL_NUMBER_SYMBOL "n"
+#define NPLURALS_SYMBOL "nplurals"
+#define LEN_NPLURAL_SYMBOL (sizeof (NPLURALS_SYMBOL) -1)
+#define PLURAL_SYMBOL "plural"
+#define LEN_PLURAL_SYMBOL (sizeof (PLURAL_SYMBOL) -1)
+#define PLURAL_FORMS "Plural-Forms:"
+#define LEN_PLURAL_FORMS (sizeof (PLURAL_FORMS) -1)
+
+/* ----------------------------------------------------------------------
+ * tokenizer part
+ */
+
+union token_data
+{
+ unsigned long constant;
+#ifdef ALLOW_ARBITRARY_IDENTIFIER
+ char identifier[MAX_LEN_ATOM+1];
+#endif
+ char op;
+};
+
+struct tokenizer_context
+{
+ struct _memstream memstream;
+ struct {
+ int token;
+ union token_data token_data;
+ } token0;
+};
+
+/* initialize a tokenizer context */
+static void
+init_tokenizer_context(struct tokenizer_context *tcx)
+{
+ tcx->token0.token = T_NONE;
+}
+
+/* get an atom (identifier or constant) */
+static int
+tokenize_atom(struct tokenizer_context *tcx, union token_data *token_data)
+{
+ int ch, len;
+ char buf[MAX_LEN_ATOM+1];
+
+ len = 0;
+ while (/*CONSTCOND*/1) {
+ ch = _memstream_getc(&tcx->memstream);
+ if (!(_bcs_isalnum(ch) || ch == '_')) {
+ _memstream_ungetc(&tcx->memstream, ch);
+ break;
+ }
+ if (len == MAX_LEN_ATOM)
+ return T_TOOLONG;
+ buf[len++] = ch;
+ }
+ buf[len] = '\0';
+ if (len == 0)
+ return T_ILCHAR;
+
+ if (_bcs_isdigit((int)(unsigned char)buf[0])) {
+ unsigned long ul;
+ char *post;
+ ul = strtoul(buf, &post, 0);
+ if (buf+len != post)
+ return T_ILCHAR;
+ token_data->constant = ul;
+ return T_CONSTANT;
+ }
+
+#ifdef ALLOW_ARBITRARY_IDENTIFIER
+ strcpy(token_data->identifier, buf);
+ return T_IDENTIFIER;
+#else
+ if (!strcmp(buf, PLURAL_NUMBER_SYMBOL))
+ return T_IDENTIFIER;
+ return T_ILCHAR;
+#endif
+}
+
+/* tokenizer main routine */
+static int
+tokenize(struct tokenizer_context *tcx, union token_data *token_data)
+{
+ int ch, prevch;
+
+retry:
+ ch = _memstream_getc(&tcx->memstream);
+ if (_bcs_isspace(ch))
+ goto retry;
+
+ switch (ch) {
+ case T_EOF:
+ return ch;
+ case '+': case '-':
+ token_data->op = ch;
+ return T_ADDITIVE;
+ case '*': case '/': case '%':
+ token_data->op = ch;
+ return T_MULTIPLICATIVE;
+ case '?': case ':': case '(': case ')':
+ token_data->op = ch;
+ return ch;
+ case '&': case '|':
+ prevch = ch;
+ ch = _memstream_getc(&tcx->memstream);
+ if (ch != prevch) {
+ _memstream_ungetc(&tcx->memstream, ch);
+ return T_ILCHAR;
+ }
+ token_data->op = ch;
+ switch (ch) {
+ case '&':
+ return T_LAND;
+ case '|':
+ return T_LOR;
+ }
+ /*NOTREACHED*/
+ case '=': case '!': case '<': case '>':
+ prevch = ch;
+ ch = _memstream_getc(&tcx->memstream);
+ if (ch != '=') {
+ _memstream_ungetc(&tcx->memstream, ch);
+ switch (prevch) {
+ case '=':
+ return T_ILCHAR;
+ case '!':
+ return '!';
+ case '<':
+ case '>':
+ token_data->op = prevch; /* OP_LT or OP_GT */
+ return T_RELATIONAL;
+ }
+ }
+ /* '==', '!=', '<=' or '>=' */
+ token_data->op = ch+prevch;
+ switch (prevch) {
+ case '=':
+ case '!':
+ return T_EQUALITY;
+ case '<':
+ case '>':
+ return T_RELATIONAL;
+ }
+ /*NOTREACHED*/
+ }
+
+ _memstream_ungetc(&tcx->memstream, ch);
+ return tokenize_atom(tcx, token_data);
+}
+
+/* get the next token */
+static int
+get_token(struct tokenizer_context *tcx, union token_data *token_data)
+{
+ if (tcx->token0.token != T_NONE) {
+ int token = tcx->token0.token;
+ tcx->token0.token = T_NONE;
+ *token_data = tcx->token0.token_data;
+ return token;
+ }
+ return tokenize(tcx, token_data);
+}
+
+/* push back the last token */
+static void
+unget_token(struct tokenizer_context *tcx,
+ int token, union token_data *token_data)
+{
+ tcx->token0.token = token;
+ tcx->token0.token_data = *token_data;
+}
+
+#ifdef TEST_TOKENIZER
+#include <stdio.h>
+
+int
+main(int argc, char **argv)
+{
+ struct tokenizer_context tcx;
+ union token_data token_data;
+ int token;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <expression>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ init_tokenizer_context(&tcx);
+ _memstream_bind_ptr(&tcx.memstream, argv[1], strlen(argv[1]));
+
+ while (1) {
+ token = get_token(&tcx, &token_data);
+ switch (token) {
+ case T_EOF:
+ goto quit;
+ case T_ILCHAR:
+ printf("illegal character.\n");
+ goto quit;
+ case T_TOOLONG:
+ printf("too long atom.\n");
+ goto quit;
+ case T_CONSTANT:
+ printf("constant: %lu\n", token_data.constant);
+ break;
+ case T_IDENTIFIER:
+ printf("symbol: %s\n", token_data.identifier);
+ break;
+ default:
+ printf("operator: ");
+ switch (token) {
+ case T_LAND:
+ printf("&&\n");
+ break;
+ case T_LOR:
+ printf("||\n");
+ break;
+ case T_EQUALITY:
+ printf("%c=\n", token_data.op-'=');
+ break;
+ case T_RELATIONAL:
+ switch(token_data.op) {
+ case OP_LTEQ:
+ case OP_GTEQ:
+ printf("%c=\n", token_data.op-'=');
+ break;
+ default:
+ printf("%c\n", token_data.op);
+ break;
+ }
+ break;
+ case T_ADDITIVE:
+ case T_MULTIPLICATIVE:
+ printf("%c\n", token_data.op);
+ break;
+ default:
+ printf("operator: %c\n", token);
+ }
+ }
+ }
+quit:
+ return 0;
+}
+#endif /* TEST_TOKENIZER */
+
+
+/* ----------------------------------------------------------------------
+ * parser part
+ *
+ * exp := cond
+ *
+ * cond := lor | lor '?' cond ':' cond
+ *
+ * lor := land ( '||' land )*
+ *
+ * land := equality ( '&&' equality )*
+ *
+ * equality := relational ( equalityops relational )*
+ * equalityops := '==' | '!='
+ *
+ * relational := additive ( relationalops additive )*
+ * relationalops := '<' | '>' | '<=' | '>='
+ *
+ * additive := multiplicative ( additiveops multiplicative )*
+ * additiveops := '+' | '-'
+ *
+ * multiplicative := lnot ( multiplicativeops lnot )*
+ * multiplicativeops := '*' | '/' | '%'
+ *
+ * lnot := '!' lnot | term
+ *
+ * term := literal | identifier | '(' exp ')'
+ *
+ */
+
+#define T_ENSURE_OK(token, label) \
+do { \
+ if (T_IS_ERROR(token)) \
+ goto label; \
+} while (/*CONSTCOND*/0)
+#define T_ENSURE_SOMETHING(token, label) \
+do { \
+ if ((token) == T_EOF) { \
+ token = T_ILEND; \
+ goto label; \
+ } else if (T_IS_ERROR(token)) \
+ goto label; \
+} while (/*CONSTCOND*/0)
+
+#define parser_element plural_element
+
+struct parser_element;
+struct parser_op
+{
+ char op;
+ struct parser_element *operands[MAX_NUM_OPERANDS];
+};
+struct parser_element
+{
+ int kind;
+ union
+ {
+ struct parser_op parser_op;
+ union token_data token_data;
+ } u;
+};
+
+struct parser_op2_transition
+{
+ int kind;
+ const struct parser_op2_transition *next;
+};
+
+/* prototypes */
+static int parse_cond(struct tokenizer_context *, struct parser_element *);
+
+
+/* transition table for the 2-operand operators */
+#define DEF_TR(t, k, n) \
+static struct parser_op2_transition exp_tr_##t = { \
+ k, &exp_tr_##n \
+}
+#define DEF_TR0(t, k) \
+static struct parser_op2_transition exp_tr_##t = { \
+ k, NULL /* expect lnot */ \
+}
+
+DEF_TR0(multiplicative, T_MULTIPLICATIVE);
+DEF_TR(additive, T_ADDITIVE, multiplicative);
+DEF_TR(relational, T_RELATIONAL, additive);
+DEF_TR(equality, T_EQUALITY, relational);
+DEF_TR(land, T_LAND, equality);
+DEF_TR(lor, T_LOR, land);
+
+/* init a parser element structure */
+static void
+init_parser_element(struct parser_element *pe)
+{
+ int i;
+
+ pe->kind = T_NONE;
+ for (i=0; i<MAX_NUM_OPERANDS; i++)
+ pe->u.parser_op.operands[i] = NULL;
+}
+
+/* uninitialize a parser element structure with freeing children */
+static void free_parser_element(struct parser_element *);
+static void
+uninit_parser_element(struct parser_element *pe)
+{
+ int i;
+
+ if (T_IS_OPERATOR(pe->kind))
+ for (i=0; i<MAX_NUM_OPERANDS; i++)
+ if (pe->u.parser_op.operands[i])
+ free_parser_element(
+ pe->u.parser_op.operands[i]);
+}
+
+/* free a parser element structure with freeing children */
+static void
+free_parser_element(struct parser_element *pe)
+{
+ if (pe) {
+ uninit_parser_element(pe);
+ free(pe);
+ }
+}
+
+
+/* copy a parser element structure shallowly */
+static void
+copy_parser_element(struct parser_element *dpe,
+ const struct parser_element *spe)
+{
+ memcpy(dpe, spe, sizeof *dpe);
+}
+
+/* duplicate a parser element structure shallowly */
+static struct parser_element *
+dup_parser_element(const struct parser_element *pe)
+{
+ struct parser_element *dpe = malloc(sizeof *dpe);
+ if (dpe)
+ copy_parser_element(dpe, pe);
+ return dpe;
+}
+
+/* term := identifier | constant | '(' exp ')' */
+static int
+parse_term(struct tokenizer_context *tcx, struct parser_element *pelem)
+{
+ struct parser_element pe0;
+ int token;
+ union token_data token_data;
+
+ token = get_token(tcx, &token_data);
+ switch (token) {
+ case '(':
+ /* '(' exp ')' */
+ init_parser_element(&pe0);
+ /* expect exp */
+ token = parse_cond(tcx, &pe0);
+ T_ENSURE_OK(token, err);
+ /* expect ')' */
+ token = get_token(tcx, &token_data);
+ T_ENSURE_SOMETHING(token, err);
+ if (token != ')') {
+ unget_token(tcx, token, &token_data);
+ token = T_ILTOKEN;
+ goto err;
+ }
+ copy_parser_element(pelem, &pe0);
+ return token;
+err:
+ uninit_parser_element(&pe0);
+ return token;
+ case T_IDENTIFIER:
+ case T_CONSTANT:
+ pelem->kind = token;
+ pelem->u.token_data = token_data;
+ return token;
+ case T_EOF:
+ return T_ILEND;
+ default:
+ return T_ILTOKEN;
+ }
+}
+
+/* lnot := '!' lnot | term */
+static int
+parse_lnot(struct tokenizer_context *tcx, struct parser_element *pelem)
+{
+ struct parser_element pe0;
+ int token;
+ union token_data token_data;
+
+ init_parser_element(&pe0);
+
+ /* '!' or not */
+ token = get_token(tcx, &token_data);
+ if (token != '!') {
+ /* stop: term */
+ unget_token(tcx, token, &token_data);
+ return parse_term(tcx, pelem);
+ }
+
+ /* '!' term */
+ token = parse_lnot(tcx, &pe0);
+ T_ENSURE_OK(token, err);
+
+ pelem->kind = '!';
+ pelem->u.parser_op.operands[0] = dup_parser_element(&pe0);
+ return pelem->kind;
+err:
+ uninit_parser_element(&pe0);
+ return token;
+}
+
+/* ext_op := ext_next ( op ext_next )* */
+static int
+parse_op2(struct tokenizer_context *tcx, struct parser_element *pelem,
+ const struct parser_op2_transition *tr)
+{
+ struct parser_element pe0, pe1, peop;
+ int token;
+ union token_data token_data;
+ char op;
+
+ /* special case: expect lnot */
+ if (tr == NULL)
+ return parse_lnot(tcx, pelem);
+
+ init_parser_element(&pe0);
+ init_parser_element(&pe1);
+ token = parse_op2(tcx, &pe0, tr->next);
+ T_ENSURE_OK(token, err);
+
+ while (/*CONSTCOND*/1) {
+ /* expect op or empty */
+ token = get_token(tcx, &token_data);
+ if (token != tr->kind) {
+ /* stop */
+ unget_token(tcx, token, &token_data);
+ copy_parser_element(pelem, &pe0);
+ break;
+ }
+ op = token_data.op;
+ /* right hand */
+ token = parse_op2(tcx, &pe1, tr->next);
+ T_ENSURE_OK(token, err);
+
+ init_parser_element(&peop);
+ peop.kind = tr->kind;
+ peop.u.parser_op.op = op;
+ peop.u.parser_op.operands[0] = dup_parser_element(&pe0);
+ init_parser_element(&pe0);
+ peop.u.parser_op.operands[1] = dup_parser_element(&pe1);
+ init_parser_element(&pe1);
+ copy_parser_element(&pe0, &peop);
+ }
+ return pelem->kind;
+err:
+ uninit_parser_element(&pe1);
+ uninit_parser_element(&pe0);
+ return token;
+}
+
+/* cond := lor | lor '?' cond ':' cond */
+static int
+parse_cond(struct tokenizer_context *tcx, struct parser_element *pelem)
+{
+ struct parser_element pe0, pe1, pe2;
+ int token;
+ union token_data token_data;
+
+ init_parser_element(&pe0);
+ init_parser_element(&pe1);
+ init_parser_element(&pe2);
+
+ /* expect lor or empty */
+ token = parse_op2(tcx, &pe0, &exp_tr_lor);
+ T_ENSURE_OK(token, err);
+
+ /* '?' or not */
+ token = get_token(tcx, &token_data);
+ if (token != '?') {
+ /* stop: lor */
+ unget_token(tcx, token, &token_data);
+ copy_parser_element(pelem, &pe0);
+ return pe0.kind;
+ }
+
+ /* lor '?' cond ':' cond */
+ /* expect cond */
+ token = parse_cond(tcx, &pe1);
+ T_ENSURE_OK(token, err);
+
+ /* expect ':' */
+ token = get_token(tcx, &token_data);
+ T_ENSURE_OK(token, err);
+ if (token != ':') {
+ unget_token(tcx, token, &token_data);
+ token = T_ILTOKEN;
+ goto err;
+ }
+
+ /* expect cond */
+ token = parse_cond(tcx, &pe2);
+ T_ENSURE_OK(token, err);
+
+ pelem->kind = '?';
+ pelem->u.parser_op.operands[0] = dup_parser_element(&pe0);
+ pelem->u.parser_op.operands[1] = dup_parser_element(&pe1);
+ pelem->u.parser_op.operands[2] = dup_parser_element(&pe2);
+ return pelem->kind;
+err:
+ uninit_parser_element(&pe2);
+ uninit_parser_element(&pe1);
+ uninit_parser_element(&pe0);
+ return token;
+}
+
+static int
+parse_exp(struct tokenizer_context *tcx, struct parser_element *pelem)
+{
+ int token, token1;
+ union token_data token_data;
+
+#ifdef ALLOW_EMPTY
+ /* empty check */
+ token = get_token(tcx, &token_data);
+ if (token == T_EOF)
+ return token;
+ unget_token(tcx, token, &token_data);
+#endif
+
+ token = parse_cond(tcx, pelem);
+ if (!T_IS_ERROR(token)) {
+ /* termination check */
+ token1 = get_token(tcx, &token_data);
+ if (token1 == T_EOF)
+ return token;
+ else if (!T_IS_ERROR(token))
+ unget_token(tcx, token1, &token_data);
+ return T_ILTOKEN;
+ }
+ return token;
+}
+
+
+#if defined(TEST_PARSER) || defined(TEST_PARSE_PLURAL)
+#include <stdio.h>
+
+static void dump_elem(struct parser_element *);
+
+static void
+dump_op2(struct parser_element *pelem)
+{
+ dump_elem(pelem->u.parser_op.operands[0]);
+ printf(" ");
+ dump_elem(pelem->u.parser_op.operands[1]);
+ printf(")");
+}
+
+static void
+dump_op3(struct parser_element *pelem)
+{
+ dump_elem(pelem->u.parser_op.operands[0]);
+ printf(" ");
+ dump_elem(pelem->u.parser_op.operands[1]);
+ printf(" ");
+ dump_elem(pelem->u.parser_op.operands[2]);
+ printf(")");
+}
+
+static void
+dump_elem(struct parser_element *pelem)
+{
+ switch (pelem->kind) {
+ case T_LAND:
+ printf("(&& ");
+ dump_op2(pelem);
+ break;
+ case T_LOR:
+ printf("(|| ");
+ dump_op2(pelem);
+ break;
+ case T_EQUALITY:
+ switch (pelem->u.parser_op.op) {
+ case OP_EQ:
+ printf("(== ");
+ break;
+ case OP_NEQ:
+ printf("(!= ");
+ break;
+ }
+ dump_op2(pelem);
+ break;
+ case T_RELATIONAL:
+ switch (pelem->u.parser_op.op) {
+ case '<':
+ case '>':
+ printf("(%c ", pelem->u.parser_op.op);
+ break;
+ case OP_LTEQ:
+ case OP_GTEQ:
+ printf("(%c= ", pelem->u.parser_op.op-'=');
+ break;
+ }
+ dump_op2(pelem);
+ break;
+ case T_ADDITIVE:
+ case T_MULTIPLICATIVE:
+ printf("(%c ", pelem->u.parser_op.op);
+ dump_op2(pelem);
+ break;
+ case '!':
+ printf("(! ");
+ dump_elem(pelem->u.parser_op.operands[0]);
+ printf(")");
+ break;
+ case '?':
+ printf("(? ");
+ dump_op3(pelem);
+ break;
+ case T_CONSTANT:
+ printf("%d", pelem->u.token_data.constant);
+ break;
+ case T_IDENTIFIER:
+#ifdef ALLOW_ARBITRARY_IDENTIFIER
+ printf("%s", pelem->u.token_data.identifier);
+#else
+ printf(PLURAL_NUMBER_SYMBOL);
+#endif
+ break;
+ }
+}
+#endif
+#ifdef TEST_PARSER
+int
+main(int argc, char **argv)
+{
+ struct tokenizer_context tcx;
+ struct parser_element pelem;
+ int token;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <expression>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ init_tokenizer_context(&tcx);
+ _memstream_bind_ptr(&tcx.memstream, argv[1], strlen(argv[1]));
+
+ init_parser_element(&pelem);
+ token = parse_exp(&tcx, &pelem);
+
+ if (token == T_EOF)
+ printf("none");
+ else if (T_IS_ERROR(token))
+ printf("error: 0x%X", token);
+ else
+ dump_elem(&pelem);
+ printf("\n");
+
+ uninit_parser_element(&pelem);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PARSER */
+
+/* ----------------------------------------------------------------------
+ * calcurate plural number
+ */
+static unsigned long
+calculate_plural(const struct parser_element *pe, unsigned long n)
+{
+ unsigned long val0, val1;
+ switch (pe->kind) {
+ case T_IDENTIFIER:
+ return n;
+ case T_CONSTANT:
+ return pe->u.token_data.constant;
+ case '?':
+ val0 = calculate_plural(pe->u.parser_op.operands[0], n);
+ if (val0)
+ val1=calculate_plural(pe->u.parser_op.operands[1], n);
+ else
+ val1=calculate_plural(pe->u.parser_op.operands[2], n);
+ return val1;
+ case '!':
+ return !calculate_plural(pe->u.parser_op.operands[0], n);
+ case T_MULTIPLICATIVE:
+ case T_ADDITIVE:
+ case T_RELATIONAL:
+ case T_EQUALITY:
+ case T_LOR:
+ case T_LAND:
+ val0 = calculate_plural(pe->u.parser_op.operands[0], n);
+ val1 = calculate_plural(pe->u.parser_op.operands[1], n);
+ switch (pe->u.parser_op.op) {
+ case '*':
+ return val0*val1;
+ case '/':
+ return val0/val1;
+ case '%':
+ return val0%val1;
+ case '+':
+ return val0+val1;
+ case '-':
+ return val0-val1;
+ case '<':
+ return val0<val1;
+ case '>':
+ return val0>val1;
+ case OP_LTEQ:
+ return val0<=val1;
+ case OP_GTEQ:
+ return val0>=val1;
+ case OP_EQ:
+ return val0==val1;
+ case OP_NEQ:
+ return val0!=val1;
+ case '|':
+ return val0||val1;
+ case '&':
+ return val0&&val1;
+ }
+ }
+ return 0;
+}
+
+#ifdef TEST_CALC_PLURAL
+#include <stdio.h>
+
+int
+main(int argc, char **argv)
+{
+ struct tokenizer_context tcx;
+ struct parser_element pelem;
+ int token;
+
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s <expression> <n>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ init_tokenizer_context(&tcx);
+ _memstream_bind_ptr(&tcx.memstream, argv[1], strlen(argv[1]));
+
+ init_parser_element(&pelem);
+ token = parse_exp(&tcx, &pelem);
+
+ if (token == T_EOF)
+ printf("none");
+ else if (T_IS_ERROR(token))
+ printf("error: 0x%X", token);
+ else {
+ printf("plural = %lu",
+ calculate_plural(&pelem, atoi(argv[2])));
+ }
+ printf("\n");
+
+ uninit_parser_element(&pelem);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_CALC_PLURAL */
+
+
+/* ----------------------------------------------------------------------
+ * parse plural forms
+ */
+
+static void
+region_skip_ws(struct _region *r)
+{
+ const char *str = _region_head(r);
+ size_t len = _region_size(r);
+
+ str = _bcs_skip_ws_len(str, &len);
+ _region_init(r, __UNCONST(str), len);
+}
+
+static void
+region_trunc_rws(struct _region *r)
+{
+ const char *str = _region_head(r);
+ size_t len = _region_size(r);
+
+ _bcs_trunc_rws_len(str, &len);
+ _region_init(r, __UNCONST(str), len);
+}
+
+static int
+region_check_prefix(struct _region *r, const char *pre, size_t prelen,
+ int ignorecase)
+{
+ if (_region_size(r) < prelen)
+ return -1;
+
+ if (ignorecase) {
+ if (_bcs_strncasecmp(_region_head(r), pre, prelen))
+ return -1;
+ } else {
+ if (memcmp(_region_head(r), pre, prelen))
+ return -1;
+ }
+ return 0;
+}
+
+static int
+cut_trailing_semicolon(struct _region *r)
+{
+
+ region_trunc_rws(r);
+ if (_region_size(r) == 0 || _region_peek8(r, _region_size(r)-1) != ';')
+ return -1;
+ _region_get_subregion(r, r, 0, _region_size(r)-1);
+ return 0;
+}
+
+static int
+find_plural_forms(struct _region *r)
+{
+ struct _memstream ms;
+ struct _region rr;
+
+ _memstream_bind(&ms, r);
+
+ while (!_memstream_getln_region(&ms, &rr)) {
+ if (!region_check_prefix(&rr,
+ PLURAL_FORMS, LEN_PLURAL_FORMS, 1)) {
+ _region_get_subregion(
+ r, &rr, LEN_PLURAL_FORMS,
+ _region_size(&rr)-LEN_PLURAL_FORMS);
+ region_skip_ws(r);
+ region_trunc_rws(r);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int
+skip_assignment(struct _region *r, const char *sym, size_t symlen)
+{
+ region_skip_ws(r);
+ if (region_check_prefix(r, sym, symlen, 0))
+ return -1;
+ _region_get_subregion(r, r, symlen, _region_size(r)-symlen);
+ region_skip_ws(r);
+ if (_region_size(r) == 0 || _region_peek8(r, 0) != '=')
+ return -1;
+ _region_get_subregion(r, r, 1, _region_size(r)-1);
+ region_skip_ws(r);
+ return 0;
+}
+
+static int
+skip_nplurals(struct _region *r, unsigned long *rnp)
+{
+ unsigned long np;
+ char buf[MAX_LEN_ATOM+2], *endptr;
+ const char *endptrconst;
+ size_t ofs;
+
+ if (skip_assignment(r, NPLURALS_SYMBOL, LEN_NPLURAL_SYMBOL))
+ return -1;
+ if (_region_size(r) == 0 || !_bcs_isdigit(_region_peek8(r, 0)))
+ return -1;
+ strlcpy(buf, _region_head(r), sizeof (buf));
+ np = strtoul(buf, &endptr, 0);
+ endptrconst = _bcs_skip_ws(endptr);
+ if (*endptrconst != ';')
+ return -1;
+ ofs = endptrconst+1-buf;
+ if (_region_get_subregion(r, r, ofs, _region_size(r)-ofs))
+ return -1;
+ if (rnp)
+ *rnp = np;
+ return 0;
+}
+
+static int
+parse_plural_body(struct _region *r, struct parser_element **rpe)
+{
+ int token;
+ struct tokenizer_context tcx;
+ struct parser_element pelem, *ppe;
+
+ init_tokenizer_context(&tcx);
+ _memstream_bind(&tcx.memstream, r);
+
+ init_parser_element(&pelem);
+ token = parse_exp(&tcx, &pelem);
+ if (T_IS_ERROR(token))
+ return token;
+
+ ppe = dup_parser_element(&pelem);
+ if (ppe == NULL) {
+ uninit_parser_element(&pelem);
+ return T_NOMEM;
+ }
+
+ *rpe = ppe;
+
+ return 0;
+}
+
+static int
+parse_plural(struct parser_element **rpe, unsigned long *rnp,
+ const char *str, size_t len)
+{
+ struct _region r;
+
+ _region_init(&r, __UNCONST(str), len);
+
+ if (find_plural_forms(&r))
+ return T_NOTFOUND;
+ if (skip_nplurals(&r, rnp))
+ return T_ILPLURAL;
+ if (skip_assignment(&r, PLURAL_SYMBOL, LEN_PLURAL_SYMBOL))
+ return T_ILPLURAL;
+ if (cut_trailing_semicolon(&r))
+ return T_ILPLURAL;
+ return parse_plural_body(&r, rpe);
+}
+
+#ifdef TEST_PARSE_PLURAL
+int
+main(int argc, char **argv)
+{
+ int ret;
+ struct parser_element *pelem;
+ unsigned long np;
+
+ if (argc != 2 && argc != 3) {
+ fprintf(stderr, "usage: %s <mime-header> [n]\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ ret = parse_plural(&pelem, &np, argv[1], strlen(argv[1]));
+
+ if (ret == T_EOF)
+ printf("none");
+ else if (T_IS_ERROR(ret))
+ printf("error: 0x%X", ret);
+ else {
+ printf("syntax tree: ");
+ dump_elem(pelem);
+ printf("\nnplurals = %lu", np);
+ if (argv[2])
+ printf(", plural = %lu",
+ calculate_plural(pelem, atoi(argv[2])));
+ free_parser_element(pelem);
+ }
+ printf("\n");
+
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PARSE_PLURAL */
+
+/*
+ * external interface
+ */
+
+int
+_gettext_parse_plural(struct gettext_plural **rpe, unsigned long *rnp,
+ const char *str, size_t len)
+{
+ return parse_plural((struct parser_element **)rpe, rnp, str, len);
+}
+
+unsigned long
+_gettext_calculate_plural(const struct gettext_plural *pe, unsigned long n)
+{
+ return calculate_plural((void *)__UNCONST(pe), n);
+}
+
+void
+_gettext_free_plural(struct gettext_plural *pe)
+{
+ free_parser_element((void *)pe);
+}
+
+#ifdef TEST_PLURAL
+#include <libintl.h>
+#include <locale.h>
+
+#define PR(n) printf("n=%d: \"%s\"\n", n, dngettext("test", "1", "2", n))
+
+int
+main(void)
+{
+ bindtextdomain("test", "."); /* ./LANG/LC_MESSAGES/test.mo */
+ PR(1);
+ PR(2);
+ PR(3);
+ PR(4);
+
+ return 0;
+}
+#endif
diff --git a/lib/libintl/plural_parser.h b/lib/libintl/plural_parser.h
new file mode 100644
index 00000000000..7e2a19a36b6
--- /dev/null
+++ b/lib/libintl/plural_parser.h
@@ -0,0 +1,43 @@
+/* $NetBSD: plural_parser.h,v 1.1 2005/05/14 17:58:57 tshiozak Exp $ */
+
+/*-
+ * Copyright (c) 2005 Citrus Project,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef PLURAL_PARSER_H_
+#define PLURAL_PARSER_H_
+
+struct gettext_plural;
+
+__BEGIN_DECLS
+extern int _gettext_parse_plural(struct gettext_plural **,
+ unsigned long *, const char *, size_t);
+extern unsigned long _gettext_calculate_plural(const struct gettext_plural *,
+ unsigned long);
+extern void _gettext_free_plural(struct gettext_plural *);
+__END_DECLS
+
+#endif