Javascript size optimization

From CodeCodex

Implementations[edit]

Java[edit]

/*
 * JSMin.java 2006-02-13
 * 
 * Copyright (c) 2006 John Reilly (www.inconspicuous.org)
 * 
 * This work is a translation from C to Java of jsmin.c published by
 * Douglas Crockford.  Permission is hereby granted to use the Java 
 * version under the same conditions as the jsmin.c on which it is
 * based.  
 * 
 * 
 * 
 * 
 * jsmin.c 2003-04-21
 * 
 * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * The Software shall be used for Good, not Evil.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.inconspicuous.jsmin;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;


public class JSMin {
	private static final int EOF = -1;

	private PushbackInputStream in;
	private OutputStream out;

	private int theA;
	private int theB;
	
	public JSMin(InputStream in, OutputStream out) {
		this.in = new PushbackInputStream(in);
		this.out = out;
	}

	/**
	 * isAlphanum -- return true if the character is a letter, digit,
	 * underscore, dollar sign, or non-ASCII character.
	 */
	static boolean isAlphanum(int c) {
		return ( (c >= 'a' && c <= 'z') || 
				 (c >= '0' && c <= '9') || 
				 (c >= 'A' && c <= 'Z') || 
				 c == '_' || 
				 c == '$' || 
				 c == '\\' || 
				 c > 126);
	}

	/**
	 * get -- return the next character from stdin. Watch out for lookahead. If
	 * the character is a control character, translate it to a space or
	 * linefeed.
	 */
	int get() throws IOException {
		int c = in.read();

		if (c >= ' ' || c == '\n' || c == EOF) {
			return c;
		}

		if (c == '\r') {
			return '\n';
		}
		
		return ' ';
	}

	
	
	/**
	 * Get the next character without getting it.
	 */
	int peek() throws IOException {
		int lookaheadChar = in.read();
		in.unread(lookaheadChar);
		return lookaheadChar;
	}

	/**
	 * next -- get the next character, excluding comments. peek() is used to see
	 * if a '/' is followed by a '/' or '*'.
	 */
	int next() throws IOException, UnterminatedCommentException {
		int c = get();
		if (c == '/') {
			switch (peek()) {
			case '/':
				for (;;) {
					c = get();
					if (c <= '\n') {
						return c;
					}
				}

			case '*':
				get();
				for (;;) {
					switch (get()) {
					case '*':
						if (peek() == '/') {
							get();
							return ' ';
						}
						break;
					case EOF:
						throw new UnterminatedCommentException();
					}
				}

			default:
				return c;
			}

		}
		return c;
	}

	/**
	 * action -- do something! What you do is determined by the argument: 1
	 * Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
	 * (Delete A). 3 Get the next B. (Delete B). action treats a string as a
	 * single character. Wow! action recognizes a regular expression if it is
	 * preceded by ( or , or =.
	 */

	void action(int d) throws IOException, UnterminatedRegExpLiteralException,
			UnterminatedCommentException, UnterminatedStringLiteralException {
		switch (d) {
		case 1:
			out.write(theA);
		case 2:
			theA = theB;

			if (theA == '\'' || theA == '"') {
				for (;;) {
					out.write(theA);
					theA = get();
					if (theA == theB) {
						break;
					}
					if (theA <= '\n') {
						throw new UnterminatedStringLiteralException();
					}
					if (theA == '\\') {
						out.write(theA);
						theA = get();
					}
				}
			}
			
		case 3:
			theB = next();
			if (theB == '/' && (theA == '(' || theA == ',' || theA == '=')) {
				out.write(theA);
				out.write(theB);
				for (;;) {
					theA = get();
					if (theA == '/') {
						break;
					} else if (theA == '\\') {
						out.write(theA);
						theA = get();
					} else if (theA <= '\n') {
						throw new UnterminatedRegExpLiteralException();
					}
					out.write(theA);
				}
				theB = next();
			}
		}
	}

	/**
	 * jsmin -- Copy the input to the output, deleting the characters which are
	 * insignificant to JavaScript. Comments will be removed. Tabs will be
	 * replaced with spaces. Carriage returns will be replaced with linefeeds.
	 * Most spaces and linefeeds will be removed.
	 */
	public void jsmin() throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, UnterminatedStringLiteralException{
		theA = '\n';
		action(3);
		while (theA != EOF) {
			switch (theA) {
			case ' ':
				if (isAlphanum(theB)) {
					action(1);
				} else {
					action(2);
				}
				break;
			case '\n':
				switch (theB) {
				case '{':
				case '[':
				case '(':
				case '+':
				case '-':
					action(1);
					break;
				case ' ':
					action(3);
					break;
				default:
					if (isAlphanum(theB)) {
						action(1);
					} else {
						action(2);
					}
				}
				break;
			default:
				switch (theB) {
				case ' ':
					if (isAlphanum(theA)) {
						action(1);
						break;
					}
					action(3);
					break;
				case '\n':
					switch (theA) {
					case '}':
					case ']':
					case ')':
					case '+':
					case '-':
					case '"':
					case '\'':
						action(1);
						break;
					default:
						if (isAlphanum(theA)) {
							action(1);
						} else {
							action(3);
						}
					}
					break;
				default:
					action(1);
					break;
				}
			}
		}
		out.flush();
	}

	class UnterminatedCommentException extends Exception {
	}

	class UnterminatedStringLiteralException extends Exception {
	}

	class UnterminatedRegExpLiteralException extends Exception {
	}

	public static void main(String arg[]) {
		try {
			JSMin jsmin = new JSMin(new FileInputStream(arg[0]), System.out);
			jsmin.jsmin();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (ArrayIndexOutOfBoundsException e) {
			e.printStackTrace();			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (UnterminatedRegExpLiteralException e) {
			e.printStackTrace();
		} catch (UnterminatedCommentException e) {
			e.printStackTrace();
		} catch (UnterminatedStringLiteralException e) {
			e.printStackTrace();
		}
	}
}

C[edit]

/* jsmin.c
   2006-05-04

Copyright (c) 2002 Douglas Crockford  (www.crockford.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <stdlib.h>
#include <stdio.h>

static int   theA;
static int   theB;
static int   theLookahead = EOF;


/* isAlphanum -- return true if the character is a letter, digit, underscore,
        dollar sign, or non-ASCII character.
*/

static int isAlphanum(int c)
{
    return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
        (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
        c > 126;
}


/* get -- return the next character from stdin. Watch out for lookahead. If
        the character is a control character, translate it to a space or
        linefeed.
*/

static int get()
{
    int c = theLookahead;
    theLookahead = EOF;
    if (c == EOF) {
        c = getchar();
    }
    if (c >= ' ' || c == '\n' || c == EOF) {
        return c;
    }
    if (c == '\r') {
        return '\n';
    }
    return ' ';
}


/* peek -- get the next character without getting it.
*/

static int peek()
{
    theLookahead = get();
    return theLookahead;
}


/* next -- get the next character, excluding comments. peek() is used to see
        if a '/' is followed by a '/' or '*'.
*/

static int next()
{
    int c = get();
    if  (c == '/') {
        switch (peek()) {
        case '/':
            for (;;) {
                c = get();
                if (c <= '\n') {
                    return c;
                }
            }
        case '*':
            get();
            for (;;) {
                switch (get()) {
                case '*':
                    if (peek() == '/') {
                        get();
                        return ' ';
                    }
                    break;
                case EOF:
                    fprintf(stderr, "Error: JSMIN Unterminated comment.\n");
                    exit(1);
                }
            }
        default:
            return c;
        }
    }
    return c;
}


/* action -- do something! What you do is determined by the argument:
        1   Output A. Copy B to A. Get the next B.
        2   Copy B to A. Get the next B. (Delete A).
        3   Get the next B. (Delete B).
   action treats a string as a single character. Wow!
   action recognizes a regular expression if it is preceded by ( or , or =.
*/

static void action(int d)
{
    switch (d) {
    case 1:
        putchar(theA);
    case 2:
        theA = theB;
        if (theA == '\'' || theA == '"') {
            for (;;) {
                putchar(theA);
                theA = get();
                if (theA == theB) {
                    break;
                }
                if (theA <= '\n') {
                    fprintf(stderr,
"Error: JSMIN unterminated string literal: %c\n", theA);
                    exit(1);
                }
                if (theA == '\\') {
                    putchar(theA);
                    theA = get();
                }
            }
        }
    case 3:
        theB = next();
        if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
                theA == ':' || theA == '[' || theA == '!' || theA == '&' || 
                theA == '|')) {
            putchar(theA);
            putchar(theB);
            for (;;) {
                theA = get();
                if (theA == '/') {
                    break;
                } else if (theA =='\\') {
                    putchar(theA);
                    theA = get();
                } else if (theA <= '\n') {
                    fprintf(stderr,
"Error: JSMIN unterminated Regular Expression literal.\n", theA);
                    exit(1);
                }
                putchar(theA);
            }
            theB = next();
        }
    }
}


/* jsmin -- Copy the input to the output, deleting the characters which are
        insignificant to JavaScript. Comments will be removed. Tabs will be
        replaced with spaces. Carriage returns will be replaced with linefeeds.
        Most spaces and linefeeds will be removed.
*/

static void jsmin()
{
    theA = '\n';
    action(3);
    while (theA != EOF) {
        switch (theA) {
        case ' ':
            if (isAlphanum(theB)) {
                action(1);
            } else {
                action(2);
            }
            break;
        case '\n':
            switch (theB) {
            case '{':
            case '[':
            case '(':
            case '+':
            case '-':
                action(1);
                break;
            case ' ':
                action(3);
                break;
            default:
                if (isAlphanum(theB)) {
                    action(1);
                } else {
                    action(2);
                }
            }
            break;
        default:
            switch (theB) {
            case ' ':
                if (isAlphanum(theA)) {
                    action(1);
                    break;
                }
                action(3);
                break;
            case '\n':
                switch (theA) {
                case '}':
                case ']':
                case ')':
                case '+':
                case '-':
                case '"':
                case '\'':
                    action(1);
                    break;
                default:
                    if (isAlphanum(theA)) {
                        action(1);
                    } else {
                        action(3);
                    }
                }
                break;
            default:
                action(1);
                break;
            }
        }
    }
}


/* main -- Output any command line arguments as comments
        and then minify the input.
*/
extern int main(int argc, char* argv[])
{
    int i;
    for (i = 1; i < argc; i += 1) {
        printf("// %s\n", argv[i]);
    }
    jsmin();
    return 0;
}

C#[edit]

using System;
using System.IO;

/* Originally written in 'C', this code has been converted to the C# language.
 * The author's copyright message is reproduced below.
 * All modifications from the original to C# are placed in the public domain.
 */

/* jsmin.c
   2003-04-21

Copyright (c) 2002 Douglas Crockford  (www.crockford.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

namespace JavaScriptSupport
{
	class JavaScriptMinifier
	{
		const int EOF = -1;

		StreamReader sr;
		StreamWriter sw;
		int theA;
		int theB;
		int theLookahead = EOF;


		static void Main( string[] args )
		{
			if( args.Length != 2 )
			{
				Console.WriteLine( "invalid arguments, 2 required, 1 in, 1 out" );
				return;
			}
			new JavaScriptMinifier().Minify( args[0], args[1] );
		}

		public void Minify( string src, string dst )
		{
			using( sr = new StreamReader( src ) )
			{
				using( sw = new StreamWriter( dst ) )
				{
					jsmin();
				}
			}
		}

		/* jsmin -- Copy the input to the output, deleting the characters which are
				insignificant to JavaScript. Comments will be removed. Tabs will be
				replaced with spaces. Carriage returns will be replaced with linefeeds.
				Most spaces and linefeeds will be removed.
		*/
		void jsmin()
		{
			theA = '\n';
			action( 3 );
			while( theA != EOF )
			{
				switch( theA )
				{
					case ' ':
					{
						if( isAlphanum( theB ) )
						{
							action( 1 );
						}
						else
						{
							action( 2 );
						}
						break;
					}
					case '\n':
					{
						switch( theB )
						{
							case '{':
							case '[':
							case '(':
							case '+':
							case '-':
							{
								action( 1 );
								break;
							}
							case ' ':
							{
								action( 3 );
								break;
							}
							default:
							{
								if( isAlphanum( theB ) )
								{
									action( 1 );
								}
								else
								{
									action( 2 );
								}
								break;
							}
						}
						break;
					}
					default:
					{
						switch( theB )
						{
							case ' ':
							{
								if( isAlphanum( theA ) )
								{
									action( 1 );
									break;
								}
								action( 3 );
								break;
							}
							case '\n':
							{
								switch( theA )
								{
									case '}':
									case ']':
									case ')':
									case '+':
									case '-':
									case '"':
									case '\'':
									{
										action( 1 );
										break;
									}
									default:
									{
										if( isAlphanum( theA ) )
										{
											action( 1 );
										}
										else
										{
											action( 3 );
										}
										break;
									}
								}
								break;
							}
							default:
							{
								action( 1 );
								break;
							}
						}
						break;
					}
				}
			}
		}
		/* action -- do something! What you do is determined by the argument:
				1   Output A. Copy B to A. Get the next B.
				2   Copy B to A. Get the next B. (Delete A).
				3   Get the next B. (Delete B).
		   action treats a string as a single character. Wow!
		   action recognizes a regular expression if it is preceded by ( or , or =.
		*/
		void action( int d )
		{
			if( d <= 1 )
			{
				put( theA );
			}
			if( d <= 2 )
			{
				theA = theB;
				if( theA == '\'' || theA == '"' )
				{
					for( ; ; )
					{
						put( theA );
						theA = get();
						if( theA == theB )
						{
							break;
						}
						if( theA <= '\n' )
						{
							throw new Exception( string.Format( "Error: JSMIN unterminated string literal: {0}\n", theA ) );
						}
						if( theA == '\\' )
						{
							put( theA );
							theA = get();
						}
					}
				}
			}
			if( d <= 3 )
			{
				theB = next();
				if( theB == '/' && (theA == '(' || theA == ',' || theA == '=') )
				{
					put( theA );
					put( theB );
					for( ; ; )
					{
						theA = get();
						if( theA == '/' )
						{
							break;
						}
						else if( theA == '\\' )
						{
							put( theA );
							theA = get();
						}
						else if( theA <= '\n' )
						{
							throw new Exception( string.Format( "Error: JSMIN unterminated Regular Expression literal : {0}.\n", theA ) );
						}
						put( theA );
					}
					theB = next();
				}
			}
		}
		/* next -- get the next character, excluding comments. peek() is used to see
				if a '/' is followed by a '/' or '*'.
		*/
		int next()
		{
			int c = get();
			if( c == '/' )
			{
				switch( peek() )
				{
					case '/':
					{
						for( ; ; )
						{
							c = get();
							if( c <= '\n' )
							{
								return c;
							}
						}
					}
					case '*':
					{
						get();
						for( ; ; )
						{
							switch( get() )
							{
								case '*':
								{
									if( peek() == '/' )
									{
										get();
										return ' ';
									}
									break;
								}
								case EOF:
								{
									throw new Exception( "Error: JSMIN Unterminated comment.\n" );
								}
							}
						}
					}
					default:
					{
						return c;
					}
				}
			}
			return c;
		}
		/* peek -- get the next character without getting it.
		*/
		int peek()
		{
			theLookahead = get();
			return theLookahead;
		}
		/* get -- return the next character from stdin. Watch out for lookahead. If
				the character is a control character, translate it to a space or
				linefeed.
		*/
		int get()
		{
			int c = theLookahead;
			theLookahead = EOF;
			if( c == EOF )
			{
				c = sr.Read();
			}
			if( c >= ' ' || c == '\n' || c == EOF )
			{
				return c;
			}
			if( c == '\r' )
			{
				return '\n';
			}
			return ' ';
		}
		void put( int c )
		{
			sw.Write( (char)c );
		}
		/* isAlphanum -- return true if the character is a letter, digit, underscore,
				dollar sign, or non-ASCII character.
		*/
		bool isAlphanum( int c )
		{
			return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
				(c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
				c > 126);
		}
	}
}

Python[edit]

#!/usr/bin/python

# This code is original from jsmin by Douglas Crockford, it was translated to
# Python by Baruch Even. The original code had the following copyright and
# license.
#
# /* jsmin.c
#    2003-04-21
# 
# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# The Software shall be used for Good, not Evil.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# */

from StringIO import StringIO

def jsmin(js):
	ins = StringIO(js)
	outs = StringIO()
	JavascriptMinify().minify(ins, outs)
	str = outs.getvalue()
	if len(str) > 0 and str[0] == '\n':
		str = str[1:]
	return str

def isAlphanum(c):
	"""return true if the character is a letter, digit, underscore,
           dollar sign, or non-ASCII character.
	"""
	return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
	        (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));

class UnterminatedComment(Exception):
	pass

class UnterminatedStringLiteral(Exception):
	pass

class UnterminatedRegularExpression(Exception):
	pass

class JavascriptMinify(object):

	def _outA(self):
		self.outstream.write(self.theA)
	def _outB(self):
		self.outstream.write(self.theB)

	def _get(self):
		"""return the next character from stdin. Watch out for lookahead. If
		   the character is a control character, translate it to a space or
		   linefeed.
		"""
		c = self.theLookahead
		self.theLookahead = None
		if c == None:
			c = self.instream.read(1)
		if c >= ' ' or c == '\n':
			return c
		if c == '': # EOF
			return '\000'
		if c == '\r':
			return '\n'
		return ' '

	def _peek(self):
		self.theLookahead = self._get()
		return self.theLookahead

	def _next(self):
		"""get the next character, excluding comments. peek() is used to see
		   if a '/' is followed by a '/' or '*'.
		"""
		c = self._get()
		if c == '/':
			p = self._peek()
			if p == '/':
				c = self._get()
				while c > '\n':
					c = self._get()
				return c
			if p == '*':
				c = self._get()
				while 1:
					c = self._get()
					if c == '*':
						if self._peek() == '/':
							self._get()
							return ' '
					if c == '\000':
						raise UnterminatedComment()

		return c

	def _action(self, action):
		"""do something! What you do is determined by the argument:
		   1   Output A. Copy B to A. Get the next B.
		   2   Copy B to A. Get the next B. (Delete A).
		   3   Get the next B. (Delete B).
		   action treats a string as a single character. Wow!
		   action recognizes a regular expression if it is preceded by ( or , or =.
		"""
		if action <= 1:
			self._outA()
			
		if action <= 2:
			self.theA = self.theB
			if self.theA == "'" or self.theA == '"':
				while 1:
					self._outA()
					self.theA = self._get()
					if self.theA == self.theB:
						break
					if self.theA <= '\n':
						raise UnterminatedStringLiteral()
					if self.theA == '\\':
						self._outA()
						self.theA = self._get()


		if action <= 3:
			self.theB = self._next()
			if self.theB == '/' and (self.theA == '(' or self.theA == ',' or self.theA == '='):
				self._outA()
				self._outB()
				while 1:
					self.theA = self._get()
					if self.theA == '/':
						break
					elif self.theA == '\\':
						self._outA()
						self.theA = self._get()
					elif self.theA <= '\n':
						raise UnterminatedRegularExpression()
					self._outA()
				self.theB = self._next()


	def _jsmin(self):
		"""Copy the input to the output, deleting the characters which are
		   insignificant to JavaScript. Comments will be removed. Tabs will be
		   replaced with spaces. Carriage returns will be replaced with linefeeds.
		   Most spaces and linefeeds will be removed.
		"""
		self.theA = '\n'
		self._action(3)

		while self.theA != '\000':
			if self.theA == ' ':
				if isAlphanum(self.theB):
					self._action(1)
				else:
					self._action(2)
			elif self.theA == '\n':
				if self.theB in ['{', '[', '(', '+', '-']:
					self._action(1)
				elif self.theB == ' ':
					self._action(3)
				else:
					if isAlphanum(self.theB):
						self._action(1)
					else:
						self._action(2)
			else:
				if self.theB == ' ':
					if isAlphanum(self.theA):
						self._action(1)
					else:
						self._action(3)
				elif self.theB == '\n':
					if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
						self._action(1)
					else:
						if isAlphanum(self.theA):
							self._action(1)
						else:
							self._action(3)
				else:
					self._action(1)

	def minify(self, instream, outstream):
		self.instream = instream
		self.outstream = outstream
		self.theA = None
		self.thaB = None
		self.theLookahead = None

		self._jsmin()
		self.instream.close()

if __name__ == '__main__':
	import sys
	jsm = JavascriptMinify()
	jsm.minify(sys.stdin, sys.stdout)

Ruby[edit]

#!/usr/bin/ruby
# jsmin.rb 2006-03-21
# Author: Uladzislau Latynski
# This work is a translation from C to Ruby of jsmin.c published by
# Douglas Crockford.  Permission is hereby granted to use the Ruby
# version under the same conditions as the jsmin.c on which it is
# based.
#
# /* jsmin.c
#    2003-04-21
#
# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# The Software shall be used for Good, not Evil.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

EOF = -1
$theA = ""
$theB = ""

# isAlphanum -- return true if the character is a letter, digit, underscore,
# dollar sign, or non-ASCII character
def isAlphanum(c)
   return false if !c || c == EOF
   return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
           (c >= 'A' && c <= 'Z') || c == '_' || c == '$' ||
           c == '\\' || c[0] > 126)
end

# get -- return the next character from stdin. Watch out for lookahead. If
# the character is a control character, translate it to a space or linefeed.
def get()
  c = $stdin.getc
  return EOF if(!c)
  c = c.chr
  return c if (c >= " " || c == "\n" || c.unpack("c") == EOF)
  return "\n" if (c == "\r")
  return " "
end

# Get the next character without getting it.
def peek()
    lookaheadChar = $stdin.getc
    $stdin.ungetc(lookaheadChar)
    return lookaheadChar.chr
end

# mynext -- get the next character, excluding comments.
# peek() is used to see if a '/' is followed by a '/' or '*'.
def mynext()
    c = get
    if (c == "/")
        if(peek == "/")
            while(true)
                c = get
                if (c <= "\n")
                return c
                end
            end
        end
        if(peek == "*")
            get
            while(true)
                case get
                when "*"
                   if (peek == "/")
                        get
                        return " "
                    end
                when EOF
                    raise "Unterminated comment"
                end
            end
        end
    end
    return c
end


# action -- do something! What you do is determined by the argument: 1
# Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
# (Delete A). 3 Get the next B. (Delete B). action treats a string as a
# single character. Wow! action recognizes a regular expression if it is
# preceded by ( or , or =.
def action(a)
    if(a==1)
        $stdout.write $theA
    end
    if(a==1 || a==2)
        $theA = $theB
        if ($theA == "\'" || $theA == "\"")
            while (true)
                $stdout.write $theA
                $theA = get
                break if ($theA == $theB)
                raise "Unterminated string literal" if ($theA <= "\n")
                if ($theA == "\\")
                    $stdout.write $theA
                    $theA = get
                end
            end
        end
    end
    if(a==1 || a==2 || a==3)
        $theB = mynext
        if ($theB == "/" && ($theA == "(" || $theA == "," || $theA == "="))
            $stdout.write $theA
            $stdout.write $theB
            while (true)
                $theA = get
                if ($theA == "/")
                    break
                elsif ($theA == "\\")
                    $stdout.write $theA
                    $theA = get
                elsif ($theA <= "\n")
                    raise "Unterminated RegExp Literal"
                end
                $stdout.write $theA
            end
            $theB = mynext
        end
    end
end

# jsmin -- Copy the input to the output, deleting the characters which are
# insignificant to JavaScript. Comments will be removed. Tabs will be
# replaced with spaces. Carriage returns will be replaced with linefeeds.
# Most spaces and linefeeds will be removed.
def jsmin
    $theA = "\n"
    action(3)
    while ($theA != EOF)
        case $theA
        when " "
            if (isAlphanum($theB))
                action(1)
            else
                action(2)
            end
        when "\n"
            case ($theB)
            when "{","[","(","+","-"
                action(1)
            when " "
                action(3)
            else
                if (isAlphanum($theB))
                    action(1)
                else
                    action(2)
                end
            end
        else
            case ($theB)
            when " "
                if (isAlphanum($theA))
                    action(1)
                else
                    action(3)
                end
            when "\n"
                case ($theA)
                when "}","]",")","+","-","\"","\\"
                    action(1)
                else
                    if (isAlphanum($theA))
                        action(1)
                    else
                        action(3)
                    end
                end
            else
                action(1)
            end
        end
    end
end

jsmin

PHP[edit]

#!/usr/bin/php
<?php

/**
* JSMin.php (for PHP 5)
*
* PHP adaptation of JSMin, published by Douglas Crockford as jsmin.c, also based
* on its Java translation by John Reilly.
*
* Permission is hereby granted to use the PHP version under the same conditions
* as jsmin.c, which has the following notice :
*
* ----------------------------------------------------------------------------
*
* Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* The Software shall be used for Good, not Evil.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ----------------------------------------------------------------------------
*
* @copyright   No new copyright ; please keep above and following information.
* @author      David Holmes <dholmes@cfdsoftware.net> of CFD Labs, France
* @version     0.1 (PHP translation)   2006-04-11
*
* Please note, this is a brutal and simple conversion : it could undoubtedly be
* improved, as a PHP implementation, by applying more PHP-specific programming
* features.
*
* PHP 5 is required because OO style of programming is used, as well as classes
* from the Standard PHP Library (SPL).
*
* Note : whereas jsmin.c works specifically with the standard input and output
* streams, this implementation only falls back on them if file pathnames are
* not provided to the JSMin() constructor.
*
* Examples comparing with the application compiled from jsmin.c :
*
* jsmin < orig.js > mini.js        JSMin.php orig.js mini.js
*                                  JSMin.php orig.js > mini.js
*                                  JSMin.php - mini.js < orig.js
* jsmin < orig.js                  JSMin.php orig.js
*                                  JSMin.php orig.js -
* jsmin > mini.js                  JSMin.php - mini.js
*                                  JSMin.php > mini.js
* jsmin comm1 comm2 < a.js > b.js  JSMin.php a.js b.js comm1 comm2
*                                  JSMin.php a.js b.js -c comm1 comm2
*                                  JSMin.php a.js --comm comm1 comm2 > b.js
*                                  JSMin.php -c comm1 comm2 < a.js > b.js
* (etc...)
*
* See JSMin.php -h (or --help) for command-line documentation.
*/

/**
* Version of this PHP translation.
*/

define('VERSION', '0.1');

/**
* How fgetc() reports an End Of File.
* N.B. : use === and not == to test the result of fgetc() ! (see manual)
*/

define('EOF', FALSE);

/**
* Some ASCII character ordinals.
* N.B. : PHP identifiers are case-insensitive !
*/

define('ORD_NL', ord("\n"));
define('ORD_space', ord(' '));
define('ORD_cA', ord('A'));
define('ORD_cZ', ord('Z'));
define('ORD_a', ord('a'));
define('ORD_z', ord('z'));
define('ORD_0', ord('0'));
define('ORD_9', ord('9'));

/**
* Generic exception class related to JSMin.
*/

class JSMinException extends Exception {
}

/**
* A JSMin exception indicating that a file provided for input or output could not be properly opened.
*/

class FileOpenFailedJSMinException extends JSMinException {
}

/**
* A JSMin exception indicating that an unterminated comment was encountered in input.
*/

class UnterminatedCommentJSMinException extends JSMinException {
}

/**
* A JSMin exception indicatig that an unterminated string literal was encountered in input.
*/

class UnterminatedStringLiteralJSMinException extends JSMinException {
}

/**
* A JSMin exception indicatig that an unterminated regular expression lieteral was encountered in input.
*/

class UnterminatedRegExpLiteralJSMinException extends JSMinException {
}

/**
* Main JSMin application class.
*
* Example of use :
*
* $jsMin = new JSMin(...input..., ...output...);
* $jsMin -> minify();
*
* Do not specify input and/or output (or default to '-') to use stdin and/or stdout.
*/

class JSMin {

    /**
     * Constant describing an {@link action()} : Output A. Copy B to A. Get the next B.
     */

    const ACT_FULL = 1;

    /**
     * Constant describing an {@link action()} : Copy B to A. Get the next B. (Delete A).
     */

    const ACT_BUF = 2;

    /**
     * Constant describing an {@link action()} : Get the next B. (Delete B).
     */

    const ACT_IMM = 3;

    /**
     * The input stream, from which to read a JS file to minimize. Obtained by fopen().
     * @var SplFileObject
     */

    private $in;

    /**
     * The output stream, in which to write the minimized JS file. Obtained by fopen().
     * @var SplFileObject
     */

    private $out;

    /**
     * Temporary I/O character (A).
     * @var string
     */

    private $theA;

    /**
     * Temporary I/O character (B).
     * @var string
     */

    private $theB;

    /**
     * Indicates whether a character is alphanumeric or _, $, \ or non-ASCII.
     *
     * @param   string      $c  The single character to test.
     * @return  boolean     Whether the char is a letter, digit, underscore, dollar, backslash, or non-ASCII.
     */

    private static function isAlphaNum($c) {

        // Get ASCII value of character for C-like comparisons

        $a = ord($c);

        // Compare using defined character ordinals, or between PHP strings
        // Note : === is micro-faster than == when types are known to be the same

        return
            ($a >= ORD_a && $a <= ORD_z) ||
            ($a >= ORD_0 && $a <= ORD_9) ||
            ($a >= ORD_cA && $a <= ORD_cZ) ||
            $c === '_' || $c === '$' || $c === '\\' || $a > 126
        ;
    }

    /**
     * Get the next character from the input stream.
     *
     * If said character is a control character, translate it to a space or linefeed.
     *
     * @return  string      The next character from the specified input stream.
     * @see     $in
     * @see     peek()
     */

    private function get() {

        // Get next input character and advance position in file

        $c = $this -> in -> fgetc();

        // Test for non-problematic characters

        if ($c === "\n" || $c === EOF || ord($c) >= ORD_space) {
            return $c;
        }

        // else
        // Make linefeeds into newlines

        if ($c === "\r") {
            return "\n";
        }

        // else
        // Consider space

        return ' ';
    }

    /**
     * Get the next character from the input stream, without gettng it.
     *
     * @return  string      The next character from the specified input stream, without advancing the position
     *                      in the underlying file.
     * @see     $in
     * @see     get()
     */

    private function peek() {

        // Get next input character

        $c = $this -> in -> fgetc();

        // Regress position in file

        $this -> in -> fseek(-1, SEEK_CUR);

        // Return character obtained

        return $c;
    }

    /**
     * Get the next character from the input stream, excluding comments.
     *
     * {@link peek()} is used to see if a '/' is followed by a '*' or '/'.
     * Multiline comments are actually returned as a single space.
     *
     * @return  string  The next character from the specified input stream, skipping comments.
     * @see     $in
     */

    private function next() {

        // Get next char from input, translated if necessary

        $c = $this -> get();

        // Check comment possibility

        if ($c == '/') {

            // Look ahead : a comment is two slashes or slashes followed by asterisk (to be closed)

            switch ($this -> peek()) {

                case '/' :

                    // Comment is up to the end of the line
                    // TOTEST : simple $this -> in -> fgets()

                    while (true) {

                        $c = $this -> get();

                        if (ord($c) <= ORD_NL) {
                            return $c;
                        }
                    }

                case '*' :

                    // Comment is up to comment close.
                    // Might not be terminated, if we hit the end of file.

                    while (true) {

                        // N.B. not using switch() because of having to test EOF with ===

                        $c = $this -> get();

                        if ($c == '*') {

                            // Comment termination if the char ahead is a slash

                            if ($this -> peek() == '/') {

                                // Advance again and make into a single space

                                $this -> get();
                                return ' ';
                            }
                        }
                        else if ($c === EOF) {

                            // Whoopsie

                            throw new UnterminatedCommentJSMinException();
                        }
                    }

                default :

                    // Not a comment after all

                    return $c;
            }
        }

        // No risk of a comment

        return $c;
    }

    /**
     * Do something !
     *
     * The action to perform is determined by the argument :
     *
     * JSMin :: ACT_FULL : Output A. Copy B to A. Get the next B.
     * JSMin :: ACT_BUF  : Copy B to A. Get the next B. (Delete A).
     * JSMin :: ACT_IMM  : Get the next B. (Delete B).
     *
     * A string is treated as a single character. Also, regular expressions are recognized if preceded
     * by '(', ',' or '='.
     *
     * @param   int     $action     The action to perform : one of the JSMin :: ACT_* constants.
     */

    private function action($action) {

        // Choice of possible actions
        // Note the frequent fallthroughs : the actions are decrementally "long"

        switch ($action) {

            case self :: ACT_FULL :

                // Write A to output, then fall through

                $this -> out -> fwrite($this -> theA);

            case self :: ACT_BUF : // N.B. possible fallthrough from above

                // Copy B to A

                $tmpA = $this -> theA = $this -> theB;

                // Treating a string as a single char : outputting it whole
                // Note that the string-opening char (" or ') is memorized in B

                if ($tmpA == '\'' || $tmpA == '"') {

                    while (true) {

                        // Output string contents

                        $this -> out -> fwrite($tmpA);

                        // Get next character, watching out for termination of the current string,
                        // new line & co (then the string is not terminated !), or a backslash
                        // (upon which the following char is directly output to serve the escape mechanism)

                        $tmpA = $this -> theA = $this -> get();

                        if ($tmpA == $this -> theB) {

                            // String terminated

                            break; // from while(true)
                        }

                        // else

                        if (ord($tmpA) <= ORD_NL) {

                            // Whoopsie

                            throw new UnterminatedStringLiteralJSMinException();
                        }

                        // else

                        if ($tmpA == '\\') {

                            // Escape next char immediately

                            $this -> out -> fwrite($tmpA);
                            $tmpA = $this -> theA = $this -> get();
                        }
                    }
                }

            case self :: ACT_IMM : // N.B. possible fallthrough from above

                // Get the next B

                $this -> theB = $this -> next();

                // Special case of recognising regular expressions (beginning with /) that are
                // preceded by '(', ',' or '='

                $tmpA = $this -> theA;

                if ($this -> theB == '/' && ($tmpA == '(' || $tmpA == ',' || $tmpA == '=')) {

                    // Output the two successive chars

                    $this -> out -> fwrite($tmpA);
                    $this -> out -> fwrite($this -> theB);

                    // Look for the end of the RE literal, watching out for escaped chars or a control /
                    // end of line char (the RE literal then being unterminated !)

                    while (true) {

                        $tmpA = $this -> theA = $this -> get();

                        if ($tmpA == '/') {

                            // RE literal terminated

                            break; // from while(true)
                        }

                        // else

                        if ($tmpA == '\\') {

                            // Escape next char immediately

                            $this -> out -> fwrite($tmpA);
                            $tmpA = $this -> theA = $this -> get();
                        }
                        else if (ord($tmpA) <= ORD_NL) {

                            // Whoopsie

                            throw new UnterminatedRegExpLiteralJSMinException();
                        }

                        // Output RE characters

                        $this -> out -> fwrite($tmpA);
                    }

                    // Move forward after the RE literal

                    $this -> theB = $this -> next();
                }

            break;
            default : throw new JSMinException('Expected a JSMin :: ACT_* constant in action().');
        }
    }

    /**
     * Run the JSMin application : minify some JS code.
     *
     * The code is read from the input stream, and its minified version is written to the output one.
     * That is : characters which are insignificant to JavaScript are removed, as well as comments ;
     * tabs are replaced with spaces ; carriage returns are replaced with linefeeds, and finally most
     * spaces and linefeeds are deleted.
     *
     * Note : name was changed from jsmin() because PHP identifiers are case-insensitive, and it is already
     * the name of this class.
     *
     * @see     __construct()
     */

    public function minify() {

        // Initialize A and run the first (minimal) action

        $this -> theA = "\n";
        $this -> action(self :: ACT_IMM);

        // Proceed all the way to the end of the input file

        while ($this -> theA !== EOF) {

            switch ($this -> theA) {

                case ' ' :

                    if (self :: isAlphaNum($this -> theB)) {
                        $this -> action(self :: ACT_FULL);
                    }
                    else {
                        $this -> action(self :: ACT_BUF);
                    }

                break;
                case "\n" :

                    switch ($this -> theB) {

                        case '{' : case '[' : case '(' :
                        case '+' : case '-' :

                            $this -> action(self :: ACT_FULL);

                        break;
                        case ' ' :

                            $this -> action(self :: ACT_IMM);

                        break;
                        default :

                            if (self :: isAlphaNum($this -> theB)) {
                                $this -> action(self :: ACT_FULL);
                            }
                            else {
                                $this -> action(self :: ACT_BUF);
                            }

                        break;
                    }

                break;
                default :

                    switch ($this -> theB) {

                        case ' ' :

                            if (self :: isAlphaNum($this -> theA)) {

                                $this -> action(self :: ACT_FULL);
                                break;
                            }

                            // else

                            $this -> action(self :: ACT_IMM);

                        break;
                        case "\n" :

                            switch ($this -> theA) {

                                case '}' : case ']' : case ')' : case '+' :
                                case '-' : case '"' : case '\'' :

                                    $this -> action(self :: ACT_FULL);

                                break;
                                default :

                                    if (self :: isAlphaNum($this -> theA)) {
                                        $this -> action(self :: ACT_FULL);
                                    }
                                    else {
                                        $this -> action(self :: ACT_IMM);
                                    }

                                break;
                            }

                        break;
                        default :

                            $this -> action(self :: ACT_FULL);

                        break;
                    }

                break;
            }
        }
    }

    /**
     * Prepare a new JSMin application.
     *
     * The next step is to {@link minify()} the input into the output.
     *
     * @param   string  $inFileName     The pathname of the input (unminified JS) file. STDIN if '-' or absent.
     * @param   string  $outFileName    The pathname of the output (minified JS) file. STDOUT if '-' or absent.
     * @param   array   $comments       Optional lines to present as comments at the beginning of the output.
     * @throws  FileOpenFailedJSMinException    If the input and/or output file pathname is not provided, and
     *      respectively STDIN and/or STDOUT are not available (ie, script is not being used in CLI).
     */

    public function __construct($inFileName = '-', $outFileName = '-', $comments = NULL) {

        // Recuperate input and output streams.
        // Use STDIN and STDOUT by default, if they are defined (CLI mode) and no file names are provided

        if ($inFileName == '-')  $inFileName  = 'php://stdin';
        if ($outFileName == '-') $outFileName = 'php://stdout';

        try {

            $this -> in = new SplFileObject($inFileName, 'rb', TRUE);
        }
        catch (Exception $e) {

            throw new FileOpenFailedJSMinException(
                'Failed to open "'.$inFileName.'" for reading only.'
            );
        }

        try {

            $this -> out = new SplFileObject($outFileName, 'wb', TRUE);
        }
        catch (Exception $e) {

            throw new FileOpenFailedJSMinException(
                'Failed to open "'.$outFileName.'" for writing only.'
            );
        }

        // Present possible initial comments

        if ($comments !== NULL) {
            foreach ($comments as $comm) {
                $this -> out -> fwrite('// '.$comm."\n");
            }
        }
    }
}

//
// OTHER FUNCTIONS
//

/**
* Displays inline help for the application.
*/

function printHelp() {

    // All the inline help

    echo "\n";
    echo "Usage : JSMin.php [inputFile] [outputFile] [[-c] comm1 comm2 ...]\n";
    echo "        JSMin.php [-v|-h]\n";
    echo "\n";
    echo "Minify JavaScript code using JSMin, the JavaScript Minifier.\n";
    echo "\n";
    echo "JSMin is a filter which removes comments and unnecessary whitespace\n";
    echo "from a script read in the inputFile (stdin by default), as well as\n";
    echo "omitting or modifying some characters, before writing the results to\n";
    echo "the outputFile (stdout by default).\n";
    echo "It does not change the behaviour of the program that is minifies.\n";
    echo "The result may be harder to debug. It will definitely be harder to\n";
    echo "read. It typically reduces filesize by half, resulting in faster\n";
    echo "downloads. It also encourages a more expressive programming style\n";
    echo "because it eliminates the download cost of clean, literate self-\n";
    echo "documentation.\n";
    echo "\n";
    echo "The '-' character can be used to explicitely specify a standard\n";
    echo "stream for input or output.\n";
    echo "\n";
    echo "With the optional -c (--comm) option, all following parameters will\n";
    echo "be listed at the beginning of the output as comments. This is a\n";
    echo "convenient way of replacing copyright messages and other info. The\n";
    echo "option is unnecessary if an input and output file are specified :\n";
    echo "following parameters will then automatically be treated thus.\n";
    echo "\n";
    echo "Options :\n";
    echo "\n";
    echo "  -c, --comm      Present following parameters as initial comments.\n";
    echo "  -h, --help      Display this information.\n";
    echo "  -v, --version   Display short version information.\n";
    echo "\n";
    echo "The JavaScript Minifier is copyright (c) 2002 by Douglas Crockford\n";
    echo "and available online at http://javascript.crockford.com/jsmin.html.\n";
    echo "This PHP translation is by David Holmes of CFD Labs, France, 2006.\n";
    echo "\n";
}

/**
* Displays version information for the application.
*/

function printVersion() {

    // Minimum info

    echo "JSMin, the JavaScript Minifier, copyright (c) 2002 by Douglas Crockford.\n";
    echo "PHP translation (for PHP 5) version ".VERSION." by David Holmes, CFD Labs.\n";
}

//
// ENTRY POINT
//

define('EXIT_SUCCESS', 0);
define('EXIT_FAILURE', 1);

// Examine command-line parameters
// First shift off the first parameter, the executable's name

array_shift($argv);

$inFileName = NULL;
$outFileName = NULL;

$haveCommentParams = FALSE;
$comments = array();

foreach ($argv as $arg) {

    // Bypass the rest if we are now considering initial comments

    if ($haveCommentParams) {

        $comments[] = $arg;
        continue;
    }

    // else
    // Look for an option (length > 1 because of '-' for indicating stdin or
    // stdout)

    if ($arg[0] == '-' && strlen($arg) > 1) {

        switch ($arg) {

            case '-c' : case '--comm' :

                // Following parameters will be initial comments

                $haveCommentParams = TRUE;

            break;
            case '-h' : case '--help' :

                // Display inline help and exit normally

                printHelp();
                exit(EXIT_SUCCESS);

            case '-v' : case '--version' :

                // Display short version information and exit normally

                printVersion();
                exit(EXIT_SUCCESS);

            default :

                // Reject any other (unknown) option

                echo "\n";
                echo "ERROR : unknown option \"$arg\", sorry.\n";

                printHelp();
                exit(EXIT_FAILURE);
        }

        continue;
    }

    // else
    // At this point, parameter is neither to be considered as an initial
    // comment, nor is it an option. It is an input or output file.

    if ($inFileName === NULL) {

        // No input file yet, this is it

        $inFileName = $arg;
    }
    else if ($outFileName === NULL) {

        // An input file but no output file yet, this is it

        $outFileName = $arg;
    }
    else {

        // Already have input and output file, this is a first initial comment

        $haveCommentParams = TRUE;
        $comments[] = $arg;
    }
}

if ($inFileName === NULL)  $inFileName  = '-';
if ($outFileName === NULL) $outFileName = '-';

// Prepare and run the JSMin application
// If pathnames are not provided or '-', standard input/output streams are used

$jsMin = new JSMin($inFileName, $outFileName, $comments);
$jsMin -> minify();

?>