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


Generated: Sun Sep 15 01:00:03 2019 Cross-referenced by PHPXref 0.7.1