Make WordPress Core


Ignore:
Timestamp:
08/10/2020 11:32:01 AM (6 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.