[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-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           * @throws Exception If there is a syntax or parsing error with the string.
  95           *
  96           * @param string $str String to parse.
  97           */
  98  		protected function parse( $str ) {
  99              $pos = 0;
 100              $len = strlen( $str );
 101  
 102              // Convert infix operators to postfix using the shunting-yard algorithm.
 103              $output = array();
 104              $stack  = array();
 105              while ( $pos < $len ) {
 106                  $next = substr( $str, $pos, 1 );
 107  
 108                  switch ( $next ) {
 109                      // Ignore whitespace.
 110                      case ' ':
 111                      case "\t":
 112                          $pos++;
 113                          break;
 114  
 115                      // Variable (n).
 116                      case 'n':
 117                          $output[] = array( 'var' );
 118                          $pos++;
 119                          break;
 120  
 121                      // Parentheses.
 122                      case '(':
 123                          $stack[] = $next;
 124                          $pos++;
 125                          break;
 126  
 127                      case ')':
 128                          $found = false;
 129                          while ( ! empty( $stack ) ) {
 130                              $o2 = $stack[ count( $stack ) - 1 ];
 131                              if ( '(' !== $o2 ) {
 132                                  $output[] = array( 'op', array_pop( $stack ) );
 133                                  continue;
 134                              }
 135  
 136                              // Discard open paren.
 137                              array_pop( $stack );
 138                              $found = true;
 139                              break;
 140                          }
 141  
 142                          if ( ! $found ) {
 143                              throw new Exception( 'Mismatched parentheses' );
 144                          }
 145  
 146                          $pos++;
 147                          break;
 148  
 149                      // Operators.
 150                      case '|':
 151                      case '&':
 152                      case '>':
 153                      case '<':
 154                      case '!':
 155                      case '=':
 156                      case '%':
 157                      case '?':
 158                          $end_operator = strspn( $str, self::OP_CHARS, $pos );
 159                          $operator     = substr( $str, $pos, $end_operator );
 160                          if ( ! array_key_exists( $operator, self::$op_precedence ) ) {
 161                              throw new Exception( sprintf( 'Unknown operator "%s"', $operator ) );
 162                          }
 163  
 164                          while ( ! empty( $stack ) ) {
 165                              $o2 = $stack[ count( $stack ) - 1 ];
 166  
 167                              // Ternary is right-associative in C.
 168                              if ( '?:' === $operator || '?' === $operator ) {
 169                                  if ( self::$op_precedence[ $operator ] >= self::$op_precedence[ $o2 ] ) {
 170                                      break;
 171                                  }
 172                              } elseif ( self::$op_precedence[ $operator ] > self::$op_precedence[ $o2 ] ) {
 173                                  break;
 174                              }
 175  
 176                              $output[] = array( 'op', array_pop( $stack ) );
 177                          }
 178                          $stack[] = $operator;
 179  
 180                          $pos += $end_operator;
 181                          break;
 182  
 183                      // Ternary "else".
 184                      case ':':
 185                          $found = false;
 186                          $s_pos = count( $stack ) - 1;
 187                          while ( $s_pos >= 0 ) {
 188                              $o2 = $stack[ $s_pos ];
 189                              if ( '?' !== $o2 ) {
 190                                  $output[] = array( 'op', array_pop( $stack ) );
 191                                  $s_pos--;
 192                                  continue;
 193                              }
 194  
 195                              // Replace.
 196                              $stack[ $s_pos ] = '?:';
 197                              $found           = true;
 198                              break;
 199                          }
 200  
 201                          if ( ! $found ) {
 202                              throw new Exception( 'Missing starting "?" ternary operator' );
 203                          }
 204                          $pos++;
 205                          break;
 206  
 207                      // Default - number or invalid.
 208                      default:
 209                          if ( $next >= '0' && $next <= '9' ) {
 210                              $span     = strspn( $str, self::NUM_CHARS, $pos );
 211                              $output[] = array( 'value', intval( substr( $str, $pos, $span ) ) );
 212                              $pos     += $span;
 213                              break;
 214                          }
 215  
 216                          throw new Exception( sprintf( 'Unknown symbol "%s"', $next ) );
 217                  }
 218              }
 219  
 220              while ( ! empty( $stack ) ) {
 221                  $o2 = array_pop( $stack );
 222                  if ( '(' === $o2 || ')' === $o2 ) {
 223                      throw new Exception( 'Mismatched parentheses' );
 224                  }
 225  
 226                  $output[] = array( 'op', $o2 );
 227              }
 228  
 229              $this->tokens = $output;
 230          }
 231  
 232          /**
 233           * Get the plural form for a number.
 234           *
 235           * Caches the value for repeated calls.
 236           *
 237           * @since 4.9.0
 238           *
 239           * @param int $num Number to get plural form for.
 240           * @return int Plural form value.
 241           */
 242  		public function get( $num ) {
 243              if ( isset( $this->cache[ $num ] ) ) {
 244                  return $this->cache[ $num ];
 245              }
 246              $this->cache[ $num ] = $this->execute( $num );
 247              return $this->cache[ $num ];
 248          }
 249  
 250          /**
 251           * Execute the plural form function.
 252           *
 253           * @since 4.9.0
 254           *
 255           * @throws Exception If the plural form value cannot be calculated.
 256           *
 257           * @param int $n Variable "n" to substitute.
 258           * @return int Plural form value.
 259           */
 260  		public function execute( $n ) {
 261              $stack = array();
 262              $i     = 0;
 263              $total = count( $this->tokens );
 264              while ( $i < $total ) {
 265                  $next = $this->tokens[ $i ];
 266                  $i++;
 267                  if ( 'var' === $next[0] ) {
 268                      $stack[] = $n;
 269                      continue;
 270                  } elseif ( 'value' === $next[0] ) {
 271                      $stack[] = $next[1];
 272                      continue;
 273                  }
 274  
 275                  // Only operators left.
 276                  switch ( $next[1] ) {
 277                      case '%':
 278                          $v2      = array_pop( $stack );
 279                          $v1      = array_pop( $stack );
 280                          $stack[] = $v1 % $v2;
 281                          break;
 282  
 283                      case '||':
 284                          $v2      = array_pop( $stack );
 285                          $v1      = array_pop( $stack );
 286                          $stack[] = $v1 || $v2;
 287                          break;
 288  
 289                      case '&&':
 290                          $v2      = array_pop( $stack );
 291                          $v1      = array_pop( $stack );
 292                          $stack[] = $v1 && $v2;
 293                          break;
 294  
 295                      case '<':
 296                          $v2      = array_pop( $stack );
 297                          $v1      = array_pop( $stack );
 298                          $stack[] = $v1 < $v2;
 299                          break;
 300  
 301                      case '<=':
 302                          $v2      = array_pop( $stack );
 303                          $v1      = array_pop( $stack );
 304                          $stack[] = $v1 <= $v2;
 305                          break;
 306  
 307                      case '>':
 308                          $v2      = array_pop( $stack );
 309                          $v1      = array_pop( $stack );
 310                          $stack[] = $v1 > $v2;
 311                          break;
 312  
 313                      case '>=':
 314                          $v2      = array_pop( $stack );
 315                          $v1      = array_pop( $stack );
 316                          $stack[] = $v1 >= $v2;
 317                          break;
 318  
 319                      case '!=':
 320                          $v2      = array_pop( $stack );
 321                          $v1      = array_pop( $stack );
 322                          $stack[] = $v1 != $v2;
 323                          break;
 324  
 325                      case '==':
 326                          $v2      = array_pop( $stack );
 327                          $v1      = array_pop( $stack );
 328                          $stack[] = $v1 == $v2;
 329                          break;
 330  
 331                      case '?:':
 332                          $v3      = array_pop( $stack );
 333                          $v2      = array_pop( $stack );
 334                          $v1      = array_pop( $stack );
 335                          $stack[] = $v1 ? $v2 : $v3;
 336                          break;
 337  
 338                      default:
 339                          throw new Exception( sprintf( 'Unknown operator "%s"', $next[1] ) );
 340                  }
 341              }
 342  
 343              if ( count( $stack ) !== 1 ) {
 344                  throw new Exception( 'Too many values remaining on the stack' );
 345              }
 346  
 347              return (int) $stack[0];
 348          }
 349      }
 350  endif;


Generated: Sun Apr 28 01:00:03 2024 Cross-referenced by PHPXref 0.7.1