[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
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;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |