[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[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 #[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;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Nov 23 08:20:01 2024 | Cross-referenced by PHPXref |