diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2011-12-03 13:29:22 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2011-12-03 13:29:22 +0100 |
commit | ca32f08966f1b51fcb19460f0996bb0c4048e6fe (patch) | |
tree | ec04cc15b867bc21eedca904cea9af0254531a11 /includes/libs | |
parent | a22fbfc60f36f5f7ee10d5ae6fe347340c2ee67c (diff) |
Update to MediaWiki 1.18.0
* also update ArchLinux skin to chagnes in MonoBook
* Use only css to hide our menu bar when printing
Diffstat (limited to 'includes/libs')
-rw-r--r-- | includes/libs/CSSMin.php | 52 | ||||
-rw-r--r-- | includes/libs/HttpStatus.php | 68 | ||||
-rw-r--r-- | includes/libs/jsminplus.php | 2094 | ||||
-rw-r--r-- | includes/libs/spyc.php | 248 |
4 files changed, 2192 insertions, 270 deletions
diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php index c0e78112..4012b695 100644 --- a/includes/libs/CSSMin.php +++ b/includes/libs/CSSMin.php @@ -1,24 +1,24 @@ <?php /* * Copyright 2010 Wikimedia Foundation - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS * OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. + * specific language governing permissions and limitations under the License. */ /** * Transforms CSS data - * + * * This class provides minification, URL remapping, URL extracting, and data-URL embedding. - * + * * @file * @version 0.1.1 -- 2010-09-11 * @author Trevor Parscal <tparscal@wikimedia.org> @@ -26,9 +26,9 @@ * @license http://www.apache.org/licenses/LICENSE-2.0 */ class CSSMin { - + /* Constants */ - + /** * Maximum file size to still qualify for in-line embedding as a data-URI * @@ -37,9 +37,9 @@ class CSSMin { */ const EMBED_SIZE_LIMIT = 24576; const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*)(?P<query>\??[^\)\'"]*)[\'"]?\s*\)'; - + /* Protected Static Members */ - + /** @var array List of common image files extensions and mime-types */ protected static $mimeTypes = array( 'gif' => 'image/gif', @@ -51,9 +51,9 @@ class CSSMin { 'tiff' => 'image/tiff', 'xbm' => 'image/x-xbitmap', ); - + /* Static Methods */ - + /** * Gets a list of local file paths which are referenced in a CSS style sheet * @@ -78,7 +78,7 @@ class CSSMin { } return $files; } - + protected static function getMimeType( $file ) { $realpath = realpath( $file ); // Try a couple of different ways to get the mime-type of a file, in order of @@ -92,7 +92,7 @@ class CSSMin { // As of PHP 5.3, this is how you get the mime-type of a file; it uses the Fileinfo // PECL extension return finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $realpath ); - } else if ( function_exists( 'mime_content_type' ) ) { + } elseif ( function_exists( 'mime_content_type' ) ) { // Before this was deprecated in PHP 5.3, this was how you got the mime-type of a file return mime_content_type( $file ); } else { @@ -104,7 +104,7 @@ class CSSMin { } return false; } - + /** * Remaps CSS URL paths and automatically embeds data URIs for URL rules * preceded by an /* @embed * / comment @@ -130,12 +130,20 @@ class CSSMin { // URLs with absolute paths like /w/index.php need to be expanded // to absolute URLs but otherwise left alone if ( $match['file'][0] !== '' && $match['file'][0][0] === '/' ) { - // Replace the file path with an expanded URL - $source = substr_replace( $source, wfExpandUrl( $match['file'][0] ), - $match['file'][1], strlen( $match['file'][0] ) - ); + // Replace the file path with an expanded (possibly protocol-relative) URL + // ...but only if wfExpandUrl() is even available. + // This will not be the case if we're running outside of MW + $lengthIncrease = 0; + if ( function_exists( 'wfExpandUrl' ) ) { + $expanded = wfExpandUrl( $match['file'][0], PROTO_RELATIVE ); + $origLength = strlen( $match['file'][0] ); + $lengthIncrease = strlen( $expanded ) - $origLength; + $source = substr_replace( $source, $expanded, + $match['file'][1], $origLength + ); + } // Move the offset to the end of the match, leaving it alone - $offset = $match[0][1] + strlen( $match[0][0] ); + $offset = $match[0][1] + strlen( $match[0][0] ) + $lengthIncrease; continue; } // Shortcuts @@ -175,9 +183,9 @@ class CSSMin { } if ( $replacement === false ) { // Assume that all paths are relative to $remote, and make them absolute - $replacement = "{$embed}{$pre}url({$url}){$post};"; + $replacement = "{$embed}{$pre}url({$url}){$post};"; } - } else if ( $local === false ) { + } elseif ( $local === false ) { // Assume that all paths are relative to $remote, and make them absolute $replacement = "{$embed}{$pre}url({$url}{$query}){$post};"; } diff --git a/includes/libs/HttpStatus.php b/includes/libs/HttpStatus.php new file mode 100644 index 00000000..2985c652 --- /dev/null +++ b/includes/libs/HttpStatus.php @@ -0,0 +1,68 @@ +<?php +/** + * @todo document + */ +class HttpStatus { + + /** + * Get the message associed with the HTTP response code $code + * + * Replace OutputPage::getStatusMessage( $code ) + * + * @param $code Integer: status code + * @return String or null: message or null if $code is not in the list of + * messages + */ + public static function getMessage( $code ) { + static $statusMessage = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Request Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 507 => 'Insufficient Storage' + ); + return isset( $statusMessage[$code] ) ? $statusMessage[$code] : null; + } + +} diff --git a/includes/libs/jsminplus.php b/includes/libs/jsminplus.php new file mode 100644 index 00000000..bab4ff49 --- /dev/null +++ b/includes/libs/jsminplus.php @@ -0,0 +1,2094 @@ +<?php + +/** + * JSMinPlus version 1.3 + * + * Minifies a javascript file using a javascript parser + * + * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript) + * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine) + * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/ + * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716 + * + * Tino Zijdel <crisp@tweakers.net> + * + * Usage: $minified = JSMinPlus::minify($script [, $filename]) + * + * Versionlog (see also changelog.txt): + * 19-07-2011 - expanded operator and keyword defines. Fixes the notices when creating several JSTokenizer + * 17-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs + * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes + * 12-04-2009 - some small bugfixes and performance improvements + * 09-04-2009 - initial open sourced version 1.0 + * + * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip + * + */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Narcissus JavaScript engine. + * + * The Initial Developer of the Original Code is + * Brendan Eich <brendan@mozilla.org>. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): Tino Zijdel <crisp@tweakers.net> + * PHP port, modifications and minifier routine are (C) 2009 + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define('TOKEN_END', 1); +define('TOKEN_NUMBER', 2); +define('TOKEN_IDENTIFIER', 3); +define('TOKEN_STRING', 4); +define('TOKEN_REGEXP', 5); +define('TOKEN_NEWLINE', 6); +define('TOKEN_CONDCOMMENT_START', 7); +define('TOKEN_CONDCOMMENT_END', 8); + +define('JS_SCRIPT', 100); +define('JS_BLOCK', 101); +define('JS_LABEL', 102); +define('JS_FOR_IN', 103); +define('JS_CALL', 104); +define('JS_NEW_WITH_ARGS', 105); +define('JS_INDEX', 106); +define('JS_ARRAY_INIT', 107); +define('JS_OBJECT_INIT', 108); +define('JS_PROPERTY_INIT', 109); +define('JS_GETTER', 110); +define('JS_SETTER', 111); +define('JS_GROUP', 112); +define('JS_LIST', 113); + +define('DECLARED_FORM', 0); +define('EXPRESSED_FORM', 1); +define('STATEMENT_FORM', 2); + +/* Operators */ +define('OP_SEMICOLON', ';'); +define('OP_COMMA', ','); +define('OP_HOOK', '?'); +define('OP_COLON', ':'); +define('OP_OR', '||'); +define('OP_AND', '&&'); +define('OP_BITWISE_OR', '|'); +define('OP_BITWISE_XOR', '^'); +define('OP_BITWISE_AND', '&'); +define('OP_STRICT_EQ', '==='); +define('OP_EQ', '=='); +define('OP_ASSIGN', '='); +define('OP_STRICT_NE', '!=='); +define('OP_NE', '!='); +define('OP_LSH', '<<'); +define('OP_LE', '<='); +define('OP_LT', '<'); +define('OP_URSH', '>>>'); +define('OP_RSH', '>>'); +define('OP_GE', '>='); +define('OP_GT', '>'); +define('OP_INCREMENT', '++'); +define('OP_DECREMENT', '--'); +define('OP_PLUS', '+'); +define('OP_MINUS', '-'); +define('OP_MUL', '*'); +define('OP_DIV', '/'); +define('OP_MOD', '%'); +define('OP_NOT', '!'); +define('OP_BITWISE_NOT', '~'); +define('OP_DOT', '.'); +define('OP_LEFT_BRACKET', '['); +define('OP_RIGHT_BRACKET', ']'); +define('OP_LEFT_CURLY', '{'); +define('OP_RIGHT_CURLY', '}'); +define('OP_LEFT_PAREN', '('); +define('OP_RIGHT_PAREN', ')'); +define('OP_CONDCOMMENT_END', '@*/'); + +define('OP_UNARY_PLUS', 'U+'); +define('OP_UNARY_MINUS', 'U-'); + +/* Keywords */ +define('KEYWORD_BREAK', 'break'); +define('KEYWORD_CASE', 'case'); +define('KEYWORD_CATCH', 'catch'); +define('KEYWORD_CONST', 'const'); +define('KEYWORD_CONTINUE', 'continue'); +define('KEYWORD_DEBUGGER', 'debugger'); +define('KEYWORD_DEFAULT', 'default'); +define('KEYWORD_DELETE', 'delete'); +define('KEYWORD_DO', 'do'); +define('KEYWORD_ELSE', 'else'); +define('KEYWORD_ENUM', 'enum'); +define('KEYWORD_FALSE', 'false'); +define('KEYWORD_FINALLY', 'finally'); +define('KEYWORD_FOR', 'for'); +define('KEYWORD_FUNCTION', 'function'); +define('KEYWORD_IF', 'if'); +define('KEYWORD_IN', 'in'); +define('KEYWORD_INSTANCEOF', 'instanceof'); +define('KEYWORD_NEW', 'new'); +define('KEYWORD_NULL', 'null'); +define('KEYWORD_RETURN', 'return'); +define('KEYWORD_SWITCH', 'switch'); +define('KEYWORD_THIS', 'this'); +define('KEYWORD_THROW', 'throw'); +define('KEYWORD_TRUE', 'true'); +define('KEYWORD_TRY', 'try'); +define('KEYWORD_TYPEOF', 'typeof'); +define('KEYWORD_VAR', 'var'); +define('KEYWORD_VOID', 'void'); +define('KEYWORD_WHILE', 'while'); +define('KEYWORD_WITH', 'with'); + + +class JSMinPlus +{ + private $parser; + private $reserved = array( + 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do', + 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof', + 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', + 'void', 'while', 'with', + // Words reserved for future use + 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger', + 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto', + 'implements', 'import', 'int', 'interface', 'long', 'native', + 'package', 'private', 'protected', 'public', 'short', 'static', + 'super', 'synchronized', 'throws', 'transient', 'volatile', + // These are not reserved, but should be taken into account + // in isValidIdentifier (See jslint source code) + 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined' + ); + + private function __construct() + { + $this->parser = new JSParser(); + } + + public static function minify($js, $filename='') + { + static $instance; + + // this is a singleton + if(!$instance) + $instance = new JSMinPlus(); + + return $instance->min($js, $filename); + } + + private function min($js, $filename) + { + try + { + $n = $this->parser->parse($js, $filename, 1); + return $this->parseTree($n); + } + catch(Exception $e) + { + echo $e->getMessage() . "\n"; + } + + return false; + } + + private function parseTree($n, $noBlockGrouping = false) + { + $s = ''; + + switch ($n->type) + { + case KEYWORD_FUNCTION: + $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '('; + $params = $n->params; + for ($i = 0, $j = count($params); $i < $j; $i++) + $s .= ($i ? ',' : '') . $params[$i]; + $s .= '){' . $this->parseTree($n->body, true) . '}'; + break; + + case JS_SCRIPT: + // we do nothing with funDecls or varDecls + $noBlockGrouping = true; + // FALL THROUGH + + case JS_BLOCK: + $childs = $n->treeNodes; + $lastType = 0; + for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++) + { + $type = $childs[$i]->type; + $t = $this->parseTree($childs[$i]); + if (strlen($t)) + { + if ($c) + { + $s = rtrim($s, ';'); + + if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM) + { + // put declared functions on a new line + $s .= "\n"; + } + elseif ($type == KEYWORD_VAR && $type == $lastType) + { + // mutiple var-statements can go into one + $t = ',' . substr($t, 4); + } + else + { + // add terminator + $s .= ';'; + } + } + + $s .= $t; + + $c++; + $lastType = $type; + } + } + + if ($c > 1 && !$noBlockGrouping) + { + $s = '{' . $s . '}'; + } + break; + + case KEYWORD_IF: + $s = 'if(' . $this->parseTree($n->condition) . ')'; + $thenPart = $this->parseTree($n->thenPart); + $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null; + + // empty if-statement + if ($thenPart == '') + $thenPart = ';'; + + if ($elsePart) + { + // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble + if ($thenPart != ';' && $thenPart[0] != '{') + $thenPart = '{' . $thenPart . '}'; + + $s .= $thenPart . 'else'; + + // we could check for more, but that hardly ever applies so go for performance + if ($elsePart[0] != '{') + $s .= ' '; + + $s .= $elsePart; + } + else + { + $s .= $thenPart; + } + break; + + case KEYWORD_SWITCH: + $s = 'switch(' . $this->parseTree($n->discriminant) . '){'; + $cases = $n->cases; + for ($i = 0, $j = count($cases); $i < $j; $i++) + { + $case = $cases[$i]; + if ($case->type == KEYWORD_CASE) + $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':'; + else + $s .= 'default:'; + + $statement = $this->parseTree($case->statements, true); + if ($statement) + { + $s .= $statement; + // no terminator for last statement + if ($i + 1 < $j) + $s .= ';'; + } + } + $s .= '}'; + break; + + case KEYWORD_FOR: + $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '') + . ';' . ($n->condition ? $this->parseTree($n->condition) : '') + . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')'; + + $body = $this->parseTree($n->body); + if ($body == '') + $body = ';'; + + $s .= $body; + break; + + case KEYWORD_WHILE: + $s = 'while(' . $this->parseTree($n->condition) . ')'; + + $body = $this->parseTree($n->body); + if ($body == '') + $body = ';'; + + $s .= $body; + break; + + case JS_FOR_IN: + $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')'; + + $body = $this->parseTree($n->body); + if ($body == '') + $body = ';'; + + $s .= $body; + break; + + case KEYWORD_DO: + $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')'; + break; + + case KEYWORD_BREAK: + case KEYWORD_CONTINUE: + $s = $n->value . ($n->label ? ' ' . $n->label : ''); + break; + + case KEYWORD_TRY: + $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}'; + $catchClauses = $n->catchClauses; + for ($i = 0, $j = count($catchClauses); $i < $j; $i++) + { + $t = $catchClauses[$i]; + $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}'; + } + if ($n->finallyBlock) + $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}'; + break; + + case KEYWORD_THROW: + $s = 'throw ' . $this->parseTree($n->exception); + break; + + case KEYWORD_RETURN: + $s = 'return'; + if ($n->value) + { + $t = $this->parseTree($n->value); + if (strlen($t)) + { + if ( $t[0] != '(' && $t[0] != '[' && $t[0] != '{' && + $t[0] != '"' && $t[0] != "'" && $t[0] != '/' + ) + $s .= ' '; + + $s .= $t; + } + } + break; + + case KEYWORD_WITH: + $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body); + break; + + case KEYWORD_VAR: + case KEYWORD_CONST: + $s = $n->value . ' '; + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) + { + $t = $childs[$i]; + $s .= ($i ? ',' : '') . $t->name; + $u = $t->initializer; + if ($u) + $s .= '=' . $this->parseTree($u); + } + break; + + case KEYWORD_DEBUGGER: + throw new Exception('NOT IMPLEMENTED: DEBUGGER'); + break; + + case TOKEN_CONDCOMMENT_START: + case TOKEN_CONDCOMMENT_END: + $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : ''); + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) + $s .= $this->parseTree($childs[$i]); + break; + + case OP_SEMICOLON: + if ($expression = $n->expression) + $s = $this->parseTree($expression); + break; + + case JS_LABEL: + $s = $n->label . ':' . $this->parseTree($n->statement); + break; + + case OP_COMMA: + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) + $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); + break; + + case OP_ASSIGN: + $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]); + break; + + case OP_HOOK: + $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]); + break; + + case OP_OR: case OP_AND: + case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND: + case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE: + case OP_LT: case OP_LE: case OP_GE: case OP_GT: + case OP_LSH: case OP_RSH: case OP_URSH: + case OP_MUL: case OP_DIV: case OP_MOD: + $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]); + break; + + case OP_PLUS: + case OP_MINUS: + $left = $this->parseTree($n->treeNodes[0]); + $right = $this->parseTree($n->treeNodes[1]); + + switch ($n->treeNodes[1]->type) + { + case OP_PLUS: + case OP_MINUS: + case OP_INCREMENT: + case OP_DECREMENT: + case OP_UNARY_PLUS: + case OP_UNARY_MINUS: + $s = $left . $n->type . ' ' . $right; + break; + + case TOKEN_STRING: + //combine concatted strings with same quotestyle + if ($n->type == OP_PLUS && substr($left, -1) == $right[0]) + { + $s = substr($left, 0, -1) . substr($right, 1); + break; + } + // FALL THROUGH + + default: + $s = $left . $n->type . $right; + } + break; + + case KEYWORD_IN: + $s = $this->parseTree($n->treeNodes[0]) . ' in ' . $this->parseTree($n->treeNodes[1]); + break; + + case KEYWORD_INSTANCEOF: + $s = $this->parseTree($n->treeNodes[0]) . ' instanceof ' . $this->parseTree($n->treeNodes[1]); + break; + + case KEYWORD_DELETE: + $s = 'delete ' . $this->parseTree($n->treeNodes[0]); + break; + + case KEYWORD_VOID: + $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')'; + break; + + case KEYWORD_TYPEOF: + $s = 'typeof ' . $this->parseTree($n->treeNodes[0]); + break; + + case OP_NOT: + case OP_BITWISE_NOT: + case OP_UNARY_PLUS: + case OP_UNARY_MINUS: + $s = $n->value . $this->parseTree($n->treeNodes[0]); + break; + + case OP_INCREMENT: + case OP_DECREMENT: + if ($n->postfix) + $s = $this->parseTree($n->treeNodes[0]) . $n->value; + else + $s = $n->value . $this->parseTree($n->treeNodes[0]); + break; + + case OP_DOT: + $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]); + break; + + case JS_INDEX: + $s = $this->parseTree($n->treeNodes[0]); + // See if we can replace named index with a dot saving 3 bytes + if ( $n->treeNodes[0]->type == TOKEN_IDENTIFIER && + $n->treeNodes[1]->type == TOKEN_STRING && + $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1)) + ) + $s .= '.' . substr($n->treeNodes[1]->value, 1, -1); + else + $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']'; + break; + + case JS_LIST: + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) + $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); + break; + + case JS_CALL: + $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')'; + break; + + case KEYWORD_NEW: + case JS_NEW_WITH_ARGS: + $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')'; + break; + + case JS_ARRAY_INIT: + $s = '['; + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) + { + $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); + } + $s .= ']'; + break; + + case JS_OBJECT_INIT: + $s = '{'; + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) + { + $t = $childs[$i]; + if ($i) + $s .= ','; + if ($t->type == JS_PROPERTY_INIT) + { + // Ditch the quotes when the index is a valid identifier + if ( $t->treeNodes[0]->type == TOKEN_STRING && + $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1)) + ) + $s .= substr($t->treeNodes[0]->value, 1, -1); + else + $s .= $t->treeNodes[0]->value; + + $s .= ':' . $this->parseTree($t->treeNodes[1]); + } + else + { + $s .= $t->type == JS_GETTER ? 'get' : 'set'; + $s .= ' ' . $t->name . '('; + $params = $t->params; + for ($i = 0, $j = count($params); $i < $j; $i++) + $s .= ($i ? ',' : '') . $params[$i]; + $s .= '){' . $this->parseTree($t->body, true) . '}'; + } + } + $s .= '}'; + break; + + case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE: + case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP: + $s = $n->value; + break; + + case JS_GROUP: + $s = '(' . $this->parseTree($n->treeNodes[0]) . ')'; + break; + + default: + throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type); + } + + return $s; + } + + private function isValidIdentifier($string) + { + return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved); + } +} + +class JSParser +{ + private $t; + + private $opPrecedence = array( + ';' => 0, + ',' => 1, + '=' => 2, '?' => 2, ':' => 2, + // The above all have to have the same precedence, see bug 330975 + '||' => 4, + '&&' => 5, + '|' => 6, + '^' => 7, + '&' => 8, + '==' => 9, '!=' => 9, '===' => 9, '!==' => 9, + '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10, + '<<' => 11, '>>' => 11, '>>>' => 11, + '+' => 12, '-' => 12, + '*' => 13, '/' => 13, '%' => 13, + 'delete' => 14, 'void' => 14, 'typeof' => 14, + '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14, + '++' => 15, '--' => 15, + 'new' => 16, + '.' => 17, + JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0, + JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0 + ); + + private $opArity = array( + ',' => -2, + '=' => 2, + '?' => 3, + '||' => 2, + '&&' => 2, + '|' => 2, + '^' => 2, + '&' => 2, + '==' => 2, '!=' => 2, '===' => 2, '!==' => 2, + '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2, + '<<' => 2, '>>' => 2, '>>>' => 2, + '+' => 2, '-' => 2, + '*' => 2, '/' => 2, '%' => 2, + 'delete' => 1, 'void' => 1, 'typeof' => 1, + '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1, + '++' => 1, '--' => 1, + 'new' => 1, + '.' => 2, + JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2, + JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1, + TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1 + ); + + public function __construct() + { + $this->t = new JSTokenizer(); + } + + public function parse($s, $f, $l) + { + // initialize tokenizer + $this->t->init($s, $f, $l); + + $x = new JSCompilerContext(false); + $n = $this->Script($x); + if (!$this->t->isDone()) + throw $this->t->newSyntaxError('Syntax error'); + + return $n; + } + + private function Script($x) + { + $n = $this->Statements($x); + $n->type = JS_SCRIPT; + $n->funDecls = $x->funDecls; + $n->varDecls = $x->varDecls; + + return $n; + } + + private function Statements($x) + { + $n = new JSNode($this->t, JS_BLOCK); + array_push($x->stmtStack, $n); + + while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY) + $n->addNode($this->Statement($x)); + + array_pop($x->stmtStack); + + return $n; + } + + private function Block($x) + { + $this->t->mustMatch(OP_LEFT_CURLY); + $n = $this->Statements($x); + $this->t->mustMatch(OP_RIGHT_CURLY); + + return $n; + } + + private function Statement($x) + { + $tt = $this->t->get(); + $n2 = null; + + // Cases for statements ending in a right curly return early, avoiding the + // common semicolon insertion magic after this switch. + switch ($tt) + { + case KEYWORD_FUNCTION: + return $this->FunctionDefinition( + $x, + true, + count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM + ); + break; + + case OP_LEFT_CURLY: + $n = $this->Statements($x); + $this->t->mustMatch(OP_RIGHT_CURLY); + return $n; + + case KEYWORD_IF: + $n = new JSNode($this->t); + $n->condition = $this->ParenExpression($x); + array_push($x->stmtStack, $n); + $n->thenPart = $this->Statement($x); + $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null; + array_pop($x->stmtStack); + return $n; + + case KEYWORD_SWITCH: + $n = new JSNode($this->t); + $this->t->mustMatch(OP_LEFT_PAREN); + $n->discriminant = $this->Expression($x); + $this->t->mustMatch(OP_RIGHT_PAREN); + $n->cases = array(); + $n->defaultIndex = -1; + + array_push($x->stmtStack, $n); + + $this->t->mustMatch(OP_LEFT_CURLY); + + while (($tt = $this->t->get()) != OP_RIGHT_CURLY) + { + switch ($tt) + { + case KEYWORD_DEFAULT: + if ($n->defaultIndex >= 0) + throw $this->t->newSyntaxError('More than one switch default'); + // FALL THROUGH + case KEYWORD_CASE: + $n2 = new JSNode($this->t); + if ($tt == KEYWORD_DEFAULT) + $n->defaultIndex = count($n->cases); + else + $n2->caseLabel = $this->Expression($x, OP_COLON); + break; + default: + throw $this->t->newSyntaxError('Invalid switch case'); + } + + $this->t->mustMatch(OP_COLON); + $n2->statements = new JSNode($this->t, JS_BLOCK); + while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY) + $n2->statements->addNode($this->Statement($x)); + + array_push($n->cases, $n2); + } + + array_pop($x->stmtStack); + return $n; + + case KEYWORD_FOR: + $n = new JSNode($this->t); + $n->isLoop = true; + $this->t->mustMatch(OP_LEFT_PAREN); + + if (($tt = $this->t->peek()) != OP_SEMICOLON) + { + $x->inForLoopInit = true; + if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST) + { + $this->t->get(); + $n2 = $this->Variables($x); + } + else + { + $n2 = $this->Expression($x); + } + $x->inForLoopInit = false; + } + + if ($n2 && $this->t->match(KEYWORD_IN)) + { + $n->type = JS_FOR_IN; + if ($n2->type == KEYWORD_VAR) + { + if (count($n2->treeNodes) != 1) + { + throw $this->t->SyntaxError( + 'Invalid for..in left-hand side', + $this->t->filename, + $n2->lineno + ); + } + + // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name. + $n->iterator = $n2->treeNodes[0]; + $n->varDecl = $n2; + } + else + { + $n->iterator = $n2; + $n->varDecl = null; + } + + $n->object = $this->Expression($x); + } + else + { + $n->setup = $n2 ? $n2 : null; + $this->t->mustMatch(OP_SEMICOLON); + $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x); + $this->t->mustMatch(OP_SEMICOLON); + $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x); + } + + $this->t->mustMatch(OP_RIGHT_PAREN); + $n->body = $this->nest($x, $n); + return $n; + + case KEYWORD_WHILE: + $n = new JSNode($this->t); + $n->isLoop = true; + $n->condition = $this->ParenExpression($x); + $n->body = $this->nest($x, $n); + return $n; + + case KEYWORD_DO: + $n = new JSNode($this->t); + $n->isLoop = true; + $n->body = $this->nest($x, $n, KEYWORD_WHILE); + $n->condition = $this->ParenExpression($x); + if (!$x->ecmaStrictMode) + { + // <script language="JavaScript"> (without version hints) may need + // automatic semicolon insertion without a newline after do-while. + // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945. + $this->t->match(OP_SEMICOLON); + return $n; + } + break; + + case KEYWORD_BREAK: + case KEYWORD_CONTINUE: + $n = new JSNode($this->t); + + if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER) + { + $this->t->get(); + $n->label = $this->t->currentToken()->value; + } + + $ss = $x->stmtStack; + $i = count($ss); + $label = $n->label; + if ($label) + { + do + { + if (--$i < 0) + throw $this->t->newSyntaxError('Label not found'); + } + while ($ss[$i]->label != $label); + } + else + { + do + { + if (--$i < 0) + throw $this->t->newSyntaxError('Invalid ' . $tt); + } + while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH)); + } + + $n->target = $ss[$i]; + break; + + case KEYWORD_TRY: + $n = new JSNode($this->t); + $n->tryBlock = $this->Block($x); + $n->catchClauses = array(); + + while ($this->t->match(KEYWORD_CATCH)) + { + $n2 = new JSNode($this->t); + $this->t->mustMatch(OP_LEFT_PAREN); + $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value; + + if ($this->t->match(KEYWORD_IF)) + { + if ($x->ecmaStrictMode) + throw $this->t->newSyntaxError('Illegal catch guard'); + + if (count($n->catchClauses) && !end($n->catchClauses)->guard) + throw $this->t->newSyntaxError('Guarded catch after unguarded'); + + $n2->guard = $this->Expression($x); + } + else + { + $n2->guard = null; + } + + $this->t->mustMatch(OP_RIGHT_PAREN); + $n2->block = $this->Block($x); + array_push($n->catchClauses, $n2); + } + + if ($this->t->match(KEYWORD_FINALLY)) + $n->finallyBlock = $this->Block($x); + + if (!count($n->catchClauses) && !$n->finallyBlock) + throw $this->t->newSyntaxError('Invalid try statement'); + return $n; + + case KEYWORD_CATCH: + case KEYWORD_FINALLY: + throw $this->t->newSyntaxError($tt + ' without preceding try'); + + case KEYWORD_THROW: + $n = new JSNode($this->t); + $n->exception = $this->Expression($x); + break; + + case KEYWORD_RETURN: + if (!$x->inFunction) + throw $this->t->newSyntaxError('Invalid return'); + + $n = new JSNode($this->t); + $tt = $this->t->peekOnSameLine(); + if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY) + $n->value = $this->Expression($x); + else + $n->value = null; + break; + + case KEYWORD_WITH: + $n = new JSNode($this->t); + $n->object = $this->ParenExpression($x); + $n->body = $this->nest($x, $n); + return $n; + + case KEYWORD_VAR: + case KEYWORD_CONST: + $n = $this->Variables($x); + break; + + case TOKEN_CONDCOMMENT_START: + case TOKEN_CONDCOMMENT_END: + $n = new JSNode($this->t); + return $n; + + case KEYWORD_DEBUGGER: + $n = new JSNode($this->t); + break; + + case TOKEN_NEWLINE: + case OP_SEMICOLON: + $n = new JSNode($this->t, OP_SEMICOLON); + $n->expression = null; + return $n; + + default: + if ($tt == TOKEN_IDENTIFIER) + { + $this->t->scanOperand = false; + $tt = $this->t->peek(); + $this->t->scanOperand = true; + if ($tt == OP_COLON) + { + $label = $this->t->currentToken()->value; + $ss = $x->stmtStack; + for ($i = count($ss) - 1; $i >= 0; --$i) + { + if ($ss[$i]->label == $label) + throw $this->t->newSyntaxError('Duplicate label'); + } + + $this->t->get(); + $n = new JSNode($this->t, JS_LABEL); + $n->label = $label; + $n->statement = $this->nest($x, $n); + + return $n; + } + } + + $n = new JSNode($this->t, OP_SEMICOLON); + $this->t->unget(); + $n->expression = $this->Expression($x); + $n->end = $n->expression->end; + break; + } + + if ($this->t->lineno == $this->t->currentToken()->lineno) + { + $tt = $this->t->peekOnSameLine(); + if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY) + throw $this->t->newSyntaxError('Missing ; before statement'); + } + + $this->t->match(OP_SEMICOLON); + + return $n; + } + + private function FunctionDefinition($x, $requireName, $functionForm) + { + $f = new JSNode($this->t); + + if ($f->type != KEYWORD_FUNCTION) + $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER; + + if ($this->t->match(TOKEN_IDENTIFIER)) + $f->name = $this->t->currentToken()->value; + elseif ($requireName) + throw $this->t->newSyntaxError('Missing function identifier'); + + $this->t->mustMatch(OP_LEFT_PAREN); + $f->params = array(); + + while (($tt = $this->t->get()) != OP_RIGHT_PAREN) + { + if ($tt != TOKEN_IDENTIFIER) + throw $this->t->newSyntaxError('Missing formal parameter'); + + array_push($f->params, $this->t->currentToken()->value); + + if ($this->t->peek() != OP_RIGHT_PAREN) + $this->t->mustMatch(OP_COMMA); + } + + $this->t->mustMatch(OP_LEFT_CURLY); + + $x2 = new JSCompilerContext(true); + $f->body = $this->Script($x2); + + $this->t->mustMatch(OP_RIGHT_CURLY); + $f->end = $this->t->currentToken()->end; + + $f->functionForm = $functionForm; + if ($functionForm == DECLARED_FORM) + array_push($x->funDecls, $f); + + return $f; + } + + private function Variables($x) + { + $n = new JSNode($this->t); + + do + { + $this->t->mustMatch(TOKEN_IDENTIFIER); + + $n2 = new JSNode($this->t); + $n2->name = $n2->value; + + if ($this->t->match(OP_ASSIGN)) + { + if ($this->t->currentToken()->assignOp) + throw $this->t->newSyntaxError('Invalid variable initialization'); + + $n2->initializer = $this->Expression($x, OP_COMMA); + } + + $n2->readOnly = $n->type == KEYWORD_CONST; + + $n->addNode($n2); + array_push($x->varDecls, $n2); + } + while ($this->t->match(OP_COMMA)); + + return $n; + } + + private function Expression($x, $stop=false) + { + $operators = array(); + $operands = array(); + $n = false; + + $bl = $x->bracketLevel; + $cl = $x->curlyLevel; + $pl = $x->parenLevel; + $hl = $x->hookLevel; + + while (($tt = $this->t->get()) != TOKEN_END) + { + if ($tt == $stop && + $x->bracketLevel == $bl && + $x->curlyLevel == $cl && + $x->parenLevel == $pl && + $x->hookLevel == $hl + ) + { + // Stop only if tt matches the optional stop parameter, and that + // token is not quoted by some kind of bracket. + break; + } + + switch ($tt) + { + case OP_SEMICOLON: + // NB: cannot be empty, Statement handled that. + break 2; + + case OP_HOOK: + if ($this->t->scanOperand) + break 2; + + while ( !empty($operators) && + $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] + ) + $this->reduce($operators, $operands); + + array_push($operators, new JSNode($this->t)); + + ++$x->hookLevel; + $this->t->scanOperand = true; + $n = $this->Expression($x); + + if (!$this->t->match(OP_COLON)) + break 2; + + --$x->hookLevel; + array_push($operands, $n); + break; + + case OP_COLON: + if ($x->hookLevel) + break 2; + + throw $this->t->newSyntaxError('Invalid label'); + break; + + case OP_ASSIGN: + if ($this->t->scanOperand) + break 2; + + // Use >, not >=, for right-associative ASSIGN + while ( !empty($operators) && + $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] + ) + $this->reduce($operators, $operands); + + array_push($operators, new JSNode($this->t)); + end($operands)->assignOp = $this->t->currentToken()->assignOp; + $this->t->scanOperand = true; + break; + + case KEYWORD_IN: + // An in operator should not be parsed if we're parsing the head of + // a for (...) loop, unless it is in the then part of a conditional + // expression, or parenthesized somehow. + if ($x->inForLoopInit && !$x->hookLevel && + !$x->bracketLevel && !$x->curlyLevel && + !$x->parenLevel + ) + break 2; + // FALL THROUGH + case OP_COMMA: + // A comma operator should not be parsed if we're parsing the then part + // of a conditional expression unless it's parenthesized somehow. + if ($tt == OP_COMMA && $x->hookLevel && + !$x->bracketLevel && !$x->curlyLevel && + !$x->parenLevel + ) + break 2; + // Treat comma as left-associative so reduce can fold left-heavy + // COMMA trees into a single array. + // FALL THROUGH + case OP_OR: + case OP_AND: + case OP_BITWISE_OR: + case OP_BITWISE_XOR: + case OP_BITWISE_AND: + case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE: + case OP_LT: case OP_LE: case OP_GE: case OP_GT: + case KEYWORD_INSTANCEOF: + case OP_LSH: case OP_RSH: case OP_URSH: + case OP_PLUS: case OP_MINUS: + case OP_MUL: case OP_DIV: case OP_MOD: + case OP_DOT: + if ($this->t->scanOperand) + break 2; + + while ( !empty($operators) && + $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt] + ) + $this->reduce($operators, $operands); + + if ($tt == OP_DOT) + { + $this->t->mustMatch(TOKEN_IDENTIFIER); + array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t))); + } + else + { + array_push($operators, new JSNode($this->t)); + $this->t->scanOperand = true; + } + break; + + case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF: + case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS: + case KEYWORD_NEW: + if (!$this->t->scanOperand) + break 2; + + array_push($operators, new JSNode($this->t)); + break; + + case OP_INCREMENT: case OP_DECREMENT: + if ($this->t->scanOperand) + { + array_push($operators, new JSNode($this->t)); // prefix increment or decrement + } + else + { + // Don't cross a line boundary for postfix {in,de}crement. + $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3]; + if ($t && $t->lineno != $this->t->lineno) + break 2; + + if (!empty($operators)) + { + // Use >, not >=, so postfix has higher precedence than prefix. + while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]) + $this->reduce($operators, $operands); + } + + $n = new JSNode($this->t, $tt, array_pop($operands)); + $n->postfix = true; + array_push($operands, $n); + } + break; + + case KEYWORD_FUNCTION: + if (!$this->t->scanOperand) + break 2; + + array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM)); + $this->t->scanOperand = false; + break; + + case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE: + case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP: + if (!$this->t->scanOperand) + break 2; + + array_push($operands, new JSNode($this->t)); + $this->t->scanOperand = false; + break; + + case TOKEN_CONDCOMMENT_START: + case TOKEN_CONDCOMMENT_END: + if ($this->t->scanOperand) + array_push($operators, new JSNode($this->t)); + else + array_push($operands, new JSNode($this->t)); + break; + + case OP_LEFT_BRACKET: + if ($this->t->scanOperand) + { + // Array initialiser. Parse using recursive descent, as the + // sub-grammar here is not an operator grammar. + $n = new JSNode($this->t, JS_ARRAY_INIT); + while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET) + { + if ($tt == OP_COMMA) + { + $this->t->get(); + $n->addNode(null); + continue; + } + + $n->addNode($this->Expression($x, OP_COMMA)); + if (!$this->t->match(OP_COMMA)) + break; + } + + $this->t->mustMatch(OP_RIGHT_BRACKET); + array_push($operands, $n); + $this->t->scanOperand = false; + } + else + { + // Property indexing operator. + array_push($operators, new JSNode($this->t, JS_INDEX)); + $this->t->scanOperand = true; + ++$x->bracketLevel; + } + break; + + case OP_RIGHT_BRACKET: + if ($this->t->scanOperand || $x->bracketLevel == $bl) + break 2; + + while ($this->reduce($operators, $operands)->type != JS_INDEX) + continue; + + --$x->bracketLevel; + break; + + case OP_LEFT_CURLY: + if (!$this->t->scanOperand) + break 2; + + // Object initialiser. As for array initialisers (see above), + // parse using recursive descent. + ++$x->curlyLevel; + $n = new JSNode($this->t, JS_OBJECT_INIT); + while (!$this->t->match(OP_RIGHT_CURLY)) + { + do + { + $tt = $this->t->get(); + $tv = $this->t->currentToken()->value; + if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER) + { + if ($x->ecmaStrictMode) + throw $this->t->newSyntaxError('Illegal property accessor'); + + $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM)); + } + else + { + switch ($tt) + { + case TOKEN_IDENTIFIER: + case TOKEN_NUMBER: + case TOKEN_STRING: + $id = new JSNode($this->t); + break; + + case OP_RIGHT_CURLY: + if ($x->ecmaStrictMode) + throw $this->t->newSyntaxError('Illegal trailing ,'); + break 3; + + default: + throw $this->t->newSyntaxError('Invalid property name'); + } + + $this->t->mustMatch(OP_COLON); + $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA))); + } + } + while ($this->t->match(OP_COMMA)); + + $this->t->mustMatch(OP_RIGHT_CURLY); + break; + } + + array_push($operands, $n); + $this->t->scanOperand = false; + --$x->curlyLevel; + break; + + case OP_RIGHT_CURLY: + if (!$this->t->scanOperand && $x->curlyLevel != $cl) + throw new Exception('PANIC: right curly botch'); + break 2; + + case OP_LEFT_PAREN: + if ($this->t->scanOperand) + { + array_push($operators, new JSNode($this->t, JS_GROUP)); + } + else + { + while ( !empty($operators) && + $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW] + ) + $this->reduce($operators, $operands); + + // Handle () now, to regularize the n-ary case for n > 0. + // We must set scanOperand in case there are arguments and + // the first one is a regexp or unary+/-. + $n = end($operators); + $this->t->scanOperand = true; + if ($this->t->match(OP_RIGHT_PAREN)) + { + if ($n && $n->type == KEYWORD_NEW) + { + array_pop($operators); + $n->addNode(array_pop($operands)); + } + else + { + $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST)); + } + + array_push($operands, $n); + $this->t->scanOperand = false; + break; + } + + if ($n && $n->type == KEYWORD_NEW) + $n->type = JS_NEW_WITH_ARGS; + else + array_push($operators, new JSNode($this->t, JS_CALL)); + } + + ++$x->parenLevel; + break; + + case OP_RIGHT_PAREN: + if ($this->t->scanOperand || $x->parenLevel == $pl) + break 2; + + while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP && + $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS + ) + { + continue; + } + + if ($tt != JS_GROUP) + { + $n = end($operands); + if ($n->treeNodes[1]->type != OP_COMMA) + $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]); + else + $n->treeNodes[1]->type = JS_LIST; + } + + --$x->parenLevel; + break; + + // Automatic semicolon insertion means we may scan across a newline + // and into the beginning of another statement. If so, break out of + // the while loop and let the t.scanOperand logic handle errors. + default: + break 2; + } + } + + if ($x->hookLevel != $hl) + throw $this->t->newSyntaxError('Missing : in conditional expression'); + + if ($x->parenLevel != $pl) + throw $this->t->newSyntaxError('Missing ) in parenthetical'); + + if ($x->bracketLevel != $bl) + throw $this->t->newSyntaxError('Missing ] in index expression'); + + if ($this->t->scanOperand) + throw $this->t->newSyntaxError('Missing operand'); + + // Resume default mode, scanning for operands, not operators. + $this->t->scanOperand = true; + $this->t->unget(); + + while (count($operators)) + $this->reduce($operators, $operands); + + return array_pop($operands); + } + + private function ParenExpression($x) + { + $this->t->mustMatch(OP_LEFT_PAREN); + $n = $this->Expression($x); + $this->t->mustMatch(OP_RIGHT_PAREN); + + return $n; + } + + // Statement stack and nested statement handler. + private function nest($x, $node, $end = false) + { + array_push($x->stmtStack, $node); + $n = $this->statement($x); + array_pop($x->stmtStack); + + if ($end) + $this->t->mustMatch($end); + + return $n; + } + + private function reduce(&$operators, &$operands) + { + $n = array_pop($operators); + $op = $n->type; + $arity = $this->opArity[$op]; + $c = count($operands); + if ($arity == -2) + { + // Flatten left-associative trees + if ($c >= 2) + { + $left = $operands[$c - 2]; + if ($left->type == $op) + { + $right = array_pop($operands); + $left->addNode($right); + return $left; + } + } + $arity = 2; + } + + // Always use push to add operands to n, to update start and end + $a = array_splice($operands, $c - $arity); + for ($i = 0; $i < $arity; $i++) + $n->addNode($a[$i]); + + // Include closing bracket or postfix operator in [start,end] + $te = $this->t->currentToken()->end; + if ($n->end < $te) + $n->end = $te; + + array_push($operands, $n); + + return $n; + } +} + +class JSCompilerContext +{ + public $inFunction = false; + public $inForLoopInit = false; + public $ecmaStrictMode = false; + public $bracketLevel = 0; + public $curlyLevel = 0; + public $parenLevel = 0; + public $hookLevel = 0; + + public $stmtStack = array(); + public $funDecls = array(); + public $varDecls = array(); + + public function __construct($inFunction) + { + $this->inFunction = $inFunction; + } +} + +class JSNode +{ + private $type; + private $value; + private $lineno; + private $start; + private $end; + + public $treeNodes = array(); + public $funDecls = array(); + public $varDecls = array(); + + public function __construct($t, $type=0) + { + if ($token = $t->currentToken()) + { + $this->type = $type ? $type : $token->type; + $this->value = $token->value; + $this->lineno = $token->lineno; + $this->start = $token->start; + $this->end = $token->end; + } + else + { + $this->type = $type; + $this->lineno = $t->lineno; + } + + if (($numargs = func_num_args()) > 2) + { + $args = func_get_args(); + for ($i = 2; $i < $numargs; $i++) + $this->addNode($args[$i]); + } + } + + // we don't want to bloat our object with all kind of specific properties, so we use overloading + public function __set($name, $value) + { + $this->$name = $value; + } + + public function __get($name) + { + if (isset($this->$name)) + return $this->$name; + + return null; + } + + public function addNode($node) + { + if ($node !== null) + { + if ($node->start < $this->start) + $this->start = $node->start; + if ($this->end < $node->end) + $this->end = $node->end; + } + + $this->treeNodes[] = $node; + } +} + +class JSTokenizer +{ + private $cursor = 0; + private $source; + + public $tokens = array(); + public $tokenIndex = 0; + public $lookahead = 0; + public $scanNewlines = false; + public $scanOperand = true; + + public $filename; + public $lineno; + + private $keywords = array( + 'break', + 'case', 'catch', 'const', 'continue', + 'debugger', 'default', 'delete', 'do', + 'else', 'enum', + 'false', 'finally', 'for', 'function', + 'if', 'in', 'instanceof', + 'new', 'null', + 'return', + 'switch', + 'this', 'throw', 'true', 'try', 'typeof', + 'var', 'void', + 'while', 'with' + ); + + private $opTypeNames = array( + ';' => 'SEMICOLON', + ',' => 'COMMA', + '?' => 'HOOK', + ':' => 'COLON', + '||' => 'OR', + '&&' => 'AND', + '|' => 'BITWISE_OR', + '^' => 'BITWISE_XOR', + '&' => 'BITWISE_AND', + '===' => 'STRICT_EQ', + '==' => 'EQ', + '=' => 'ASSIGN', + '!==' => 'STRICT_NE', + '!=' => 'NE', + '<<' => 'LSH', + '<=' => 'LE', + '<' => 'LT', + '>>>' => 'URSH', + '>>' => 'RSH', + '>=' => 'GE', + '>' => 'GT', + '++' => 'INCREMENT', + '--' => 'DECREMENT', + '+' => 'PLUS', + '-' => 'MINUS', + '*' => 'MUL', + '/' => 'DIV', + '%' => 'MOD', + '!' => 'NOT', + '~' => 'BITWISE_NOT', + '.' => 'DOT', + '[' => 'LEFT_BRACKET', + ']' => 'RIGHT_BRACKET', + '{' => 'LEFT_CURLY', + '}' => 'RIGHT_CURLY', + '(' => 'LEFT_PAREN', + ')' => 'RIGHT_PAREN', + '@*/' => 'CONDCOMMENT_END' + ); + + private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%'); + private $opRegExp; + + public function __construct() + { + $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames))) . ')#'; + } + + public function init($source, $filename = '', $lineno = 1) + { + $this->source = $source; + $this->filename = $filename ? $filename : '[inline]'; + $this->lineno = $lineno; + + $this->cursor = 0; + $this->tokens = array(); + $this->tokenIndex = 0; + $this->lookahead = 0; + $this->scanNewlines = false; + $this->scanOperand = true; + } + + public function getInput($chunksize) + { + if ($chunksize) + return substr($this->source, $this->cursor, $chunksize); + + return substr($this->source, $this->cursor); + } + + public function isDone() + { + return $this->peek() == TOKEN_END; + } + + public function match($tt) + { + return $this->get() == $tt || $this->unget(); + } + + public function mustMatch($tt) + { + if (!$this->match($tt)) + throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected'); + + return $this->currentToken(); + } + + public function peek() + { + if ($this->lookahead) + { + $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3]; + if ($this->scanNewlines && $next->lineno != $this->lineno) + $tt = TOKEN_NEWLINE; + else + $tt = $next->type; + } + else + { + $tt = $this->get(); + $this->unget(); + } + + return $tt; + } + + public function peekOnSameLine() + { + $this->scanNewlines = true; + $tt = $this->peek(); + $this->scanNewlines = false; + + return $tt; + } + + public function currentToken() + { + if (!empty($this->tokens)) + return $this->tokens[$this->tokenIndex]; + } + + public function get($chunksize = 1000) + { + while($this->lookahead) + { + $this->lookahead--; + $this->tokenIndex = ($this->tokenIndex + 1) & 3; + $token = $this->tokens[$this->tokenIndex]; + if ($token->type != TOKEN_NEWLINE || $this->scanNewlines) + return $token->type; + } + + $conditional_comment = false; + + // strip whitespace and comments + while(true) + { + $input = $this->getInput($chunksize); + + // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!) + $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/'; + if (preg_match($re, $input, $match)) + { + $spaces = $match[0]; + $spacelen = strlen($spaces); + $this->cursor += $spacelen; + if (!$this->scanNewlines) + $this->lineno += substr_count($spaces, "\n"); + + if ($spacelen == $chunksize) + continue; // complete chunk contained whitespace + + $input = $this->getInput($chunksize); + if ($input == '' || $input[0] != '/') + break; + } + + // Comments + if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match)) + { + if (!$chunksize) + break; + + // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment) + $chunksize = null; + continue; + } + + // check if this is a conditional (JScript) comment + if (!empty($match[1])) + { + $match[0] = '/*' . $match[1]; + $conditional_comment = true; + break; + } + else + { + $this->cursor += strlen($match[0]); + $this->lineno += substr_count($match[0], "\n"); + } + } + + if ($input == '') + { + $tt = TOKEN_END; + $match = array(''); + } + elseif ($conditional_comment) + { + $tt = TOKEN_CONDCOMMENT_START; + } + else + { + switch ($input[0]) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (preg_match('/^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+/', $input, $match)) + { + $tt = TOKEN_NUMBER; + } + else if (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match)) + { + // this should always match because of \d+ + $tt = TOKEN_NUMBER; + } + break; + + case '"': + case "'": + if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n]+)*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n]+)*\'/', $input, $match)) + { + $tt = TOKEN_STRING; + } + else + { + if ($chunksize) + return $this->get(null); // retry with a full chunk fetch + + throw $this->newSyntaxError('Unterminated string literal'); + } + break; + + case '/': + if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match)) + { + $tt = TOKEN_REGEXP; + break; + } + // FALL THROUGH + + case '|': + case '^': + case '&': + case '<': + case '>': + case '+': + case '-': + case '*': + case '%': + case '=': + case '!': + // should always match + preg_match($this->opRegExp, $input, $match); + $op = $match[0]; + if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=') + { + $tt = OP_ASSIGN; + $match[0] .= '='; + } + else + { + $tt = $op; + if ($this->scanOperand) + { + if ($op == OP_PLUS) + $tt = OP_UNARY_PLUS; + elseif ($op == OP_MINUS) + $tt = OP_UNARY_MINUS; + } + $op = null; + } + break; + + case '.': + if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match)) + { + $tt = TOKEN_NUMBER; + break; + } + // FALL THROUGH + + case ';': + case ',': + case '?': + case ':': + case '~': + case '[': + case ']': + case '{': + case '}': + case '(': + case ')': + // these are all single + $match = array($input[0]); + $tt = $input[0]; + break; + + case '@': + // check end of conditional comment + if (substr($input, 0, 3) == '@*/') + { + $match = array('@*/'); + $tt = TOKEN_CONDCOMMENT_END; + } + else + throw $this->newSyntaxError('Illegal token'); + break; + + case "\n": + if ($this->scanNewlines) + { + $match = array("\n"); + $tt = TOKEN_NEWLINE; + } + else + throw $this->newSyntaxError('Illegal token'); + break; + + default: + // Fast path for identifiers: word chars followed by whitespace or various other tokens. + // Note we don't need to exclude digits in the first char, as they've already been found + // above. + if (!preg_match('/^[$\w]+(?=[\s\/\|\^\&<>\+\-\*%=!.;,\?:~\[\]\{\}\(\)@])/', $input, $match)) + { + // Character classes per ECMA-262 edition 5.1 section 7.6 + // Per spec, must accept Unicode 3.0, *may* accept later versions. + // We'll take whatever PCRE understands, which should be more recent. + $identifierStartChars = "\\p{L}\\p{Nl}" . # UnicodeLetter + "\$" . + "_"; + $identifierPartChars = $identifierStartChars . + "\\p{Mn}\\p{Mc}" . # UnicodeCombiningMark + "\\p{Nd}" . # UnicodeDigit + "\\p{Pc}"; # UnicodeConnectorPunctuation + $unicodeEscape = "\\\\u[0-9A-F-a-f]{4}"; + $identifierRegex = "/^" . + "(?:[$identifierStartChars]|$unicodeEscape)" . + "(?:[$identifierPartChars]|$unicodeEscape)*" . + "/uS"; + if (preg_match($identifierRegex, $input, $match)) + { + if (strpos($match[0], '\\') !== false) { + // Per ECMA-262 edition 5.1, section 7.6 escape sequences should behave as if they were + // the original chars, but only within the boundaries of the identifier. + $decoded = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/', + array(__CLASS__, 'unicodeEscapeCallback'), + $match[0]); + + // Since our original regex didn't de-escape the originals, we need to check for validity again. + // No need to worry about token boundaries, as anything outside the identifier is illegal! + if (!preg_match("/^[$identifierStartChars][$identifierPartChars]*$/u", $decoded)) { + throw $this->newSyntaxError('Illegal token'); + } + + // Per spec it _ought_ to work to use these escapes for keywords words as well... + // but IE rejects them as invalid, while Firefox and Chrome treat them as identifiers + // that don't match the keyword. + if (in_array($decoded, $this->keywords)) { + throw $this->newSyntaxError('Illegal token'); + } + + // TODO: save the decoded form for output? + } + } + else + throw $this->newSyntaxError('Illegal token'); + } + $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; + } + } + + $this->tokenIndex = ($this->tokenIndex + 1) & 3; + + if (!isset($this->tokens[$this->tokenIndex])) + $this->tokens[$this->tokenIndex] = new JSToken(); + + $token = $this->tokens[$this->tokenIndex]; + $token->type = $tt; + + if ($tt == OP_ASSIGN) + $token->assignOp = $op; + + $token->start = $this->cursor; + + $token->value = $match[0]; + $this->cursor += strlen($match[0]); + + $token->end = $this->cursor; + $token->lineno = $this->lineno; + + return $tt; + } + + public function unget() + { + if (++$this->lookahead == 4) + throw $this->newSyntaxError('PANIC: too much lookahead!'); + + $this->tokenIndex = ($this->tokenIndex - 1) & 3; + } + + public function newSyntaxError($m) + { + return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno); + } + + public static function unicodeEscapeCallback($m) + { + return html_entity_decode('&#x' . $m[1]. ';', ENT_QUOTES, 'UTF-8'); + } +} + +class JSToken +{ + public $type; + public $value; + public $start; + public $end; + public $lineno; + public $assignOp; +} + diff --git a/includes/libs/spyc.php b/includes/libs/spyc.php deleted file mode 100644 index bc92e869..00000000 --- a/includes/libs/spyc.php +++ /dev/null @@ -1,248 +0,0 @@ -<?php -/** - * Spyc -- A Simple PHP YAML Class - * - * @file - * @version 0.2.3 -- 2006-02-04 - * @author Chris Wanstrath <chris@ozmm.org> - * @see http://spyc.sourceforge.net/ - * @copyright Copyright 2005-2006 Chris Wanstrath - * @license http://www.opensource.org/licenses/mit-license.php MIT License - */ - -/** - * The Simple PHP YAML Class. - * - * This class can be used to read a YAML file and convert its contents - * into a PHP array. It currently supports a very limited subsection of - * the YAML spec. - * - * @ingroup API - */ -class Spyc { - - /** - * Dump YAML from PHP array statically - * - * The dump method, when supplied with an array, will do its best - * to convert the array into friendly YAML. Pretty simple. Feel free to - * save the returned string as nothing.yml and pass it around. - * - * Oh, and you can decide how big the indent is and what the wordwrap - * for folding is. Pretty cool -- just pass in 'false' for either if - * you want to use the default. - * - * Indent's default is 2 spaces, wordwrap's default is 40 characters. And - * you can turn off wordwrap by passing in 0. - * - * @param $array Array: PHP array - * @param $indent Integer: Pass in false to use the default, which is 2 - * @param $wordwrap Integer: Pass in 0 for no wordwrap, false for default (40) - * @return String - */ - public static function YAMLDump( $array, $indent = false, $wordwrap = false ) { - $spyc = new Spyc; - return $spyc->dump( $array, $indent, $wordwrap ); - } - - /** - * Dump PHP array to YAML - * - * The dump method, when supplied with an array, will do its best - * to convert the array into friendly YAML. Pretty simple. Feel free to - * save the returned string as tasteful.yml and pass it around. - * - * Oh, and you can decide how big the indent is and what the wordwrap - * for folding is. Pretty cool -- just pass in 'false' for either if - * you want to use the default. - * - * Indent's default is 2 spaces, wordwrap's default is 40 characters. And - * you can turn off wordwrap by passing in 0. - * - * @param $array Array: PHP array - * @param $indent Integer: Pass in false to use the default, which is 2 - * @param $wordwrap Integer: Pass in 0 for no wordwrap, false for default (40) - * @return String - */ - public function dump( $array, $indent = false, $wordwrap = false ) { - // Dumps to some very clean YAML. We'll have to add some more features - // and options soon. And better support for folding. - - // New features and options. - if ( $indent === false or !is_numeric( $indent ) ) { - $this->_dumpIndent = 2; - } else { - $this->_dumpIndent = $indent; - } - - if ( $wordwrap === false or !is_numeric( $wordwrap ) ) { - $this->_dumpWordWrap = 40; - } else { - $this->_dumpWordWrap = $wordwrap; - } - - // New YAML document - $string = "---\n"; - - // Start at the base of the array and move through it. - foreach ( $array as $key => $value ) { - $string .= $this->_yamlize( $key, $value, 0 ); - } - return $string; - } - - /**** Private Properties ****/ - - /** - * Unused variables, but just commented rather than deleting - * to save altering the library - private $_haveRefs; - private $_allNodes; - private $_lastIndent; - private $_lastNode; - private $_inBlock; - private $_isInline; - **/ - private $_dumpIndent; - private $_dumpWordWrap; - - /**** Private Methods ****/ - - /** - * Attempts to convert a key / value array item to YAML - * - * @param $key Mixed: the name of the key - * @param $value Mixed: the value of the item - * @param $indent Integer: the indent of the current node - * @return String - */ - private function _yamlize( $key, $value, $indent ) { - if ( is_array( $value ) ) { - // It has children. What to do? - // Make it the right kind of item - $string = $this->_dumpNode( $key, null, $indent ); - // Add the indent - $indent += $this->_dumpIndent; - // Yamlize the array - $string .= $this->_yamlizeArray( $value, $indent ); - } elseif ( !is_array( $value ) ) { - // It doesn't have children. Yip. - $string = $this->_dumpNode( $key, $value, $indent ); - } - return $string; - } - - /** - * Attempts to convert an array to YAML - * - * @param $array Array: the array you want to convert - * @param $indent Integer: the indent of the current level - * @return String - */ - private function _yamlizeArray( $array, $indent ) { - if ( is_array( $array ) ) { - $string = ''; - foreach ( $array as $key => $value ) { - $string .= $this->_yamlize( $key, $value, $indent ); - } - return $string; - } else { - return false; - } - } - - /** - * Find out whether a string needs to be output as a literal rather than in plain style. - * Added by Roan Kattouw 13-03-2008 - * - * @param $value String: the string to check - * @return Boolean - */ - function _needLiteral( $value ) { - // Check whether the string contains # or : or begins with any of: - // [ - ? , [ ] { } ! * & | > ' " % @ ` ] - // or is a number or contains newlines - return (bool)( gettype( $value ) == "string" && - ( is_numeric( $value ) || - strpos( $value, "\n" ) || - preg_match( "/[#:]/", $value ) || - preg_match( "/^[-?,[\]{}!*&|>'\"%@`]/", $value ) ) ); - } - - /** - * Returns YAML from a key and a value - * - * @param $key Mixed: the name of the key - * @param $value Mixed: the value of the item - * @param $indent Integer: the indent of the current node - * @return String - */ - private function _dumpNode( $key, $value, $indent ) { - // do some folding here, for blocks - if ( $this->_needLiteral( $value ) ) { - $value = $this->_doLiteralBlock( $value, $indent ); - } else { - $value = $this->_doFolding( $value, $indent ); - } - - $spaces = str_repeat( ' ', $indent ); - - if ( is_int( $key ) ) { - // It's a sequence - if ( $value !== '' && !is_null( $value ) ) - $string = $spaces . '- ' . $value . "\n"; - else - $string = $spaces . "-\n"; - } else { - if ( $key == '*' ) // bug 21922 - Quote asterix used as keys - $key = "'*'"; - - // It's mapped - if ( $value !== '' && !is_null( $value ) ) - $string = $spaces . $key . ': ' . $value . "\n"; - else - $string = $spaces . $key . ":\n"; - } - return $string; - } - - /** - * Creates a literal block for dumping - * - * @param $value String - * @param $indent Integer: the value of the indent - * @return String - */ - private function _doLiteralBlock( $value, $indent ) { - $exploded = explode( "\n", $value ); - $newValue = '|-'; - $indent += $this->_dumpIndent; - $spaces = str_repeat( ' ', $indent ); - foreach ( $exploded as $line ) { - $newValue .= "\n" . $spaces . trim( $line ); - } - return $newValue; - } - - /** - * Folds a string of text, if necessary - * - * @param $value String: the string you wish to fold - * @param $indent Integer: the indent of the current node - * @return String - */ - private function _doFolding( $value, $indent ) { - // Don't do anything if wordwrap is set to 0 - if ( $this->_dumpWordWrap === 0 ) { - return $value; - } - - if ( strlen( $value ) > $this->_dumpWordWrap ) { - $indent += $this->_dumpIndent; - $indent = str_repeat( ' ', $indent ); - $wrapped = wordwrap( $value, $this->_dumpWordWrap, "\n$indent" ); - $value = ">-\n" . $indent . $wrapped; - } - return $value; - } -} |