Make WordPress Core

Changeset 48769


Ignore:
Timestamp:
08/10/2020 11:32:01 AM (4 years ago)
Author:
SergeyBiryukov
Message:

I18N: Add a class_exists() check to Plural_Forms class for consistency with other POMO library classes.

Follow-up to [41722].

Fixes #50881.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/pomo/plural-forms.php

    r47219 r48769  
    66 * @since 4.9.0
    77 */
    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 ] ) {
     8if ( ! 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 ] ) {
    167171                                break;
    168172                            }
    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;
    170196                            break;
    171197                        }
    172198
    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;
    215228        }
    216229
    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 );
    241245            return $this->cache[ $num ];
    242246        }
    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        }
    245345    }
    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 }
     346endif;
Note: See TracChangeset for help on using the changeset viewer.