r13521 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r13520‎ | r13521 | r13522 >
Date:08:14, 6 April 2006
Author:tstarling
Status:old
Tags:
Comment:
* Better error reporting
* Inequality operators
* rand function
Modified paths:
  • /trunk/extensions/ParserFunctions/Expr.php (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions.php (modified) (history)

Diff [purge]

Index: trunk/extensions/ParserFunctions/Expr.php
@@ -9,42 +9,74 @@
1010 define( 'EXPR_NUMBER_CLASS', '0123456789.' );
1111
1212 // Token types
13 -define( 'EXPR_WHITE', 'WHITE' );
14 -define( 'EXPR_NUMBER', 'NUMBER' );
15 -define( 'EXPR_NEGATIVE', 'NEGATIVE' );
16 -define( 'EXPR_POSITIVE', 'POSITIVE' );
17 -define( 'EXPR_PLUS', 'PLUS' );
18 -define( 'EXPR_MINUS', 'MINUS' );
19 -define( 'EXPR_TIMES', 'TIMES' );
20 -define( 'EXPR_DIVIDE', 'DIVIDE' );
21 -define( 'EXPR_MOD', 'MOD' );
22 -define( 'EXPR_OPEN', 'OPEN' );
23 -define( 'EXPR_CLOSE', 'CLOSE' );
24 -define( 'EXPR_AND', 'AND' );
25 -define( 'EXPR_OR', 'OR' );
26 -define( 'EXPR_NOT', 'NOT' );
27 -define( 'EXPR_EQUALITY', 'EQUALITY' );
28 -define( 'EXPR_ROUND', 'ROUND' );
 13+define( 'EXPR_WHITE', 1 );
 14+define( 'EXPR_NUMBER', 2 );
 15+define( 'EXPR_NEGATIVE', 3 );
 16+define( 'EXPR_POSITIVE', 4 );
 17+define( 'EXPR_PLUS', 5 );
 18+define( 'EXPR_MINUS', 6 );
 19+define( 'EXPR_TIMES', 7 );
 20+define( 'EXPR_DIVIDE', 8 );
 21+define( 'EXPR_MOD', 9 );
 22+define( 'EXPR_OPEN', 10 );
 23+define( 'EXPR_CLOSE', 11 );
 24+define( 'EXPR_AND', 12 );
 25+define( 'EXPR_OR', 13 );
 26+define( 'EXPR_NOT', 14 );
 27+define( 'EXPR_EQUALITY', 15 );
 28+define( 'EXPR_LESS', 16 );
 29+define( 'EXPR_GREATER', 17 );
 30+define( 'EXPR_LESSEQ', 18 );
 31+define( 'EXPR_GREATEREQ', 19 );
 32+define( 'EXPR_NOTEQ', 20 );
 33+define( 'EXPR_ROUND', 21 );
2934
3035 class ExprParser {
31 - var $expr, $end;
 36+ var $maxStackSize = 1000;
3237
3338 var $precedence = array(
34 - EXPR_NEGATIVE => 9,
35 - EXPR_POSITIVE => 9,
36 - EXPR_TIMES => 8,
37 - EXPR_DIVIDE => 8,
38 - EXPR_MOD => 8,
39 - EXPR_PLUS => 6,
40 - EXPR_MINUS => 6,
41 - EXPR_ROUND => 5,
42 - EXPR_EQUALITY => 4,
43 - EXPR_AND => 3,
44 - EXPR_OR => 2,
45 - EXPR_OPEN => -1,
46 - EXPR_CLOSE => -1
47 - );
 39+ EXPR_NEGATIVE => 9,
 40+ EXPR_POSITIVE => 9,
 41+ EXPR_NOT => 9,
 42+ EXPR_TIMES => 8,
 43+ EXPR_DIVIDE => 8,
 44+ EXPR_MOD => 8,
 45+ EXPR_PLUS => 6,
 46+ EXPR_MINUS => 6,
 47+ EXPR_ROUND => 5,
 48+ EXPR_EQUALITY => 4,
 49+ EXPR_LESS => 4,
 50+ EXPR_GREATER => 4,
 51+ EXPR_LESSEQ => 4,
 52+ EXPR_GREATEREQ => 4,
 53+ EXPR_NOTEQ => 4,
 54+ EXPR_AND => 3,
 55+ EXPR_OR => 2,
 56+ EXPR_OPEN => -1,
 57+ EXPR_CLOSE => -1
 58+ );
4859
 60+ var $names = array(
 61+ EXPR_NEGATIVE => '-',
 62+ EXPR_POSITIVE => '+',
 63+ EXPR_NOT => 'not',
 64+ EXPR_TIMES => '*',
 65+ EXPR_DIVIDE => '/',
 66+ EXPR_MOD => 'mod',
 67+ EXPR_PLUS => '+',
 68+ EXPR_MINUS => '-',
 69+ EXPR_ROUND => 'round',
 70+ EXPR_EQUALITY => '=',
 71+ EXPR_LESS => '<',
 72+ EXPR_GREATER => '>',
 73+ EXPR_LESSEQ => '<=',
 74+ EXPR_GREATEREQ => '>=',
 75+ EXPR_NOTEQ => '<>',
 76+ EXPR_AND => 'and',
 77+ EXPR_OR => 'or',
 78+ );
 79+
 80+
4981 var $words = array(
5082 'mod' => EXPR_MOD,
5183 'and' => EXPR_AND,
@@ -53,10 +85,37 @@
5486 'round' => EXPR_ROUND,
5587 );
5688
57 - function error( $msg ) {
58 - $this->lastError = $msg;
 89+
 90+ /**
 91+ * Add expression messages to the message cache
 92+ * @static
 93+ */
 94+ function addMessages() {
 95+ global $wgMessageCache;
 96+ $wgMessageCache->addMessages( array(
 97+ 'expr_stack_exhausted' => 'Expression error: stack exhausted',
 98+ 'expr_unexpected_number' => 'Expression error: unexpected number',
 99+ 'expr_preg_match_failure' => 'Expression error: unexpected preg_match failure',
 100+ 'expr_unrecognised_word' => 'Expression error: unrecognised word "$1"',
 101+ 'expr_unexpected_operator' => 'Expression error: unexpected $1 operator',
 102+ 'expr_missing_operand' => 'Expression error: Missing operand for $1',
 103+ 'expr_unexpected_closing_bracket' => 'Expression error: unexpected closing bracket',
 104+ 'expr_unrecognised_punctuation' => 'Expression error: unrecognised punctuation character "$1"',
 105+ 'expr_unclosed_bracket' => 'Expression error: unclosed bracket',
 106+ ));
59107 }
 108+
60109
 110+ function error( $msg, $parameter = false ) {
 111+ $this->lastErrorKey = $msg;
 112+ $this->lastErrorParameter = $parameter;
 113+ if ( $parameter === false ) {
 114+ $this->lastErrorMessage = wfMsg( "expr_$msg" );
 115+ } else {
 116+ $this->lastErrorMessage = wfMsg( "expr_$msg", htmlspecialchars( $parameter ) );
 117+ }
 118+ }
 119+
61120 /**
62121 * Evaluate a mathematical expression
63122 *
@@ -67,16 +126,27 @@
68127 function doExpression( $expr ) {
69128 $operands = array();
70129 $operators = array();
 130+
 131+ # Unescape inequality operators
 132+ $expr = strtr( $expr, array( '&lt;' => '<', '&gt;' => '>' ) );
 133+
71134 $p = 0;
72135 $end = strlen( $expr );
73136 $expecting = 'expression';
74137
 138+
75139 while ( $p < $end ) {
 140+ if ( count( $operands ) > $this->maxStackSize || count( $operands ) > $this->maxStackSize ) {
 141+ $this->error( 'stack_exhausted' );
 142+ }
76143 $char = $expr[$p];
77 -
 144+ $char2 = substr( $expr, $p, 2 );
 145+
78146 // Mega if-elseif-else construct
79147 // Only binary operators fall through for processing at the bottom, the rest
80148 // finish their processing and continue
 149+
 150+ // First the unlimited length classes
81151
82152 if ( false !== strpos( EXPR_WHITE_CLASS, $char ) ) {
83153 // Whitespace
@@ -85,7 +155,7 @@
86156 } elseif ( false !== strpos( EXPR_NUMBER_CLASS, $char ) ) {
87157 // Number
88158 if ( $expecting != 'expression' ) {
89 - $this->error( 'Unexpected number' );
 159+ $this->error( 'unexpected_number' );
90160 return false;
91161 }
92162
@@ -102,7 +172,7 @@
103173 $remaining = substr( $expr, $p );
104174 if ( !preg_match( '/^[A-Za-z]*/', $remaining, $matches ) ) {
105175 // This should be unreachable
106 - $this->error( 'Unexpected preg_match failure' );
 176+ $this->error( 'preg_match_failure' );
107177 return false;
108178 }
109179 $word = strtolower( $matches[0] );
@@ -110,26 +180,42 @@
111181
112182 // Interpret the word
113183 if ( !isset( $this->words[$word] ) ){
114 - $this->error( 'Unrecognised word' );
 184+ $this->error( 'unrecognised_word', $word );
115185 return false;
116186 }
117187 $op = $this->words[$word];
118188 if ( $op == EXPR_NOT ) {
119189 // Unary operator
120190 if ( $expecting != 'expression' ) {
121 - $this->error( "Unexpected $op operator" );
 191+ $this->error( 'unexpected_operator', $word );
122192 return false;
123193 }
124194 $operators[] = $op;
125195 continue;
126 - } else {
127 - // Binary operator
128 - if ( $expecting == 'expression' ) {
129 - $this->error( "Unexpected $op operator" );
130 - return false;
131 - }
132196 }
133 - } elseif ( $char == '+' ) {
 197+ // Binary operator, fall through
 198+ $name = $word;
 199+ }
 200+
 201+ // Next the two-character operators
 202+
 203+ elseif ( $char2 == '<=' ) {
 204+ $name = $char2;
 205+ $op = EXPR_LESSEQ;
 206+ $p += 2;
 207+ } elseif ( $char2 == '>=' ) {
 208+ $name = $char2;
 209+ $op = EXPR_GREATEREQ;
 210+ $p += 2;
 211+ } elseif ( $char2 == '<>' || $char2 == '!=' ) {
 212+ $name = $char2;
 213+ $op = EXPR_NOTEQ;
 214+ $p += 2;
 215+ }
 216+
 217+ // Finally the single-character operators
 218+
 219+ elseif ( $char == '+' ) {
134220 ++$p;
135221 if ( $expecting == 'expression' ) {
136222 // Unary plus
@@ -150,24 +236,18 @@
151237 $op = EXPR_MINUS;
152238 }
153239 } elseif ( $char == '*' ) {
154 - if ( $expecting == 'expression' ) {
155 - $this->error( 'Unexpected * operator' );
156 - return false;
157 - }
 240+ $name = $char;
158241 $op = EXPR_TIMES;
159242 ++$p;
160243 } elseif ( $char == '/' ) {
161 - if ( $expecting == 'expression' ) {
162 - $this->error( 'Unexpected / operator' );
163 - return false;
164 - }
 244+ $name = $char;
165245 $op = EXPR_DIVIDE;
166246 ++$p;
167247 } elseif ( $char == '(' ) {
168248 if ( $expecting == 'operator' ) {
169 - $this->error( 'Unexpected opening bracket' );
 249+ $this->error( 'unexpected_operator', '(' );
170250 return false;
171 - }
 251+ }
172252 $operators[] = EXPR_OPEN;
173253 ++$p;
174254 continue;
@@ -175,7 +255,7 @@
176256 $lastOp = end( $operators );
177257 while ( $lastOp && $lastOp != EXPR_OPEN ) {
178258 if ( !$this->doOperation( $lastOp, $operands ) ) {
179 - $this->error( "Missing operand for $lastOp" );
 259+ $this->error( 'missing_operand', $this->names[$lastOp] );
180260 return false;
181261 }
182262 array_pop( $operators );
@@ -184,29 +264,40 @@
185265 if ( $lastOp ) {
186266 array_pop( $operators );
187267 } else {
188 - $this->error( "Unexpected closing bracket" );
 268+ $this->error( "unexpected_closing_bracket" );
189269 return false;
190270 }
191271 $expecting = 'operator';
192272 ++$p;
193273 continue;
194 - } elseif ( $char = '=' ) {
195 - if ( $expecting == 'expression' ) {
196 - $this->error( 'Unexpected = operator' );
197 - return false;
198 - }
 274+ } elseif ( $char == '=' ) {
 275+ $name = $char;
199276 $op = EXPR_EQUALITY;
200277 ++$p;
 278+ } elseif ( $char == '<' ) {
 279+ $name = $char;
 280+ $op = EXPR_LESS;
 281+ ++$p;
 282+ } elseif ( $char == '>' ) {
 283+ $name = $char;
 284+ $op = EXPR_GREATER;
 285+ ++$p;
201286 } else {
202 - $this->error( "Unrecognised punctuation character" );
 287+ $this->error( 'unrecognised_punctuation', $char );
203288 return false;
204289 }
205290
206 - // Shunting yard magic for binary operators
 291+ // Binary operator processing
 292+ if ( $expecting == 'expression' ) {
 293+ $this->error( 'unexpected_operator', $name );
 294+ return false;
 295+ }
 296+
 297+ // Shunting yard magic
207298 $lastOp = end( $operators );
208299 while ( $lastOp && $this->precedence[$op] <= $this->precedence[$lastOp] ) {
209300 if ( !$this->doOperation( $lastOp, $operands ) ) {
210 - $this->error( "Missing operand for $lastOp" );
 301+ $this->error( 'missing_operand', $this->names[$lastOp] );
211302 return false;
212303 }
213304 array_pop( $operators );
@@ -219,11 +310,11 @@
220311 // Finish off the operator array
221312 while ( $op = array_pop( $operators ) ) {
222313 if ( $op == EXPR_OPEN ) {
223 - $this->error( "Unclosed bracket" );
 314+ $this->error( 'unclosed_bracket' );
224315 return false;
225316 }
226317 if ( !$this->doOperation( $op, $operands ) ) {
227 - $this->error( "Missing operand for $lastOp" );
 318+ $this->error( 'missing_operand', $this->names[$op] );
228319 return false;
229320 }
230321 }
@@ -300,7 +391,36 @@
301392 $value = array_pop( $stack );
302393 $stack[] = round( $value, $digits );
303394 break;
304 -
 395+ case EXPR_LESS:
 396+ if ( count( $stack ) < 2 ) return false;
 397+ $right = array_pop( $stack );
 398+ $left = array_pop( $stack );
 399+ $stack[] = ( $left < $right ) ? 1 : 0;
 400+ break;
 401+ case EXPR_GREATER:
 402+ if ( count( $stack ) < 2 ) return false;
 403+ $right = array_pop( $stack );
 404+ $left = array_pop( $stack );
 405+ $stack[] = ( $left > $right ) ? 1 : 0;
 406+ break;
 407+ case EXPR_LESSEQ:
 408+ if ( count( $stack ) < 2 ) return false;
 409+ $right = array_pop( $stack );
 410+ $left = array_pop( $stack );
 411+ $stack[] = ( $left <= $right ) ? 1 : 0;
 412+ break;
 413+ case EXPR_GREATEREQ:
 414+ if ( count( $stack ) < 2 ) return false;
 415+ $right = array_pop( $stack );
 416+ $left = array_pop( $stack );
 417+ $stack[] = ( $left >= $right ) ? 1 : 0;
 418+ break;
 419+ case EXPR_NOTEQ:
 420+ if ( count( $stack ) < 2 ) return false;
 421+ $right = array_pop( $stack );
 422+ $left = array_pop( $stack );
 423+ $stack[] = ( $left != $right ) ? 1 : 0;
 424+ break;
305425 }
306426 return true;
307427 }
Index: trunk/extensions/ParserFunctions/ParserFunctions.php
@@ -9,16 +9,17 @@
1010 class ExtParserFunctions {
1111 var $mExprParser;
1212
13 - function exprHook( &$parser, $expr = '' ) {
 13+ function expr( &$parser, $expr = '' ) {
1414 if ( !isset( $this->mExpr ) ) {
1515 if ( !class_exists( 'ExprParser' ) ) {
16 - require_once( dirname( __FILE__ ) . '/Expr.php' );
 16+ require( dirname( __FILE__ ) . '/Expr.php' );
 17+ ExprParser::addMessages();
1718 }
1819 $this->mExprParser = new ExprParser;
1920 }
2021 $result = $this->mExprParser->doExpression( $expr );
2122 if ( $result === false ) {
22 - return wfMsg( 'expr_parse_error' );
 23+ return $this->mExprParser->lastErrorMessage;
2324 } else {
2425 return $result;
2526 }
@@ -32,13 +33,17 @@
3334 }
3435 }
3536
36 - function ifeqHook( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
 37+ function ifeq( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
3738 if ( trim( $left ) == trim( $right ) ) {
3839 return $then;
3940 } else {
4041 return $else;
4142 }
4243 }
 44+
 45+ function rand( &$parser, $min = 1, $max = 100 ) {
 46+ return mt_rand( $min, $max );
 47+ }
4348 }
4449
4550 function wfSetupParserFunctions() {
@@ -46,11 +51,10 @@
4752
4853 $wgExtParserFunctions = new ExtParserFunctions;
4954
50 - $wgParser->setFunctionHook( 'expr', array( &$wgExtParserFunctions, 'exprHook' ) );
51 - $wgParser->setFunctionHook( 'if', array( &$wgExtParserFunctions, 'ifHook' ) ) ;
52 - $wgParser->setFunctionHook( 'ifeq', array( &$wgExtParserFunctions, 'ifeqHook' ) ) ;
53 -
54 - $wgMessageCache->addMessage( 'expr_parse_error', 'Invalid expression' );
 55+ $wgParser->setFunctionHook( 'expr', array( &$wgExtParserFunctions, 'expr' ) );
 56+ $wgParser->setFunctionHook( 'if', array( &$wgExtParserFunctions, 'ifHook' ) );
 57+ $wgParser->setFunctionHook( 'ifeq', array( &$wgExtParserFunctions, 'ifeq' ) );
 58+ $wgParser->setFunctionHook( 'rand', array( &$wgExtParserFunctions, 'rand' ) );
5559 }
5660
5761 ?>

Status & tagging log