[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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


Generated : Fri Mar 29 08:20:02 2024 Cross-referenced by PHPXref