Blogs

Scripting How-To: Check SLAX scripts for common syntax and programming errors

By Erdem posted 08-14-2015 16:00

  

Check SLAX Scripts for Common Syntax Errors

 

You can use the slax-doctor script to check SLAX scripts for common syntax and programming errors. One common problem with writing large SLAX scripts is the difficulty in isolating the location of the inevitable syntax errors. Unterminated comments or strings and other syntax problems tend to cause cascading errors, which do not point clearly to the line where the problem occurred.

 

The slax-doctor script helps locate these types of errors. It is not perfect but it can detect the following problems:

 

  • Unterminated comments
  • Unterminated strings
  • Lines that lack proper termination
  • Mixed up parenthesis and curly braces
  • Missing brackets
  • Missing parenthesis
  • Unknown parameters included in template calls
  • Missing default-less parameters in template calls
  • Trying to treat template parameters like function arguments
  • Trying to treat function arguments like template parameters
  • Missing call statements when redirecting template results to variables
  • Missing namespaces for <func:function> function names

 

Caveats When Using the slax-doctor Script

 

  • Comment delimiters within quotes will cause false errors
  • Comment delimiters within comments will cause false errors
  • Brackets, parenthesis, and braces within quotes will cause false errors
  • Equal signs within quotes passed as function arguments will cause false errors
  • More than one single-line comment within a line will cause false errors
  • Location paths with /* will cause false errors
  • Parameter checking only works within parenthesis, not using the with statement
  • Code lines that are split across multiple file lines could cause false errors

To use the script, install it on a JUNOS device which contains the script that you want to check.

 

user@JUNOS> op slax-doctor filename problem-script.slax

 

The slax-doctor script will process the script file line and report any problems it finds.

 

Minimum JUNOS Version: 9.4.
Latest Script Version: 1.42
SHA-256 Checksum: b165914b2409c6aaa69b3f97a72dd225cdb6c882391ff436ae5f50d31487000e

 

Source Code and GitHub Links

 

The source code below is also available from the following GitHub locations:
 
 
Example Output
01 user@JUNOS> op slax-doctor filename /var/db/scripts/op/demo-safe-file-get.slax
02 ->Total lines in /var/db/scripts/op/demo-safe-file-get.slax = 650
03 ->Found 13 templates in script file
04 ->Found 0 functions in script file
05 ->SLAX Doctor - starting line by line checkup...
06 [Processing lines 1-101]
07 [Processing lines 101-201]
08 [Processing lines 201-301]
09 [Processing lines 301-401]
10 [Processing lines 401-501]
11 [Processing lines 501-601]
12 [Processing lines 601-701]
13 Line 623: Possible unterminated quote
14 Line 634: Possible missing bracket
15 Line 635: Possible missing bracket
16 ->Checkup Done!

SLAX Script Contents

001 /* 

002  * Author        : Curtis Call 

003  * Version       : 1.43 

004  * Last Modified : September 21, 2011 

005  * Platform      : all 

006  * Release       : 9.4 and above 

007  * License       : Public Domain 

008  * 

009  * Description   : slax-doctor detects common SLAX syntax errors that can be hard 

010  * to locate manually and can cause confusing error messages. 

011  * 

012  * While not perfect, the following errors will typically be caught: 

013  * - Unterminated comments 

014  * - Unterminated strings 

015  * - Lines that lack proper termination 

016  * - Mixed up parenthesis and curly braces 

017  * - Missing brackets 

018  * - Missing parenthesis 

019  * - Unknown parameters included in template calls 

020  * - Missing default-less parameters in template calls 

021  * - Trying to treat template parameters like function arguments 

022  * - Trying to treat function arguments like template parameters 

023  * - Missing call statements when redirecting template results to variables 

024  * - Missing namespaces for <func:function> function names 

025  * 

026  * Caveats: 

027  * - Comment delimiters within quotes will cause false errors 

028  * - Comment delimiters within comments will cause false errors 

029  * - Brackets, parenthesis, and braces within quotes will cause false errors 

030  * - Equal signs within quotes passed as function arguments will cause false errors 

031  * - More than one single-line comment within a line will cause false errors 

032  * - Location paths with /* will cause false errors 

033  * - Parameter checking only works within parenthesis, not using the with statement 

034  * - Code lines that are split across multiple file lines could cause false errors 

035  * 

036  * Revisions: 

037  * 1.1 - Don't complain about missing jcs templates because they are included in 

038  * junos.xsl. 

039  * 1.2 - Switched from jcs:invoke() to jcs:execute() because it performs much faster 

040  * for bulk operations.  Also cleared up the status output. 

041  * 1.3 - Clarified error message when parameters look incorrect, it could be a false 

042  * error, so insert a "might" 

043  *     - Added check for missing call statement when redirecting template results to 

044  * a variable. 

045  * 1.4 - Added check for closing curly brace on lines where it likely was mistakenly 

046  * added (will result in false errors for scripts where the closing brace is not 

047  * typically put on a line by itself.) 

048  * 1.41 - Added check for back function argument syntax 

049  *      - Added check for missing namespace on <func:function> function name 

050  * 1.42 - Fixed function argument syntax check to not mistake templates for functions 

051  * 1.43 - Made minor syntax change to workaround a PR - no change in functionality 

052  * 

053  */ 

054   

055 version 1.0; 

056   

057 ns junos = "http://xml.juniper.net/junos/*/junos"; 

058 ns xnm = "http://xml.juniper.net/xnm/1.1/xnm"; 

059 ns jcs = "http://xml.juniper.net/junos/commit-scripts/1.0"; 

060 ns str = "http://exslt.org/strings"; 

061   

062 import "../import/junos.xsl"; 

063   

064 var $arguments = { 

065     <argument> { 

066         <name> "filename"; 

067         <description> "Script filename to check"; 

068     } 

069 } 

070   

071 param $filename; 

072   

073 /* Single session for all SNMP operations */ 

074 var $connection = jcs:open(); 

075   

076 var $comment-start-instance = "Doctor-Comment-Start"; 

077 var $base-index-instance = "Doctor-Base-Index"; 

078   

079 /* The size of lines that should be processed at the same time - is the recursion boundary */ 

080 var $line-chunk-size = 100; 

081   

082 match / { 

083   

084     /* Check minimum version */ 

085     call check-version( $minimum-version = "9.4", $failure-message = "Sorry, the SLAX Doctor needs JUNOS 9.4 or above." ); 

086   

087     /* Retrieve from prompt if not specified as an argument */ 

088     var $script-file = { 

089         if( string-length( $filename) == 0 ) { 

090             var $name = jcs:input( "Enter script filename to check: " ); 

091             expr $name; 

092         } 

093         else { 

094             expr $filename; 

095         } 

096     } 

097   

098     /* Open the file */ 

099     var $show-file-rpc = { 

100         <file-show> { 

101             <filename> $script-file; 

102         } 

103     } 

104       

105     var $show-file-contents = jcs:execute( $connection, $show-file-rpc ); 

106       

107     /* Exit if an error */ 

108     if( $show-file-contents/..//xnm:error ) { 

109         <xsl:message terminate="yes"> "Invalid script file"; 

110     } 

111       

112     var $lines = jcs:break-lines( $show-file-contents ); 

113       

114     /* Assemble a string of all characters except for the quote (space won't be recorded due to whitespace trimming) */ 

115     var $no-quote = { 

116         for-each( $ascii/char[num != 34 ] ) { 

117             expr sym; 

118         } 

119     } 

120   

121     /* Assemble a string of all characters except {(}) (space and tab won't be recorded) */ 

122     var $no-containers = { 

123         for-each( $ascii/char[sym != "{" && sym != "}" && sym != "(" && sym != ")" ] ) { 

124             expr sym; 

125         } 

126     } 

127       

128     /* Assemble a string of all characters except {(}) and ; (space and tab won't be recorded) */ 

129     var $no-containers-or-semi-colon = { 

130         for-each( $ascii/char[sym != "{" && sym != "}" && sym != "(" && sym != ")" && sym != ";" ] ) { 

131             expr sym; 

132         } 

133     } 

134   

135     /* Set the comment start to -1 to indicate no comment */ 

136     call write-integer( $name = $comment-start-instance, $integer = -1 ); 

137   

138     /* Set the base index at 0 */ 

139     call write-integer( $name = $base-index-instance, $integer = 0 ); 

140       

141     /* Announce the total lines, minus 1 for the extra blank line added */ 

142     var $total = count( $lines ) - 1; 

143     expr jcs:output( "->Total lines in ", $script-file, " = ", $total ); 

144       

145     /* Load the templates so they can be referenced line by line */ 

146     var $templates := { call load-templates( $lines ); } 

147     expr jcs:output( "->Found ", count( $templates/template ), " templates in script file" ); 

148       

149     /* Load the functions so they can be referenced line by line */ 

150     var $functions := { call load-functions( $lines ); } 

151     expr jcs:output( "->Found ", count( $functions/function ), " functions in script file" ); 

152   

153     /* Check for duplicate functions - also report about missing namespaces */ 

154     call check-for-duplicate-functions( $functions ); 

155       

156     expr jcs:output( "->SLAX Doctor - starting line by line checkup..."); 

157   

158       

159     for-each( $lines[ position() mod $line-chunk-size == 1 ] ) { 

160       

161         var  $base-index = { call read-integer( $name = $base-index-instance ); } 

162   

163         /* Retrieve a chunk of a hundred lines */ 

164         var $chunk = $lines[ position() > $base-index && $base-index + $line-chunk-size >= position() ]; 

165           

166         /* Record the comment status */ 

167         var $comment-line = { call read-integer( $name = $comment-start-instance ); } 

168   

169         /* Announce position */ 

170         var $adjusted-position = $base-index + 1; 

171         var $adjusted-max = $adjusted-position + $line-chunk-size; 

172         expr jcs:output( "[Processing lines ", $adjusted-position, "-", $adjusted-max, "]" ); 

173   

174         var $new-comment-line = { call process-lines( $chunk, $base-index, $no-quote, $no-containers,$no-containers-or-semi-colon, $templates, $comment-line, $index = 1 ); } 

175       

176         /* Store the comment line */ 

177         call write-integer( $name = $comment-start-instance, $integer = $new-comment-line ); 

178           

179         /* Advance the base-index */ 

180         call write-integer( $name = $base-index-instance, $integer = $base-index + $line-chunk-size ); 

181     } 

182       

183     /* Check if there is an open comment */ 

184     var $comment-line = { call read-integer( $name = $comment-start-instance ); } 

185     if( $comment-line != -1 ) { 

186         var $real-line = $comment-line - 1; 

187         expr jcs:output( "Possible unterminated comment at line ", $real-line ); 

188     } 

189       

190     expr jcs:output( "->Checkup Done!" ); 

191       

192     /* Cleanup */ 

193     call clear-integer( $name = $comment-start-instance ); 

194     call clear-integer( $name = $base-index-instance ); 

195     expr jcs:close( $connection ); 

196 } 

197   

198 /* 

199  * Parses $functions XML structure looking for dups 

200  */ 

201 template check-for-duplicate-functions( $functions ) { 

202     for-each( $functions/function ) { 

203         var $outer-position = position(); 

204         var $outer-name = name; 

205           

206         if( not( contains( name, ":" ) ) ) { 

207             expr jcs:output( "Function ", name, " is missing its namespace" ); 

208         } 

209           

210         for-each( $functions/function ) { 

211           

212             if( position != $outer-position ) { 

213               

214                 if( name == $outer-name ) { 

215                     expr jcs:output( "Function ", name, " is declared more than once" ); 

216                 } 

217             } 

218         } 

219     } 

220 } 

221   

222 /* 

223  * Processes the given line. Compares the included parameters against the 

224  * parameters gathered within $templates and reports unknown parameters as well 

225  * as missing default-less parameters. 

226  */ 

227 template check-parameters( $line, $real-line, $templates ) { 

228   

229     var $after-call = substring-after( $line, "call " ); 

230     var $before-parenthesis = substring-before( $after-call, "(" ); 

231     var $template-name = normalize-space( $before-parenthesis ); 

232               

233     if( jcs:empty( $templates/template[name == $template-name] ) ) { 

234         /* Don't complain about any jcs: functions because they are in import.xsl */ 

235         if( not( starts-with( $template-name, "jcs:" ) ) ) { 

236             expr jcs:output("Line ", $real-line, ": Template ", $template-name, " is not in script file"); 

237         } 

238     } 

239     else { 

240         var $after-parenthesis = substring-after( $line, "(" ); 

241         var $parameter-string = { call substring-before-last( $string = $after-parenthesis, $pattern =")" ); } 

242               

243         var $parameters = jcs:split( ",", $parameter-string ); 

244         var $template = $templates/template[name == $template-name]; 

245         var $included := { 

246             for-each( $parameters ) { 

247                 var $name-raw = { 

248                     if( contains( ., "=" ) ) { 

249                         expr substring-before( ., "=" ); 

250                     } 

251                     else { 

252                         expr .; 

253                     } 

254                 } 

255                 var $name = normalize-space( $name-raw ); 

256                  

257                 if( string-length( $name ) > 0 ) { 

258                     /* If it doesn't start with a $ then it isn't a parameter */ 

259                     if( not( starts-with( $name, "$" ) ) ) { 

260                         expr jcs:output( "Line ", $real-line, ": Template parameter value might not be assigned correctly" ); 

261                     } 

262                     else if( jcs:empty( $template/parameter[name == $name] ) ) { 

263                         expr jcs:output( "Line ", $real-line, ": Unknown template parameter ", $name, " for template ", $template-name ); 

264                     } 

265                     else { 

266                         <name> $name; 

267                     } 

268                 } 

269             } 

270         } 

271         /* Compare against the non-default parameters for the template */ 

272         for-each( $template/parameter[ jcs:empty( has-default ) ] ) { 

273             var $parameter-name = name; 

274             if( jcs:empty( $included[name == $parameter-name] ) ) { 

275                 expr jcs:output( "Line ", $real-line, ": Template ", $template-name, " called without parameter ", $parameter-name, " (It has no default)" ); 

276             } 

277         } 

278     } 

279 } 

280   

281 /* 

282  * Pulls out all the declared templates and records their parameters.  Uses 

283  * this XML format: 

284  * <template> { 

285  *     <name> "template-name"; 

286  *     <parameter> { 

287  *         <name> "parameter-name"; 

288  *         <has-default>; - Included when the parameters has a default value 

289  *     } 

290  * } 

291  */ 

292 template load-templates( $lines ) { 

293       

294     for-each( $lines ) { 

295         var $line = normalize-space( . ); 

296           

297         if( starts-with( $line, "template" ) ) { 

298             <template> { 

299                 var $after-template = substring-after( $line, "template " ); 

300                 var $before-brace = { 

301                     if( contains( $after-template, "{" ) ) { 

302                         expr substring-before( $after-template, "{" ); 

303                     } 

304                     else { 

305                         expr $after-template; 

306                     } 

307                 } 

308                 var $before-parenthesis = { 

309                     if( contains( $before-brace, "(" ) ) { 

310                         expr substring-before( $before-brace, "(" ); 

311                     } 

312                     else { 

313                         expr $before-brace; 

314                     } 

315                 } 

316                 <name> normalize-space( $before-parenthesis ); 

317                   

318                 var $after-parenthesis = substring-after( $line, "(" ); 

319                 var $parameter-string = substring-before( $after-parenthesis, ")" ); 

320                   

321                 var $parameters = jcs:split( ",", $parameter-string ); 

322                 for-each( $parameters ) { 

323                     var $has-default = contains( ., "=" ); 

324                     var $name-raw = { 

325                         if( contains( ., "=" ) ) { 

326                             expr substring-before( ., "=" ); 

327                         } 

328                         else { 

329                             expr .; 

330                         } 

331                     } 

332                     var $name = normalize-space( $name-raw ); 

333                     if( string-length( $name ) > 0 ) { 

334                         <parameter> { 

335                             <name> $name; 

336                             if( $has-default ) { 

337                                 <has-default>; 

338                             } 

339                         } 

340                     } 

341                 } 

342             } 

343         } 

344     } 

345 } 

346   

347 /* 

348  * Pulls out all the declared functions.  Uses this XML format: 

349  * <function> { 

350  *     <name> "function-name"; 

351  * } 

352  */ 

353 template load-functions( $lines ) { 

354       

355     for-each( $lines ) { 

356         var $line = normalize-space( . ); 

357         var $test-string = "<func:function name"; /* "> */ 

358         if( starts-with( $line, $test-string ) ) { 

359             <function> { 

360                 var $quote-raw = { 

361                     expr "\""; 

362                 } 

363                 var $quote = { 

364                     /* Do this because of PR 436699 */ 

365                     if( string-length( $quote-raw ) == 2 ) { 

366                         expr substring( $quote-raw, 2, 1 ); 

367                     } 

368                     else { 

369                         expr $quote-raw; 

370                     } 

371                 } 

372                 var $after-quote = substring-after( $line, $quote ); 

373                 var $before-quote = substring-before( $after-quote, $quote ); 

374                 <name> $before-quote; 

375             } 

376         } 

377     } 

378 } 

379   

380 /* 

381  * Returns the substring before the last occurrence of $pattern 

382  */ 

383 template substring-before-last( $string, $pattern ) { 

384   

385     /* Is pattern at the end? */ 

386     var $pattern-length = string-length( $pattern ); 

387     var $string-length = string-length( $string ); 

388       

389     var $slice = substring( $string, $string-length - $pattern-length + 1 ); 

390   

391     if( $string-length == 0 ) { 

392         expr ""; 

393     } 

394     else if( $slice == $pattern ) { 

395         expr substring( $string, 1, $string-length - $pattern-length - 1 ); 

396     } 

397     else { 

398         call substring-before-last( $string = substring( $string, 1, $string-length - 1 ), $pattern ); 

399     } 

400 } 

401   

402 template count-substring( $string, $substring, $count = 0 ) { 

403     if( contains( $string, $substring ) ) { 

404         var $before = substring-before( $string, $substring ); 

405         var $after = substring-after( $string, $substring ); 

406         call count-substring( $string = $before _ $after, $substring, $count = $count + 1 ); 

407     } 

408     else { 

409         expr $count; 

410     } 

411 } 

412   

413 template process-lines( $chunk, $base-index, $no-quote, $no-containers, $no-containers-or-semi-colon,$templates, $comment-line, $index ) { 

414       

415     /* Get the current line */ 

416     var $line = $chunk[ $index ]; 

417   

418     /* The actual line in the output - including the added blank line at the beginnig */ 

419     var $current-line = $base-index + $index; 

420       

421     /* The reported line in the output - after removing the added blank line */ 

422     var $real-line = $current-line - 1; 

423     var $real-comment-line = $comment-line - 1; 

424       

425     /* Look for starting and ending comment delimiters */ 

426     var $comment-start = contains( $line, "/*" ); 

427     var $comment-stop = contains( $line, "*/" ); 

428   

429     /* Check comments, return the $comment-line variable */ 

430     var $new-comment-line = {    

431         /* Look for a /* when a comment is already in progress */ 

432         if( $comment-line != -1 && $comment-start ) { 

433             /* Record an error */ 

434             expr jcs:output( "Line ", $real-comment-line, ": Possible unterminated comment detected at line ", $real-line ); 

435               

436             /* Change the comment position to the current line if the comment wasn't stopped */ 

437             if( not( $comment-stop ) ) { 

438                 expr $current-line; 

439             } 

440             else { 

441                 /* comment stopped so set it to -1 */ 

442                 expr -1; 

443             } 

444         } 

445         else if( $comment-start && not( $comment-stop ) ) { 

446             /* Record the starting comment */ 

447             expr $current-line; 

448         } 

449         /* Look for a stopped comment with no start comment */ 

450         else if( $comment-stop && not( $comment-start) && $comment-line == -1 ) { 

451             /* Record an error */ 

452             expr jcs:output( "Line ", $real-line, ": Comment stop with no comment start" ); 

453             /* Comment remains at -1 */ 

454             expr -1; 

455         } 

456         /* Stop the comment */ 

457         else if( $comment-stop && $comment-line > -1 ) { 

458             expr -1; 

459         } 

460         else { 

461             expr $comment-line; 

462         } 

463     } 

464       

465     /* Only perform the other checks if they aren't part of a multi-line comment and the line has somelength to it */ 

466     if( string-length( $line ) > 0 && ( $comment-line == -1 && ( not( $comment-start ) || $comment-stop  ) ) ) { 

467       

468         var $check-string = { 

469             /* Single line comment is there, remove it */ 

470             if( $comment-start && $comment-stop  ) { 

471                 expr substring-before( $line, "/*" ); 

472                 expr substring-after( $line, "*/" ); 

473             } 

474             else { 

475                 expr $line; 

476             } 

477         } 

478         /* For when no extra whitespace is desired */ 

479         var $normalized-check-string = normalize-space( $check-string ); 

480       

481         /* Check quotes */ 

482         /* Translate everything except quotes to nothing - do this because of the quote PR */ 

483         var $only-quotes-raw = translate( $check-string, $no-quote, "" ); 

484       

485         /* Remove spaces and tabs now because the no-quotes string didn't remove them */ 

486         var $only-quotes = translate( $only-quotes-raw, " \t", "" ); 

487         if( string-length( $only-quotes ) mod 2 == 1 ) { 

488             expr jcs:output( "Line ", $real-line, ": Possible unterminated quote" ); 

489         } 

490           

491         /* Check line termination */ 

492         if( string-length( $normalized-check-string ) > 0 ) { 

493             var $end-1 = substring( $normalized-check-string, string-length( $normalized-check-string) ); 

494             var $end-2 = substring( $normalized-check-string, string-length( $normalized-check-string) - 1 ); 

495             var $end-3 = substring( $normalized-check-string, string-length( $normalized-check-string) - 2 ); 

496             var $end-4 = substring( $normalized-check-string, string-length( $normalized-check-string) - 3 ); 

497           

498             /* Allow it to be either { } _ + - / , ; || | && and or mod div */ 

499             if( $end-1 != "{" && $end-1 != "}" && $end-1 != "_" && $end-1 != "+" && $end-1 != "-" &&$end-1 != "/" && 

500                 $end-1 != ";" && $end-1 != "," && $end-1 != "|" && $end-2 != "||" && $end-2 != "&&" &&$end-3 != " or" && 

501                 $end-4 != " and" && $end-4 != " mod" && $end-4 != " div" ) { 

502   

503                 expr jcs:output( "Line ", $real-line, ": Check line termination" ); 

504             } 

505         } 

506           

507         /* Look for ( where { should be or ) where } should be */ 

508         /* Get rid of all spaces and tabs */ 

509         var $no-white-space = translate( $check-string, " \t", "" ); 

510           

511         /* This first check could be seen when using ( instead of } in a RTF variable assignment from a template */ 

512         if( contains( $no-white-space, "=(call" ) ) { 

513             expr jcs:output( "Line ", $real-line, ": Possible use of opening parenthesis instead of opening curly brace" ); 

514         } 

515           

516         /* This second check could be seen when using ) instead of } in a RTF variable assignment from a template */ 

517         if( contains( $no-white-space, ");)" ) ) { 

518             expr jcs:output( "Line ", $real-line, ": Possible use of closing parenthesis instead of closing curly brace" ); 

519         } 

520           

521         /* Look for ({ or }) */ 

522         /* Get rid of everything except for ({}) */ 

523         var $only-container = translate( $no-white-space, $no-containers, "" ); 

524           

525         if( contains( $only-container, "({" ) || contains( $only-container, "})" ) ) { 

526             expr jcs:output( "Line ", $real-line, ": Possible incorrect parenthesis and curly brace usage" ); 

527         } 

528           

529         /* Look for )}; */ 

530         /* Get rid of everything except for ({}) */ 

531         var $only-container-or-semi-colon = translate( $no-white-space, $no-containers-or-semi-colon, ""); 

532           

533         if( contains( $only-container-or-semi-colon, ")};" ) ) { 

534             expr jcs:output( "Line ", $real-line, ": Possible incorrect semi-colon placement" ); 

535         } 

536         /* Look for )} with no semi-colon */ 

537         /* Get rid of everything except for ({}); */ 

538         else if( contains( $only-container-or-semi-colon, ")}" ) ) { 

539             expr jcs:output( "Line ", $real-line, ": Possible missing semi-colon" ); 

540         } 

541   

542         /* Look for a } in a line that isn't alone, and that doesn't also contain a { */ 

543         if( string-length( $no-white-space ) > 1 && contains( $no-white-space, "}" ) && not( contains($no-white-space, "{" ) ) ) { 

544             expr jcs:output( "Line ", $real-line, ": Possible extra closing curly brace." ); 

545         } 

546           

547         /* Check for missing call statements in single-line template to variable assignments */ 

548         /* i.e. var $example = { get-value(); } */ 

549         if( contains( $no-white-space, "var" ) && contains( $no-white-space, "={" ) && 

550             contains( $no-white-space, ");}" ) && not( contains( $no-white-space, "{call" ) ) ) { 

551           

552             expr jcs:output( "Line ", $real-line, ": Possible missing call statement" ); 

553         } 

554           

555         /* Check for invalid function argument syntax */ 

556         /* i.e. var $example = function( $var = "something" ); */ 

557         if( ( starts-with( $no-white-space, "var" ) || starts-with( $no-white-space, "expr" ) ) && not( contains( $no-white-space, "{call" ) ) ) { 

558             var $equal-sign-count = { call count-substring( $string = $no-white-space, $substring = "="); } 

559             var $equality-check-count = { call count-substring( $string = $no-white-space, $substring ="==" ); } 

560               

561             /* Don't get confused by equality checks */ 

562             var $final-equal-sign-count = $equal-sign-count - ( $equality-check-count * 2 ); 

563               

564             if( $final-equal-sign-count > 1 || ( starts-with( $no-white-space, "expr" ) && $final-equal-sign-count == 1 ) ) { 

565                 expr jcs:output( "Line ", $real-line, ": Possible bad function argument syntax (ignore if equal sign is inside quotes)" ); 

566             } 

567         } 

568           

569         /* Verify that func:function names have a : in them */ 

570         if( contains( $no-white-space, "func:function"  ) ) { 

571             var $colon-count = { call count-substring( $string = $no-white-space, $substring = ":" ); } 

572               

573             if( 2 > $colon-count ) { 

574                 expr jcs:output( "Line ", $real-line, ": Possible missing namespace in function name" ); 

575             } 

576         } 

577   

578         /* Bracket and parenthesis count checks */ 

579           

580         var $end-1 = substring( $no-white-space, string-length( $no-white-space ) ); 

581   

582         /* A complete line starts with param, call, var, for-each, expr, templatee, if, or < and ends with ; } or {     > */ 

583         var $complete-line = ( (starts-with( $no-white-space, "param" ) || starts-with( $no-white-space,"var" ) || starts-with( $no-white-space, "for-each" ) || starts-with( $no-white-space, "call" ) || 

584                                 starts-with( $no-white-space, "expr") || starts-with( $no-white-space,"template" ) || starts-with( $no-white-space, "if" ) || starts-with( $no-white-space, "<" ) ) 

585                                 && ( $end-1 == ";" || $end-1 == "{" || $end-1 == "}" ) ); /* "> */ 

586                                   

587         if( $complete-line ) { 

588             /* Check for miscount of [ and ] */ 

589             var $opening-bracket-count = { call count-substring( $string = $no-white-space, $substring ="[" ); } 

590             var $closing-bracket-count = { call count-substring( $string = $no-white-space, $substring ="]" ); } 

591             if( $opening-bracket-count != $closing-bracket-count  ) { 

592                 expr jcs:output( "Line ", $real-line, ": Possible missing bracket" ); 

593             } 

594           

595             /* Check for miscount of ( and  )  */ 

596             var $opening-parenthesis-count = { call count-substring( $string = $no-white-space,$substring = "(" ); } 

597             var $closing-parenthesis-count = { call count-substring( $string = $no-white-space,$substring = ")" ); } 

598             if( $closing-parenthesis-count != $opening-parenthesis-count ) { 

599                 expr jcs:output( "Line ", $real-line, ": Possible missing parenthesis" ); 

600             } 

601               

602             /* Check for back to back opening braces {{ this probably shouldn't happen */ 

603             if( contains( $no-white-space, "{{" ) ) { 

604                 expr jcs:output( "Line ", $real-line, ": Possible extra curly brace" ); 

605             } 

606         } 

607           

608         /* Check the parameters of lines with template calls in them */ 

609         if( ( contains( $check-string, " call " ) || contains( $check-string, "{call" )  ) && ( contains($check-string, "(" ) || contains( $check-string, "{" ) ) ) { 

610             call check-parameters( $line = $check-string, $real-line, $templates ); 

611         } 

612     } 

613       

614     /* Recurse through the chunk... */ 

615     var $new_test_var = count( $chunk ); 

616     if( ($new_test_var >= $index + 1) ) { 

617         call process-lines( $chunk, $base-index, $no-quote, $no-containers, $no-containers-or-semi-colon,$templates, $comment-line = $new-comment-line, $index = $index + 1 ); 

618     } 

619     else { 

620         /* Otherwise, output the current value of $new-comment-line */ 

621         expr $new-comment-line; 

622     } 

623 } 

624   

625   

626 /* 

627  * Writes the given $integer to the Utility Mib using the instance $name 

628  */ 

629 template write-integer( $name, $integer ) { 

630   

631     var $rpc = { 

632         <request-snmp-utility-mib-set> { 

633             <instance> $name; 

634             <object-type> "integer"; 

635             <object-value> $integer; 

636         } 

637     } 

638     var $results = jcs:execute( $connection, $rpc ); 

639   

640 } 

641   

642   

643 /* 

644  * Clears the integer in the Utility Mib using the instance $name 

645  */ 

646 template clear-integer( $name ) { 

647   

648     var $rpc = { 

649         <request-snmp-utility-mib-clear> { 

650             <instance> $name; 

651             <object-type> "integer"; 

652         } 

653     } 

654     var $results = jcs:execute( $connection, $rpc ); 

655   

656 } 

657   

658   

659 /* 

660  * Reads the instance $name integer from the Utility Mib, converts it to oid form first 

661  */ 

662 template read-integer( $name ) { 

663     var $oid-name = { call build-oid-name( $name, $type = "integer" ); } 

664       

665     var $rpc = { 

666         <get-snmp-object> { 

667             <snmp-object-name> $oid-name; 

668         } 

669     } 

670     var $object = jcs:execute( $connection, $rpc ); 

671     expr $object/snmp-object/object-value; 

672       

673 } 

674   

675   

676 /* 

677  * This template takes the $instance name and converts it to an oid name for its value. 

678  * The type should be specified as well, either "string", "counter", "counter64", 

679  * "integer", or "unsigned-integer". 

680  * This is used with <get-snmp-object> 

681  */ 

682 template build-oid-name( $name, $type ) { 

683   

684     /* Build the type portion of the name */ 

685     var $string-portion = { 

686         if( $type == "string" ) { 

687             expr "jnxUtilStringValue"; 

688         } 

689         else if( $type == "counter" ) { 

690             expr "jnxUtilCounter32Value"; 

691         } 

692         else if( $type == "integer" ) { 

693             expr "jnxUtilIntegerValue"; 

694         } 

695         else if( $type == "counter64" ) { 

696             expr "jnxUtilCounter64Value"; 

697         } 

698         else if( $type == "counter" ) { 

699             expr "jnxUtilUintValue"; 

700         } 

701     } 

702   

703     /* Split the name into characters */    

704     var $characters = str:tokenize( $name, "" ); 

705       

706     var $number-portion = { 

707         for-each( $characters ) { 

708       

709             /* Convert the characters to their ASCII code equivalent */ 

710             var $char = .; 

711             var $ascii-char = $ascii/char[ sym == $char ]; 

712             expr "." _ $ascii-char/num; 

713         } 

714     } 

715       

716     /* Return the full string */ 

717     expr $string-portion _ $number-portion; 

718 } 

719   

720   

721 var $ascii := { 

722 <char> { <num> 32; <sym> " "; }<char> { <num> 33; <sym> "!"; }<char> { <num> 34; <sym> "\""; }<char> { <num> 35; <sym> "#"; }<char> { <num> 36; <sym> "$"; } 

723 <char> { <num> 37; <sym> "%"; }<char> { <num> 38; <sym> "&"; }<char> { <num> 39; <sym> "'"; }<char> { <num> 40; <sym> "("; }<char> { <num> 41; <sym> ")"; } 

724 <char> { <num> 42; <sym> "*"; }<char> { <num> 43; <sym> "+"; }<char> { <num> 44; <sym> ","; }<char> { <num> 45; <sym> "-"; }<char> { <num> 46; <sym> "."; } 

725 <char> { <num> 47; <sym> "/"; }<char> { <num> 48; <sym> "0"; }<char> { <num> 49; <sym> "1"; }<char> { <num> 50; <sym> "2"; }<char> { <num> 51; <sym> "3"; } 

726 <char> { <num> 52; <sym> "4"; }<char> { <num> 53; <sym> "5"; }<char> { <num> 54; <sym> "6"; }<char> { <num> 55; <sym> "7"; }<char> { <num> 56; <sym> "8"; } 

727 <char> { <num> 57; <sym> "9"; }<char> { <num> 58; <sym> ":"; }<char> { <num> 59; <sym> ";"; }<char> { <num> 60; <sym> "<"; }<char> { <num> 61; <sym> "="; } /* >" */ 

728 <char> { <num> 62; <sym> ">"; }<char> { <num> 63; <sym> "?"; }<char> { <num> 64; <sym> "@"; }<char> { <num> 65; <sym> "A"; }<char> { <num> 66; <sym> "B"; } 

729 <char> { <num> 67; <sym> "C"; }<char> { <num> 68; <sym> "D"; }<char> { <num> 69; <sym> "E"; }<char> { <num> 70; <sym> "F"; }<char> { <num> 71; <sym> "G"; } 

730 <char> { <num> 72; <sym> "H"; }<char> { <num> 73; <sym> "I"; }<char> { <num> 74; <sym> "J"; }<char> { <num> 75; <sym> "K"; }<char> { <num> 76; <sym> "L"; } 

731 <char> { <num> 77; <sym> "M"; }<char> { <num> 78; <sym> "N"; }<char> { <num> 79; <sym> "O"; }<char> { <num> 80; <sym> "P"; }<char> { <num> 81; <sym> "Q"; } 

732 <char> { <num> 82; <sym> "R"; }<char> { <num> 83; <sym> "S"; }<char> { <num> 84; <sym> "T"; }<char> { <num> 85; <sym> "U"; }<char> { <num> 86; <sym> "V"; } 

733 <char> { <num> 87; <sym> "W"; }<char> { <num> 88; <sym> "X"; }<char> { <num> 89; <sym> "Y"; }<char> { <num> 90; <sym> "Z"; }<char> { <num> 91; <sym> "["; } 

734 <char> { <num> 92; <sym> "\\"; }<char> { <num> 93; <sym> "]"; }<char> { <num> 94; <sym> "^"; }<char> { <num> 95; <sym> "_"; }<char> { <num> 96; <sym> "`"; } 

735 <char> { <num> 97; <sym> "a"; }<char> { <num> 98; <sym> "b"; }<char> { <num> 99; <sym> "c"; }<char> { <num> 100; <sym> "d"; }<char> { <num> 101; <sym> "e"; } 

736 <char> { <num> 102; <sym> "f"; }<char> { <num> 103; <sym> "g"; }<char> { <num> 104; <sym> "h"; }<char> { <num> 105; <sym> "i"; }<char> { <num> 106; <sym> "j"; } 

737 <char> { <num> 107; <sym> "k"; }<char> { <num> 108; <sym> "l"; }<char> { <num> 109; <sym> "m"; }<char> { <num> 110; <sym> "n"; }<char> { <num> 111; <sym> "o"; } 

738 <char> { <num> 112; <sym> "p"; }<char> { <num> 113; <sym> "q"; }<char> { <num> 114; <sym> "r"; }<char> { <num> 115; <sym> "s"; }<char> { <num> 116; <sym> "t"; } 

739 <char> { <num> 117; <sym> "u"; }<char> { <num> 118; <sym> "v"; }<char> { <num> 119; <sym> "w"; }<char> { <num> 120; <sym> "x"; }<char> { <num> 121; <sym> "y"; } 

740 <char> { <num> 122; <sym> "z"; }<char> { <num> 123; <sym> "{"; }<char> { <num> 124; <sym> "|"; }<char> { <num> 125; <sym> "}"; }<char> { <num> 126; <sym> "~"; } 

741 } 

742   

743   

744 /* 

745  * Checks if JUNOS version is at the minimum version or above, displays the 

746  * failure message and exits if it is not. 

747  */ 

748 template check-version( $minimum-version, $failure-message ) { 

749   

750     var $kern-osrelease = jcs:sysctl("kern.osrelease", "s"); 

751   

752     var $version-set = jcs:split("[IRSB-]", $kern-osrelease ); 

753   

754     var $version = $version-set[1]; 

755       

756     if( $minimum-version > $version ) { 

757         <xsl:message terminate="yes"> $failure-message; 

758     } 

759 } 
 
XML Script Contents
 
01 <?xml version="1.0"?> 

02 <script> 

03   <title>slax-doctor.slax</title> 

04   <author>curtisstephencall</author> 

05   <synopsis> 

06     Checks SLAX scripts for common syntax and programming errors 

07   </synopsis> 

08   <coe>op</coe> 

09   <type>debug</type> 

10   

11   <description> 

12     <![CDATA[ 

13          One common problem with writing large SLAX scripts is the difficulty in isolating the location of the inevitable 

14          syntax errors.  Unterminated comments or strings and other syntax problems tend to cause cascading errors, which 

15          do not point clearly to the line where the problem occurred. 

16   

17          This script helps to locate these types of errors.  It is not perfect but it can detect the following problems: 

18   

19               * Unterminated comments 

20               * Unterminated strings 

21               * Lines that lack proper termination 

22               * Mixed up parenthesis and curly braces 

23               * Missing brackets 

24               * Missing parenthesis 

25               * Unknown parameters included in template calls 

26               * Missing default-less parameters in template calls 

27               * Trying to treat template parameters like function arguments 

28               * Trying to treat function arguments like template parameters 

29               * Missing call statements when redirecting template results to variables 

30               * Missing namespaces for <func:function> function names 

31   

32          Caveats: 

33   

34               * Comment delimiters within quotes will cause false errors 

35               * Comment delimiters within comments will cause false errors 

36               * Brackets, parenthesis, and braces within quotes will cause false errors 

37               * Equal signs within quotes passed as function arguments will cause false errors 

38               * More than one single-line comment within a line will cause false errors 

39               * Location paths with /* will cause false errors 

40               * Parameter checking only works within parenthesis, not using the with statement 

41               * Code lines that are split across multiple file lines could cause false errors 

42   

43          To use, install it on a JUNOS device which also has the script that needs to be checked.  It is run like this: 

44   

45               user@JUNOS> op slax-doctor filename problem-script.slax 

46   

47 The slax-doctor script will process the script file line and line and report any possible problems it finds. 

48   

49 Minimum JUNOS Version: 9.4. 

50 Latest Script Version: 1.43 

51 SHA-256 Checksum: c00ff4623602b8d8f1f6b7461e15245a89f5b486876b1f0a538f5bcfc32afb90]]> 

52   </description> 

53   <example> 

54     <description> 

55      <![CDATA[ 

56 user@JUNOS> op slax-doctor filename /var/db/scripts/op/demo-safe-file-get.slax 

57 ->Total lines in /var/db/scripts/op/demo-safe-file-get.slax = 650 

58 ->Found 13 templates in script file 

59 ->Found 0 functions in script file 

60 ->SLAX Doctor - starting line by line checkup... 

61 [Processing lines 1-101] 

62 [Processing lines 101-201] 

63 [Processing lines 201-301] 

64 [Processing lines 301-401] 

65 [Processing lines 401-501] 

66 [Processing lines 501-601] 

67 [Processing lines 601-701] 

68 Line 623: Possible unterminated quote 

69 Line 634: Possible missing bracket 

70 Line 635: Possible missing bracket 

71 ->Checkup Done!]]> 

72     </description> 

73   </example> 

74   <keyword>debug</keyword> 

75   <keyword>slax</keyword> 

76   <keyword>syntax</keyword> 

77   <keyword>doctor</keyword> 

78   

79   <xhtml:script xmlns:xhtml="http://www.w3.org/1999/xhtml" 

80                 src="../../../../../web/leaf.js" 

81             type="text/javascript"/> 

82 </script> 

 


#How-To
#ScriptingHow-To
#opscript
#Slax
#syntaxerrors