Index: trunk/extensions/InlineScripts/i18n/Messages.php |
— | — | @@ -18,11 +18,12 @@ |
19 | 19 | 'inlinescripts-call-fromwikitext' => '$1::$2 called by wikitext', |
20 | 20 | 'inlinescripts-call-parse' => 'parse( "$1" )', |
21 | 21 | |
| 22 | + 'inlinescripts-error' => 'Following parsing {{plural:$1|error|errors}} detected:', |
22 | 23 | 'inlinescripts-codelocation' => 'in module $1 at line $2', |
23 | 24 | |
24 | 25 | 'inlinescripts-exception-unexceptedtoken' => 'Unexpected token $2 $1: expected $3 (parser state $4)', |
25 | | - 'inlinescripts-exception-unclosedstring' => 'Unclosed string in module $1 at char $2', |
26 | | - 'inlinescripts-exception-unrecognisedtoken' => 'Unrecognized token in module $1 at char $2', |
| 26 | + 'inlinescripts-exception-unclosedstring' => 'Unclosed string $1', |
| 27 | + 'inlinescripts-exception-unrecognisedtoken' => 'Unrecognized token $1', |
27 | 28 | 'inlinescripts-exception-toomanytokens' => 'Exceeded tokens limit', |
28 | 29 | 'inlinescripts-exception-toomanyevals' => 'Exceeded evaluations limit $1', |
29 | 30 | 'inlinescripts-exception-recoverflow' => 'Too deep abstract syntax tree', |
— | — | @@ -42,10 +43,12 @@ |
43 | 44 | 'inlinescripts-exception-nonexistent-module' => 'Call to non-existent module $2 $1', |
44 | 45 | 'inlinescripts-exception-unknownfunction-user' => 'Trying to use an unnknown user function $2::$3 $1', |
45 | 46 | 'inlinescripts-exception-recursion' => 'Function loop detected when calling function $2::$3 $1', |
| 47 | + 'inlinescripts-exception-toodeeprecursion' => 'The maximum function nesting limit of $2 exceeded $1', |
46 | 48 | |
47 | 49 | 'inlinescripts-transerror-notenoughargs-user' => 'Not enough arguments for function $1::$2', |
48 | 50 | 'inlinescripts-transerror-nonexistent-module' => 'Call to non-existent module $1', |
49 | 51 | 'inlinescripts-transerror-unknownfunction-user' => 'Trying to use an unnknown user function $1::$2', |
50 | 52 | 'inlinescripts-transerror-recursion' => 'Function loop detected when calling function $1::$2', |
51 | 53 | 'inlinescripts-transerror-nofunction' => 'Missing function name when invoking the script', |
| 54 | + 'inlinescripts-transerror-toodeeprecursion' => 'The maximum function nesting limit of $1 exceeded', |
52 | 55 | ); |
Index: trunk/extensions/InlineScripts/interpreter/LRParser.php |
— | — | @@ -15,7 +15,7 @@ |
16 | 16 | const Reduce = 1; |
17 | 17 | const Accept = 2; |
18 | 18 | |
19 | | - var $mLoaded, $mNonterminals, $mProductions, $mAction, $mGoto; |
| 19 | + static $mLoaded, $mNonterminals, $mProductions, $mAction, $mGoto; |
20 | 20 | |
21 | 21 | public static function getVersion() { |
22 | 22 | return IS_LR_VERSION; |
— | — | @@ -24,16 +24,16 @@ |
25 | 25 | private function loadGrammar() { |
26 | 26 | wfProfileIn( __METHOD__ ); |
27 | 27 | |
28 | | - if( $this->mLoaded ) |
| 28 | + if( self::$mLoaded ) |
29 | 29 | return; |
30 | 30 | |
31 | 31 | require_once( 'LRTable.php' ); |
32 | 32 | |
33 | | - $this->mNonterminals = ISLRTable::$nonterminals; |
34 | | - $this->mProductions = ISLRTable::$productions; |
35 | | - $this->mAction = ISLRTable::$action; |
36 | | - $this->mGoto = ISLRTable::$goto; |
37 | | - $this->mLoaded = true; |
| 33 | + self::$mNonterminals = ISLRTable::$nonterminals; |
| 34 | + self::$mProductions = ISLRTable::$productions; |
| 35 | + self::$mAction = ISLRTable::$action; |
| 36 | + self::$mGoto = ISLRTable::$goto; |
| 37 | + self::$mLoaded = true; |
38 | 38 | |
39 | 39 | wfProfileOut( __METHOD__ ); |
40 | 40 | } |
— | — | @@ -43,7 +43,7 @@ |
44 | 44 | } |
45 | 45 | |
46 | 46 | public function parse( $scanner, $module, $maxTokens ) { |
47 | | - $this->loadGrammar(); |
| 47 | + self::loadGrammar(); |
48 | 48 | |
49 | 49 | $states = array( array( null, 0 ) ); |
50 | 50 | $scanner->rewind(); |
— | — | @@ -66,17 +66,17 @@ |
67 | 67 | } |
68 | 68 | |
69 | 69 | list( $stateval, $state ) = end( $states ); |
70 | | - $act = @$this->mAction[$state][$cur]; |
| 70 | + $act = @self::$mAction[$state][$cur]; |
71 | 71 | if( !$act ) { |
72 | 72 | wfProfileOut( __METHOD__ ); |
73 | 73 | throw new ISUserVisibleException( 'unexceptedtoken', $module, $token->line, |
74 | | - array( $token, implode( ', ', array_keys( @$this->mAction[$state] ) ), $state ) ); |
| 74 | + array( $token, implode( ', ', array_keys( @self::$mAction[$state] ) ), $state ) ); |
75 | 75 | } |
76 | 76 | if( $act[0] == self::Shift ) { |
77 | 77 | $states[] = array( $token, $act[1] ); |
78 | 78 | $scanner->next(); |
79 | 79 | } elseif( $act[0] == self::Reduce ) { |
80 | | - list( $nonterm, $prod ) = $this->mProductions[$act[1]]; |
| 80 | + list( $nonterm, $prod ) = self::$mProductions[$act[1]]; |
81 | 81 | $len = count( $prod ); |
82 | 82 | |
83 | 83 | // Change state |
— | — | @@ -91,7 +91,7 @@ |
92 | 92 | list( $val ) = $symbol; |
93 | 93 | $node->addChild( $val ); |
94 | 94 | } |
95 | | - $states[] = array( $node, $this->mGoto[$state][$nonterm] ); |
| 95 | + $states[] = array( $node, self::$mGoto[$state][$nonterm] ); |
96 | 96 | } elseif( $act[0] == self::Accept ) { |
97 | 97 | break; |
98 | 98 | } |
— | — | @@ -101,4 +101,14 @@ |
102 | 102 | |
103 | 103 | return new ISParserOutput( $states[1][0], $tokenCount ); |
104 | 104 | } |
| 105 | + |
| 106 | + public function getSyntaxErrors( $input, $module, $maxTokens ) { |
| 107 | + try { |
| 108 | + $this->parse( $input, $module, $maxTokens ); |
| 109 | + } catch( ISUserVisibleException $e ) { |
| 110 | + return array( $e->getMessage() ); |
| 111 | + } |
| 112 | + |
| 113 | + return array(); |
| 114 | + } |
105 | 115 | } |
Index: trunk/extensions/InlineScripts/interpreter/CallStack.php |
— | — | @@ -47,6 +47,8 @@ |
48 | 48 | |
49 | 49 | public function isFull() { |
50 | 50 | global $wgInlineScriptsMaxCallStackDepth; |
| 51 | + |
| 52 | + return count( $this->mStack ) >= $wgInlineScriptsMaxCallStackDepth; |
51 | 53 | } |
52 | 54 | |
53 | 55 | public function contains( $module, $name ) { |
Index: trunk/extensions/InlineScripts/interpreter/Interpreter.php |
— | — | @@ -34,27 +34,59 @@ |
35 | 35 | class ISInterpreter { |
36 | 36 | const ParserVersion = 1; |
37 | 37 | |
38 | | - var $mParser, $mCodeParser, $mUseCache, $mUsedModules, $mCallStack; |
39 | | - var $mMaxRecursion, $mEvaluations, $mTokens; |
40 | | - |
| 38 | + var $mParser, $mUseCache, $mUsedModules, $mCallStack; |
| 39 | + var $mMaxRecursion, $mEvaluations; |
41 | 40 | var $mParserCache; // Unserializing can be expensive as well |
42 | 41 | |
| 42 | + static $mCodeParser; |
| 43 | + |
43 | 44 | public function __construct( $parser ) { |
44 | | - global $wgInlineScriptsParserClass, $wgInlineScriptsUseCache; |
| 45 | + global $wgInlineScriptsUseCache; |
45 | 46 | |
46 | 47 | $this->mParser = $parser; |
47 | | - $this->mCodeParser = new $wgInlineScriptsParserClass( $this ); |
48 | 48 | $this->mUseCache = $wgInlineScriptsUseCache; |
49 | 49 | |
50 | 50 | $this->mCallStack = new ISCallStack( $this ); |
51 | 51 | $this->mUsedModules = array(); |
52 | 52 | $this->mMaxRecursion = |
53 | | - $this->mEvaluations = |
54 | | - $this->mTokens = |
| 53 | + $this->mEvaluations = |
55 | 54 | 0; |
56 | 55 | } |
57 | 56 | |
| 57 | + public static function invokeCodeParser( $code, $module, $method = 'parse' ) { |
| 58 | + global $wgInlineScriptsParserClass, $wgInlineScriptsLimits; |
| 59 | + |
| 60 | + if( !self::$mCodeParser ) { |
| 61 | + self::$mCodeParser = new $wgInlineScriptsParserClass(); |
| 62 | + } |
| 63 | + |
| 64 | + if( self::$mCodeParser->needsScanner() ) { |
| 65 | + $input = new ISScanner( $module, $code ); |
| 66 | + } else { |
| 67 | + $input = $code; |
| 68 | + } |
| 69 | + |
| 70 | + return self::$mCodeParser->$method( $input, $module, $wgInlineScriptsLimits['tokens'] ); |
| 71 | + } |
| 72 | + |
58 | 73 | /** |
| 74 | + * Invalidate the module by title. |
| 75 | + */ |
| 76 | + public static function invalidateModule( $title ) { |
| 77 | + global $parserMemc; |
| 78 | + |
| 79 | + $key = ISModule::getCacheKey( $title ); |
| 80 | + $parserMemc->delete( $key ); |
| 81 | + } |
| 82 | + |
| 83 | + /** |
| 84 | + * Checks the syntax of the script and returns an array of syntax errors. |
| 85 | + */ |
| 86 | + public static function getSyntaxErrors( $module, $text ) { |
| 87 | + return self::invokeCodeParser( $text, $module, 'getSyntaxErrors' ); |
| 88 | + } |
| 89 | + |
| 90 | + /** |
59 | 91 | * Disable cache for benchmarking or debugging purposes. |
60 | 92 | */ |
61 | 93 | public function disableCache() { |
— | — | @@ -98,7 +130,7 @@ |
99 | 131 | |
100 | 132 | public function getMaxTokensLeft() { |
101 | 133 | global $wgInlineScriptsLimits; |
102 | | - return $wgInlineScriptsLimits['tokens'] - $this->mTokens; |
| 134 | + return $wgInlineScriptsLimits['tokens']; |
103 | 135 | } |
104 | 136 | |
105 | 137 | /** |
— | — | @@ -154,9 +186,8 @@ |
155 | 187 | |
156 | 188 | // Parse |
157 | 189 | $moduleName = $rev->getTitle()->getText(); |
158 | | - $scanner = new ISScanner( $moduleName, $rev->getText() ); |
159 | | - $out = $this->mCodeParser->parse( $scanner, $moduleName, $this->getMaxTokensLeft() ); |
160 | | - $module = ISModule::newFromParserOutput( $this, $rev->getTitle(), $out ); |
| 190 | + $out = self::invokeCodeParser( $rev->getText(), $moduleName ); |
| 191 | + $module = ISModule::newFromParserOutput( $this, $rev->getTitle(), $rev->getId(), $out ); |
161 | 192 | |
162 | 193 | // Save to cache |
163 | 194 | $this->mParserCache[$key] = $module; |
— | — | @@ -191,7 +222,7 @@ |
192 | 223 | * @return ISData |
193 | 224 | */ |
194 | 225 | public function invokeUserFunctionFromModule( $module, $name, $args, $parentContext, $line ) { |
195 | | - global $wgInlineScriptsAllowRecursion; |
| 226 | + global $wgInlineScriptsAllowRecursion, $wgInlineScriptsMaxCallStackDepth; |
196 | 227 | |
197 | 228 | // Load module |
198 | 229 | if( $module instanceof ISModule ) { |
— | — | @@ -221,6 +252,9 @@ |
222 | 253 | if( !$wgInlineScriptsAllowRecursion && $this->mCallStack->contains( $moduleName, $name ) ) { |
223 | 254 | throw new ISUserVisibleException( 'recursion', $parentContext->mModuleName, $line, array( $moduleName, $name ) ); |
224 | 255 | } |
| 256 | + if( $this->mCallStack->isFull() ) { |
| 257 | + throw new ISUserVisibleException( 'toodeeprecursion', $parentContext->mModuleName, $line, array( $wgInlineScriptsMaxCallStackDepth ) ); |
| 258 | + } |
225 | 259 | |
226 | 260 | // Prepare the context and the arguments |
227 | 261 | $context = new ISEvaluationContext( $this, $module, $name, $parentContext->getFrame() ); |
— | — | @@ -276,6 +310,11 @@ |
277 | 311 | if( !$wgInlineScriptsAllowRecursion && $this->mCallStack->contains( $moduleName, $name ) ) { |
278 | 312 | throw new ISTransclusionException( 'recursion', array( $moduleName, $name ) ); |
279 | 313 | } |
| 314 | + if( $this->mCallStack->isFull() ) { |
| 315 | + // Depsite seeming an unlikely place, this may actually happen if the user will try to bypass the |
| 316 | + // stack depth limit by using parse( '{{i:module|func}}' ) |
| 317 | + throw new ISTransclusionException( 'toodeeprecursion', array( $wgInlineScriptsMaxCallStackDepth ) ); |
| 318 | + } |
280 | 319 | |
281 | 320 | // Prepare the context and the arguments |
282 | 321 | $context = new ISEvaluationContext( $this, $module, $name, $frame ); |
— | — | @@ -327,15 +366,20 @@ |
328 | 367 | */ |
329 | 368 | class ISModule { |
330 | 369 | var $mTitle, $mFunctions, $mParserVersion; |
| 370 | + |
| 371 | + // Revision ID |
| 372 | + // Not used now, will be used if we ever introduce the function output cache |
| 373 | + var $mRevID; |
331 | 374 | |
332 | 375 | protected function __construct() {} |
333 | 376 | |
334 | 377 | /** |
335 | 378 | * Initializes module from the code parser output. |
336 | 379 | */ |
337 | | - public static function newFromParserOutput( $interpreter, $title, $output ) { |
| 380 | + public static function newFromParserOutput( $interpreter, $title, $revid, $output ) { |
338 | 381 | $m = new ISModule(); |
339 | 382 | $m->mTitle = $title; |
| 383 | + $m->mRevID = $revid; |
340 | 384 | $m->mParserVersion = $output->getVersion(); |
341 | 385 | $m->mFunctions = array(); |
342 | 386 | |
Index: trunk/extensions/InlineScripts/interpreter/Shared.php |
— | — | @@ -94,7 +94,8 @@ |
95 | 95 | var $mType, $mChildren; |
96 | 96 | |
97 | 97 | public function __construct( $parser, $id ) { |
98 | | - $this->mType = $parser->mNonterminals[$id]; |
| 98 | + $parserClass = get_class( $parser ); |
| 99 | + $this->mType = $parserClass::$mNonterminals[$id]; |
99 | 100 | } |
100 | 101 | |
101 | 102 | public function addChild( $node ) { |
— | — | @@ -166,6 +167,14 @@ |
167 | 168 | * @return ISParserTreeNode |
168 | 169 | */ |
169 | 170 | public function parse( $input, $module, $maxTokens ); |
| 171 | + |
| 172 | + /** |
| 173 | + * Returns an array of the syntax errors in the code |
| 174 | + * @param input ISSCanner Input (scanner or string) |
| 175 | + * @param maxTokens int Maximal amount of tokens |
| 176 | + * @return array(string) |
| 177 | + */ |
| 178 | + public function getSyntaxErrors( $input, $moudle, $maxTokens ); |
170 | 179 | } |
171 | 180 | |
172 | 181 | class ISException extends MWException {} |
Index: trunk/extensions/InlineScripts/interpreterTests.txt |
— | — | @@ -1,135 +1,226 @@ |
2 | 2 | # Test cases for MediaWiki inline scripts engine |
3 | 3 | |
| 4 | +!! article |
| 5 | +Module:Basic mathematics |
| 6 | +!! text |
| 7 | +function run() { |
| 8 | + return -2 + 2 * 2 ** 2 - 3 * 7 % 5; |
| 9 | +} |
| 10 | +!! endarticle |
| 11 | + |
4 | 12 | !! test |
5 | 13 | Basic mathematics |
6 | 14 | !! input |
7 | | -{{#inline:-2 + 2 * 2 ** 2 - 3 * 7 % 5;}} |
| 15 | +{{i:Basic mathematics|run}} |
8 | 16 | !! result |
9 | 17 | <p>5 |
10 | 18 | </p> |
11 | 19 | !! end |
12 | 20 | |
| 21 | +!! article |
| 22 | +Module:Pow associativity |
| 23 | +!! text |
| 24 | +function run() { |
| 25 | + // Not 4096 |
| 26 | + return 4 ** 3 ** 2; |
| 27 | +} |
| 28 | +!! endarticle |
| 29 | + |
13 | 30 | !! test |
14 | 31 | ** associativity |
15 | 32 | !! input |
16 | | -{{#inline: 4 ** 3 ** 2;}}<!-- Not 4096! --> |
| 33 | +{{i:Pow associativity|run}} |
17 | 34 | !! result |
18 | 35 | <p>262144 |
19 | 36 | </p> |
20 | 37 | !! end |
21 | 38 | |
| 39 | +!! article |
| 40 | +Module:String contecation |
| 41 | +!! text |
| 42 | +function add( a, b ) { |
| 43 | + // if you pass 3 and 7, it would be 37, because all arguments from wikitext |
| 44 | + // are strings |
| 45 | + return a + b; |
| 46 | +} |
| 47 | +!! endarticle |
| 48 | + |
22 | 49 | !! test |
23 | | -String contecation and out() |
| 50 | +String contecation |
24 | 51 | !! input |
25 | | -<wikiscript> |
26 | | -out( "foo" + "bar" ); |
27 | | -</wikiscript> |
| 52 | +{{i:string contecation|add|3|7}} |
28 | 53 | !! result |
29 | | -<p>foobar |
| 54 | +<p>37 |
30 | 55 | </p> |
31 | 56 | !! end |
32 | 57 | |
| 58 | +!! article |
| 59 | +Module:Multiple variable assignment |
| 60 | +!! text |
| 61 | +function run() { |
| 62 | + // if you pass 3 and 7, it would be 37, because all arguments from wikitext |
| 63 | + // are strings |
| 64 | + a = b = 3; |
| 65 | + return a + b; |
| 66 | +} |
| 67 | +!! endarticle |
| 68 | + |
33 | 69 | !! test |
34 | 70 | Multiple variable assignment |
35 | 71 | !! input |
36 | | -{{#inline: a = b = 3; a + b; }} |
| 72 | +{{i:Multiple variable assignment|run}} |
37 | 73 | !! result |
38 | 74 | <p>6 |
39 | 75 | </p> |
40 | 76 | !! end |
41 | 77 | |
| 78 | +!! article |
| 79 | +Module:Assigment with arithmetics |
| 80 | +!! text |
| 81 | +function run() { |
| 82 | + a = 2; |
| 83 | + a += 3; |
| 84 | + a -= 7; |
| 85 | + return a; |
| 86 | +} |
| 87 | +!! endarticle |
| 88 | + |
42 | 89 | !! test |
43 | 90 | Assigment with arithmetics (+=, -=, etc) |
44 | 91 | !! input |
45 | | -{{#inline: a = 2; a += 3; a -= 7; a; }} |
| 92 | +{{i:Assigment with arithmetics|run}} |
46 | 93 | !! result |
47 | 94 | <p>-2 |
48 | 95 | </p> |
49 | 96 | !! end |
50 | 97 | |
| 98 | +!! article |
| 99 | +Module:Boolean shortcut |
| 100 | +!! text |
| 101 | +function run() { |
| 102 | + /* Only first statement should be performed */ |
| 103 | + !(b = 2) | (b = 3) | (b = 4); |
| 104 | + return b; |
| 105 | +} |
| 106 | +!! endarticle |
| 107 | + |
51 | 108 | !! test |
52 | 109 | Boolean shortcut |
53 | 110 | !! input |
54 | | -<wikiscript> |
55 | | -!(b = 2) | (b = 3) | (b = 4); |
56 | | -out( b ); |
57 | | -</wikiscript> |
| 111 | +{{i:boolean shortcut|run}} |
58 | 112 | !! result |
59 | 113 | <p>3 |
60 | 114 | </p> |
61 | 115 | !! end |
62 | 116 | |
| 117 | +!! article |
| 118 | +Module:Equality |
| 119 | +!! text |
| 120 | +function run() { |
| 121 | + return "2" == 2 & "2" !== 2 & 4 === (2 + 2) & |
| 122 | + null == "" & false == null & 0 == ""; |
| 123 | +} |
| 124 | +!! endarticle |
| 125 | + |
63 | 126 | !! test |
64 | 127 | Equality |
65 | 128 | !! input |
66 | | -{{#inline: "2" == 2 & "2" !== 2 & 4 === (2 + 2) & |
67 | | -null == "" & false == null & 0 == ""; }} |
| 129 | +{{i:equality|run}} |
68 | 130 | !! result |
69 | 131 | <p>1 |
70 | 132 | </p> |
71 | 133 | !! end |
72 | 134 | |
73 | | -!! test |
74 | | -Comments |
75 | | -!! input |
76 | | -{{#inline: 2 + /* 2 + */ 2; }} |
77 | | -!! result |
78 | | -<p>4 |
79 | | -</p> |
80 | | -!!end |
| 135 | +!! article |
| 136 | +Module:Comparisons |
| 137 | +!! text |
| 138 | +function run() { |
| 139 | + return 2 > 1 & 2 >= 2 & 2 <= 2 & 1 < 2; |
| 140 | +} |
| 141 | +!! endarticle |
81 | 142 | |
82 | 143 | !! test |
83 | 144 | Comparsions |
84 | 145 | !! input |
85 | | -{{#inline: 2 > 1 & 2 >= 2 & 2 <= 2 & 1 < 2; }} |
| 146 | +{{i:comparisons|run}} |
86 | 147 | !! result |
87 | 148 | <p>1 |
88 | 149 | </p> |
89 | 150 | !! end |
90 | 151 | |
| 152 | +!! article |
| 153 | +Module:Trivial |
| 154 | +!! text |
| 155 | +function getText() { |
| 156 | + return "AA"; |
| 157 | +} |
| 158 | +!! endarticle |
| 159 | + |
91 | 160 | !! test |
92 | | -Tag integration |
| 161 | +Integration with other functions |
93 | 162 | !! input |
94 | | -{{lc:<wikiscript>out("AA");</wikiscript>}} |
| 163 | +{{lc:{{i:trivial|getText}}}} |
95 | 164 | !! result |
96 | 165 | <p>aa |
97 | 166 | </p> |
98 | 167 | !! end |
99 | 168 | |
| 169 | +!! article |
| 170 | +Module:Conditions |
| 171 | +!! text |
| 172 | +function run() { |
| 173 | + return 2 + 2 == 4 ? "a" : "b"; |
| 174 | +} |
| 175 | +!! endarticle |
| 176 | + |
100 | 177 | !! test |
101 | 178 | Conditions (?) |
102 | 179 | !! input |
103 | | -{{#inline: 2 + 2 == 4 ? "a" : "b";}} |
| 180 | +{{i:conditions|run}} |
104 | 181 | !! result |
105 | 182 | <p>a |
106 | 183 | </p> |
107 | 184 | !! end |
108 | 185 | |
| 186 | +!! article |
| 187 | +Module:Conditions (if-then-else) |
| 188 | +!! text |
| 189 | +function run() { |
| 190 | + if( 2 * 7 > 3 * 4 ) { |
| 191 | + a = 7; |
| 192 | + } else { |
| 193 | + a = 10; |
| 194 | + } |
| 195 | + |
| 196 | + if( a ** 2 < 50 ) |
| 197 | + return "ok"; |
| 198 | +} |
| 199 | +!! endarticle |
| 200 | + |
109 | 201 | !! test |
110 | 202 | Conditions (if-then, if-then-else) |
111 | 203 | !! input |
112 | | -<wikiscript> |
113 | | -if( 2 * 7 > 3 * 4 ) { |
114 | | - a = 7; |
115 | | -} else { |
116 | | - a = 10; |
117 | | -} |
118 | | - |
119 | | -if( a ** 2 < 50 ) |
120 | | - out( "ok" ); |
121 | | -</wikiscript> |
| 204 | +{{i:Conditions (if-then-else)|run}} |
122 | 205 | !! result |
123 | 206 | <p>ok |
124 | 207 | </p> |
125 | 208 | !! end |
126 | 209 | |
127 | 210 | !! article |
| 211 | +Module:Bullets |
| 212 | +!! text |
| 213 | +function run() { |
| 214 | + out = ""; |
| 215 | + for( a in args() ) |
| 216 | + out += "* " + a + "\n"; |
| 217 | + return out; |
| 218 | +} |
| 219 | +!! endarticle |
| 220 | + |
| 221 | +!! article |
128 | 222 | Template:Bullets |
129 | 223 | !! text |
130 | | -<wikiscript> |
131 | | -for( a in args() ) |
132 | | - out( "* " + a + "\n" ); |
133 | | -</wikiscript> |
| 224 | +{{i:bullets|run}} |
134 | 225 | !! endarticle |
135 | 226 | |
136 | 227 | !! test |
— | — | @@ -145,9 +236,17 @@ |
146 | 237 | !! end |
147 | 238 | |
148 | 239 | !! article |
| 240 | +Module:TranscludedSwitch |
| 241 | +!! text |
| 242 | +function run() { |
| 243 | + return isTranscluded() ? arg(1) : "?!"; |
| 244 | +} |
| 245 | +!! endarticle |
| 246 | + |
| 247 | +!! article |
149 | 248 | Template:TranscludedSwitch |
150 | 249 | !! text |
151 | | -{{#inline: isTranscluded() ? arg(1) : "?!";}} |
| 250 | +{{i:TranscludedSwitch|run}} |
152 | 251 | !! endarticle |
153 | 252 | |
154 | 253 | !! test |
— | — | @@ -159,33 +258,55 @@ |
160 | 259 | </p> |
161 | 260 | !! end |
162 | 261 | |
| 262 | +!! article |
| 263 | +Module:Empty argument handling check |
| 264 | +!! text |
| 265 | +function run() { |
| 266 | + return arg("test") === null; |
| 267 | +} |
| 268 | +!! endarticle |
| 269 | + |
163 | 270 | !! test |
164 | 271 | Empty argument handling check |
165 | 272 | !! input |
166 | | -{{#inline: arg("test") === null;}} |
| 273 | +{{i:Empty argument handling check|run}} |
167 | 274 | !! result |
168 | 275 | <p>1 |
169 | 276 | </p> |
170 | 277 | !! end |
171 | 278 | |
| 279 | +!! article |
| 280 | +Module:Casts |
| 281 | +!! text |
| 282 | +function run() { |
| 283 | + return string(float(2)) === "2.0" & int(7.99) === 7; |
| 284 | +} |
| 285 | +!! endarticle |
| 286 | + |
172 | 287 | !! test |
173 | 288 | Casts |
174 | 289 | !! input |
175 | | -{{#inline: string(float(2)) === "2.0" & int(7.99) === 7;}} |
| 290 | +{{i:Casts|run}} |
176 | 291 | !! result |
177 | 292 | <p>1 |
178 | 293 | </p> |
179 | 294 | !! end |
180 | 295 | |
| 296 | +!! article |
| 297 | +Module:Exception handling |
| 298 | +!! text |
| 299 | +function run() { |
| 300 | + try |
| 301 | + 2 / 0; |
| 302 | + catch( e ) |
| 303 | + return e; |
| 304 | +} |
| 305 | +!! endarticle |
| 306 | + |
181 | 307 | !! test |
182 | 308 | Exception handling |
183 | 309 | !! input |
184 | | -<wikiscript> |
185 | | -try |
186 | | - 2 / 0; |
187 | | -catch( e ) |
188 | | - out( e ); |
189 | | -</wikiscript> |
| 310 | +{{i:Exception handling|run}} |
190 | 311 | !! result |
191 | 312 | <p>dividebyzero |
192 | 313 | </p> |
— | — | @@ -197,86 +318,136 @@ |
198 | 319 | 721 |
199 | 320 | !! endarticle |
200 | 321 | |
| 322 | +!! article |
| 323 | +Module:Numberofsomething |
| 324 | +!! text |
| 325 | +function run() { |
| 326 | + numofsmth = int( parse( '{{numberofsomething}}' ) ) + 279; |
| 327 | + return '{{numberofsomething}}: ' + numofsmth; |
| 328 | +} |
| 329 | +!! endarticle |
| 330 | + |
201 | 331 | !! test |
202 | 332 | Template access via parse() |
203 | 333 | !! input |
204 | | -<wikiscript noparse="1"> |
205 | | -numofsmth = int( parse( '{{numberofsomething}}' ) ) + 279; |
206 | | -out( '{{numberofsomething}}: ' + numofsmth ); |
207 | | -</wikiscript> |
| 334 | +{{i:Numberofsomething|run}} |
208 | 335 | !! result |
209 | 336 | <p>{{numberofsomething}}: 1000 |
210 | 337 | </p> |
211 | 338 | !! end |
212 | 339 | |
213 | 340 | !! article |
| 341 | +Module:123 |
| 342 | +!! text |
| 343 | +function run1() { |
| 344 | + return 123; |
| 345 | +} |
| 346 | + |
| 347 | +function run2() { |
| 348 | + return parse( '{{123}}' ); |
| 349 | +} |
| 350 | +!! endarticle |
| 351 | + |
| 352 | +!! article |
214 | 353 | Template:123 |
215 | 354 | !! text |
216 | | -<wikiscript> |
217 | | -out( 123 ); |
218 | | -</wikiscript> |
| 355 | +{{i:123|run1}} |
219 | 356 | !! endarticle |
220 | 357 | |
221 | 358 | !! test |
222 | 359 | Nested wikiscripts via parse() |
223 | 360 | !! input |
224 | | -<wikiscript> |
225 | | -out( parse( '{{123}}' ) ); |
226 | | -</wikiscript> |
| 361 | +{{i:123|run2}} |
227 | 362 | !! result |
228 | 363 | <p>123 |
229 | 364 | </p> |
230 | 365 | !! end |
231 | 366 | |
| 367 | +!! article |
| 368 | +Module:String functions 1 |
| 369 | +!! text |
| 370 | +function run() { |
| 371 | + return lc( 'FOO' ) == 'foo' & uc( 'foo' ) == 'FOO' & |
| 372 | + ucfirst( 'bar' ) == 'Bar' & urlencode( 'a="b"' ) == "a%3D%22b%22"; |
| 373 | +} |
| 374 | +!! endarticle |
| 375 | + |
232 | 376 | !! test |
233 | 377 | String functions 1 |
234 | 378 | !! input |
235 | | -{{#inline: lc( 'FOO' ) == 'foo' & uc( 'foo' ) == 'FOO' & |
236 | | -ucfirst( 'bar' ) == 'Bar' & urlencode( 'a="b"' ) == "a%3D%22b%22"; }} |
| 379 | +{{i:String functions 1|run}} |
237 | 380 | !! result |
238 | 381 | <p>1 |
239 | 382 | </p> |
240 | 383 | !! end |
241 | 384 | |
| 385 | +!! article |
| 386 | +Module:String functions 2 |
| 387 | +!! text |
| 388 | +function run() { |
| 389 | + return strlen( "тест" ) == 4 & substr( "слово", 1, 2 ) == "ло" & |
| 390 | + strreplace( "abcd", 'bc', 'ad' ) == 'aadd'; |
| 391 | +} |
| 392 | +!! endarticle |
| 393 | + |
242 | 394 | !! test |
243 | 395 | String functions 2 |
244 | 396 | !! input |
245 | | -{{#inline: strlen( "тест" ) == 4 & substr( "слово", 1, 2 ) == "ло" & |
246 | | - strreplace( "abcd", 'bc', 'ad' ) == 'aadd'; |
247 | | -}} |
| 397 | +{{i:String functions 2|run}} |
248 | 398 | !! result |
249 | 399 | <p>1 |
250 | 400 | </p> |
251 | 401 | !! end |
252 | 402 | |
| 403 | +!! article |
| 404 | +Module:split/join |
| 405 | +!! text |
| 406 | +function run() { |
| 407 | + return join( '!', split( ':', 'a:b:c:d' ) ) + join( ' ', '', 'e', 'f' ); |
| 408 | +} |
| 409 | +!! endarticle |
| 410 | + |
253 | 411 | !! test |
254 | 412 | split()/join() |
255 | 413 | !! input |
256 | | -{{#inline: join( '!', split( ':', 'a:b:c:d' ) ) + join( ' ', '', 'e', 'f' ); }} |
| 414 | +{{i:split/join|run}} |
257 | 415 | !! result |
258 | 416 | <p>a!b!c!d e f |
259 | 417 | </p> |
260 | 418 | !! end |
261 | 419 | |
| 420 | +!! article |
| 421 | +Module:isset/delete |
| 422 | +!! text |
| 423 | +function run() { |
| 424 | + a = null; |
| 425 | + b = 1; |
| 426 | + delete( b ); |
| 427 | + return 'a: ' + isset( a ) + '; b: ' + int( isset( b ) ); |
| 428 | +} |
| 429 | +!! endarticle |
| 430 | + |
262 | 431 | !! test |
263 | 432 | isset/delete |
264 | 433 | !! input |
265 | | -<wikiscript> |
266 | | -a = null; |
267 | | -b = 1; |
268 | | -delete( b ); |
269 | | -out( 'a: ' + isset( a ) + '; b: ' + int( isset( b ) ) ); |
| 434 | +{{i:isset/delete|run}} |
270 | 435 | !! result |
271 | 436 | <p>a: 1; b: 0 |
272 | 437 | </p> |
273 | 438 | !! end |
274 | 439 | |
| 440 | +!! article |
| 441 | +Module:in/contains |
| 442 | +!! text |
| 443 | +function run() { |
| 444 | + return int( "a" in "b" + "c" in "cd" + "foobar" contains "oo" + "foobar" contains "baz" ); |
| 445 | +} |
| 446 | +!! endarticle |
| 447 | + |
275 | 448 | !! test |
276 | 449 | in/contains |
277 | 450 | !! input |
278 | | -<wikiscript> |
279 | | -out( int( "a" in "b" + "c" in "cd" + "foobar" contains "oo" + "foobar" contains "baz" ) ); |
280 | | -</wikiscript> |
| 451 | +{{i:in/contains|run}} |
281 | 452 | !! result |
282 | 453 | <p>2 |
283 | 454 | </p> |
— | — | @@ -285,26 +456,39 @@ |
286 | 457 | # |
287 | 458 | ## Lists |
288 | 459 | # |
| 460 | + |
| 461 | +!! article |
| 462 | +Module:Lists: basics |
| 463 | +!! text |
| 464 | +function run() { |
| 465 | + a = [ b = "a", b = "b", b = "c" ]; |
| 466 | + return a[1] + b; |
| 467 | +} |
| 468 | +!! endarticle |
| 469 | + |
289 | 470 | !! test |
290 | 471 | Lists: basics |
291 | 472 | !! input |
292 | | -<wikiscript> |
293 | | -a = [ b = "a", b = "b", b = "c" ]; |
294 | | -out( a[1] + b ); |
295 | | -</wikiscript> |
| 473 | +{{i:Lists: basics|run}} |
296 | 474 | !! result |
297 | 475 | <p>bc |
298 | 476 | </p> |
299 | 477 | !! end |
300 | 478 | |
| 479 | +!! article |
| 480 | +Module:Lists: foreach |
| 481 | +!! text |
| 482 | +function run() { |
| 483 | + a = [ 1, 2, 3, 4, 5 ]; |
| 484 | + for( n in a ) |
| 485 | + append n * n + "\n\n"; |
| 486 | +} |
| 487 | +!! endarticle |
| 488 | + |
301 | 489 | !! test |
302 | 490 | Lists: foreach |
303 | 491 | !! input |
304 | | -<wikiscript> |
305 | | -a = [ 1, 2, 3, 4, 5 ]; |
306 | | -for( n in a ) |
307 | | - out( n * n + "\n\n"); |
308 | | -</wikiscript> |
| 492 | +{{i:Lists: foreach|run}} |
309 | 493 | !! result |
310 | 494 | <p>1 |
311 | 495 | </p><p>4 |
— | — | @@ -314,48 +498,66 @@ |
315 | 499 | </p> |
316 | 500 | !! end |
317 | 501 | |
| 502 | +!! article |
| 503 | +Module:List merging |
| 504 | +!! text |
| 505 | +function run() { |
| 506 | + for( element in [ 7, 4 ] + [ 2, 8 ] + 1 ) |
| 507 | + append string( element ); |
| 508 | +} |
| 509 | +!! endarticle |
| 510 | + |
318 | 511 | !! test |
319 | 512 | List merging |
320 | 513 | !! input |
321 | | -<wikiscript> |
322 | | -for( element in [ 7, 4 ] + [ 2, 8 ] ) |
323 | | - out( element ); |
324 | | -</wikiscript> |
| 514 | +{{i:List merging|run}} |
325 | 515 | !! result |
326 | | -<p>7428 |
| 516 | +<p>74281 |
327 | 517 | </p> |
328 | 518 | !! end |
329 | 519 | |
| 520 | +!! article |
| 521 | +Module:Lists: loop control (break/continue) |
| 522 | +!! text |
| 523 | +function run() { |
| 524 | + a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; |
| 525 | + for( e in a ) { |
| 526 | + if( e >= 6 & e < 9 ) |
| 527 | + continue; |
| 528 | + append string( e ); |
| 529 | + } |
| 530 | + for( e in a ) { |
| 531 | + if( e == 3 ) |
| 532 | + break; |
| 533 | + append e; |
| 534 | + } |
| 535 | +} |
| 536 | +!! endarticle |
| 537 | + |
330 | 538 | !! test |
331 | 539 | Lists: loop control (break/continue) |
332 | 540 | !! input |
333 | | -<wikiscript> |
334 | | -a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; |
335 | | -for( e in a ) { |
336 | | - if( e >= 6 & e < 9 ) |
337 | | - continue; |
338 | | - out( e ); |
339 | | -} |
340 | | -for( e in a ) { |
341 | | - if( e == 3 ) |
342 | | - break; |
343 | | - out( e ); |
344 | | -} |
345 | | -</wikiscript> |
| 541 | +{{i:Lists: loop control (break/continue)|run}} |
346 | 542 | !! result |
347 | 543 | <p>12345912 |
348 | 544 | </p> |
349 | 545 | !! end |
350 | 546 | |
| 547 | +!! article |
| 548 | +Module:Lists: changing value of an element |
| 549 | +!! text |
| 550 | +function run() { |
| 551 | + a = [ [ 2, 3 ], [ 5, 6 ], 7 ]; |
| 552 | + a[1][0] = 3; |
| 553 | + a[0][] = 1; |
| 554 | + return a; |
| 555 | +} |
| 556 | +!! endarticle |
| 557 | + |
351 | 558 | !! test |
352 | 559 | Lists: changing value of an element |
353 | 560 | !! input |
354 | | -<wikiscript> |
355 | | -a = [ [ 2, 3 ], [ 5, 6 ], 7 ]; |
356 | | -a[1][0] = 3; |
357 | | -a[0][] = 1; |
358 | | -out( a ); |
359 | | -</wikiscript> |
| 561 | +{{i:Lists: changing value of an element|run}} |
360 | 562 | !! result |
361 | 563 | <p>2 |
362 | 564 | 3 |
— | — | @@ -366,29 +568,41 @@ |
367 | 569 | </p> |
368 | 570 | !! end |
369 | 571 | |
| 572 | +!! article |
| 573 | +Module:Lists: isset |
| 574 | +!! text |
| 575 | +function run() { |
| 576 | + lst = [ 'a', 'b', 'c' ]; |
| 577 | + return isset( lst[1] ) + isset( lst[2] ) + isset( list[3] ); |
| 578 | +} |
| 579 | +!! endarticle |
| 580 | + |
370 | 581 | !! test |
371 | 582 | Lists: isset |
372 | 583 | !! input |
373 | | -<wikiscript> |
374 | | -lst = [ 'a', 'b', 'c' ]; |
375 | | -out( isset( lst[1] ) + isset( lst[2] ) + isset( list[3] ) ); |
376 | | -</wikiscript> |
| 584 | +{{i:Lists: isset|run}} |
377 | 585 | !! result |
378 | 586 | <p>2 |
379 | 587 | </p> |
380 | 588 | !! end |
381 | 589 | |
| 590 | +!! article |
| 591 | +Module:Associated arrays: basics |
| 592 | +!! text |
| 593 | +function run() { |
| 594 | + a = { "a" : 2, "b" : 13 }; |
| 595 | + a["c"] = 21; |
| 596 | + |
| 597 | + for( k : v in a ) { |
| 598 | + append k + " = " + v + "\n\n"; |
| 599 | + } |
| 600 | +} |
| 601 | +!! endarticle |
| 602 | + |
382 | 603 | !! test |
383 | 604 | Associated arrays: basics |
384 | 605 | !! input |
385 | | -<wikiscript> |
386 | | -a = { "a" : 2, "b" : 13 }; |
387 | | -a["c"] = 21; |
388 | | - |
389 | | -for( k : v in a ) { |
390 | | - out( k + " = " + v + "\n\n" ); |
391 | | -} |
392 | | -</wikiscript> |
| 606 | +{{i:Associated arrays: basics|run}} |
393 | 607 | !! result |
394 | 608 | <p>a = 2 |
395 | 609 | </p><p>b = 13 |
— | — | @@ -396,14 +610,21 @@ |
397 | 611 | </p> |
398 | 612 | !! end |
399 | 613 | |
| 614 | +!! article |
| 615 | +Module:Mixed arrays |
| 616 | +!! text |
| 617 | +function run() { |
| 618 | + a["b"][][]["c"] = 11; |
| 619 | + return a["b"][0][0]["c"]; |
| 620 | +} |
| 621 | +!! endarticle |
| 622 | + |
400 | 623 | !! test |
401 | 624 | Mixed arrays |
402 | 625 | !! input |
403 | | -<wikiscript> |
404 | | -a["b"][][0][]["c"] = 11; |
405 | | -out( a["b"][0][0][0]["c"] ); |
406 | | -</wikiscript> |
| 626 | +{{i:Mixed arrays|run}} |
407 | 627 | !! result |
408 | 628 | <p>11 |
409 | 629 | </p> |
410 | 630 | !! end |
| 631 | + |
Index: trunk/extensions/InlineScripts/Hooks.php |
— | — | @@ -92,7 +92,7 @@ |
93 | 93 | |
94 | 94 | $result = $i->invokeUserFunctionFromWikitext( $moduleName, $funcName, $args, $frame ); |
95 | 95 | } catch( ISException $e ) { |
96 | | - $msg = nl2br( htmlspecialchars( $e->getMessage() ) ); |
| 96 | + $msg = $e->getMessage(); |
97 | 97 | wfProfileOut( __METHOD__ ); |
98 | 98 | return "<strong class=\"error\">{$msg}</strong>"; |
99 | 99 | } |
— | — | @@ -175,4 +175,30 @@ |
176 | 176 | $list[NS_MODULE_TALK] = 'Module_talk'; |
177 | 177 | return true; |
178 | 178 | } |
| 179 | + |
| 180 | + public static function validateScript( $editor, $text, $section, &$error ) { |
| 181 | + global $wgUser; |
| 182 | + $title = $editor->mTitle; |
| 183 | + |
| 184 | + if( $title->getNamespace() == NS_MODULE ) { |
| 185 | + $errors = ISInterpreter::getSyntaxErrors( $title->getText(), $text ); |
| 186 | + if( !$errors ) { |
| 187 | + return true; |
| 188 | + } |
| 189 | + |
| 190 | + $errmsg = wfMsgExt( 'inlinescripts-error', array( 'parsemag' ), array( count( $errors ) ) ); |
| 191 | + $errlines = '* ' . implode( "\n* ", array_map( 'wfEscapeWikiText', $errors ) ); |
| 192 | + $error = <<<HTML |
| 193 | +<div class="errorbox"> |
| 194 | +{$errmsg} |
| 195 | +{$errlines} |
| 196 | +</div> |
| 197 | +<br clear="all" /> |
| 198 | +HTML; |
| 199 | + |
| 200 | + return true; |
| 201 | + } |
| 202 | + |
| 203 | + return true; |
| 204 | + } |
179 | 205 | } |
Index: trunk/extensions/InlineScripts/LinksUpdate.php |
— | — | @@ -62,9 +62,13 @@ |
63 | 63 | public static function purgeCache( &$article, &$editInfo, $changed ) { |
64 | 64 | global $wgDeferredUpdateList; |
65 | 65 | |
66 | | - // Invalidate caches of articles which include the script |
67 | | - if( $article->mTitle->getNamespace() == NS_MODULE ) |
| 66 | + if( $article->mTitle->getNamespace() == NS_MODULE ) { |
| 67 | + // Invalidate the script cache |
| 68 | + ISInterpreter::invalidateModule( $article->mTitle ); |
| 69 | + |
| 70 | + // Invalidate caches of articles which include the script |
68 | 71 | $wgDeferredUpdateList[] = new HTMLCacheUpdate( $article->mTitle, 'scriptlinks' ); |
| 72 | + } |
69 | 73 | |
70 | 74 | return true; |
71 | 75 | } |
Index: trunk/extensions/InlineScripts/InlineScripts.php |
— | — | @@ -52,6 +52,7 @@ |
53 | 53 | $wgHooks['CanonicalNamespaces'][] = 'ISHooks::addCanonicalNamespaces'; |
54 | 54 | $wgHooks['ArticleViewCustom'][] = 'ISHooks::handleScriptView'; |
55 | 55 | $wgHooks['TitleIsWikitextPage'][] = 'ISHooks::isWikitextPage'; |
| 56 | +$wgHooks['EditFilter'][] = 'ISHooks::validateScript'; |
56 | 57 | |
57 | 58 | $wgHooks['LinksUpdate'][] = 'ISLinksUpdateHooks::updateLinks'; |
58 | 59 | $wgHooks['ArticleEditUpdates'][] = 'ISLinksUpdateHooks::purgeCache'; |