343 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  **      ==============================
 | |
|  **       O           O      O   OOOO
 | |
|  **       O           O     O O  O   O
 | |
|  **       O           O     O O  O   O
 | |
|  **       OOOO   OOOO O     OOO  OOOO
 | |
|  **       O   O       O    O   O O   O
 | |
|  **       O   O       O    O   O O   O
 | |
|  **       OOOO        OOOO O   O OOOO
 | |
|  **      ==============================
 | |
|  **      Dr. Stefan Bosse http://www.bsslab.de
 | |
|  **
 | |
|  **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 | |
|  **                 BY THE AUTHOR(S).
 | |
|  **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 | |
|  **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 | |
|  **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 | |
|  **
 | |
|  **    $AUTHORS:     Stefan Bosse, Douglas Crockford (jsmin, C, 2002)
 | |
|  **    $INITIAL:     (C) 2006-2020 bLAB
 | |
|  **    $CREATED:     09-06-20 by sbosse.
 | |
|  **    $RCS:         $Id: minify.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $
 | |
|  **    $VERSION:     1.1.1
 | |
|  **
 | |
|  **    $INFO:
 | |
|  **
 | |
|  **  Fast JavaScript Code Minifier (C-to-JS port)
 | |
|  **
 | |
|  **    $ENDOFINFO
 | |
|  */
 | |
| 
 | |
| var EOF = null;
 | |
| var   theA;
 | |
| var   theB;
 | |
| var   theLookahead = EOF;
 | |
| var   theX = EOF;
 | |
| var   theY = EOF;
 | |
| var   theInbuf = null;
 | |
| var   theOutbuf = null;
 | |
| var   theInbufIndex = 0;
 | |
| var   theOutbufIndex = 0;
 | |
| 
 | |
| /* isAlphanum -- return true if the character is a letter, digit, underscore,
 | |
|         dollar sign, or non-ASCII character.
 | |
| */
 | |
| function isAlphanum(c)
 | |
| {
 | |
|     return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
 | |
|             (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
 | |
|              c > String.fromCharCode(126));
 | |
| }
 | |
| 
 | |
| function get() {
 | |
|   var c = theLookahead;
 | |
|   theLookahead = EOF;
 | |
|   if (c == EOF) {
 | |
|     c = theInbuf[theInbufIndex++];
 | |
|   }
 | |
|   if (c >= ' ' || c == '\n' || c == EOF) {
 | |
|     return c;
 | |
|   }
 | |
|   if (c == '\r') {
 | |
|     return '\n';
 | |
|   }
 | |
|   return ' ';
 | |
| }
 | |
| 
 | |
| function put(c) {
 | |
|   theOutbuf += c;
 | |
| }
 | |
| 
 | |
| function peek()
 | |
| {
 | |
|   theLookahead = get();
 | |
|   return theLookahead;
 | |
| }
 | |
| 
 | |
| function error(msg) { throw msg }
 | |
| 
 | |
| function next()
 | |
| {
 | |
|     var c = get();
 | |
|     if  (c == '/') {
 | |
|      switch (peek()) {
 | |
|         case '/':
 | |
|             for (;;) {
 | |
|                 c = get();
 | |
|                 if (c <= '\n') {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
|         case '*':
 | |
|             get();
 | |
|             while (c != ' ') {
 | |
|                 switch (get()) {
 | |
|                 case '*':
 | |
|                     if (peek() == '/') {
 | |
|                         get();
 | |
|                         c = ' ';
 | |
|                     }
 | |
|                     break;
 | |
|                 case EOF:
 | |
|                   error("Unterminated comment.");
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
|       }
 | |
|     }
 | |
|     theY = theX;
 | |
|     theX = 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 =.
 | |
| */
 | |
| 
 | |
| function action(d)
 | |
| {
 | |
|   switch (d) {
 | |
|     case 1:
 | |
|         put(theA);
 | |
|         if (
 | |
|             (theY == '\n' || theY == ' ') &&
 | |
|             (theA == '+' || theA == '-' || theA == '*' || theA == '/') &&
 | |
|             (theB == '+' || theB == '-' || theB == '*' || theB == '/')
 | |
|         ) {
 | |
|             put(theY);
 | |
|         }
 | |
|     case 2:
 | |
|         theA = theB;
 | |
|         if (theA == '\'' || theA == '"' || theA == '`') {
 | |
|             for (;;) {
 | |
|                 put(theA);
 | |
|                 theA = get();
 | |
|                 if (theA == theB) {
 | |
|                     break;
 | |
|                 }
 | |
|                 if (theA == '\\') {
 | |
|                     put(theA);
 | |
|                     theA = get();
 | |
|                 }
 | |
|                 if (theA == EOF) {
 | |
|                     error("Unterminated string literal.");
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     case 3:
 | |
|         theB = next();
 | |
|         if (theB == '/' && (
 | |
|             theA == '(' || theA == ',' || theA == '=' || theA == ':' ||
 | |
|             theA == '[' || theA == '!' || theA == '&' || theA == '|' ||
 | |
|             theA == '?' || theA == '+' || theA == '-' || theA == '~' ||
 | |
|             theA == '*' || theA == '/' || theA == '{' || theA == '\n'
 | |
|         )) {
 | |
|             put(theA);
 | |
|             if (theA == '/' || theA == '*') {
 | |
|                 put(' ');
 | |
|             }
 | |
|             put(theB);
 | |
|             for (;;) {
 | |
|                 theA = get();
 | |
|                 if (theA == '[') {
 | |
|                     for (;;) {
 | |
|                         put(theA);
 | |
|                         theA = get();
 | |
|                         if (theA == ']') {
 | |
|                             break;
 | |
|                         }
 | |
|                         if (theA == '\\') {
 | |
|                             put(theA);
 | |
|                             theA = get();
 | |
|                         }
 | |
|                         if (theA == EOF) {
 | |
|                             error("Unterminated set in Regular Expression literal.");
 | |
|                         }
 | |
|                     }
 | |
|                 } else if (theA == '/') {
 | |
|                     switch (peek()) {
 | |
|                     case '/':
 | |
|                     case '*':
 | |
|                         error("Unterminated set in Regular Expression literal.");
 | |
|                         break;
 | |
|                     case '\n': 
 | |
|                         put(theA); 
 | |
|                         theA='\n'; /* Newline required after end of regex */
 | |
|                     }                    
 | |
|                     break;
 | |
|                 } else if (theA =='\\') {
 | |
|                     put(theA);
 | |
|                     theA = get();
 | |
|                 }
 | |
|                 if (theA == EOF) {
 | |
|                     error("Unterminated Regular Expression literal.");
 | |
|                 }
 | |
|                 put(theA);
 | |
|             }
 | |
|             theB = next();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| function minify(text) {
 | |
|   theA=0;
 | |
|   theB=0;
 | |
|   theLookahead = EOF;
 | |
|   theX = EOF;
 | |
|   theY = EOF;
 | |
|   theInbuf=text;
 | |
|   theInbufIndex=0;
 | |
|   theOutbuf='';
 | |
|   if (peek() == 0xEF) {
 | |
|       get();
 | |
|       get();
 | |
|       get();
 | |
|   }
 | |
|   theA = get();
 | |
|   action(3);
 | |
|   while (theA != EOF) {
 | |
|     switch (theA) {
 | |
|       case ' ':
 | |
|           action(isAlphanum(theB) ? 1 : 2);
 | |
|           break;
 | |
|       case '\n':
 | |
|         switch (theB) {
 | |
|           case '{':
 | |
|           case '[':
 | |
|           case '(':
 | |
|           case '+':
 | |
|           case '-':
 | |
|           case '!':
 | |
|           case '~':
 | |
|               action(1);
 | |
|               break;
 | |
|           case ' ':
 | |
|               action(3);
 | |
|               break;
 | |
|           default:
 | |
|               action(isAlphanum(theB) ? 1 : 2);
 | |
|           }
 | |
|           break;
 | |
|       default:
 | |
|         switch (theB) {
 | |
|           case ' ':
 | |
|               action(isAlphanum(theA) ? 1 : 3);
 | |
|               break;
 | |
|           case '\n':
 | |
|               switch (theA) {
 | |
|               case '}':
 | |
|               case ']':
 | |
|               case ')':
 | |
|               case '+':
 | |
|               case '-':
 | |
|               case '"':
 | |
|               case '\'':
 | |
|               case '`':
 | |
|                   action(1);
 | |
|                   break;
 | |
|               default:
 | |
|                   action(isAlphanum(theA) ? 1 : 3);
 | |
|               }
 | |
|               break;
 | |
|           default:
 | |
|               action(1);
 | |
|               break;
 | |
|           }
 | |
|       }
 | |
|   }
 | |
|   return theOutbuf;    
 | |
| }
 | |
| 
 | |
| // old faulty regex minimizer from Code module!!
 | |
| function minimize0 (code) {
 | |
|   // Inline and multi-line comments
 | |
|   var regex4= /\/\*([\S\s]*?)\*\//g;
 | |
|   var regex5= /([^\\}])\\n/g;                     
 | |
|   var regex6= /\/\/[^\n]+/g;
 | |
|    // Newline after {},;
 | |
|   var regex7= /[ ]*([{},; ]|else)[ ]*\n[\n]*/g;
 | |
|   // Filter for string quotes
 | |
|   var regex8= /([^\'"]+)|([\'"](?:[^\'"\\]|\\.)+[\'"])/g;
 | |
|   // Multi-spaces reduction
 | |
|   var regex9= / [ ]+/g;
 | |
|   // relax } <identifier> syntax errors after newline removal; exclude keywords!
 | |
|   var regex10= /}\s+(?!else|finally|catch)([a-zA-Z_]+)/g;      
 | |
|   // relax ) <identifier> syntax errors after newline removal
 | |
|   var regex11= /\)\s+([a-zA-Z_]+)/g; 
 | |
| 
 | |
|   code=code.replace(regex4,"")
 | |
|            .replace(regex5,'$1\n')
 | |
|            .replace(regex5,'$1\n')
 | |
|            .replace(regex6,"")
 | |
|            .replace(regex7,"$1")
 | |
|            .replace(regex8, function($0, $1, $2) {
 | |
|               if ($1) {
 | |
|                 return $1.replace(regex9,' ').replace(regex10,'};$1').replace(regex11,')\n$1');
 | |
|               } else {
 | |
|                 return $2; 
 | |
|               } 
 | |
|             });
 | |
|   return code;
 | |
| }
 | |
| 
 | |
| // compare regex minimizer versa character state machine
 | |
| function test() {
 | |
|   var n=1E4;
 | |
|   var text=minify.toString(); 
 | |
|   var t0=Date.now()
 | |
|   for(var i=0;i<n;i++) {
 | |
|     minify(text);
 | |
|   }
 | |
|   var t1=Date.now();
 | |
|   console.log('minify: '+((t1-t0)/n/text.length*1000)+' us/char');
 | |
|   var t0=Date.now()
 | |
|   for(var i=0;i<n;i++) {
 | |
|     minimize0(text);
 | |
|   }
 | |
|   var t1=Date.now();
 | |
|   console.log('minimize0: '+((t1-t0)/n/text.length*1000)+' us/char');
 | |
|   try {
 | |
|     var M = process.binding('minify');
 | |
|     var t0=Date.now()
 | |
|     for(var i=0;i<n;i++) {
 | |
|       M.minify(text);
 | |
|     }
 | |
|     var t1=Date.now();
 | |
|     console.log('minifyC: '+((t1-t0)/n/text.length*1000)+' us/char');
 | |
|     
 | |
|   } catch (e) {
 | |
|   
 | |
|   }
 | |
| }
 | |
| minify.test=test;
 | |
| 
 | |
| module.exports = minify;
 | |
| 
 |