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