/* Unicode CLDR plural rule parser and converter.
   Copyright (C) 2015-2025 Free Software Foundation, Inc.

   This file was written by Daiki Ueno <ueno@gnu.org>, 2015.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

%{
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SB_NO_APPENDF
#include "unistr.h"
#include "xalloc.h"
#include "string-buffer.h"

#include "cldr-plural-exp.h"

/* Allocation of expressions.  */

static struct cldr_plural_rule_ty *
new_rule (char *name, struct cldr_plural_condition_ty *condition)
{
  struct cldr_plural_rule_ty *result =
    XMALLOC (struct cldr_plural_rule_ty);
  result->name = name;
  result->condition = condition;
  return result;
}

static struct cldr_plural_condition_ty *
new_leaf_condition (struct cldr_plural_relation_ty *relation)
{
  struct cldr_plural_condition_ty *result =
    XMALLOC (struct cldr_plural_condition_ty);
  result->type = CLDR_PLURAL_CONDITION_RELATION;
  result->value.relation = relation;
  return result;
}

static struct cldr_plural_condition_ty *
new_branch_condition (enum cldr_plural_condition type,
                      struct cldr_plural_condition_ty *condition0,
                      struct cldr_plural_condition_ty *condition1)
{
  struct cldr_plural_condition_ty *result =
    XMALLOC (struct cldr_plural_condition_ty);
  result->type = type;
  result->value.conditions[0] = condition0;
  result->value.conditions[1] = condition1;
  return result;
}

static struct cldr_plural_relation_ty *
new_relation (struct cldr_plural_expression_ty *expression,
              enum cldr_plural_relation type,
              struct cldr_plural_range_list_ty *ranges)
{
  struct cldr_plural_relation_ty *result =
    XMALLOC (struct cldr_plural_relation_ty);
  result->expression = expression;
  result->type = type;
  result->ranges = ranges;
  return result;
}

static struct cldr_plural_expression_ty *
new_expression (int operand, int mod)
{
  struct cldr_plural_expression_ty *result =
    XMALLOC (struct cldr_plural_expression_ty);
  result->operand = operand;
  result->mod = mod;
  return result;
}

static struct cldr_plural_range_list_ty *
add_range (struct cldr_plural_range_list_ty *ranges,
           struct cldr_plural_range_ty *range)
{
  if (ranges->nitems == ranges->nitems_max)
    {
      ranges->nitems_max = ranges->nitems_max * 2 + 1;
      ranges->items = xrealloc (ranges->items,
                                sizeof (struct cldr_plural_range_ty *)
                                * ranges->nitems_max);
    }
  ranges->items[ranges->nitems++] = range;
  return ranges;
}

static struct cldr_plural_range_ty *
new_range (struct cldr_plural_operand_ty *start,
           struct cldr_plural_operand_ty *end)
{
  struct cldr_plural_range_ty *result =
    XMALLOC (struct cldr_plural_range_ty);
  result->start = start;
  result->end = end;
  return result;
}

/* Internal state of the Bison-generated parser.  */

struct cldr_plural_parse_args
{
  /* The lifetime of cp, cp_end is limited to the cldr_plural_parse
     invocation.  */
  const char *cp;
  const char *cp_end;

  struct cldr_plural_rule_list_ty *result;
};

#include "cldr-plural.h"

/* Prototypes for local functions, that must come after the rules.  */
static int yylex (YYSTYPE *lval, struct cldr_plural_parse_args *arg);
static void yyerror (struct cldr_plural_parse_args *arg, const char *str);

%}

%require "3.0"

%parse-param {struct cldr_plural_parse_args *arg}
%lex-param {struct cldr_plural_parse_args *arg}
%define api.pure full

%union {
  char *sval;
  struct cldr_plural_condition_ty *cval;
  struct cldr_plural_relation_ty *lval;
  struct cldr_plural_expression_ty *eval;
  struct cldr_plural_range_ty *gval;
  struct cldr_plural_operand_ty *oval;
  struct cldr_plural_range_list_ty *rval;
  int ival;
}

%destructor { free ($$); } <sval>
%destructor { cldr_plural_condition_free ($$); } <cval>
%destructor { cldr_plural_relation_free ($$); } <lval>
%destructor { free ($$); } <eval>
%destructor { cldr_plural_range_free ($$); } <gval>
%destructor { free ($$); } <oval>
%destructor { cldr_plural_range_list_free ($$); } <rval>
%destructor { } <ival>

%token AND OR RANGE ELLIPSIS OTHER AT_INTEGER AT_DECIMAL
%token<sval> KEYWORD
%token<oval> INTEGER DECIMAL
%token<ival> OPERAND
%type<cval> condition and_condition
%type<lval> relation
%type<eval> expression
%type<gval> range range_or_integer
%type<rval> range_list

%%

rules: rule
        | rules ';' rule
        ;

rule:   KEYWORD ':' condition samples
        {
          struct cldr_plural_rule_ty *rule = new_rule ($1, $3);
          struct cldr_plural_rule_list_ty *result = arg->result;
          if (result->nitems == result->nitems_max)
            {
              result->nitems_max = result->nitems_max * 2 + 1;
              result->items = xrealloc (result->items,
                                        sizeof (struct cldr_plural_rule_ty *)
                                        * result->nitems_max);
            }
          result->items[result->nitems++] = rule;
        }
        | OTHER ':' samples
        ;

condition: and_condition
        {
          $$ = $1;
        }
        | condition OR and_condition
        {
          $$ = new_branch_condition (CLDR_PLURAL_CONDITION_OR, $1, $3);
        }
        ;

and_condition: relation
        {
          $$ = new_leaf_condition ($1);
        }
        | and_condition AND relation
        {
          $$ = new_branch_condition (CLDR_PLURAL_CONDITION_AND,
                                     $1,
                                     new_leaf_condition ($3));
        }
        ;

relation: expression '=' range_list
        {
          $$ = new_relation ($1, CLDR_PLURAL_RELATION_EQUAL, $3);
        }
        | expression '!' range_list
        {
          $$ = new_relation ($1, CLDR_PLURAL_RELATION_NOT_EQUAL, $3);
        }
        ;

expression: OPERAND
        {
          $$ = new_expression ($1, 0);
        }
        | OPERAND '%' INTEGER
        {
          $$ = new_expression ($1, $3->value.ival);
        }
        ;

range_list: range_or_integer
        {
          struct cldr_plural_range_list_ty *ranges =
            XMALLOC (struct cldr_plural_range_list_ty);
          memset (ranges, 0, sizeof (struct cldr_plural_range_list_ty));
          $$ = add_range (ranges, $1);
        }
        | range_list ',' range_or_integer
        {
          $$ = add_range ($1, $3);
        }
        ;

range_or_integer: range
        {
          $$ = $1;
        }
        | INTEGER
        {
          $$ = new_range ($1, $1);
        }
        ;

range: INTEGER RANGE INTEGER
        {
          $$ = new_range ($1, $3);
        }
        ;

/* FIXME: collect samples */
samples: at_integer at_decimal
        ;

at_integer: %empty
        | AT_INTEGER sample_list
        ;

at_decimal: %empty
        | AT_DECIMAL sample_list
        ;

sample_list: sample_list1 sample_ellipsis
        ;
sample_list1: sample_range
        | sample_list1 ',' sample_range
        ;
sample_ellipsis: %empty
        | ',' ELLIPSIS
        ;

sample_range: DECIMAL
        { free ($1); }
        | DECIMAL '~' DECIMAL
        { free ($1); free ($3); }
        | INTEGER
        { free ($1); }
        | INTEGER '~' INTEGER
        { free ($1); free ($3); }
        ;

%%

/* Functions invoked by the Bison-generated parser.  */

static int
yylex (YYSTYPE *lval, struct cldr_plural_parse_args *arg)
{
  const char *exp = arg->cp;
  ucs4_t uc;
  int length;
  int result;

  while (1)
    {
      if (exp[0] == '\0')
        {
          arg->cp = exp;
          return YYEOF;
        }

      if (exp[0] != ' ' && exp[0] != '\t')
        break;

      ++exp;
    }

  length = u8_mbtouc (&uc, (const uint8_t *) exp, arg->cp_end - exp);
  if (uc == 0x2026)
    {
      arg->cp = exp + length;
      return ELLIPSIS;
    }
  else if (str_startswith (exp, "..."))
    {
      arg->cp = exp + 3;
      return ELLIPSIS;
    }
  else if (str_startswith (exp, ".."))
    {
      arg->cp = exp + 2;
      return RANGE;
    }
  else if (str_startswith (exp, "other"))
    {
      arg->cp = exp + 5;
      return OTHER;
    }
  else if (str_startswith (exp, "@integer"))
    {
      arg->cp = exp + 8;
      return AT_INTEGER;
    }
  else if (str_startswith (exp, "@decimal"))
    {
      arg->cp = exp + 8;
      return AT_DECIMAL;
    }

  result = *exp++;
  switch (result)
    {
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      {
        unsigned long int ival = result - '0';

        while (exp[0] >= '0' && exp[0] <= '9')
          {
            ival *= 10;
            ival += exp[0] - '0';
            ++exp;
          }

        lval->oval = XMALLOC (struct cldr_plural_operand_ty);
        if (exp[0] == '.' && exp[1] >= '0' && exp[1] <= '9')
          {
            double dval = ival;
            int denominator = 10, nfractions = 0;
            ++exp;
            while (exp[0] >= '0' && exp[0] <= '9')
              {
                dval += (exp[0] - '0') / (double) denominator;
                denominator *= 10;
                ++nfractions;
                ++exp;
              }
            lval->oval->type = CLDR_PLURAL_OPERAND_DECIMAL;
            lval->oval->value.dval.d = dval;
            lval->oval->value.dval.nfractions = nfractions;
            result = DECIMAL;
          }
        else
          {
            lval->oval->type = CLDR_PLURAL_OPERAND_INTEGER;
            lval->oval->value.ival = ival;
            result = INTEGER;
          }
      }
      break;
    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
    case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
    case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
    case 'v': case 'w': case 'x': case 'y': case 'z':
      {
        struct string_buffer buffer;

        sb_init (&buffer);
        for (;;)
          {
            sb_xappend1 (&buffer, result);
            result = *exp;
            switch (result)
              {
              case 'a': case 'b': case 'c': case 'd': case 'e':
              case 'f': case 'g': case 'h': case 'i': case 'j':
              case 'k': case 'l': case 'm': case 'n': case 'o':
              case 'p': case 'q': case 'r': case 's': case 't':
              case 'u': case 'v': case 'w': case 'x': case 'y':
              case 'z':
                ++exp;
                continue;
              default:
                break;
              }
            break;
          }

        const char *ident = sb_xcontents_c (&buffer);

        /* Operands.  */
        if (strlen (ident) == 1)
          {
            switch (ident[0])
              {
              case 'n': case 'i': case 'f': case 't': case 'v': case 'w':
                arg->cp = exp;
                lval->ival = ident[0];
                sb_free (&buffer);
                return OPERAND;
              default:
                break;
              }
          }

        /* Keywords.  */
        if (strcmp (ident, "and") == 0)
          {
            arg->cp = exp;
            sb_free (&buffer);
            return AND;
          }
        else if (strcmp (ident, "or") == 0)
          {
            arg->cp = exp;
            sb_free (&buffer);
            return OR;
          }

        lval->sval = sb_xdupfree_c (&buffer);
        result = KEYWORD;
      }
      break;
    case '!':
      if (exp[0] == '=')
        {
          ++exp;
          result = '!';
        }
      else
        result = YYERRCODE;
      break;
    default:
      break;
    }

  arg->cp = exp;

  return result;
}

static void
yyerror (struct cldr_plural_parse_args *arg, char const *s)
{
  fprintf (stderr, "%s\n", s);
}

/* Entry point to the parser.  */

struct cldr_plural_rule_list_ty *
cldr_plural_parse (const char *input)
{
  struct cldr_plural_parse_args arg;

  memset (&arg, 0, sizeof (struct cldr_plural_parse_args));
  arg.cp = input;
  arg.cp_end = input + strlen (input);
  arg.result = XMALLOC (struct cldr_plural_rule_list_ty);
  memset (arg.result, 0, sizeof (struct cldr_plural_rule_list_ty));

  if (yyparse (&arg) != 0)
    return NULL;

  return arg.result;
}
