/*      $NetBSD: plural_parser.c,v 1.5 2025/02/26 04:49:45 andvar 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.5 2025/02/26 04:49:45 andvar 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;
               default:
                       return T_ILTOKEN;
               }
       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

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 (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 (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 */

/* ----------------------------------------------------------------------
* calculate 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