Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Replace build system with a faster new one that uses Node and UglifyJ…

…S and generates smaller minified files. Also removes builds through rake/ant since having 3 different build systems was too much to maintain (make was the only one consistently kept up-to-date). Fixes #7973.
  • Loading branch information...
commit d503845d0cf45632c0d7c3542ffd1b19257a8e5e 1 parent 9c76ac4
@csnover csnover authored
View
15 Makefile
@@ -7,11 +7,8 @@ BUILD_DIR = build
PREFIX = .
DIST_DIR = ${PREFIX}/dist
-RHINO ?= java -jar ${BUILD_DIR}/js.jar
-
-CLOSURE_COMPILER = ${BUILD_DIR}/google-compiler-20100917.jar
-
-MINJAR ?= java -jar ${CLOSURE_COMPILER}
+JS_ENGINE ?= node
+COMPILER = ${JS_ENGINE} ${BUILD_DIR}/uglify.js --unsafe
BASE_FILES = ${SRC_DIR}/core.js\
${SRC_DIR}/support.js\
@@ -93,17 +90,13 @@ ${SRC_DIR}/selector.js: ${SIZZLE_DIR}/sizzle.js
lint: ${JQ}
@@echo "Checking jQuery against JSLint..."
- @@${RHINO} build/jslint-check.js
+ @@${JS_ENGINE} build/jslint-check.js
min: ${JQ_MIN}
${JQ_MIN}: ${JQ}
@@echo "Building" ${JQ_MIN}
-
- @@head -15 ${JQ} > ${JQ_MIN}
- @@${MINJAR} --js ${JQ} --warning_level QUIET --js_output_file ${JQ_MIN}.tmp
- @@cat ${JQ_MIN}.tmp >> ${JQ_MIN}
- @@rm -f ${JQ_MIN}.tmp
+ @@${COMPILER} ${JQ} > ${JQ_MIN}
clean:
@@echo "Removing Distribution directory:" ${DIST_DIR}
View
95 README.md
@@ -1,85 +1,64 @@
[jQuery](http://jquery.com/) - New Wave Javascript
-================================
+==================================================
What you need to build your own jQuery
----------------------------------------
-* Make sure that you have Java installed (if you want to build a minified version of jQuery).
-If not, [go to this page](http://java.sun.com/javase/downloads/index.jsp) and download "Java Runtime Environment (JRE) 5.0"
+--------------------------------------
-Build Options
---------------
+In order to build jQuery, you need to have GNU make 3.8 or later, Node.js 0.2 or later, and git 1.7 or later.
+(Earlier versions might work OK, but are not tested.)
-You now have **three** options for building jQuery:
+Windows users have two options:
-* **`make`**: If you have access to common UNIX commands (like `make`, `mkdir`, `rm`, `cat`, and `echo`) then simply type `make` to build all the components.
+1. Install [msysgit](https://code.google.com/p/msysgit/) (Full installer for official Git),
+ [GNU make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm), and a
+ [binary version of Node.js](http://node-js.prcn.co.cc/). Make sure all three packages are installed to the same
+ location (by default, this is C:\Program Files\Git).
+2. Install [Cygwin](http://cygwin.com/) (remember to install the git and make packages) and follow the
+ [Node.js build instructions](https://github.com/ry/node/wiki/Building-node.js-on-Cygwin-%28Windows%29) or install
+ the [binary version of Node.js](http://node-js.prcn.co.cc/).
-* **`rake`**: If you have Ruby Rake installed (on either Windows or UNIX/Linux), you can simply type `rake` to build all the components.
+Mac OS users should install Xcode (comes on your Mac OS install DVD, or downloadable from
+[Apple's Xcode site](http://developer.apple.com/technologies/xcode.html)) and
+[http://mxcl.github.com/homebrew/](Homebrew). Once Homebrew is installed, run `brew install git` to install git,
+and `brew install node` to install Node.js.
-* **`ant`**: If you have Ant installed (or are on Windows and don't have access to make). You can download Ant from here: [http://ant.apache.org/bindownload.cgi].
+Linux/BSD users should use their appropriate package managers to install make, git, and node, or build from source
+if you swing that way. Easy-peasy.
-How to build your own jQuery
------------------------------
-
-*Note: If you are using either `rake` or `ant`, substitute your chosen method in place of `make` in the examples below. They work identically for all intents and purposes. Quick reference is also available for `rake` by typing `rake -T` in the `jquery` directory.*
-
-In the main directory of the distribution (the one that this file is in), type
-the following to make all versions of jQuery:
-
- make
-
-*Here are the individual items that are buildable from the Makefile:*
-
- make init
-Pull in all the external dependencies (QUnit, Sizzle) for the project.
-
- make jquery
-
-The standard, uncompressed, jQuery code.
-Makes: `./dist/jquery.js`
-
- make min
-
-A compressed version of jQuery (made the Closure Compiler).
-Makes: `./dist/jquery.min.js`
+How to build your own jQuery
+----------------------------
- make lint
+First, clone a copy of the main jQuery git repo by running `git clone git://github.com/jquery/jquery.git`.
-Tests a build of jQuery against JSLint, looking for potential errors or bits of confusing code.
+Then, to get a complete, minified, jslinted version of jQuery, simply `cd` to the `jquery` directory and type
+`make`. If you don't have Node installed and/or want to make a basic, uncompressed, unlinted version of jQuery, use
+`make jquery` instead of `make`.
- make selector
+The built version of jQuery will be put in the `dist/` subdirectory.
-Builds the selector library for jQuery from Sizzle.
-Makes: `./src/selector.js`
+To remove all built files, run `make clean`.
-Finally, you can remove all the built files using the command:
-
- make clean
Building to a different directory
-----------------------------------
+---------------------------------
-If you want to build jQuery to a directory that is different from the default location, you can...
+If you want to build jQuery to a directory that is different from the default location, you can specify the PREFIX
+directory: `make PREFIX=/home/jquery/test/ [command]`
-**Make only:** Specify the PREFIX directory, for example:
-
- make PREFIX=/home/john/test/ [command]
-
-With this example, the output files would be contained in `/home/john/test/dist/`
+With this example, the output files would end up in `/home/jquery/test/dist/`.
-**Rake only:** Define the DIST_DIR directory, for example:
- rake DIST_DIR=/home/john/test/ [command]
-
-With this example, the output files would be contained in `/home/john/test/`
+Troubleshooting
+---------------
-*In both examples, `[command]` is optional.*
+Sometimes, the various git repositories get into an inconsistent state where builds don't complete properly
+(usually this results in the jquery.js or jquery.min.js being 0 bytes). If this happens, run `make clean`, then
+run `make` again.
-**Ant only:** You cannot currently build to another directory when using Ant.
Questions?
----------
-If you have any questions, please feel free to ask them on the Developing jQuery Core
-forum, which can be found here:
-[http://forum.jquery.com/developing-jquery-core](http://forum.jquery.com/developing-jquery-core)
+If you have any questions, please feel free to ask on the
+[Developing jQuery Core forum](http://forum.jquery.com/developing-jquery-core) or in #jquery on irc.freenode.net.
View
162 Rakefile
@@ -1,162 +0,0 @@
-prefix = File.dirname( __FILE__ )
-
-# Directory variables
-src_dir = File.join( prefix, 'src' )
-build_dir = File.join( prefix, 'build' )
-test_dir = File.join( prefix, 'test' )
-
-# A different destination directory can be set by
-# setting DIST_DIR before calling rake
-dist_dir = ENV['DIST_DIR'] || File.join( prefix, 'dist' )
-
-base_files = %w{
- intro
- core
- support
- data
- queue
- attributes
- event
- selector
- traversing
- manipulation
- css
- ajax
- ajax/jsonp
- ajax/script
- ajax/xhr
- effects
- offset
- dimensions
- outro
-}.map { |js| File.join( src_dir, "#{js}.js" ) }
-
-# Sizzle, QUnit and jQuery files/dirs
-sizzle_dir = File.join( src_dir, "sizzle" )
-sizzle = File.join( sizzle_dir, "sizzle.js" )
-selector = File.join( src_dir, "selector.js" )
-
-qunit_dir = File.join( test_dir, "qunit" )
-qunit = File.join( qunit_dir, "qunit", "qunit.js" )
-
-jq = File.join( dist_dir, "jquery.js" )
-jq_min = File.join( dist_dir, "jquery.min.js" )
-
-# General Variables
-date = `git log -1`[/^Date:\s+(.+)$/, 1]
-version = File.read( File.join( prefix, 'version.txt' ) ).strip
-
-# Build tools
-rhino = "java -jar #{build_dir}/js.jar"
-minfier = "java -jar #{build_dir}/google-compiler-20100917.jar"
-
-# Turn off output other than needed from `sh` and file commands
-verbose(false)
-
-# Tasks
-task :default => "all"
-
-desc "Builds jQuery; Tests with JSLint; Minifies jQuery"
-task :all => [:jquery, :lint, :min] do
- puts "jQuery build complete."
-end
-
-desc "Builds jQuery: jquery.js (Default task)"
-task :jquery => [:selector, jq]
-
-desc "Builds a minified version of jQuery: jquery.min.js"
-task :min => jq_min
-
-
-task :init => [sizzle, qunit] do
- sizzle_git = File.join(sizzle_dir, '.git')
- qunit_git = File.join(qunit_dir, '.git')
-
- puts "Updating SizzleJS with latest..."
- sh "git --git-dir=#{sizzle_git} pull -q origin master"
-
- puts "Updating QUnit with latest..."
- sh "git --git-dir=#{qunit_git} pull -q origin master"
-end
-
-desc "Removes dist folder, selector.js, and Sizzle/QUnit"
-task :clean do
- puts "Removing Distribution directory: #{dist_dir}..."
- rm_rf dist_dir
-
- puts "Removing built copy of Sizzle..."
- rm_rf selector
-
- puts "Removing cloned directories..."
- rm_rf qunit_dir
- rm_rf sizzle_dir
-end
-
-desc "Rebuilds selector.js from SizzleJS"
-task :selector => [:init, selector]
-
-desc "Tests built jquery.js against JSLint"
-task :lint => jq do
- puts "Checking jQuery against JSLint..."
- sh "#{rhino} " + File.join(build_dir, 'jslint-check.js')
-end
-
-
-# File and Directory Dependencies
-directory dist_dir
-
-file jq => [dist_dir, base_files].flatten do
- puts "Building jquery.js..."
-
- File.open(jq, 'w') do |f|
- f.write cat(base_files).
- gsub(/@DATE/, date).
- gsub(/@VERSION/, version).
- gsub(/.function..jQuery...\{/, '').
- gsub(/\}...jQuery..;/, '')
- end
-end
-
-file jq_min => jq do
- puts "Building jquery.min.js..."
-
- sh "#{minfier} --js #{jq} --warning_level QUIET --js_output_file #{jq_min}"
-
- min = File.read( jq_min )
-
- # Equivilent of "head"
- File.open(jq_min, 'w') do |f|
- f.write File.readlines(jq)[0..14].join()
- f.write min
- end
-end
-
-file selector => [sizzle, :init] do
- puts "Building selector code from Sizzle..."
-
- File.open(selector, 'w') do |f|
- f.write File.read(sizzle).gsub(
- /^.+EXPOSE$\n/,
- '\0' + File.read( File.join( src_dir, 'sizzle-jquery.js' ))
- ).gsub(
- /^window.Sizzle.+$\n/, ''
- )
- end
-end
-
-file sizzle do
- puts "Retrieving SizzleJS from Github..."
- sh "git clone git://github.com/jeresig/sizzle.git #{sizzle_dir}"
-end
-
-file qunit do
- puts "Retrieving QUnit from Github..."
- sh "git clone git://github.com/jquery/qunit.git #{qunit_dir}"
-end
-
-
-def cat( files )
- files.map do |file|
- File.read(file)
- end.join('')
-end
View
132 build.xml
@@ -1,132 +0,0 @@
-<project name="jQuery" default="all" basedir=".">
-
- <loadfile property="version" srcfile="version.txt" />
- <property name="PREFIX" value="." />
- <property description="Folder for jquery and min target" name="dist" value="${PREFIX}/dist" />
-
- <property name="JQ" value="${dist}/jquery.js" />
- <property name="JQ_MIN" value="${dist}/jquery.min.js" />
-
- <loadfile property="sizzle-exports" srcfile="src/sizzle-jquery.js" />
-
- <available property="qunit" file="test/qunit" />
- <available property="sizzle" file="src/sizzle" />
-
- <target name="all" depends="jquery,lint,min" />
-
- <target name="qunit-clone" unless="qunit">
- <exec executable="git" outputproperty="git-qunit" >
- <arg line="clone git://github.com/jquery/qunit.git test/qunit" />
- </exec>
- <echo message="git clone qunit: ${git-qunit}" />
- </target>
- <target name="qunit-pull" if="qunit">
- <exec executable="git" outputproperty="git-qunit" dir="test/qunit" >
- <arg line="pull origin master" />
- </exec>
- <echo message="git pull sizzle: ${git-qunit}" />
- </target>
- <target name="sizzle-clone" unless="sizzle">
- <exec executable="git" outputproperty="git-sizzle" >
- <arg line="clone git://github.com/jeresig/sizzle.git src/sizzle" />
- </exec>
- <echo message="git clone sizzle: ${git-sizzle}" />
- </target>
- <target name="sizzle-pull" if="sizzle">
- <exec executable="git" outputproperty="git-sizzle" dir="src/sizzle" >
- <arg line="pull origin master" />
- </exec>
- <echo message="git pull sizzle: ${git-sizzle}" />
- </target>
-
- <target name="init" depends="qunit-clone,qunit-pull,sizzle-clone,sizzle-pull" />
-
- <target name="selector" depends="init" description="Builds the selector library for jQuery from Sizzle.">
- <copy file="src/sizzle/sizzle.js" tofile="src/selector.js" overwrite="true" />
- <replaceregexp match="// EXPOSE(.*)&#10;" replace="// EXPOSE\1&#10;${sizzle-exports}" file="src/selector.js" />
- <replaceregexp match="window.Sizzle(.*)&#10;" replace="" file="src/selector.js" />
- </target>
-
- <target name="jquery" depends="init,selector" description="Main jquery build, concatenates source files and replaces @VERSION">
- <echo message="Building ${JQ}" />
- <mkdir dir="${dist}" />
- <concat destfile="${JQ}">
- <fileset file="src/intro.js" />
- <fileset file="src/core.js" />
- <fileset file="src/support.js" />
- <fileset file="src/data.js" />
- <fileset file="src/queue.js" />
- <fileset file="src/attributes.js" />
- <fileset file="src/event.js" />
- <fileset file="src/selector.js" />
- <fileset file="src/traversing.js" />
- <fileset file="src/manipulation.js" />
- <fileset file="src/css.js" />
- <fileset file="src/ajax.js" />
- <fileset file="src/ajax/jsonp.js" />
- <fileset file="src/ajax/script.js" />
- <fileset file="src/ajax/xhr.js" />
- <fileset file="src/effects.js" />
- <fileset file="src/offset.js" />
- <fileset file="src/dimensions.js" />
- <fileset file="src/outro.js" />
- </concat>
- <replaceregexp match="@VERSION" replace="${version}" flags="g" byline="true" file="${JQ}" />
- <exec executable="git" outputproperty="date">
- <arg line="log -1 --pretty=format:%ad" />
- </exec>
- <replaceregexp match="(\(\s*function\s*\(\s*jQuery\s*\)\s*\{)|(\}\s*\)\s*\(\s*jQuery\s*\)\s*;)" flags="g" replace="" file="${JQ}" />
- <replaceregexp match="@DATE" replace="${date}" file="${JQ}" />
- <echo message="${JQ} built." />
- </target>
-
- <target name="lint" depends="jquery" description="Check jQuery against JSLint">
- <exec executable="java">
- <arg line="-jar build/js.jar build/jslint-check.js" />
- </exec>
- </target>
-
- <target name="min" depends="jquery" description="Remove all comments and whitespace, no compression, great in combination with GZip">
- <echo message="Building ${JQ_MIN}" />
- <apply executable="java" parallel="false" verbose="true" dest="${dist}">
- <fileset dir="${dist}">
- <include name="jquery.js" />
- </fileset>
- <arg line="-jar" />
- <arg path="build/google-compiler-20100917.jar" />
- <arg value="--warning_level" />
- <arg value="QUIET" />
- <arg value="--js_output_file" />
- <targetfile />
- <arg value="--js" />
- <mapper type="glob" from="jquery.js" to="tmpmin" />
- </apply>
- <concat destfile="${JQ_MIN}">
- <filelist files="${JQ}, ${dist}/tmpmin" />
- <filterchain>
- <headfilter lines="15" />
- </filterchain>
- </concat>
- <concat destfile="${JQ_MIN}" append="yes">
- <filelist files="${dist}/tmpmin" />
- </concat>
- <delete file="${dist}/tmpmin" />
- <echo message="${JQ_MIN} built." />
- </target>
-
- <target name="clean">
- <delete dir="${dist}" />
- <delete file="src/selector.js" />
- <delete dir="test/qunit" />
- <delete dir="src/sizzle" />
- </target>
-
- <target name="openAjaxMetadata">
- <property name="target" value="openAjaxMetadata-jquery-${version}.xml" />
- <delete file="${dist}/jquery-*.xml" />
- <get src="http://www.exfer.net/jquery/createjQueryXMLDocs.py?version=1.3" dest="${target}" />
- <xslt includes="${target}" excludes="build.xml" destdir="./dist" style="build/style.xsl" extension=".xml" />
- <delete file="${target}" />
- </target>
-
-</project>
View
BIN  build/google-compiler-20100917.jar
Binary file not shown
View
BIN  build/js.jar
Binary file not shown
View
10 build/jslint-check.js
@@ -1,6 +1,6 @@
-load("build/jslint.js");
-
-var src = readFile("dist/jquery.js");
+var JSLINT = require("./lib/jslint").JSLINT,
+ print = require("sys").print,
+ src = require("fs").readFileSync("dist/jquery.js", "utf8");
JSLINT(src, { evil: true, forin: true, maxerr: 100 });
@@ -29,8 +29,8 @@ for ( var i = 0; i < e.length; i++ ) {
}
if ( found > 0 ) {
- print( "\n" + found + " Error(s) found." );
+ print( "\n" + found + " Error(s) found.\n" );
} else {
- print( "JSLint check passed." );
+ print( "JSLint check passed.\n" );
}
View
6 build/jslint.js → build/lib/jslint.js
@@ -5495,6 +5495,10 @@ loop: for (;;) {
itself.edition = '2010-02-20';
+ if (typeof exports !== "undefined") {
+ exports.JSLINT = itself;
+ }
+
return itself;
-}());
+}());
View
1,239 build/lib/parse-js.js
@@ -0,0 +1,1239 @@
+/***********************************************************************
+
+ A JavaScript tokenizer / parser / beautifier / compressor.
+
+ This version is suitable for Node.js. With minimal changes (the
+ exports stuff) it should work on any JS platform.
+
+ This file contains the tokenizer/parser. It is a port to JavaScript
+ of parse-js [1], a JavaScript parser library written in Common Lisp
+ by Marijn Haverbeke. Thank you Marijn!
+
+ [1] http://marijn.haverbeke.nl/parse-js/
+
+ Exported functions:
+
+ - tokenizer(code) -- returns a function. Call the returned
+ function to fetch the next token.
+
+ - parse(code) -- returns an AST of the given JavaScript code.
+
+ -------------------------------- (C) ---------------------------------
+
+ Author: Mihai Bazon
+ <mihai.bazon@gmail.com>
+ http://mihai.bazon.net/blog
+
+ Distributed under the BSD license:
+
+ Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+ Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the following
+ disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+ ***********************************************************************/
+
+/* -----[ Tokenizer (constants) ]----- */
+
+var KEYWORDS = array_to_hash([
+ "break",
+ "case",
+ "catch",
+ "const",
+ "continue",
+ "default",
+ "delete",
+ "do",
+ "else",
+ "finally",
+ "for",
+ "function",
+ "if",
+ "in",
+ "instanceof",
+ "new",
+ "return",
+ "switch",
+ "throw",
+ "try",
+ "typeof",
+ "var",
+ "void",
+ "while",
+ "with"
+]);
+
+var RESERVED_WORDS = array_to_hash([
+ "abstract",
+ "boolean",
+ "byte",
+ "char",
+ "class",
+ "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"
+]);
+
+var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([
+ "return",
+ "new",
+ "delete",
+ "throw",
+ "else",
+ "case"
+]);
+
+var KEYWORDS_ATOM = array_to_hash([
+ "false",
+ "null",
+ "true",
+ "undefined"
+]);
+
+var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^"));
+
+var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
+var RE_OCT_NUMBER = /^0[0-7]+$/;
+var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
+
+var OPERATORS = array_to_hash([
+ "in",
+ "instanceof",
+ "typeof",
+ "new",
+ "void",
+ "delete",
+ "++",
+ "--",
+ "+",
+ "-",
+ "!",
+ "~",
+ "&",
+ "|",
+ "^",
+ "*",
+ "/",
+ "%",
+ ">>",
+ "<<",
+ ">>>",
+ "<",
+ ">",
+ "<=",
+ ">=",
+ "==",
+ "===",
+ "!=",
+ "!==",
+ "?",
+ "=",
+ "+=",
+ "-=",
+ "/=",
+ "*=",
+ "%=",
+ ">>=",
+ "<<=",
+ ">>>=",
+ "~=",
+ "%=",
+ "|=",
+ "^=",
+ "&=",
+ "&&",
+ "||"
+]);
+
+var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t"));
+
+var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
+
+var PUNC_CHARS = array_to_hash(characters("[]{}(),;:"));
+
+var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy"));
+
+/* -----[ Tokenizer ]----- */
+
+function is_alphanumeric_char(ch) {
+ ch = ch.charCodeAt(0);
+ return (ch >= 48 && ch <= 57) ||
+ (ch >= 65 && ch <= 90) ||
+ (ch >= 97 && ch <= 122);
+};
+
+function is_identifier_char(ch) {
+ return is_alphanumeric_char(ch) || ch == "$" || ch == "_";
+};
+
+function is_digit(ch) {
+ ch = ch.charCodeAt(0);
+ return ch >= 48 && ch <= 57;
+};
+
+function parse_js_number(num) {
+ if (RE_HEX_NUMBER.test(num)) {
+ return parseInt(num.substr(2), 16);
+ } else if (RE_OCT_NUMBER.test(num)) {
+ return parseInt(num.substr(1), 8);
+ } else if (RE_DEC_NUMBER.test(num)) {
+ return parseFloat(num);
+ }
+};
+
+function JS_Parse_Error(message, line, col, pos) {
+ this.message = message;
+ this.line = line;
+ this.col = col;
+ this.pos = pos;
+ try {
+ ({})();
+ } catch(ex) {
+ this.stack = ex.stack;
+ };
+};
+
+JS_Parse_Error.prototype.toString = function() {
+ return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack;
+};
+
+function js_error(message, line, col, pos) {
+ throw new JS_Parse_Error(message, line, col, pos);
+};
+
+function is_token(token, type, val) {
+ return token.type == type && (val == null || token.value == val);
+};
+
+var EX_EOF = {};
+
+function tokenizer($TEXT, skip_comments) {
+
+ var S = {
+ text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''),
+ pos : 0,
+ tokpos : 0,
+ line : 0,
+ tokline : 0,
+ col : 0,
+ tokcol : 0,
+ newline_before : false,
+ regex_allowed : false
+ };
+
+ function peek() { return S.text.charAt(S.pos); };
+
+ function next(signal_eof) {
+ var ch = S.text.charAt(S.pos++);
+ if (signal_eof && !ch)
+ throw EX_EOF;
+ if (ch == "\n") {
+ S.newline_before = true;
+ ++S.line;
+ S.col = 0;
+ } else {
+ ++S.col;
+ }
+ return ch;
+ };
+
+ function eof() {
+ return !S.peek();
+ };
+
+ function find(what, signal_eof) {
+ var pos = S.text.indexOf(what, S.pos);
+ if (signal_eof && pos == -1) throw EX_EOF;
+ return pos;
+ };
+
+ function start_token() {
+ S.tokline = S.line;
+ S.tokcol = S.col;
+ S.tokpos = S.pos;
+ };
+
+ function token(type, value) {
+ S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) ||
+ (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) ||
+ (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value)));
+ var ret = {
+ type : type,
+ value : value,
+ line : S.tokline,
+ col : S.tokcol,
+ pos : S.tokpos,
+ nlb : S.newline_before
+ };
+ S.newline_before = false;
+ return ret;
+ };
+
+ function skip_whitespace() {
+ while (HOP(WHITESPACE_CHARS, peek()))
+ next();
+ };
+
+ function read_while(pred) {
+ var ret = "", ch = peek(), i = 0;
+ while (ch && pred(ch, i++)) {
+ ret += next();
+ ch = peek();
+ }
+ return ret;
+ };
+
+ function parse_error(err) {
+ js_error(err, S.tokline, S.tokcol, S.tokpos);
+ };
+
+ function read_num(prefix) {
+ var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
+ var num = read_while(function(ch, i){
+ if (ch == "x" || ch == "X") {
+ if (has_x) return false;
+ return has_x = true;
+ }
+ if (!has_x && (ch == "E" || ch == "e")) {
+ if (has_e) return false;
+ return has_e = after_e = true;
+ }
+ if (ch == "-") {
+ if (after_e || (i == 0 && !prefix)) return true;
+ return false;
+ }
+ if (ch == "+") return after_e;
+ after_e = false;
+ if (ch == ".") {
+ if (!has_dot)
+ return has_dot = true;
+ return false;
+ }
+ return is_alphanumeric_char(ch);
+ });
+ if (prefix)
+ num = prefix + num;
+ var valid = parse_js_number(num);
+ if (!isNaN(valid)) {
+ return token("num", valid);
+ } else {
+ parse_error("Invalid syntax: " + num);
+ }
+ };
+
+ function read_escaped_char() {
+ var ch = next(true);
+ switch (ch) {
+ case "n" : return "\n";
+ case "r" : return "\r";
+ case "t" : return "\t";
+ case "b" : return "\b";
+ case "v" : return "\v";
+ case "f" : return "\f";
+ case "0" : return "\0";
+ case "x" : return String.fromCharCode(hex_bytes(2));
+ case "u" : return String.fromCharCode(hex_bytes(4));
+ default : return ch;
+ }
+ };
+
+ function hex_bytes(n) {
+ var num = 0;
+ for (; n > 0; --n) {
+ var digit = parseInt(next(true), 16);
+ if (isNaN(digit))
+ parse_error("Invalid hex-character pattern in string");
+ num = (num << 4) | digit;
+ }
+ return num;
+ };
+
+ function read_string() {
+ return with_eof_error("Unterminated string constant", function(){
+ var quote = next(), ret = "";
+ for (;;) {
+ var ch = next(true);
+ if (ch == "\\") ch = read_escaped_char();
+ else if (ch == quote) break;
+ ret += ch;
+ }
+ return token("string", ret);
+ });
+ };
+
+ function read_line_comment() {
+ next();
+ var i = find("\n"), ret;
+ if (i == -1) {
+ ret = S.text.substr(S.pos);
+ S.pos = S.text.length;
+ } else {
+ ret = S.text.substring(S.pos, i);
+ S.pos = i;
+ }
+ return token("comment1", ret);
+ };
+
+ function read_multiline_comment() {
+ next();
+ return with_eof_error("Unterminated multiline comment", function(){
+ var i = find("*/", true),
+ text = S.text.substring(S.pos, i),
+ tok = token("comment2", text);
+ S.pos = i + 2;
+ S.line += text.split("\n").length - 1;
+ S.newline_before = text.indexOf("\n") >= 0;
+ return tok;
+ });
+ };
+
+ function read_regexp() {
+ return with_eof_error("Unterminated regular expression", function(){
+ var prev_backslash = false, regexp = "", ch, in_class = false;
+ while ((ch = next(true))) if (prev_backslash) {
+ regexp += "\\" + ch;
+ prev_backslash = false;
+ } else if (ch == "[") {
+ in_class = true;
+ regexp += ch;
+ } else if (ch == "]" && in_class) {
+ in_class = false;
+ regexp += ch;
+ } else if (ch == "/" && !in_class) {
+ break;
+ } else if (ch == "\\") {
+ prev_backslash = true;
+ } else {
+ regexp += ch;
+ }
+ var mods = read_while(function(ch){
+ return HOP(REGEXP_MODIFIERS, ch);
+ });
+ return token("regexp", [ regexp, mods ]);
+ });
+ };
+
+ function read_operator(prefix) {
+ function grow(op) {
+ var bigger = op + peek();
+ if (HOP(OPERATORS, bigger)) {
+ next();
+ return grow(bigger);
+ } else {
+ return op;
+ }
+ };
+ return token("operator", grow(prefix || next()));
+ };
+
+ var handle_slash = skip_comments ? function() {
+ next();
+ var regex_allowed = S.regex_allowed;
+ switch (peek()) {
+ case "/": read_line_comment(); S.regex_allowed = regex_allowed; return next_token();
+ case "*": read_multiline_comment(); S.regex_allowed = regex_allowed; return next_token();
+ }
+ return S.regex_allowed ? read_regexp() : read_operator("/");
+ } : function() {
+ next();
+ switch (peek()) {
+ case "/": return read_line_comment();
+ case "*": return read_multiline_comment();
+ }
+ return S.regex_allowed ? read_regexp() : read_operator("/");
+ };
+
+ function handle_dot() {
+ next();
+ return is_digit(peek())
+ ? read_num(".")
+ : token("punc", ".");
+ };
+
+ function read_word() {
+ var word = read_while(is_identifier_char);
+ return !HOP(KEYWORDS, word)
+ ? token("name", word)
+ : HOP(OPERATORS, word)
+ ? token("operator", word)
+ : HOP(KEYWORDS_ATOM, word)
+ ? token("atom", word)
+ : token("keyword", word);
+ };
+
+ function with_eof_error(eof_error, cont) {
+ try {
+ return cont();
+ } catch(ex) {
+ if (ex === EX_EOF) parse_error(eof_error);
+ else throw ex;
+ }
+ };
+
+ function next_token(force_regexp) {
+ if (force_regexp)
+ return read_regexp();
+ skip_whitespace();
+ start_token();
+ var ch = peek();
+ if (!ch) return token("eof");
+ if (is_digit(ch)) return read_num();
+ if (ch == '"' || ch == "'") return read_string();
+ if (HOP(PUNC_CHARS, ch)) return token("punc", next());
+ if (ch == ".") return handle_dot();
+ if (ch == "/") return handle_slash();
+ if (HOP(OPERATOR_CHARS, ch)) return read_operator();
+ if (is_identifier_char(ch)) return read_word();
+ parse_error("Unexpected character '" + ch + "'");
+ };
+
+ next_token.context = function(nc) {
+ if (nc) S = nc;
+ return S;
+ };
+
+ return next_token;
+
+};
+
+/* -----[ Parser (constants) ]----- */
+
+var UNARY_PREFIX = array_to_hash([
+ "typeof",
+ "void",
+ "delete",
+ "--",
+ "++",
+ "!",
+ "~",
+ "-",
+ "+"
+]);
+
+var UNARY_POSTFIX = array_to_hash([ "--", "++" ]);
+
+var ASSIGNMENT = (function(a, ret, i){
+ while (i < a.length) {
+ ret[a[i]] = a[i].substr(0, a[i].length - 1);
+ i++;
+ }
+ return ret;
+})(
+ ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "~=", "%=", "|=", "^=", "&="],
+ { "=": true },
+ 0
+);
+
+var PRECEDENCE = (function(a, ret){
+ for (var i = 0, n = 1; i < a.length; ++i, ++n) {
+ var b = a[i];
+ for (var j = 0; j < b.length; ++j) {
+ ret[b[j]] = n;
+ }
+ }
+ return ret;
+})(
+ [
+ ["||"],
+ ["&&"],
+ ["|"],
+ ["^"],
+ ["&"],
+ ["==", "===", "!=", "!=="],
+ ["<", ">", "<=", ">=", "in", "instanceof"],
+ [">>", "<<", ">>>"],
+ ["+", "-"],
+ ["*", "/", "%"]
+ ],
+ {}
+);
+
+var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]);
+
+var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
+
+/* -----[ Parser ]----- */
+
+function NodeWithToken(str, start, end) {
+ this.name = str;
+ this.start = start;
+ this.end = end;
+};
+
+NodeWithToken.prototype.toString = function() { return this.name; };
+
+function parse($TEXT, strict_mode, embed_tokens) {
+
+ var S = {
+ input: tokenizer($TEXT, true),
+ token: null,
+ prev: null,
+ peeked: null,
+ in_function: 0,
+ in_loop: 0,
+ labels: []
+ };
+
+ S.token = next();
+
+ function is(type, value) {
+ return is_token(S.token, type, value);
+ };
+
+ function peek() { return S.peeked || (S.peeked = S.input()); };
+
+ function next() {
+ S.prev = S.token;
+ if (S.peeked) {
+ S.token = S.peeked;
+ S.peeked = null;
+ } else {
+ S.token = S.input();
+ }
+ return S.token;
+ };
+
+ function prev() {
+ return S.prev;
+ };
+
+ function croak(msg, line, col, pos) {
+ var ctx = S.input.context();
+ js_error(msg,
+ line != null ? line : ctx.tokline,
+ col != null ? col : ctx.tokcol,
+ pos != null ? pos : ctx.tokpos);
+ };
+
+ function token_error(token, msg) {
+ croak(msg, token.line, token.col);
+ };
+
+ function unexpected(token) {
+ if (token == null)
+ token = S.token;
+ token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
+ };
+
+ function expect_token(type, val) {
+ if (is(type, val)) {
+ return next();
+ }
+ token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type);
+ };
+
+ function expect(punc) { return expect_token("punc", punc); };
+
+ function can_insert_semicolon() {
+ return !strict_mode && (
+ S.token.nlb || is("eof") || is("punc", "}")
+ );
+ };
+
+ function semicolon() {
+ if (is("punc", ";")) next();
+ else if (!can_insert_semicolon()) unexpected();
+ };
+
+ function as() {
+ return slice(arguments);
+ };
+
+ function parenthesised() {
+ expect("(");
+ var ex = expression();
+ expect(")");
+ return ex;
+ };
+
+ function add_tokens(str, start, end) {
+ return new NodeWithToken(str, start, end);
+ };
+
+ var statement = embed_tokens ? function() {
+ var start = S.token;
+ var stmt = $statement();
+ stmt[0] = add_tokens(stmt[0], start, prev());
+ return stmt;
+ } : $statement;
+
+ function $statement() {
+ if (is("operator", "/")) {
+ S.peeked = null;
+ S.token = S.input(true); // force regexp
+ }
+ switch (S.token.type) {
+ case "num":
+ case "string":
+ case "regexp":
+ case "operator":
+ case "atom":
+ return simple_statement();
+
+ case "name":
+ return is_token(peek(), "punc", ":")
+ ? labeled_statement(prog1(S.token.value, next, next))
+ : simple_statement();
+
+ case "punc":
+ switch (S.token.value) {
+ case "{":
+ return as("block", block_());
+ case "[":
+ case "(":
+ return simple_statement();
+ case ";":
+ next();
+ return as("block");
+ default:
+ unexpected();
+ }
+
+ case "keyword":
+ switch (prog1(S.token.value, next)) {
+ case "break":
+ return break_cont("break");
+
+ case "continue":
+ return break_cont("continue");
+
+ case "debugger":
+ semicolon();
+ return as("debugger");
+
+ case "do":
+ return (function(body){
+ expect_token("keyword", "while");
+ return as("do", prog1(parenthesised, semicolon), body);
+ })(in_loop(statement));
+
+ case "for":
+ return for_();
+
+ case "function":
+ return function_(true);
+
+ case "if":
+ return if_();
+
+ case "return":
+ if (S.in_function == 0)
+ croak("'return' outside of function");
+ return as("return",
+ is("punc", ";")
+ ? (next(), null)
+ : can_insert_semicolon()
+ ? null
+ : prog1(expression, semicolon));
+
+ case "switch":
+ return as("switch", parenthesised(), switch_block_());
+
+ case "throw":
+ return as("throw", prog1(expression, semicolon));
+
+ case "try":
+ return try_();
+
+ case "var":
+ return prog1(var_, semicolon);
+
+ case "const":
+ return prog1(const_, semicolon);
+
+ case "while":
+ return as("while", parenthesised(), in_loop(statement));
+
+ case "with":
+ return as("with", parenthesised(), statement());
+
+ default:
+ unexpected();
+ }
+ }
+ };
+
+ function labeled_statement(label) {
+ S.labels.push(label);
+ var start = S.token, stat = statement();
+ if (strict_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0]))
+ unexpected(start);
+ S.labels.pop();
+ return as("label", label, stat);
+ };
+
+ function simple_statement() {
+ return as("stat", prog1(expression, semicolon));
+ };
+
+ function break_cont(type) {
+ var name = is("name") ? S.token.value : null;
+ if (name != null) {
+ next();
+ if (!member(name, S.labels))
+ croak("Label " + name + " without matching loop or statement");
+ }
+ else if (S.in_loop == 0)
+ croak(type + " not inside a loop or switch");
+ semicolon();
+ return as(type, name);
+ };
+
+ function for_() {
+ expect("(");
+ var has_var = is("keyword", "var");
+ if (has_var)
+ next();
+ if (is("name") && is_token(peek(), "operator", "in")) {
+ // for (i in foo)
+ var name = S.token.value;
+ next(); next();
+ var obj = expression();
+ expect(")");
+ return as("for-in", has_var, name, obj, in_loop(statement));
+ } else {
+ // classic for
+ var init = is("punc", ";") ? null : has_var ? var_() : expression();
+ expect(";");
+ var test = is("punc", ";") ? null : expression();
+ expect(";");
+ var step = is("punc", ")") ? null : expression();
+ expect(")");
+ return as("for", init, test, step, in_loop(statement));
+ }
+ };
+
+ function function_(in_statement) {
+ var name = is("name") ? prog1(S.token.value, next) : null;
+ if (in_statement && !name)
+ unexpected();
+ expect("(");
+ return as(in_statement ? "defun" : "function",
+ name,
+ // arguments
+ (function(first, a){
+ while (!is("punc", ")")) {
+ if (first) first = false; else expect(",");
+ if (!is("name")) unexpected();
+ a.push(S.token.value);
+ next();
+ }
+ next();
+ return a;
+ })(true, []),
+ // body
+ (function(){
+ ++S.in_function;
+ var loop = S.in_loop;
+ S.in_loop = 0;
+ var a = block_();
+ --S.in_function;
+ S.in_loop = loop;
+ return a;
+ })());
+ };
+
+ function if_() {
+ var cond = parenthesised(), body = statement(), belse;
+ if (is("keyword", "else")) {
+ next();
+ belse = statement();
+ }
+ return as("if", cond, body, belse);
+ };
+
+ function block_() {
+ expect("{");
+ var a = [];
+ while (!is("punc", "}")) {
+ if (is("eof")) unexpected();
+ a.push(statement());
+ }
+ next();
+ return a;
+ };
+
+ var switch_block_ = curry(in_loop, function(){
+ expect("{");
+ var a = [], cur = null;
+ while (!is("punc", "}")) {
+ if (is("eof")) unexpected();
+ if (is("keyword", "case")) {
+ next();
+ cur = [];
+ a.push([ expression(), cur ]);
+ expect(":");
+ }
+ else if (is("keyword", "default")) {
+ next();
+ expect(":");
+ cur = [];
+ a.push([ null, cur ]);
+ }
+ else {
+ if (!cur) unexpected();
+ cur.push(statement());
+ }
+ }
+ next();
+ return a;
+ });
+
+ function try_() {
+ var body = block_(), bcatch, bfinally;
+ if (is("keyword", "catch")) {
+ next();
+ expect("(");
+ if (!is("name"))
+ croak("Name expected");
+ var name = S.token.value;
+ next();
+ expect(")");
+ bcatch = [ name, block_() ];
+ }
+ if (is("keyword", "finally")) {
+ next();
+ bfinally = block_();
+ }
+ if (!bcatch && !bfinally)
+ croak("Missing catch/finally blocks");
+ return as("try", body, bcatch, bfinally);
+ };
+
+ function vardefs() {
+ var a = [];
+ for (;;) {
+ if (!is("name"))
+ unexpected();
+ var name = S.token.value;
+ next();
+ if (is("operator", "=")) {
+ next();
+ a.push([ name, expression(false) ]);
+ } else {
+ a.push([ name ]);
+ }
+ if (!is("punc", ","))
+ break;
+ next();
+ }
+ return a;
+ };
+
+ function var_() {
+ return as("var", vardefs());
+ };
+
+ function const_() {
+ return as("const", vardefs());
+ };
+
+ function new_() {
+ var newexp = expr_atom(false), args;
+ if (is("punc", "(")) {
+ next();
+ args = expr_list(")");
+ } else {
+ args = [];
+ }
+ return subscripts(as("new", newexp, args), true);
+ };
+
+ function expr_atom(allow_calls) {
+ if (is("operator", "new")) {
+ next();
+ return new_();
+ }
+ if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
+ return make_unary("unary-prefix",
+ prog1(S.token.value, next),
+ expr_atom(allow_calls));
+ }
+ if (is("punc")) {
+ switch (S.token.value) {
+ case "(":
+ next();
+ return subscripts(prog1(expression, curry(expect, ")")), allow_calls);
+ case "[":
+ next();
+ return subscripts(array_(), allow_calls);
+ case "{":
+ next();
+ return subscripts(object_(), allow_calls);
+ }
+ unexpected();
+ }
+ if (is("keyword", "function")) {
+ next();
+ return subscripts(function_(false), allow_calls);
+ }
+ if (HOP(ATOMIC_START_TOKEN, S.token.type)) {
+ var atom = S.token.type == "regexp"
+ ? as("regexp", S.token.value[0], S.token.value[1])
+ : as(S.token.type, S.token.value);
+ return subscripts(prog1(atom, next), allow_calls);
+ }
+ unexpected();
+ };
+
+ function expr_list(closing, allow_trailing_comma) {
+ var first = true, a = [];
+ while (!is("punc", closing)) {
+ if (first) first = false; else expect(",");
+ if (allow_trailing_comma && is("punc", closing))
+ break;
+ a.push(expression(false));
+ }
+ next();
+ return a;
+ };
+
+ function array_() {
+ return as("array", expr_list("]", !strict_mode));
+ };
+
+ function object_() {
+ var first = true, a = [];
+ while (!is("punc", "}")) {
+ if (first) first = false; else expect(",");
+ if (!strict_mode && is("punc", "}"))
+ // allow trailing comma
+ break;
+ var type = S.token.type;
+ var name = as_property_name();
+ if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) {
+ a.push([ as_name(), function_(false), name ]);
+ } else {
+ expect(":");
+ a.push([ name, expression(false) ]);
+ }
+ }
+ next();
+ return as("object", a);
+ };
+
+ function as_property_name() {
+ switch (S.token.type) {
+ case "num":
+ case "string":
+ return prog1(S.token.value, next);
+ }
+ return as_name();
+ };
+
+ function as_name() {
+ switch (S.token.type) {
+ case "name":
+ case "operator":
+ case "keyword":
+ case "atom":
+ return prog1(S.token.value, next);
+ default:
+ unexpected();
+ }
+ };
+
+ function subscripts(expr, allow_calls) {
+ if (is("punc", ".")) {
+ next();
+ return subscripts(as("dot", expr, as_name()), allow_calls);
+ }
+ if (is("punc", "[")) {
+ next();
+ return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls);
+ }
+ if (allow_calls && is("punc", "(")) {
+ next();
+ return subscripts(as("call", expr, expr_list(")")), true);
+ }
+ if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) {
+ return prog1(curry(make_unary, "unary-postfix", S.token.value, expr),
+ next);
+ }
+ return expr;
+ };
+
+ function make_unary(tag, op, expr) {
+ if ((op == "++" || op == "--") && !is_assignable(expr))
+ croak("Invalid use of " + op + " operator");
+ return as(tag, op, expr);
+ };
+
+ function expr_op(left, min_prec) {
+ var op = is("operator") ? S.token.value : null;
+ var prec = op != null ? PRECEDENCE[op] : null;
+ if (prec != null && prec > min_prec) {
+ next();
+ var right = expr_op(expr_atom(true), prec);
+ return expr_op(as("binary", op, left, right), min_prec);
+ }
+ return left;
+ };
+
+ function expr_ops() {
+ return expr_op(expr_atom(true), 0);
+ };
+
+ function maybe_conditional() {
+ var expr = expr_ops();
+ if (is("operator", "?")) {
+ next();
+ var yes = expression(false);
+ expect(":");
+ return as("conditional", expr, yes, expression(false));
+ }
+ return expr;
+ };
+
+ function is_assignable(expr) {
+ switch (expr[0]) {
+ case "dot":
+ case "sub":
+ return true;
+ case "name":
+ return expr[1] != "this";
+ }
+ };
+
+ function maybe_assign() {
+ var left = maybe_conditional(), val = S.token.value;
+ if (is("operator") && HOP(ASSIGNMENT, val)) {
+ if (is_assignable(left)) {
+ next();
+ return as("assign", ASSIGNMENT[val], left, maybe_assign());
+ }
+ croak("Invalid assignment");
+ }
+ return left;
+ };
+
+ function expression(commas) {
+ if (arguments.length == 0)
+ commas = true;
+ var expr = maybe_assign();
+ if (commas && is("punc", ",")) {
+ next();
+ return as("seq", expr, expression());
+ }
+ return expr;
+ };
+
+ function in_loop(cont) {
+ try {
+ ++S.in_loop;
+ return cont();
+ } finally {
+ --S.in_loop;
+ }
+ };
+
+ return as("toplevel", (function(a){
+ while (!is("eof"))
+ a.push(statement());
+ return a;
+ })([]));
+
+};
+
+/* -----[ Utilities ]----- */
+
+function curry(f) {
+ var args = slice(arguments, 1);
+ return function() { return f.apply(this, args.concat(slice(arguments))); };
+};
+
+function prog1(ret) {
+ if (ret instanceof Function)
+ ret = ret();
+ for (var i = 1, n = arguments.length; --n > 0; ++i)
+ arguments[i]();
+ return ret;
+};
+
+function array_to_hash(a) {
+ var ret = {};
+ for (var i = 0; i < a.length; ++i)
+ ret[a[i]] = true;
+ return ret;
+};
+
+function slice(a, start) {
+ return Array.prototype.slice.call(a, start == null ? 0 : start);
+};
+
+function characters(str) {
+ return str.split("");
+};
+
+function member(name, array) {
+ for (var i = array.length; --i >= 0;)
+ if (array[i] === name)
+ return true;
+ return false;
+};
+
+function HOP(obj, prop) {
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+/* -----[ Exports ]----- */
+
+exports.tokenizer = tokenizer;
+exports.parse = parse;
+exports.slice = slice;
+exports.curry = curry;
+exports.member = member;
+exports.array_to_hash = array_to_hash;
+exports.PRECEDENCE = PRECEDENCE;
+exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
+exports.RESERVED_WORDS = RESERVED_WORDS;
+exports.KEYWORDS = KEYWORDS;
+exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
+exports.OPERATORS = OPERATORS;
+exports.is_alphanumeric_char = is_alphanumeric_char;
View
1,562 build/lib/process.js
@@ -0,0 +1,1562 @@
+/***********************************************************************
+
+ A JavaScript tokenizer / parser / beautifier / compressor.
+
+ This version is suitable for Node.js. With minimal changes (the
+ exports stuff) it should work on any JS platform.
+
+ This file implements some AST processors. They work on data built
+ by parse-js.
+
+ Exported functions:
+
+ - ast_mangle(ast, include_toplevel) -- mangles the
+ variable/function names in the AST. Returns an AST. Pass true
+ as second argument to mangle toplevel names too.
+
+ - ast_squeeze(ast) -- employs various optimizations to make the
+ final generated code even smaller. Returns an AST.
+
+ - gen_code(ast, beautify) -- generates JS code from the AST. Pass
+ true (or an object, see the code for some options) as second
+ argument to get "pretty" (indented) code.
+
+ -------------------------------- (C) ---------------------------------
+
+ Author: Mihai Bazon
+ <mihai.bazon@gmail.com>
+ http://mihai.bazon.net/blog
+
+ Distributed under the BSD license:
+
+ Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the following
+ disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+ ***********************************************************************/
+
+var jsp = require("./parse-js"),
+ slice = jsp.slice,
+ member = jsp.member,
+ PRECEDENCE = jsp.PRECEDENCE,
+ OPERATORS = jsp.OPERATORS;
+
+/* -----[ helper for AST traversal ]----- */
+
+function ast_walker(ast) {
+ function _vardefs(defs) {
+ return MAP(defs, function(def){
+ var a = [ def[0] ];
+ if (def.length > 1)
+ a[1] = walk(def[1]);
+ return a;
+ });
+ };
+ var walkers = {
+ "string": function(str) {
+ return [ "string", str ];
+ },
+ "num": function(num) {
+ return [ "num", num ];
+ },
+ "name": function(name) {
+ return [ "name", name ];
+ },
+ "toplevel": function(statements) {
+ return [ "toplevel", MAP(statements, walk) ];
+ },
+ "block": function(statements) {
+ var out = [ "block" ];
+ if (statements != null)
+ out.push(MAP(statements, walk));
+ return out;
+ },
+ "var": function(defs) {
+ return [ "var", _vardefs(defs) ];
+ },
+ "const": function(defs) {
+ return [ "const", _vardefs(defs) ];
+ },
+ "try": function(t, c, f) {
+ return [
+ "try",
+ MAP(t, walk),
+ c != null ? [ c[0], MAP(c[1], walk) ] : null,
+ f != null ? MAP(f, walk) : null
+ ];
+ },
+ "throw": function(expr) {
+ return [ "throw", walk(expr) ];
+ },
+ "new": function(ctor, args) {
+ return [ "new", walk(ctor), MAP(args, walk) ];
+ },
+ "switch": function(expr, body) {
+ return [ "switch", walk(expr), MAP(body, function(branch){
+ return [ branch[0] ? walk(branch[0]) : null,
+ MAP(branch[1], walk) ];
+ }) ];
+ },
+ "break": function(label) {
+ return [ "break", label ];
+ },
+ "continue": function(label) {
+ return [ "continue", label ];
+ },
+ "conditional": function(cond, t, e) {
+ return [ "conditional", walk(cond), walk(t), walk(e) ];
+ },
+ "assign": function(op, lvalue, rvalue) {
+ return [ "assign", op, walk(lvalue), walk(rvalue) ];
+ },
+ "dot": function(expr) {
+ return [ "dot", walk(expr) ].concat(slice(arguments, 1));
+ },
+ "call": function(expr, args) {
+ return [ "call", walk(expr), MAP(args, walk) ];
+ },
+ "function": function(name, args, body) {
+ return [ "function", name, args.slice(), MAP(body, walk) ];
+ },
+ "defun": function(name, args, body) {
+ return [ "defun", name, args.slice(), MAP(body, walk) ];
+ },
+ "if": function(conditional, t, e) {
+ return [ "if", walk(conditional), walk(t), walk(e) ];
+ },
+ "for": function(init, cond, step, block) {
+ return [ "for", walk(init), walk(cond), walk(step), walk(block) ];
+ },
+ "for-in": function(has_var, key, hash, block) {
+ return [ "for-in", has_var, key, walk(hash), walk(block) ];
+ },
+ "while": function(cond, block) {
+ return [ "while", walk(cond), walk(block) ];
+ },
+ "do": function(cond, block) {
+ return [ "do", walk(cond), walk(block) ];
+ },
+ "return": function(expr) {
+ return [ "return", walk(expr) ];
+ },
+ "binary": function(op, left, right) {
+ return [ "binary", op, walk(left), walk(right) ];
+ },
+ "unary-prefix": function(op, expr) {
+ return [ "unary-prefix", op, walk(expr) ];
+ },
+ "unary-postfix": function(op, expr) {
+ return [ "unary-postfix", op, walk(expr) ];
+ },
+ "sub": function(expr, subscript) {
+ return [ "sub", walk(expr), walk(subscript) ];
+ },
+ "object": function(props) {
+ return [ "object", MAP(props, function(p){
+ return p.length == 2
+ ? [ p[0], walk(p[1]) ]
+ : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
+ }) ];
+ },
+ "regexp": function(rx, mods) {
+ return [ "regexp", rx, mods ];
+ },
+ "array": function(elements) {
+ return [ "array", MAP(elements, walk) ];
+ },
+ "stat": function(stat) {
+ return [ "stat", walk(stat) ];
+ },
+ "seq": function() {
+ return [ "seq" ].concat(MAP(slice(arguments), walk));
+ },
+ "label": function(name, block) {
+ return [ "label", name, walk(block) ];
+ },
+ "with": function(expr, block) {
+ return [ "with", walk(expr), walk(block) ];
+ },
+ "atom": function(name) {
+ return [ "atom", name ];
+ }
+ };
+
+ var user = {};
+ var stack = [];
+ function walk(ast) {
+ if (ast == null)
+ return null;
+ try {
+ stack.push(ast);
+ var type = ast[0];
+ var gen = user[type];
+ if (gen) {
+ var ret = gen.apply(ast, ast.slice(1));
+ if (ret != null)
+ return ret;
+ }
+ gen = walkers[type];
+ return gen.apply(ast, ast.slice(1));
+ } finally {
+ stack.pop();
+ }
+ };
+
+ function with_walkers(walkers, cont){
+ var save = {}, i;
+ for (i in walkers) if (HOP(walkers, i)) {
+ save[i] = user[i];
+ user[i] = walkers[i];
+ }
+ var ret = cont();
+ for (i in save) if (HOP(save, i)) {
+ if (!save[i]) delete user[i];
+ else user[i] = save[i];
+ }
+ return ret;
+ };
+
+ return {
+ walk: walk,
+ with_walkers: with_walkers,
+ parent: function() {
+ return stack[stack.length - 2]; // last one is current node
+ },
+ stack: function() {
+ return stack;
+ }
+ };
+};
+
+/* -----[ Scope and mangling ]----- */
+
+function Scope(parent) {
+ this.names = {}; // names defined in this scope
+ this.mangled = {}; // mangled names (orig.name => mangled)
+ this.rev_mangled = {}; // reverse lookup (mangled => orig.name)
+ this.cname = -1; // current mangled name
+ this.refs = {}; // names referenced from this scope
+ this.uses_with = false; // will become TRUE if eval() is detected in this or any subscopes
+ this.uses_eval = false; // will become TRUE if with() is detected in this or any subscopes
+ this.parent = parent; // parent scope
+ this.children = []; // sub-scopes
+ if (parent) {
+ this.level = parent.level + 1;
+ parent.children.push(this);
+ } else {
+ this.level = 0;
+ }
+};
+
+var base54 = (function(){
+ var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
+ return function(num) {
+ var ret = "";
+ do {
+ ret = DIGITS.charAt(num % 54) + ret;
+ num = Math.floor(num / 54);
+ } while (num > 0);
+ return ret;
+ };
+})();
+
+Scope.prototype = {
+ has: function(name) {
+ for (var s = this; s; s = s.parent)
+ if (HOP(s.names, name))
+ return s;
+ },
+ has_mangled: function(mname) {
+ for (var s = this; s; s = s.parent)
+ if (HOP(s.rev_mangled, mname))
+ return s;
+ },
+ toJSON: function() {
+ return {
+ names: this.names,
+ uses_eval: this.uses_eval,
+ uses_with: this.uses_with
+ };
+ },
+
+ next_mangled: function() {
+ // we must be careful that the new mangled name:
+ //
+ // 1. doesn't shadow a mangled name from a parent
+ // scope, unless we don't reference the original
+ // name from this scope OR from any sub-scopes!
+ // This will get slow.
+ //
+ // 2. doesn't shadow an original name from a parent
+ // scope, in the event that the name is not mangled
+ // in the parent scope and we reference that name
+ // here OR IN ANY SUBSCOPES!
+ //
+ // 3. doesn't shadow a name that is referenced but not
+ // defined (possibly global defined elsewhere).
+ for (;;) {
+ var m = base54(++this.cname), prior;
+
+ // case 1.
+ prior = this.has_mangled(m);
+ if (prior && this.refs[prior.rev_mangled[m]] === prior)
+ continue;
+
+ // case 2.
+ prior = this.has(m);
+ if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
+ continue;
+
+ // case 3.
+ if (HOP(this.refs, m) && this.refs[m] == null)
+ continue;
+
+ // I got "do" once. :-/
+ if (!is_identifier(m))
+ continue;
+
+ return m;
+ }
+ },
+ get_mangled: function(name, newMangle) {
+ if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
+ var s = this.has(name);
+ if (!s) return name; // not in visible scope, no mangle
+ if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
+ if (!newMangle) return name; // not found and no mangling requested
+
+ var m = s.next_mangled();
+ s.rev_mangled[m] = name;
+ return s.mangled[name] = m;
+ },
+ define: function(name) {
+ if (name != null)
+ return this.names[name] = name;
+ }
+};
+
+function ast_add_scope(ast) {
+
+ var current_scope = null;
+ var w = ast_walker(), walk = w.walk;
+ var having_eval = [];
+
+ function with_new_scope(cont) {
+ current_scope = new Scope(current_scope);
+ var ret = current_scope.body = cont();
+ ret.scope = current_scope;
+ current_scope = current_scope.parent;
+ return ret;
+ };
+
+ function define(name) {
+ return current_scope.define(name);
+ };
+
+ function reference(name) {
+ current_scope.refs[name] = true;
+ };
+
+ function _lambda(name, args, body) {
+ return [ this[0], define(name), args, with_new_scope(function(){
+ MAP(args, define);
+ return MAP(body, walk);
+ })];
+ };
+
+ return with_new_scope(function(){
+ // process AST
+ var ret = w.with_walkers({
+ "function": _lambda,
+ "defun": _lambda,
+ "with": function(expr, block) {
+ for (var s = current_scope; s; s = s.parent)
+ s.uses_with = true;
+ },
+ "var": function(defs) {
+ MAP(defs, function(d){ define(d[0]) });
+ },
+ "const": function(defs) {
+ MAP(defs, function(d){ define(d[0]) });
+ },
+ "try": function(t, c, f) {
+ if (c != null) return [
+ "try",
+ MAP(t, walk),
+ [ define(c[0]), MAP(c[1], walk) ],
+ f != null ? MAP(f, walk) : null
+ ];
+ },
+ "name": function(name) {
+ if (name == "eval")
+ having_eval.push(current_scope);
+ reference(name);
+ },
+ "for-in": function(has_var, name) {
+ if (has_var) define(name);
+ else reference(name);
+ }
+ }, function(){
+ return walk(ast);
+ });
+
+ // the reason why we need an additional pass here is
+ // that names can be used prior to their definition.
+
+ // scopes where eval was detected and their parents
+ // are marked with uses_eval, unless they define the
+ // "eval" name.
+ MAP(having_eval, function(scope){
+ if (!scope.has("eval")) while (scope) {
+ scope.uses_eval = true;
+ scope = scope.parent;
+ }
+ });
+
+ // for referenced names it might be useful to know
+ // their origin scope. current_scope here is the
+ // toplevel one.
+ function fixrefs(scope, i) {
+ // do children first; order shouldn't matter
+ for (i = scope.children.length; --i >= 0;)
+ fixrefs(scope.children[i]);
+ for (i in scope.refs) if (HOP(scope.refs, i)) {
+ // find origin scope and propagate the reference to origin
+ for (var origin = scope.has(i), s = scope; s; s = s.parent) {
+ s.refs[i] = origin;
+ if (s === origin) break;
+ }
+ }
+ };
+ fixrefs(current_scope);
+
+ return ret;
+ });
+
+};
+
+/* -----[ mangle names ]----- */
+
+function ast_mangle(ast, do_toplevel) {
+ var w = ast_walker(), walk = w.walk, scope;
+
+ function get_mangled(name, newMangle) {
+ if (!do_toplevel && !scope.parent) return name; // don't mangle toplevel
+ return scope.get_mangled(name, newMangle);
+ };
+
+ function _lambda(name, args, body) {
+ if (name) name = get_mangled(name);
+ body = with_scope(body.scope, function(){
+ args = MAP(args, function(name){ return get_mangled(name) });
+ return MAP(body, walk);
+ });
+ return [ this[0], name, args, body ];
+ };
+
+ function with_scope(s, cont) {
+ var _scope = scope;
+ scope = s;
+ for (var i in s.names) if (HOP(s.names, i)) {
+ get_mangled(i, true);
+ }
+ var ret = cont();
+ ret.scope = s;
+ scope = _scope;
+ return ret;
+ };
+
+ function _vardefs(defs) {
+ return MAP(defs, function(d){
+ return [ get_mangled(d[0]), walk(d[1]) ];
+ });
+ };
+
+ return w.with_walkers({
+ "function": _lambda,
+ "defun": function() {
+ // move function declarations to the top when
+ // they are not in some block.
+ var ast = _lambda.apply(this, arguments);
+ switch (w.parent()[0]) {
+ case "toplevel":
+ case "function":
+ case "defun":
+ return MAP.at_top(ast);
+ }
+ return ast;
+ },
+ "var": function(defs) {
+ return [ "var", _vardefs(defs) ];
+ },
+ "const": function(defs) {
+ return [ "const", _vardefs(defs) ];
+ },
+ "name": function(name) {
+ return [ "name", get_mangled(name) ];
+ },
+ "try": function(t, c, f) {
+ return [ "try",
+ MAP(t, walk),
+ c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
+ f != null ? MAP(f, walk) : null ];
+ },
+ "toplevel": function(body) {
+ return with_scope(this.scope, function(){
+ return [ "toplevel", MAP(body, walk) ];
+ });
+ },
+ "for-in": function(has_var, name, obj, stat) {
+ return [ "for-in", has_var, get_mangled(name), walk(obj), walk(stat) ];
+ }
+ }, function() {
+ return walk(ast_add_scope(ast));
+ });
+};
+
+/* -----[
+ - compress foo["bar"] into foo.bar,
+ - remove block brackets {} where possible
+ - join consecutive var declarations
+ - various optimizations for IFs:
+ - if (cond) foo(); else bar(); ==> cond?foo():bar();
+ - if (cond) foo(); ==> cond&&foo();
+ - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); // also for throw
+ - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
+ ]----- */
+