A 1kb JavaScript "unpacker" and "unminifier". Paste any minified or RegPacked code on top, and it'll appear beautified on the bottom. Ideal for reverse-engineering your favourite js1k demos!
for(_='")Zfunction(a,h){Y/,YX.replace(/W?jWV\\UUd+S|[^R"==QvalP*?GinF++}):E/g,"DD$1Cse|B.Pue/g,Y=[];])@@=j *);jW"D $1 [h]}Qh[0]^;]G;[Un([return Array(dU/C$2Us*.push(a);" Xs s"+g++}(UUUU|UU).joF(" Z+a(U+U+|--).match(/(S) )(cS)*/m).FnerHTML+="<textarea style=width:98%;height:48vh id=bp>"+_;bq>";(onFput=Yj=p;(zWePU((.)U)$/,"j=$1Z)!&&eP(z);csre=f=g=o=0;for(d=1;o!;)o,"/Q(k[\'"]/))?(l.FdexOf(k),n.slice(0,l+1),j[l+1][*]$/)V.*|U*[^]GU*Xc c"+eE!nUdU)U]U}]|true|falBnull|undefFed|this|sS&&n-,;=><+%*&|^~!?!:U[U({]|delete|void|F|caBelB|d|^VR+?Uw*Xr r"+fEjW/,"dZ):j="\'QkV\'\'R\'G\'):jW""R"G"dD/(=+|[-+*%=&|:?<>^!]+),D, }D}([;{}]|(caBdefault).G:)C(Uw)(Uw|U))[Ur]+DforU^] aWDZW;D; Z} +D .G{}?h+="";if("{)++;"}&&d--;})}U]]|if|while|Ud)({[,;)({[}-+Uw]*)(}|[)U]C$2$3!D!{ !C!(|)Dccssrr[h]});q})()';g=/[^ -AH-OT[-~]/.exec(_);)with(_.split(g))_=join(shift());eval(_)
Zm9yKF89JyIpWmZ1bmN0aW9uKGEsaCl7WS8sWVgucmVwbGFjZSgvVz9qV1ZcXFVVZCtTfFteUiI9PVF2YWxQKj9HaW5GKyt9KTpFL2csIkREJDFDc2V8Qi5QdWUfL2csWR49W107HV0pHEBAGz1qGiAqGSk7ahpXGCIYFxlEICQxIBcWW2hdfRgbFVFoWzBdFF47XUc7WxNVbhIoWxFyZXR1cm4QECBBcnJheShkD1UvDkMkMhcMVXMqCy5wdXNoKGEpOxAiGwlYcwlzIitnKyt9CChVVVVVfFVVBykuam9GKCIgIForYQYoVStVK3wtLSkFLm1hdGNoKC8EKFMpHhAgAykLKBtjUykqCw4vbSkCLkZuZXJIVE1MKz0iPHRleHRhcmVhIHN0eWxlPXdpZHRoOjk4JTtoZWlnaHQ6NDh2aCBpZD0BYgFwPiIrXztiAXE+Ijsob25GcHV0PVlqPXAfOyh6GldlUFUoKC4pVSkkLywiaj0kMVopIRomJmVQKHopO2Mdcx1yHWU9Zj1nPW89MDtmb3IoZD0xO28hGjspbxosIi9RKGsaBFtcJyIOXS8pKT8obBouRmRleE9mKGspLG4aLnNsaWNlKDAsbCsxKSxqGltsKzFdBFsOKl0kLylWDg4uKnwOVSpbXl1HVSoOWGMJYyIrZUUhbgQRVWRVKVVdVX1dfHRydWV8ZmFsQm51bGx8dW5kZWZGZWR8dGhpc3wbc1MCJiZuBBEtLDs9PjwrJSomfF5+IT8hOlVbVSh7XXxkZWxldGV8dm9pZHxGfGNhQmVsQhB8G2R8XgJWDgcOUg4cKz8OVXcqWHIJciIrZkVqVw4vLCIbZFopOmo9IlwnUWtWXCcHXCdSXCccR1wnCCk6alciByJSIhxHIggYG2RELxcZKD0rfFstKyolPSZ8Oj88Pl4OIV0rKRYZLBlELCAXfUQSfRcoGVs7e31dfChjYUJkZWZhdWx0KS5HOikZQxIXGQUWKFV3KRkFDAUZKFV3fFUpKQxbVXISXSsZRBIXZm9yC1URExNeXR4QIGFXEkRaVztEOyBafRggK0QgFy5HEXt9HD8SHmgrPSIiO2lmKCJ7FCkPKysGOyJ9FCYmZC0tOw8GfRgRKX1VXV18aWZ8d2hpbGV8VWQpCxEoe1ssOykcDBEoe1t9HAsRLStVd10qKQsoC318WylVXRxDJDIkMxchC0QhFxERexwgIUMhFxIoGRJ8EilEEhcbYwNjFXMDcxVyA3JbaF19KTtxHxp9KSgpJztnPS9bXiAtQUgtT1RbLX5dLy5leGVjKF8pOyl3aXRoKF8uc3BsaXQoZykpXz1qb2luKHNoaWZ0KCkpO2V2YWwoXyk=
// MINI JS BEAUTIFIER
// This is a 1kb JavaScript (ES5) beautifier able to process normal, minified or RegPacked JS code and make it as readable as possible.
// As there's no room for a real JS parser / AST builder, it relies only on regular expressions tests and replaces.
// The main challenge when modifying JS code is to never affect the content of /*comments*/, "strings" or /regular expressions/.
// So the idea is to extract comments, strings and regexes before beautifying the rest of the JS code, and put them back later.
// Though, these elements are ambiguous. For example, it's hard to tell:
// - if a slash (/) is the start or the end of a comment or a regex, a division sign, or just a character in the middle of a string, a comment or a regex.
// - if a quote or double-quote is the start or the end of a string, or just a character in the middle of a string, a comment or a regex.
// - if an antislash is used as a character or in an escape sequence in a var name, a string, a comment or a regex.
// Here's the solution I found:
// - Make a loop, and at each iteration, see which character (slash, quote or double quote) comes first,
// - Use the characters around to decide if it's a division sign or the start of a string, a regex, or a comment,
// - Then isolate the string / regex / comment / divisor sign, and replace it temporarly with a token.
// - Repeat until no more slashes or (double-)quotes are found.
// - The tokens will be replaced with their original at the end of the script.
// Add the textareas in the page (input/output)
// As a demo, the app beautifies its own minified source code, contained in the variable "_"
b.innerHTML += "<textarea style=width:98%;height:48vh id=p>" + _;
b.innerHTML += "<textarea style=width:98%;height:48vh id=q>";
// When JS sode is typed / pasted in the textarea:
(oninput = function(a,b){
// Read the JS code to beautify
j = p.value;
// Unpack regpacked code:
// If the code ends with /eval(.)/, execute everything before the eval and store the result in j
if((z = j.replace(/eval\((.)\)$/, "j=$1")) != j){
eval(z);
}
// Identify comments, regexes and divisions:
c = []; // comments
s = []; // strings
r = []; // regexes
e = f = g = // counters
o = 0; // Code backup
d = 1; // Indentation
// Loop while the code is changed inside the loop
for(; o !=j; ){
// Backup js code
o = j;
// Find first quote, double quote or slash in the code
// and if it's a slash:
if((k = j.match(/['"\/]/)) == "/"){
// Take all the chars preceding the slash
l = j.indexOf(k);
n = j.slice(0,l+1);
// Identidy a comment if the slash is followed by "/" or "*"
if(j[l + 1].match(/[\/*]$/)){
j = j.replace(/\/\/.*|\/\*[^]*?\*\//, function(a,b){
c.push(a);
return "@@c" + (e++);
});
}
// Identify a regex:
// - if we're sure the slash is not a division sign
// (it's a division if slash follows a digit, a variable, a boolean, ")", "}", "]", null, undefined, this (plus optional spaces / line jumps / comments))
// - or if we're sure it's a regex
// (It's a regex if the slash is at the start of the js code or follows an operator, ",", ";", "[", "(", "{", case, delete, else, return, return (plus optional spaces / line jumps / comments):
// Improbable operators and keywords (those who are never actually used before a regex) are ignored
else if(
!n.match(/([\d\)\]\}]|true|false|null|undefined|this|@@s\d+)\s*(@@c\d+)*\s*\//m)
&& n.match(/([-,;=><+%*&|^~!?!:\[\({]|delete|void|in|case|else|return|@@d|^)\s*(@@c\d+)*\s*\//m)
){
j = j.replace(/\/(\\\\|\\\/|[^\/])+?\/\w*/, function(a,b){
r.push(a);
return "@@r" + (f++);
});
}
// Else, assume it's a division sign
else {
j = j.replace(/\//, "@@d");
}
}
// Isolate strings, including escape sequences
// Single quotes
else if(k == "'"){
j = j.replace(/'(\\\\|\\'|[^'])*?'/, function(a,b){
s.push(a);
return "@@s" + (g++);
});
}
// Double quotes
else{
j = j.replace(/"(\\\\|\\"|[^"])*?"/, function(a,b){
s.push(a);
return "@@s" + (g++);
});
}
}
// Put back the division signs
j = j.replace(/@@d/g, "/");
// Now the comments, strings and regexes are extracted and replaced with @@ tokens, and the rest of the code can be beautified:
// Ensure there are spaces around strings, operators ( /, +, -, *, %, =, &, |, :, ?, <, >, ^, !) and composed operators, (>>=, ...)
// (but do not compose "=" with other operators, only with itself. ex: "a=!b" becomes "a = ! b", "a===b" becomes "a === b")
j = j.replace(/ *(=+|[-+*%=&|:?<>^\/!]+) */g, " $1 ");
// Ensure there are spaces after ","
j = j.replace(/ *, */g, ", ");
// Ensure there are line jumps after "}"
j = j.replace(/}/g, "\n}");
// Ensure there are line jumps after ";", "{", "}", "case ...:" and "default :"
j = j.replace(/( *[;{}]|(case|default).*?:) */g, "$1\n");
// Fix spaces around ++ / -- operators
j = j.replace(/ *(\+\+|--) */g, " $1 ");
j = j.replace(/(\w) *(\+\+|--)/g, "$1$2");
j = j.replace(/(\+\+|--) *(\w|\))/g, "$1$2");
// Clean line breaks
j = j.replace(/[\r\n]+ */g, "\n");
// Put for loops on one line and ensure there are spaces after the two ";" inside
j = j.replace(/for\s*\([^;]*?;[^;]*?;[^]/g, function(a,b){
return a.replace(/\n/g, "").replace(/;/g, "; ");
})
// Clean spaces
j = j.replace(/ +/g, " ");
// Reindent all the lines
j = j.replace(/.*?([{}])?\n/g, function(a,b){
// Ensure the match is a string (if it's undefined, that makes an empty string)
b += "";
// After an "{", indent with two more spaces
if(b[0] == "{"){
return Array(d++).join(" ") + a;
}
// After a "}", unindent
else if(b[0] == "}"){
// Uncomment the next line to ensure the indentation is never negative (should only happen on malformed code)
// d &&
d--;
}
return Array(d).join(" ") + a;
});
// Remove spaces and line breaks between "{", "[", "(", ")", "]", "}", ",", ";", ":"
j = j.replace(/([)}\]]|if|while|\d)\s*([({[,;)])/g, "$1$2");
j = j.replace(/([({[}])\s*([-+\w]*)\s*(\s*}|[)\]])/g, "$1$2$3");
j = j.replace(/!\s*/g, "!");
j = j.replace(/([([{]) !/g, "$1!");
// Clean line breaks and empty lines
j = j.replace(/\n( *\n|\n)/g, "\n");
// Put back comments
j = j.replace(/@@c(\d+)/g, function(a,b){
return c[b]
});
// Put back strings
j = j.replace(/@@s(\d+)/g, function(a,b){
return s[b];
});
// Put back regexes
j = j.replace(/@@r(\d+)/g, function(a,b){
return r[b]
});
// Output
q.value = j;
})();
// Final compression:
// - Closure Compiler, advanced mode.
// - Remove line spaces, remove trailing semicolon, put the two parameters (a,h) in all the functions definitions. => 1812b.
// - RegPack with all checkboxes unchecked and score = 2 / 1 / 0 => 1022b.
// Regpack puts the minified code in the variable "_" and evaluates it. (this "_" is the one used in the textarea)