Changeset 48769
- Timestamp:
- 08/10/2020 11:32:01 AM (4 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/pomo/plural-forms.php
r47219 r48769 6 6 * @since 4.9.0 7 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 ] ) { 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 ] ) { 167 171 break; 168 172 } 169 } elseif ( self::$op_precedence[ $operator ] > self::$op_precedence[ $o2 ] ) { 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; 170 196 break; 171 197 } 172 198 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 } 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; 215 228 } 216 229 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 ] ) ) { 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 ); 241 245 return $this->cache[ $num ]; 242 246 } 243 $this->cache[ $num ] = $this->execute( $num ); 244 return $this->cache[ $num ]; 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 } 245 345 } 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 ( 'var' === $next[0] ) { 263 $stack[] = $n; 264 continue; 265 } elseif ( 'value' === $next[0] ) { 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 } 346 endif;
Note: See TracChangeset
for help on using the changeset viewer.