[ Index ]

PHP Cross Reference of BackPress

title

Body

[close]

/includes/pomo/ -> plural-forms.php (source)

   1  <?php
   2  
   3  /**
   4   * A gettext Plural-Forms parser.
   5   *
   6   * @since 4.9.0
   7   */
   8  if ( ! class_exists( 'Plural_Forms', false ) ) :
   9      class Plural_Forms {
  10          /**
  11           * Operator characters.
  12           *
  13           * @since 4.9.0
  14           * @var string OP_CHARS Operator characters.
  15           */
  16          const OP_CHARS = '|&><!=%?:';
  17  
  18          /**
  19           * Valid number characters.
  20           *
  21           * @since 4.9.0
  22           * @var string NUM_CHARS Valid number characters.
  23           */
  24          const NUM_CHARS = '0123456789';
  25  
  26          /**
  27           * Operator precedence.
  28           *
  29           * Operator precedence from highest to lowest. Higher numbers indicate
  30           * higher precedence, and are executed first.
  31           *
  32           * @see https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence
  33           *
  34           * @since 4.9.0
  35           * @var array $op_precedence Operator precedence from highest to lowest.
  36           */
  37          protected static $op_precedence = array(
  38              '%'  => 6,
  39  
  40              '<'  => 5,
  41              '<=' => 5,
  42              '>'  => 5,
  43              '>=' => 5,
  44  
  45              '==' => 4,
  46              '!=' => 4,
  47  
  48              '&&' => 3,
  49  
  50              '||' => 2,
  51  
  52              '?:' => 1,
  53              '?'  => 1,
  54  
  55              '('  => 0,
  56              ')'  => 0,
  57          );
  58  
  59          /**
  60           * Tokens generated from the string.
  61           *
  62           * @since 4.9.0
  63           * @var array $tokens List of tokens.
  64           */
  65          protected $tokens = array();
  66  
  67          /**
  68           * Cache for repeated calls to the function.
  69           *
  70           * @since 4.9.0
  71           * @var array $cache Map of $n => $result
  72           */
  73          protected $cache = array();
  74  
  75          /**
  76           * Constructor.
  77           *
  78           * @since 4.9.0
  79           *
  80           * @param string $str Plural function (just the bit after `plural=` from Plural-Forms)
  81           */
  82  		public function __construct( $str ) {
  83              $this->parse( $str );
  84          }
  85  
  86          /**
  87           * Parse a Plural-Forms string into tokens.
  88           *
  89           * Uses the shunting-yard algorithm to convert the string to Reverse Polish
  90           * Notation tokens.
  91           *
  92           * @since 4.9.0
  93           *
  94           * @param string $str String to parse.
  95           */
  96  		protected function parse( $str ) {
  97              $pos = 0;
  98              $len = strlen( $str );
  99  
 100              // Convert infix operators to postfix using the shunting-yard algorithm.
 101              $output = array();
 102              $stack  = array();
 103              while ( $pos < $len ) {
 104                  $next = substr( $str, $pos, 1 );
 105  
 106                  switch ( $next ) {
 107                      // Ignore whitespace.
 108                      case ' ':
 109                      case "\t":
 110                          $pos++;
 111                          break;
 112  
 113                      // Variable (n).
 114                      case 'n':
 115                          $output[] = array( 'var' );
 116                          $pos++;
 117                          break;
 118  
 119                      // Parentheses.
 120                      case '(':
 121                          $stack[] = $next;
 122                          $pos++;
 123                          break;
 124  
 125                      case ')':
 126                          $found = false;
 127                          while ( ! empty( $stack ) ) {
 128                              $o2 = $stack[ count( $stack ) - 1 ];
 129                              if ( '(' !== $o2 ) {
 130                                  $output[] = array( 'op', array_pop( $stack ) );
 131                                  continue;
 132                              }
 133  
 134                              // Discard open paren.
 135                              array_pop( $stack );
 136                              $found = true;
 137                              break;
 138                          }
 139  
 140                          if ( ! $found ) {
 141                              throw new Exception( 'Mismatched parentheses' );
 142                          }
 143  
 144                          $pos++;
 145                          break;
 146  
 147                      // Operators.
 148                      case '|':
 149                      case '&':
 150                      case '>':
 151                      case '<':
 152                      case '!':
 153                      case '=':
 154                      case '%':
 155                      case '?':
 156                          $end_operator = strspn( $str, self::OP_CHARS, $pos );
 157                          $operator     = substr( $str, $pos, $end_operator );
 158                          if ( ! array_key_exists( $operator, self::$op_precedence ) ) {
 159                              throw new Exception( sprintf( 'Unknown operator "%s"', $operator ) );
 160                          }
 161  
 162                          while ( ! empty( $stack ) ) {
 163                              $o2 = $stack[ count( $stack ) - 1 ];
 164  
 165                              // Ternary is right-associative in C.
 166                              if ( '?:' === $operator || '?' === $operator ) {
 167                                  if ( self::$op_precedence[ $operator ] >= self::$op_precedence[ $o2 ] ) {
 168                                      break;
 169                                  }
 170                              } elseif ( self::$op_precedence[ $operator ] > self::$op_precedence[ $o2 ] ) {
 171                                  break;
 172                              }
 173  
 174                              $output[] = array( 'op', array_pop( $stack ) );
 175                          }
 176                          $stack[] = $operator;
 177  
 178                          $pos += $end_operator;
 179                          break;
 180  
 181                      // Ternary "else".
 182                      case ':':
 183                          $found = false;
 184                          $s_pos = count( $stack ) - 1;
 185                          while ( $s_pos >= 0 ) {
 186                              $o2 = $stack[ $s_pos ];
 187                              if ( '?' !== $o2 ) {
 188                                  $output[] = array( 'op', array_pop( $stack ) );
 189                                  $s_pos--;
 190                                  continue;
 191                              }
 192  
 193                              // Replace.
 194                              $stack[ $s_pos ] = '?:';
 195                              $found           = true;
 196                              break;
 197                          }
 198  
 199                          if ( ! $found ) {
 200                              throw new Exception( 'Missing starting "?" ternary operator' );
 201                          }
 202                          $pos++;
 203                          break;
 204  
 205                      // Default - number or invalid.
 206                      default:
 207                          if ( $next >= '0' && $next <= '9' ) {
 208                              $span     = strspn( $str, self::NUM_CHARS, $pos );
 209                              $output[] = array( 'value', intval( substr( $str, $pos, $span ) ) );
 210                              $pos     += $span;
 211                              break;
 212                          }
 213  
 214                          throw new Exception( sprintf( 'Unknown symbol "%s"', $next ) );
 215                  }
 216              }
 217  
 218              while ( ! empty( $stack ) ) {
 219                  $o2 = array_pop( $stack );
 220                  if ( '(' === $o2 || ')' === $o2 ) {
 221                      throw new Exception( 'Mismatched parentheses' );
 222                  }
 223  
 224                  $output[] = array( 'op', $o2 );
 225              }
 226  
 227              $this->tokens = $output;
 228          }
 229  
 230          /**
 231           * Get the plural form for a number.
 232           *
 233           * Caches the value for repeated calls.
 234           *
 235           * @since 4.9.0
 236           *
 237           * @param int $num Number to get plural form for.
 238           * @return int Plural form value.
 239           */
 240  		public function get( $num ) {
 241              if ( isset( $this->cache[ $num ] ) ) {
 242                  return $this->cache[ $num ];
 243              }
 244              $this->cache[ $num ] = $this->execute( $num );
 245              return $this->cache[ $num ];
 246          }
 247  
 248          /**
 249           * Execute the plural form function.
 250           *
 251           * @since 4.9.0
 252           *
 253           * @param int $n Variable "n" to substitute.
 254           * @return int Plural form value.
 255           */
 256  		public function execute( $n ) {
 257              $stack = array();
 258              $i     = 0;
 259              $total = count( $this->tokens );
 260              while ( $i < $total ) {
 261                  $next = $this->tokens[ $i ];
 262                  $i++;
 263                  if ( 'var' === $next[0] ) {
 264                      $stack[] = $n;
 265                      continue;
 266                  } elseif ( 'value' === $next[0] ) {
 267                      $stack[] = $next[1];
 268                      continue;
 269                  }
 270  
 271                  // Only operators left.
 272                  switch ( $next[1] ) {
 273                      case '%':
 274                          $v2      = array_pop( $stack );
 275                          $v1      = array_pop( $stack );
 276                          $stack[] = $v1 % $v2;
 277                          break;
 278  
 279                      case '||':
 280                          $v2      = array_pop( $stack );
 281                          $v1      = array_pop( $stack );
 282                          $stack[] = $v1 || $v2;
 283                          break;
 284  
 285                      case '&&':
 286                          $v2      = array_pop( $stack );
 287                          $v1      = array_pop( $stack );
 288                          $stack[] = $v1 && $v2;
 289                          break;
 290  
 291                      case '<':
 292                          $v2      = array_pop( $stack );
 293                          $v1      = array_pop( $stack );
 294                          $stack[] = $v1 < $v2;
 295                          break;
 296  
 297                      case '<=':
 298                          $v2      = array_pop( $stack );
 299                          $v1      = array_pop( $stack );
 300                          $stack[] = $v1 <= $v2;
 301                          break;
 302  
 303                      case '>':
 304                          $v2      = array_pop( $stack );
 305                          $v1      = array_pop( $stack );
 306                          $stack[] = $v1 > $v2;
 307                          break;
 308  
 309                      case '>=':
 310                          $v2      = array_pop( $stack );
 311                          $v1      = array_pop( $stack );
 312                          $stack[] = $v1 >= $v2;
 313                          break;
 314  
 315                      case '!=':
 316                          $v2      = array_pop( $stack );
 317                          $v1      = array_pop( $stack );
 318                          $stack[] = $v1 != $v2;
 319                          break;
 320  
 321                      case '==':
 322                          $v2      = array_pop( $stack );
 323                          $v1      = array_pop( $stack );
 324                          $stack[] = $v1 == $v2;
 325                          break;
 326  
 327                      case '?:':
 328                          $v3      = array_pop( $stack );
 329                          $v2      = array_pop( $stack );
 330                          $v1      = array_pop( $stack );
 331                          $stack[] = $v1 ? $v2 : $v3;
 332                          break;
 333  
 334                      default:
 335                          throw new Exception( sprintf( 'Unknown operator "%s"', $next[1] ) );
 336                  }
 337              }
 338  
 339              if ( count( $stack ) !== 1 ) {
 340                  throw new Exception( 'Too many values remaining on the stack' );
 341              }
 342  
 343              return (int) $stack[0];
 344          }
 345      }
 346  endif;


Generated: Fri Apr 26 01:01:08 2024 Cross-referenced by PHPXref 0.7.1