r94505 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r94504‎ | r94505 | r94506 >
Date:12:31, 15 August 2011
Author:vasilievvv
Status:deferred
Tags:
Comment:
InlineScripts:
* Invalidate script cache when script is changed
* Do not allow user to save code with syntax errors
* Repair parser tests
* Make all tables in LRParser static
* Make call stack size limit actually work
Modified paths:
  • /trunk/extensions/InlineScripts/Hooks.php (modified) (history)
  • /trunk/extensions/InlineScripts/InlineScripts.php (modified) (history)
  • /trunk/extensions/InlineScripts/LinksUpdate.php (modified) (history)
  • /trunk/extensions/InlineScripts/i18n/Messages.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreter/CallStack.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreter/Interpreter.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreter/LRParser.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreter/Shared.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreterTests.txt (modified) (history)

Diff [purge]

Index: trunk/extensions/InlineScripts/i18n/Messages.php
@@ -18,11 +18,12 @@
1919 'inlinescripts-call-fromwikitext' => '$1::$2 called by wikitext',
2020 'inlinescripts-call-parse' => 'parse( "$1" )',
2121
 22+ 'inlinescripts-error' => 'Following parsing {{plural:$1|error|errors}} detected:',
2223 'inlinescripts-codelocation' => 'in module $1 at line $2',
2324
2425 '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',
2728 'inlinescripts-exception-toomanytokens' => 'Exceeded tokens limit',
2829 'inlinescripts-exception-toomanyevals' => 'Exceeded evaluations limit $1',
2930 'inlinescripts-exception-recoverflow' => 'Too deep abstract syntax tree',
@@ -42,10 +43,12 @@
4344 'inlinescripts-exception-nonexistent-module' => 'Call to non-existent module $2 $1',
4445 'inlinescripts-exception-unknownfunction-user' => 'Trying to use an unnknown user function $2::$3 $1',
4546 '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',
4648
4749 'inlinescripts-transerror-notenoughargs-user' => 'Not enough arguments for function $1::$2',
4850 'inlinescripts-transerror-nonexistent-module' => 'Call to non-existent module $1',
4951 'inlinescripts-transerror-unknownfunction-user' => 'Trying to use an unnknown user function $1::$2',
5052 'inlinescripts-transerror-recursion' => 'Function loop detected when calling function $1::$2',
5153 'inlinescripts-transerror-nofunction' => 'Missing function name when invoking the script',
 54+ 'inlinescripts-transerror-toodeeprecursion' => 'The maximum function nesting limit of $1 exceeded',
5255 );
Index: trunk/extensions/InlineScripts/interpreter/LRParser.php
@@ -15,7 +15,7 @@
1616 const Reduce = 1;
1717 const Accept = 2;
1818
19 - var $mLoaded, $mNonterminals, $mProductions, $mAction, $mGoto;
 19+ static $mLoaded, $mNonterminals, $mProductions, $mAction, $mGoto;
2020
2121 public static function getVersion() {
2222 return IS_LR_VERSION;
@@ -24,16 +24,16 @@
2525 private function loadGrammar() {
2626 wfProfileIn( __METHOD__ );
2727
28 - if( $this->mLoaded )
 28+ if( self::$mLoaded )
2929 return;
3030
3131 require_once( 'LRTable.php' );
3232
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;
3838
3939 wfProfileOut( __METHOD__ );
4040 }
@@ -43,7 +43,7 @@
4444 }
4545
4646 public function parse( $scanner, $module, $maxTokens ) {
47 - $this->loadGrammar();
 47+ self::loadGrammar();
4848
4949 $states = array( array( null, 0 ) );
5050 $scanner->rewind();
@@ -66,17 +66,17 @@
6767 }
6868
6969 list( $stateval, $state ) = end( $states );
70 - $act = @$this->mAction[$state][$cur];
 70+ $act = @self::$mAction[$state][$cur];
7171 if( !$act ) {
7272 wfProfileOut( __METHOD__ );
7373 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 ) );
7575 }
7676 if( $act[0] == self::Shift ) {
7777 $states[] = array( $token, $act[1] );
7878 $scanner->next();
7979 } elseif( $act[0] == self::Reduce ) {
80 - list( $nonterm, $prod ) = $this->mProductions[$act[1]];
 80+ list( $nonterm, $prod ) = self::$mProductions[$act[1]];
8181 $len = count( $prod );
8282
8383 // Change state
@@ -91,7 +91,7 @@
9292 list( $val ) = $symbol;
9393 $node->addChild( $val );
9494 }
95 - $states[] = array( $node, $this->mGoto[$state][$nonterm] );
 95+ $states[] = array( $node, self::$mGoto[$state][$nonterm] );
9696 } elseif( $act[0] == self::Accept ) {
9797 break;
9898 }
@@ -101,4 +101,14 @@
102102
103103 return new ISParserOutput( $states[1][0], $tokenCount );
104104 }
 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+ }
105115 }
Index: trunk/extensions/InlineScripts/interpreter/CallStack.php
@@ -47,6 +47,8 @@
4848
4949 public function isFull() {
5050 global $wgInlineScriptsMaxCallStackDepth;
 51+
 52+ return count( $this->mStack ) >= $wgInlineScriptsMaxCallStackDepth;
5153 }
5254
5355 public function contains( $module, $name ) {
Index: trunk/extensions/InlineScripts/interpreter/Interpreter.php
@@ -34,27 +34,59 @@
3535 class ISInterpreter {
3636 const ParserVersion = 1;
3737
38 - var $mParser, $mCodeParser, $mUseCache, $mUsedModules, $mCallStack;
39 - var $mMaxRecursion, $mEvaluations, $mTokens;
40 -
 38+ var $mParser, $mUseCache, $mUsedModules, $mCallStack;
 39+ var $mMaxRecursion, $mEvaluations;
4140 var $mParserCache; // Unserializing can be expensive as well
4241
 42+ static $mCodeParser;
 43+
4344 public function __construct( $parser ) {
44 - global $wgInlineScriptsParserClass, $wgInlineScriptsUseCache;
 45+ global $wgInlineScriptsUseCache;
4546
4647 $this->mParser = $parser;
47 - $this->mCodeParser = new $wgInlineScriptsParserClass( $this );
4848 $this->mUseCache = $wgInlineScriptsUseCache;
4949
5050 $this->mCallStack = new ISCallStack( $this );
5151 $this->mUsedModules = array();
5252 $this->mMaxRecursion =
53 - $this->mEvaluations =
54 - $this->mTokens =
 53+ $this->mEvaluations =
5554 0;
5655 }
5756
 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+
5873 /**
 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+ /**
5991 * Disable cache for benchmarking or debugging purposes.
6092 */
6193 public function disableCache() {
@@ -98,7 +130,7 @@
99131
100132 public function getMaxTokensLeft() {
101133 global $wgInlineScriptsLimits;
102 - return $wgInlineScriptsLimits['tokens'] - $this->mTokens;
 134+ return $wgInlineScriptsLimits['tokens'];
103135 }
104136
105137 /**
@@ -154,9 +186,8 @@
155187
156188 // Parse
157189 $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 );
161192
162193 // Save to cache
163194 $this->mParserCache[$key] = $module;
@@ -191,7 +222,7 @@
192223 * @return ISData
193224 */
194225 public function invokeUserFunctionFromModule( $module, $name, $args, $parentContext, $line ) {
195 - global $wgInlineScriptsAllowRecursion;
 226+ global $wgInlineScriptsAllowRecursion, $wgInlineScriptsMaxCallStackDepth;
196227
197228 // Load module
198229 if( $module instanceof ISModule ) {
@@ -221,6 +252,9 @@
222253 if( !$wgInlineScriptsAllowRecursion && $this->mCallStack->contains( $moduleName, $name ) ) {
223254 throw new ISUserVisibleException( 'recursion', $parentContext->mModuleName, $line, array( $moduleName, $name ) );
224255 }
 256+ if( $this->mCallStack->isFull() ) {
 257+ throw new ISUserVisibleException( 'toodeeprecursion', $parentContext->mModuleName, $line, array( $wgInlineScriptsMaxCallStackDepth ) );
 258+ }
225259
226260 // Prepare the context and the arguments
227261 $context = new ISEvaluationContext( $this, $module, $name, $parentContext->getFrame() );
@@ -276,6 +310,11 @@
277311 if( !$wgInlineScriptsAllowRecursion && $this->mCallStack->contains( $moduleName, $name ) ) {
278312 throw new ISTransclusionException( 'recursion', array( $moduleName, $name ) );
279313 }
 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+ }
280319
281320 // Prepare the context and the arguments
282321 $context = new ISEvaluationContext( $this, $module, $name, $frame );
@@ -327,15 +366,20 @@
328367 */
329368 class ISModule {
330369 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;
331374
332375 protected function __construct() {}
333376
334377 /**
335378 * Initializes module from the code parser output.
336379 */
337 - public static function newFromParserOutput( $interpreter, $title, $output ) {
 380+ public static function newFromParserOutput( $interpreter, $title, $revid, $output ) {
338381 $m = new ISModule();
339382 $m->mTitle = $title;
 383+ $m->mRevID = $revid;
340384 $m->mParserVersion = $output->getVersion();
341385 $m->mFunctions = array();
342386
Index: trunk/extensions/InlineScripts/interpreter/Shared.php
@@ -94,7 +94,8 @@
9595 var $mType, $mChildren;
9696
9797 public function __construct( $parser, $id ) {
98 - $this->mType = $parser->mNonterminals[$id];
 98+ $parserClass = get_class( $parser );
 99+ $this->mType = $parserClass::$mNonterminals[$id];
99100 }
100101
101102 public function addChild( $node ) {
@@ -166,6 +167,14 @@
167168 * @return ISParserTreeNode
168169 */
169170 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 );
170179 }
171180
172181 class ISException extends MWException {}
Index: trunk/extensions/InlineScripts/interpreterTests.txt
@@ -1,135 +1,226 @@
22 # Test cases for MediaWiki inline scripts engine
33
 4+!! article
 5+Module:Basic mathematics
 6+!! text
 7+function run() {
 8+ return -2 + 2 * 2 ** 2 - 3 * 7 % 5;
 9+}
 10+!! endarticle
 11+
412 !! test
513 Basic mathematics
614 !! input
7 -{{#inline:-2 + 2 * 2 ** 2 - 3 * 7 % 5;}}
 15+{{i:Basic mathematics|run}}
816 !! result
917 <p>5
1018 </p>
1119 !! end
1220
 21+!! article
 22+Module:Pow associativity
 23+!! text
 24+function run() {
 25+ // Not 4096
 26+ return 4 ** 3 ** 2;
 27+}
 28+!! endarticle
 29+
1330 !! test
1431 ** associativity
1532 !! input
16 -{{#inline: 4 ** 3 ** 2;}}<!-- Not 4096! -->
 33+{{i:Pow associativity|run}}
1734 !! result
1835 <p>262144
1936 </p>
2037 !! end
2138
 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+
2249 !! test
23 -String contecation and out()
 50+String contecation
2451 !! input
25 -<wikiscript>
26 -out( "foo" + "bar" );
27 -</wikiscript>
 52+{{i:string contecation|add|3|7}}
2853 !! result
29 -<p>foobar
 54+<p>37
3055 </p>
3156 !! end
3257
 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+
3369 !! test
3470 Multiple variable assignment
3571 !! input
36 -{{#inline: a = b = 3; a + b; }}
 72+{{i:Multiple variable assignment|run}}
3773 !! result
3874 <p>6
3975 </p>
4076 !! end
4177
 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+
4289 !! test
4390 Assigment with arithmetics (+=, -=, etc)
4491 !! input
45 -{{#inline: a = 2; a += 3; a -= 7; a; }}
 92+{{i:Assigment with arithmetics|run}}
4693 !! result
4794 <p>-2
4895 </p>
4996 !! end
5097
 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+
51108 !! test
52109 Boolean shortcut
53110 !! input
54 -<wikiscript>
55 -!(b = 2) | (b = 3) | (b = 4);
56 -out( b );
57 -</wikiscript>
 111+{{i:boolean shortcut|run}}
58112 !! result
59113 <p>3
60114 </p>
61115 !! end
62116
 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+
63126 !! test
64127 Equality
65128 !! input
66 -{{#inline: "2" == 2 & "2" !== 2 & 4 === (2 + 2) &
67 -null == "" & false == null & 0 == ""; }}
 129+{{i:equality|run}}
68130 !! result
69131 <p>1
70132 </p>
71133 !! end
72134
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
81142
82143 !! test
83144 Comparsions
84145 !! input
85 -{{#inline: 2 > 1 & 2 >= 2 & 2 <= 2 & 1 < 2; }}
 146+{{i:comparisons|run}}
86147 !! result
87148 <p>1
88149 </p>
89150 !! end
90151
 152+!! article
 153+Module:Trivial
 154+!! text
 155+function getText() {
 156+ return "AA";
 157+}
 158+!! endarticle
 159+
91160 !! test
92 -Tag integration
 161+Integration with other functions
93162 !! input
94 -{{lc:<wikiscript>out("AA");</wikiscript>}}
 163+{{lc:{{i:trivial|getText}}}}
95164 !! result
96165 <p>aa
97166 </p>
98167 !! end
99168
 169+!! article
 170+Module:Conditions
 171+!! text
 172+function run() {
 173+ return 2 + 2 == 4 ? "a" : "b";
 174+}
 175+!! endarticle
 176+
100177 !! test
101178 Conditions (?)
102179 !! input
103 -{{#inline: 2 + 2 == 4 ? "a" : "b";}}
 180+{{i:conditions|run}}
104181 !! result
105182 <p>a
106183 </p>
107184 !! end
108185
 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+
109201 !! test
110202 Conditions (if-then, if-then-else)
111203 !! 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}}
122205 !! result
123206 <p>ok
124207 </p>
125208 !! end
126209
127210 !! 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
128222 Template:Bullets
129223 !! text
130 -<wikiscript>
131 -for( a in args() )
132 - out( "* " + a + "\n" );
133 -</wikiscript>
 224+{{i:bullets|run}}
134225 !! endarticle
135226
136227 !! test
@@ -145,9 +236,17 @@
146237 !! end
147238
148239 !! article
 240+Module:TranscludedSwitch
 241+!! text
 242+function run() {
 243+ return isTranscluded() ? arg(1) : "?!";
 244+}
 245+!! endarticle
 246+
 247+!! article
149248 Template:TranscludedSwitch
150249 !! text
151 -{{#inline: isTranscluded() ? arg(1) : "?!";}}
 250+{{i:TranscludedSwitch|run}}
152251 !! endarticle
153252
154253 !! test
@@ -159,33 +258,55 @@
160259 </p>
161260 !! end
162261
 262+!! article
 263+Module:Empty argument handling check
 264+!! text
 265+function run() {
 266+ return arg("test") === null;
 267+}
 268+!! endarticle
 269+
163270 !! test
164271 Empty argument handling check
165272 !! input
166 -{{#inline: arg("test") === null;}}
 273+{{i:Empty argument handling check|run}}
167274 !! result
168275 <p>1
169276 </p>
170277 !! end
171278
 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+
172287 !! test
173288 Casts
174289 !! input
175 -{{#inline: string(float(2)) === "2.0" & int(7.99) === 7;}}
 290+{{i:Casts|run}}
176291 !! result
177292 <p>1
178293 </p>
179294 !! end
180295
 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+
181307 !! test
182308 Exception handling
183309 !! input
184 -<wikiscript>
185 -try
186 - 2 / 0;
187 -catch( e )
188 - out( e );
189 -</wikiscript>
 310+{{i:Exception handling|run}}
190311 !! result
191312 <p>dividebyzero
192313 </p>
@@ -197,86 +318,136 @@
198319 721
199320 !! endarticle
200321
 322+!! article
 323+Module:Numberofsomething
 324+!! text
 325+function run() {
 326+ numofsmth = int( parse( '{{numberofsomething}}' ) ) + 279;
 327+ return '{{numberofsomething}}: ' + numofsmth;
 328+}
 329+!! endarticle
 330+
201331 !! test
202332 Template access via parse()
203333 !! input
204 -<wikiscript noparse="1">
205 -numofsmth = int( parse( '{{numberofsomething}}' ) ) + 279;
206 -out( '{{numberofsomething}}: ' + numofsmth );
207 -</wikiscript>
 334+{{i:Numberofsomething|run}}
208335 !! result
209336 <p>{{numberofsomething}}: 1000
210337 </p>
211338 !! end
212339
213340 !! 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
214353 Template:123
215354 !! text
216 -<wikiscript>
217 -out( 123 );
218 -</wikiscript>
 355+{{i:123|run1}}
219356 !! endarticle
220357
221358 !! test
222359 Nested wikiscripts via parse()
223360 !! input
224 -<wikiscript>
225 -out( parse( '{{123}}' ) );
226 -</wikiscript>
 361+{{i:123|run2}}
227362 !! result
228363 <p>123
229364 </p>
230365 !! end
231366
 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+
232376 !! test
233377 String functions 1
234378 !! 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}}
237380 !! result
238381 <p>1
239382 </p>
240383 !! end
241384
 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+
242394 !! test
243395 String functions 2
244396 !! input
245 -{{#inline: strlen( "тест" ) == 4 & substr( "слово", 1, 2 ) == "ло" &
246 - strreplace( "abcd", 'bc', 'ad' ) == 'aadd';
247 -}}
 397+{{i:String functions 2|run}}
248398 !! result
249399 <p>1
250400 </p>
251401 !! end
252402
 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+
253411 !! test
254412 split()/join()
255413 !! input
256 -{{#inline: join( '!', split( ':', 'a:b:c:d' ) ) + join( ' ', '', 'e', 'f' ); }}
 414+{{i:split/join|run}}
257415 !! result
258416 <p>a!b!c!d e f
259417 </p>
260418 !! end
261419
 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+
262431 !! test
263432 isset/delete
264433 !! 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}}
270435 !! result
271436 <p>a: 1; b: 0
272437 </p>
273438 !! end
274439
 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+
275448 !! test
276449 in/contains
277450 !! 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}}
281452 !! result
282453 <p>2
283454 </p>
@@ -285,26 +456,39 @@
286457 #
287458 ## Lists
288459 #
 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+
289470 !! test
290471 Lists: basics
291472 !! input
292 -<wikiscript>
293 -a = [ b = "a", b = "b", b = "c" ];
294 -out( a[1] + b );
295 -</wikiscript>
 473+{{i:Lists: basics|run}}
296474 !! result
297475 <p>bc
298476 </p>
299477 !! end
300478
 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+
301489 !! test
302490 Lists: foreach
303491 !! 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}}
309493 !! result
310494 <p>1
311495 </p><p>4
@@ -314,48 +498,66 @@
315499 </p>
316500 !! end
317501
 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+
318511 !! test
319512 List merging
320513 !! input
321 -<wikiscript>
322 -for( element in [ 7, 4 ] + [ 2, 8 ] )
323 - out( element );
324 -</wikiscript>
 514+{{i:List merging|run}}
325515 !! result
326 -<p>7428
 516+<p>74281
327517 </p>
328518 !! end
329519
 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+
330538 !! test
331539 Lists: loop control (break/continue)
332540 !! 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}}
346542 !! result
347543 <p>12345912
348544 </p>
349545 !! end
350546
 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+
351558 !! test
352559 Lists: changing value of an element
353560 !! 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}}
360562 !! result
361563 <p>2
362564 3
@@ -366,29 +568,41 @@
367569 </p>
368570 !! end
369571
 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+
370581 !! test
371582 Lists: isset
372583 !! 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}}
377585 !! result
378586 <p>2
379587 </p>
380588 !! end
381589
 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+
382603 !! test
383604 Associated arrays: basics
384605 !! 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}}
393607 !! result
394608 <p>a = 2
395609 </p><p>b = 13
@@ -396,14 +610,21 @@
397611 </p>
398612 !! end
399613
 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+
400623 !! test
401624 Mixed arrays
402625 !! input
403 -<wikiscript>
404 -a["b"][][0][]["c"] = 11;
405 -out( a["b"][0][0][0]["c"] );
406 -</wikiscript>
 626+{{i:Mixed arrays|run}}
407627 !! result
408628 <p>11
409629 </p>
410630 !! end
 631+
Index: trunk/extensions/InlineScripts/Hooks.php
@@ -92,7 +92,7 @@
9393
9494 $result = $i->invokeUserFunctionFromWikitext( $moduleName, $funcName, $args, $frame );
9595 } catch( ISException $e ) {
96 - $msg = nl2br( htmlspecialchars( $e->getMessage() ) );
 96+ $msg = $e->getMessage();
9797 wfProfileOut( __METHOD__ );
9898 return "<strong class=\"error\">{$msg}</strong>";
9999 }
@@ -175,4 +175,30 @@
176176 $list[NS_MODULE_TALK] = 'Module_talk';
177177 return true;
178178 }
 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+ }
179205 }
Index: trunk/extensions/InlineScripts/LinksUpdate.php
@@ -62,9 +62,13 @@
6363 public static function purgeCache( &$article, &$editInfo, $changed ) {
6464 global $wgDeferredUpdateList;
6565
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
6871 $wgDeferredUpdateList[] = new HTMLCacheUpdate( $article->mTitle, 'scriptlinks' );
 72+ }
6973
7074 return true;
7175 }
Index: trunk/extensions/InlineScripts/InlineScripts.php
@@ -52,6 +52,7 @@
5353 $wgHooks['CanonicalNamespaces'][] = 'ISHooks::addCanonicalNamespaces';
5454 $wgHooks['ArticleViewCustom'][] = 'ISHooks::handleScriptView';
5555 $wgHooks['TitleIsWikitextPage'][] = 'ISHooks::isWikitextPage';
 56+$wgHooks['EditFilter'][] = 'ISHooks::validateScript';
5657
5758 $wgHooks['LinksUpdate'][] = 'ISLinksUpdateHooks::updateLinks';
5859 $wgHooks['ArticleEditUpdates'][] = 'ISLinksUpdateHooks::purgeCache';

Status & tagging log