PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/htdocs/wp-includes/pomo/plural-forms.php

https://gitlab.com/VTTE/sitios-vtte
PHP | 344 lines | 209 code | 49 blank | 86 comment | 27 complexity | 45dc94eed9d0f5dfdcd12cebbbdc073b MD5 | raw file
  1. <?php
  2. /**
  3. * A gettext Plural-Forms parser.
  4. *
  5. * @since 4.9.0
  6. */
  7. class Plural_Forms {
  8. /**
  9. * Operator characters.
  10. *
  11. * @since 4.9.0
  12. * @var string OP_CHARS Operator characters.
  13. */
  14. const OP_CHARS = '|&><!=%?:';
  15. /**
  16. * Valid number characters.
  17. *
  18. * @since 4.9.0
  19. * @var string NUM_CHARS Valid number characters.
  20. */
  21. const NUM_CHARS = '0123456789';
  22. /**
  23. * Operator precedence.
  24. *
  25. * Operator precedence from highest to lowest. Higher numbers indicate
  26. * higher precedence, and are executed first.
  27. *
  28. * @see https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence
  29. *
  30. * @since 4.9.0
  31. * @var array $op_precedence Operator precedence from highest to lowest.
  32. */
  33. protected static $op_precedence = array(
  34. '%' => 6,
  35. '<' => 5,
  36. '<=' => 5,
  37. '>' => 5,
  38. '>=' => 5,
  39. '==' => 4,
  40. '!=' => 4,
  41. '&&' => 3,
  42. '||' => 2,
  43. '?:' => 1,
  44. '?' => 1,
  45. '(' => 0,
  46. ')' => 0,
  47. );
  48. /**
  49. * Tokens generated from the string.
  50. *
  51. * @since 4.9.0
  52. * @var array $tokens List of tokens.
  53. */
  54. protected $tokens = array();
  55. /**
  56. * Cache for repeated calls to the function.
  57. *
  58. * @since 4.9.0
  59. * @var array $cache Map of $n => $result
  60. */
  61. protected $cache = array();
  62. /**
  63. * Constructor.
  64. *
  65. * @since 4.9.0
  66. *
  67. * @param string $str Plural function (just the bit after `plural=` from Plural-Forms)
  68. */
  69. public function __construct( $str ) {
  70. $this->parse( $str );
  71. }
  72. /**
  73. * Parse a Plural-Forms string into tokens.
  74. *
  75. * Uses the shunting-yard algorithm to convert the string to Reverse Polish
  76. * Notation tokens.
  77. *
  78. * @since 4.9.0
  79. *
  80. * @param string $str String to parse.
  81. */
  82. protected function parse( $str ) {
  83. $pos = 0;
  84. $len = strlen( $str );
  85. // Convert infix operators to postfix using the shunting-yard algorithm.
  86. $output = array();
  87. $stack = array();
  88. while ( $pos < $len ) {
  89. $next = substr( $str, $pos, 1 );
  90. switch ( $next ) {
  91. // Ignore whitespace.
  92. case ' ':
  93. case "\t":
  94. $pos++;
  95. break;
  96. // Variable (n).
  97. case 'n':
  98. $output[] = array( 'var' );
  99. $pos++;
  100. break;
  101. // Parentheses.
  102. case '(':
  103. $stack[] = $next;
  104. $pos++;
  105. break;
  106. case ')':
  107. $found = false;
  108. while ( ! empty( $stack ) ) {
  109. $o2 = $stack[ count( $stack ) - 1 ];
  110. if ( '(' !== $o2 ) {
  111. $output[] = array( 'op', array_pop( $stack ) );
  112. continue;
  113. }
  114. // Discard open paren.
  115. array_pop( $stack );
  116. $found = true;
  117. break;
  118. }
  119. if ( ! $found ) {
  120. throw new Exception( 'Mismatched parentheses' );
  121. }
  122. $pos++;
  123. break;
  124. // Operators.
  125. case '|':
  126. case '&':
  127. case '>':
  128. case '<':
  129. case '!':
  130. case '=':
  131. case '%':
  132. case '?':
  133. $end_operator = strspn( $str, self::OP_CHARS, $pos );
  134. $operator = substr( $str, $pos, $end_operator );
  135. if ( ! array_key_exists( $operator, self::$op_precedence ) ) {
  136. throw new Exception( sprintf( 'Unknown operator "%s"', $operator ) );
  137. }
  138. while ( ! empty( $stack ) ) {
  139. $o2 = $stack[ count( $stack ) - 1 ];
  140. // Ternary is right-associative in C.
  141. if ( '?:' === $operator || '?' === $operator ) {
  142. if ( self::$op_precedence[ $operator ] >= self::$op_precedence[ $o2 ] ) {
  143. break;
  144. }
  145. } elseif ( self::$op_precedence[ $operator ] > self::$op_precedence[ $o2 ] ) {
  146. break;
  147. }
  148. $output[] = array( 'op', array_pop( $stack ) );
  149. }
  150. $stack[] = $operator;
  151. $pos += $end_operator;
  152. break;
  153. // Ternary "else".
  154. case ':':
  155. $found = false;
  156. $s_pos = count( $stack ) - 1;
  157. while ( $s_pos >= 0 ) {
  158. $o2 = $stack[ $s_pos ];
  159. if ( '?' !== $o2 ) {
  160. $output[] = array( 'op', array_pop( $stack ) );
  161. $s_pos--;
  162. continue;
  163. }
  164. // Replace.
  165. $stack[ $s_pos ] = '?:';
  166. $found = true;
  167. break;
  168. }
  169. if ( ! $found ) {
  170. throw new Exception( 'Missing starting "?" ternary operator' );
  171. }
  172. $pos++;
  173. break;
  174. // Default - number or invalid.
  175. default:
  176. if ( $next >= '0' && $next <= '9' ) {
  177. $span = strspn( $str, self::NUM_CHARS, $pos );
  178. $output[] = array( 'value', intval( substr( $str, $pos, $span ) ) );
  179. $pos += $span;
  180. break;
  181. }
  182. throw new Exception( sprintf( 'Unknown symbol "%s"', $next ) );
  183. }
  184. }
  185. while ( ! empty( $stack ) ) {
  186. $o2 = array_pop( $stack );
  187. if ( '(' === $o2 || ')' === $o2 ) {
  188. throw new Exception( 'Mismatched parentheses' );
  189. }
  190. $output[] = array( 'op', $o2 );
  191. }
  192. $this->tokens = $output;
  193. }
  194. /**
  195. * Get the plural form for a number.
  196. *
  197. * Caches the value for repeated calls.
  198. *
  199. * @since 4.9.0
  200. *
  201. * @param int $num Number to get plural form for.
  202. * @return int Plural form value.
  203. */
  204. public function get( $num ) {
  205. if ( isset( $this->cache[ $num ] ) ) {
  206. return $this->cache[ $num ];
  207. }
  208. $this->cache[ $num ] = $this->execute( $num );
  209. return $this->cache[ $num ];
  210. }
  211. /**
  212. * Execute the plural form function.
  213. *
  214. * @since 4.9.0
  215. *
  216. * @param int $n Variable "n" to substitute.
  217. * @return int Plural form value.
  218. */
  219. public function execute( $n ) {
  220. $stack = array();
  221. $i = 0;
  222. $total = count( $this->tokens );
  223. while ( $i < $total ) {
  224. $next = $this->tokens[ $i ];
  225. $i++;
  226. if ( 'var' === $next[0] ) {
  227. $stack[] = $n;
  228. continue;
  229. } elseif ( 'value' === $next[0] ) {
  230. $stack[] = $next[1];
  231. continue;
  232. }
  233. // Only operators left.
  234. switch ( $next[1] ) {
  235. case '%':
  236. $v2 = array_pop( $stack );
  237. $v1 = array_pop( $stack );
  238. $stack[] = $v1 % $v2;
  239. break;
  240. case '||':
  241. $v2 = array_pop( $stack );
  242. $v1 = array_pop( $stack );
  243. $stack[] = $v1 || $v2;
  244. break;
  245. case '&&':
  246. $v2 = array_pop( $stack );
  247. $v1 = array_pop( $stack );
  248. $stack[] = $v1 && $v2;
  249. break;
  250. case '<':
  251. $v2 = array_pop( $stack );
  252. $v1 = array_pop( $stack );
  253. $stack[] = $v1 < $v2;
  254. break;
  255. case '<=':
  256. $v2 = array_pop( $stack );
  257. $v1 = array_pop( $stack );
  258. $stack[] = $v1 <= $v2;
  259. break;
  260. case '>':
  261. $v2 = array_pop( $stack );
  262. $v1 = array_pop( $stack );
  263. $stack[] = $v1 > $v2;
  264. break;
  265. case '>=':
  266. $v2 = array_pop( $stack );
  267. $v1 = array_pop( $stack );
  268. $stack[] = $v1 >= $v2;
  269. break;
  270. case '!=':
  271. $v2 = array_pop( $stack );
  272. $v1 = array_pop( $stack );
  273. $stack[] = $v1 != $v2;
  274. break;
  275. case '==':
  276. $v2 = array_pop( $stack );
  277. $v1 = array_pop( $stack );
  278. $stack[] = $v1 == $v2;
  279. break;
  280. case '?:':
  281. $v3 = array_pop( $stack );
  282. $v2 = array_pop( $stack );
  283. $v1 = array_pop( $stack );
  284. $stack[] = $v1 ? $v2 : $v3;
  285. break;
  286. default:
  287. throw new Exception( sprintf( 'Unknown operator "%s"', $next[1] ) );
  288. }
  289. }
  290. if ( count( $stack ) !== 1 ) {
  291. throw new Exception( 'Too many values remaining on the stack' );
  292. }
  293. return (int) $stack[0];
  294. }
  295. }