diff options
| author | tshiozak <tshiozak@NetBSD.org> | 2005-05-14 17:58:56 +0000 |
|---|---|---|
| committer | tshiozak <tshiozak@NetBSD.org> | 2005-05-14 17:58:56 +0000 |
| commit | dd416aa8683822d3dd271095da0be90cdfd3efde (patch) | |
| tree | 19b69ea6a4acc7cb2548d3a0e8a6fca41cd21252 /lib | |
| parent | a023aa0fb7fc6c6d1c980210b8bd19cedc8e70b5 (diff) | |
add plural support.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/libintl/Makefile | 6 | ||||
| -rw-r--r-- | lib/libintl/gettext.c | 85 | ||||
| -rw-r--r-- | lib/libintl/libintl_local.h | 5 | ||||
| -rw-r--r-- | lib/libintl/plural_parser.c | 1120 | ||||
| -rw-r--r-- | lib/libintl/plural_parser.h | 43 |
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 |
