Repository: Siorki/RegPack Branch: master Commit: a710c19a4ebe Files: 133 Total size: 423.1 KB Directory structure: gitextract_zqrzria2/ ├── .gitattributes ├── .gitignore ├── .gitignore.default ├── .npmignore ├── LICENSE ├── README.md ├── TestCases/ │ ├── audioContext_create1.js │ ├── audioContext_create2.js │ ├── audioContext_create3.js │ ├── audioContext_create4.js │ ├── double_renaming.js │ ├── gitHub#17-multipleContexts.js │ ├── gitHub#19-setInterval_declarationAlone.js │ ├── gitHub#19-setInterval_declarationAtBegin1.js │ ├── gitHub#19-setInterval_declarationAtBegin2.js │ ├── gitHub#19-setInterval_declarationAtEnd1.js │ ├── gitHub#19-setInterval_declarationAtEnd2.js │ ├── gitHub#19-setInterval_declarationChained1.js │ ├── gitHub#19-setInterval_declarationChained2.js │ ├── gitHub#19-setInterval_declarationInArray1.js │ ├── gitHub#19-setInterval_declarationInArray2.js │ ├── gitHub#19-setInterval_declarationInFunction1.js │ ├── gitHub#19-setInterval_declarationInFunction2.js │ ├── gitHub#19-setInterval_declarationInFunction3.js │ ├── gitHub#2-unicode.js │ ├── gitHub#30-webglContext_create_charset.js │ ├── gitHub#31-direct-hyphenBeginsBlock.js │ ├── gitHub#31-direct-hyphenEndsBlock.js │ ├── gitHub#31-direct-singleHyphen.js │ ├── gitHub#31-negated-hyphenBeginsBlock.js │ ├── gitHub#31-negated-hyphenEndsBlock.js │ ├── gitHub#31-negated-singleHyphen.js │ ├── gitHub#44-setInterval_arrowFunctionMultiParam.js │ ├── gitHub#44-setInterval_arrowFunctionNoParam.js │ ├── gitHub#44-setInterval_arrowFunctionSingleParam.js │ ├── gitHub#47-packer_from92AndReplace.js │ ├── gitHub#47-packer_from92NoReplace.js │ ├── gitHub#47-packer_from92ReplaceFails.js │ ├── gitHub#47-packer_from93AndReplace.js │ ├── gitHub#47-packer_from93NoReplace.js │ ├── gitHub#47-packer_rangesBeyond.js │ ├── gitHub#47-packer_to92AndReplace.js │ ├── gitHub#47-packer_to92NoReplace.js │ ├── gitHub#47-packer_to93AndReplace.js │ ├── gitHub#47-packer_to93NoReplace.js │ ├── gitHub#47-packer_to93ReplaceFails.js │ ├── gitHub#55-bothQuotesInUse.js │ ├── gitHub#55-quotesAsOnlyTokens.js │ ├── gitHub#55-sameStringInAllQuotes.js │ ├── gitHub#56-setInterval_arrowComplexValues.js │ ├── gitHub#56-setInterval_arrowMultipleValues.js │ ├── gitHub#56-setInterval_arrowNoValue.js │ ├── gitHub#56-setInterval_arrowOneValue.js │ ├── gitHub#56-setInterval_arrowSingleValue.js │ ├── gitHub#56-setInterval_standardComplexValues.js │ ├── gitHub#56-setInterval_standardMultipleValues.js │ ├── gitHub#56-setInterval_standardNoValue.js │ ├── gitHub#56-setInterval_standardOneValue.js │ ├── gitHub#56-setInterval_standardSingleValue.js │ ├── gitHub#57-templateLiteralOneVariable.js │ ├── gitHub#58-lettersOnly.js │ ├── gitHub#58-numberAsMostFrequent.js │ ├── gitHub#59-negatedRangeMerge-1vs3.js │ ├── gitHub#63-backtick2DContext.js │ ├── gitHub#63-backtickWebGLContext.js │ ├── gitHub#64-URIError.js │ ├── gitHub#65-backslash_invalidEscapeSequence.js │ ├── gitHub#73-backslash_unexpandedToken.js │ ├── gitHub#83-backslash_largerOutput.js │ ├── gitHub#83-packer_9Alone.js │ ├── gitHub#85-backslashSequence.js │ ├── gitHub#85-flappyDragon.js │ ├── gitHub#9-hashloop.js │ ├── hash_using_length.js │ ├── webglContext_create1.js │ ├── webglContext_create2.js │ ├── webglContext_create3.js │ ├── webglContext_create4.js │ ├── webglContext_create5.js │ ├── webglContext_create6.js │ └── webglContext_substringHash.js ├── bin/ │ └── regpack ├── changelog.txt ├── contextDescriptor_browser.js ├── contextDescriptor_node.js ├── package.json ├── packerData.js ├── patternViewer.js ├── regPack.html ├── regPack.js ├── shapeShifter.js ├── stringHelper.js ├── tests/ │ ├── allTests.js │ ├── documentMock.js │ ├── runBenchmark.js │ ├── testAudioContextCreate.js │ ├── testIssue0002_UnicodeSupport.js │ ├── testIssue0009_HashLoopVariable.js │ ├── testIssue0017_MultipleContexts.js │ ├── testIssue0019_setInterval.js │ ├── testIssue0030_webGLContextCreate.js │ ├── testIssue0031_hyphenInRegex.js │ ├── testIssue0042_patternViewer.js │ ├── testIssue0044_setIntervalArrowFunction.js │ ├── testIssue0045_closingBracket.js │ ├── testIssue0047_EscapeInCharClass.js │ ├── testIssue0050_unicodeSurrogate.js │ ├── testIssue0055_stringDelimiters.js │ ├── testIssue0056_setIntervalDefaultParams.js │ ├── testIssue0057_replacementInString.js │ ├── testIssue0058_numberAsLoopVariable.js │ ├── testIssue0059_negatedRangeMerge.js │ ├── testIssue0063_backtickFunctionParam.js │ ├── testIssue0064_utf8EncodeURI.js │ ├── testIssue0065_invalidEscapeSequence.js │ ├── testIssue0072_setIntervalNoInitCode.js │ ├── testIssue0074_keepWhiteSpaceSeparator.js │ ├── testIssue0076_listVariablesInString.js │ ├── testIssue0079_CandXMLComments.js │ ├── testIssue0082_backticksInTemplateLiterals.js │ ├── testIssue0083_backslashToken.js │ ├── testIssue0085_backslashSequenceLength.js │ ├── testIssue0087_firstCharacterInPattern.js │ ├── testIssue0088_setIntervalAllocateVariable.js │ ├── testIssue0089_emptyThermalMapping.js │ ├── testIssue0094_missingVariableBlock.js │ ├── testIssue0096_multiLineMinification.js │ ├── testPackingConsistency.js │ ├── testStringHelper.js │ └── testWebGLContextCreate.js ├── thermalViewer.js └── wiki_images/ ├── thermal_view2.xcf └── thermal_view4.xcf ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp *.sln merge=union *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain bin/regpack -crlf ================================================ FILE: .gitignore ================================================ node_modules ================================================ FILE: .gitignore.default ================================================ ################# ## Eclipse ################# *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.vspscc .builds *.dotCover ## TODO: If you have NuGet Package Restore enabled, uncomment this #packages/ # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf # Visual Studio profiler *.psess *.vsp # ReSharper is a .NET coding add-in _ReSharper* # Installshield output folder [Ee]xpress # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish # Others [Bb]in [Oo]bj sql TestResults *.Cache ClientBin stylecop.* ~$* *.dbmdl Generated_Code #added for RIA/Silverlight projects # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML ############ ## Windows ############ # Windows image file caches Thumbs.db # Folder config file Desktop.ini ############# ## Python ############# *.py[co] # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg # Mac crap .DS_Store ================================================ FILE: .npmignore ================================================ TestCases tests ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2012-2016 Siorki 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 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. ------------------------------------------------------------------------------ Code produced by RegPack, including the hashing (if included) and unpacking routines, is not affected by this license. No restriction to its usage or redistribution arise from its compression by RegPack. ================================================ FILE: README.md ================================================ **RegPack** is a packer intended for use on minified Javascript code. Current revision is 5.0.4 It is mostly suited to small size constraints (1kb, up to 4kb). The current version works in seven stages : - *(if enabled)* 2D, WebGL and Audio contexts method shortcuts are defined and used instead of the long, original names. - *(if enabled)* variables are renamed in order to reduce character footprint and free tokens for compression. - *(if enabled)* Initialization code is pushed inside the main loop, with conditional execution on the time variable, so that the unpacker can execute everything through ``setInterval()`` - string quotes are altered in order to minimize escaping inside the strings - redundancies are eliminated and replaced by tokens, as done by JS Crush (by Aivo Paas) and First Crush (by Tim Down). - the token list is turned into a regular expression consisting in a character class. - the tokens are rearranged to create a negated character class (starting with a hyphen ^ then listing nontoken characters) The text boxes show intermediate stage results. Best one gets a green highlight : - Preprocessed : after the first four stages. Hidden if no change was brought to the initial code. - Crushed : after the fifth stage - RegPack'ed : best between last two stages. Depends on how the characters present in the string are spread in the ASCII table. -- ## Usage tips - Toggle method hashing for any type of context you use. If the method renaming yields a longer code, RegPack will revert to the original one. - "Assume global .. is a context" is for environments where the canvas is declared before your code. If entering [js1k](http://www.js1k.com), keep this on, variable is c for classic and g for webgl. - Variable renaming is also performed inside strings, RegPack does not infer whether they are eval()ed or not. Disable if facing issues with this. Only one-letter variables are considered, others are ignored. - Crusher settings alter the choice of strings to compress. 1/0/0 is a good allrounder, although more exotic values can yield better results depending on your code. - Some preprocessing options negatively affect the performance and should be used with caution. Always test your packed code for speed after using these. - "Encapsulate with(Math)" get rid of all "Math." references in the code and enclose the evaluation with(Math). - "run with setInterval()" executes the unpacked code with ``setInterval()`` instead of ``eval()`` (meaning the evaluation is performed every frame). [Use it online](http://siorki.github.io/regPack.html) or integrate it into your Node workflow (thanks to kanaka) ## CLI usage ``` regpack input.js > output.js regpack input.js --crushGainFactor 1 --crushLengthFactor 0 --crushCopiesFactor 0 > output.js ``` From STDIN ``` cat input.js | regpack - > output.js ``` -- ## Running unit tests with Node ``` cd tests node AllTests ``` -- ## License Licensed under [MIT license](http://opensource.org/licenses/mit-license.html). Code produced by RegPack, including the hashing (if included) and unpacking routines, is not affected by the license. No restriction to its usage or redistribution arise from its compression by RegPack. -- Any feedback or improvement suggestions appreciated. Contributions welcome. @Siorki on Twitter ================================================ FILE: TestCases/audioContext_create1.js ================================================ try { this.webAudioSupport = true; if (typeof AudioContext !== "undefined") { this.audioContext = new AudioContext() } else if (typeof webkitAudioContext !== "undefined") { this.audioContext = new webkitAudioContext() } else { this.webAudioSupport = false } } catch (e) { this.webAudioSupport = false } } this.audioContext.createBuffer(2,22050,22050); ================================================ FILE: TestCases/audioContext_create2.js ================================================ var stdContext=null, webkitContext=null; if (typeof AudioContext !== "undefined") { stdContext = new AudioContext() } if (typeof webkitAudioContext !== "undefined") { webkitContext = new webkitAudioContext() } stdContext.createBuffer(1, 22050, 22050); stdContext.createGain(); webkitContext.createChannelMerger(2); var c=15; var d=7; ================================================ FILE: TestCases/audioContext_create3.js ================================================ var stdContext=null, webkitContext=null; if (typeof webkitAudioContext !== "undefined") { webkitContext = new webkitAudioContext() } if (typeof AudioContext !== "undefined") { stdContext = new AudioContext() } stdContext.createBuffer(1, 22050, 22050); stdContext.createGain(); webkitContext.createChannelMerger(2); var c=15; var d=7; ================================================ FILE: TestCases/audioContext_create4.js ================================================ m=[];s=[0,3,5,7,10];pl=0;for(i=0;16>i;i++)m[i]=0;ac=new webkitAudioContext()||new AudioContext();var nb=ac.createBuffer(1,16384,ac.sampleRate);d=nb.getChannelData(0);for(i=0;16384>i;i++)d[i]=2*Math.random()-1;d=ac.createDelay();d.delayTime.value=60/138;g=ac.createGain();g.gain.value=0.5;c=ac.createDynamicsCompressor();d.connect(g);g.connect(d);g.connect(c);c.connect(ac.destination);setInterval(function(){t=ac.currentTime;n=m[pl];0!=n&&(o=ac.createOscillator(),o.frequency.value=440*Math.pow(2,(n-69)/12),o.type="sawtooth",a=ac.createGain(),g=a.gain,g.setValueAtTime(1,t),g.linearRampToValueAtTime(0,t+1),f=ac.createBiquadFilter(),p=f.frequency,p.setValueAtTime(3E3+2E3*Math.random(),t),p.exponentialRampToValueAtTime(20,t+0.6),f.Q.value=5,o.connect(a),a.connect(f),f.connect(d),o.start(t),o.stop(t+0.5));0==pl%4?(o=ac.createOscillator(),f=o.frequency,f.setValueAtTime(200,t),f.exponentialRampToValueAtTime(10,t+0.25),a=ac.createGain(),g=a.gain,g.setValueAtTime(1.25,t),g.linearRampToValueAtTime(0,t+0.5),o.connect(a),a.connect(c),o.start(t),o.stop(t+0.5)):0==(pl+2)%4?(n=ac.createBufferSource(),n.buffer=nb,n.loop=!0,a=ac.createGain(),g=a.gain,g.setValueAtTime(1.25,t),g.linearRampToValueAtTime(0,t+0.2),f=ac.createBiquadFilter(),p=f.frequency,p.setValueAtTime(2500,t),p.exponentialRampToValueAtTime(500,t+0.1),n.connect(a),a.connect(f),f.connect(c),n.start(t),n.stop(t+1)):(o=ac.createBufferSource(),o.buffer=nb,o.loop=!0,f=ac.createBiquadFilter(),f.type="highpass",f.frequency.value=8E3+2E3*Math.random(),f.Q.value=5,o.connect(f),f.connect(c),o.start(t),o.stop(t+0.02));0==pl&&(i=Math.floor(16*Math.random()),0.1>Math.random()?m[i]=0:(j=Math.floor(Math.random()*s.length),o=Math.floor(Math.random()*Math.random()*3),m[i]=40+s[j]+12*o));pl=(pl+1)%16},60/138*1E3/2); ================================================ FILE: TestCases/double_renaming.js ================================================ a.fillRect(0,0,91,91); setInterval( function () { k = 2+2*Math.cos(3.1416*t/64); i=0; while(i<300) { a.strokeStyle = "hsl(20,0%,"+(k*x.charCodeAt(i+300)+(4-k)*x.charCodeAt(i++)-140)/4+"%)"; a.lineWidth=0|(k*x.charCodeAt(i+300)+(4-k)*x.charCodeAt(i++)-140); a.beginPath(); a.moveTo(k*x.charCodeAt(i+300)+(4-k)*x.charCodeAt(i++)-140, k*x.charCodeAt(i+300)+(4-k)*x.charCodeAt(i++)-140); a.lineTo(k*x.charCodeAt(i+300)+(4-k)*x.charCodeAt(i++)-140, k*x.charCodeAt(i+300)+(4-k)*x.charCodeAt(i++)-140); a.stroke(); } w=w?--w:(++t&63)?w:64; }, 40); ================================================ FILE: TestCases/gitHub#17-multipleContexts.js ================================================ a=a.cloneNode(p=[]);cc=a.getContext("2d");cc.fillStyle=n=cc.createRadialGradient(225,75,40,225,75,60);n.addColorStop(t=0,"#332");n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);e=cc.getImageData(0,0,150,150);d=e.data;setInterval(function(){if(t%1200<1){for(x=y=f=s=0;f<256*256;++f)p[f]=Math.min(0,3*Math.cos((f&255)/40)-4*Math.sin(f/12800)-3);for(n=5e5;--n;s=13*s+2+9*Math.cos(s)|0)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=-6;++i;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(t/200);for(y=-75;++y<75;)for(u=Math.sqrt(75*75-y*y)|0,q=4*((y+75)*150+75-u),x=-u;++x";for(j=h;j--;)for(a+="",k=n;k--;)d[i]=d[i]||0,a+="
"+"X\xa0O"[d[i++]+1];B.innerHTML+=a}C.innerHTML="O"}; m=function(l,h){if(e&&!d[h]){if(1==f)for(l.innerHTML="XnO"[c+1],a=3*(d[h]=c),i=3;i--;)for(j=3;j--;){if(d[9*i+j]+d[9*i+j+3]+d[9*i+j+6]==a||d[9*i+3*j]+d[9*i+3*j+1]+d[9*i+3*j+2]==a||d[9*i+0]+d[9*i+4]+d[9*i+8]==a||d[9*i+2]+d[9*i+4]+d[9*i+6]==a||d[3*i]+d[3*i+10]+d[3*i+20]==a||d[3*i+2]+d[3*i+10]+d[18+3*i]==a||d[i]+d[9+i+3]+d[18+i+6]==a||d[i+6]+d[9+i+3]+d[18+i]==a||d[0]+d[13]+d[26]==a||d[2]+d[13]+d[24]==a||d[8]+d[13]+d[18]==a||d[6]+d[13]+d[20]==a||d[3*i+j]+d[9+3*i+j]+d[18+3*i+j]==a){C.innerHTML="XnO"[c+ 1]+"✌";e=0;return}}else if(2==f&&34j&&d[7*i+j]+d[7*i+j+1]+d[7*i+j+2]+d[7*i+j+3]==a||3>i&&d[7*i+j]+d[7*i+j+7]+d[7*i+j+14]+d[7*i+j+21]==a||3>i&&4>j&&d[7*i+j]+d[7*i+j+8]+d[7*i+j+16]+d[7*i+j+24]==a||3>i&&2?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\]^_`abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst ================================================ FILE: TestCases/gitHub#31-direct-hyphenEndsBlock.js ================================================   !'#$%&'() ./0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\]^_`abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst ================================================ FILE: TestCases/gitHub#31-direct-singleHyphen.js ================================================   !'#$%&'()*+, ./012345678:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\]^_`abcdefghijklmnopqrstuvwx {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst ================================================ FILE: TestCases/gitHub#31-negated-hyphenBeginsBlock.js ================================================ - abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst uvwxyz ================================================ FILE: TestCases/gitHub#31-negated-hyphenEndsBlock.js ================================================   - !"#$%&'()*+,!"#$%&'()*+,!"#$%&'()*+,!"#$%&'()*+, ================================================ FILE: TestCases/gitHub#31-negated-singleHyphen.js ================================================ - $%&'$%&'$%&'$%&'$%&'$%&'$%&'$%&' #()+#()+#()+#()+#()+#()+#()+#()+   012345678901234567890123456789 :!<=>?@ ABCDEFGHIJKLMNOPQRSTUVXWYZABCDEFGHIJKLMNOPQRSTUVXWYZ [\]^_` abcdefghijklmnopqrstuvwxyz {|}~ ================================================ FILE: TestCases/gitHub#44-setInterval_arrowFunctionMultiParam.js ================================================ t=0;a=a.cloneNode(p=[s=5]);cc=a.getContext("2d");e=cc.getImageData(0,0,150,150);d=e.data;setInterval((x,y,z)=>{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_from92NoReplace.js ================================================ ' ' '! !#$%&'()*+,-./ 0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_from92ReplaceFails.js ================================================   ! !'#$%&'()*+,-./ 3456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_from93AndReplace.js ================================================   ! !'#$%&'()*+,-./ 3456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\bcdefghijklmnopqrstuvwxyz {|}~ uvwxuvwxuvwx efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_from93NoReplace.js ================================================ ' ' '! !#$%&'()*+,-./ 0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_rangesBeyond.js ================================================   ! !'#$%&'()*+,-./ 0123456789:;<=>? AFGHIJKQRSTUVWXYZ [\]^_`abcdefghijklmnopqrstuvwxyz` {|} abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_to92AndReplace.js ================================================ ' ' '! !#$%&'()*+,-./ 3456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVW ]^_`abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_to92NoReplace.js ================================================ ' ' '! !#$%&'()*+,-./ 0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWX ]^_`abcdefghijklmnopqrstuvwxyz` {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_to93AndReplace.js ================================================ ' ' '! !#$%&'()*+,-./ 3456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVW ^_`abcdefghijklmnopqrstuvwxyz` {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_to93NoReplace.js ================================================ ' ' '! !#$%&'()*+,-./ 0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWX ^_`abcdefghijklmnopqrstuvwxyz {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#47-packer_to93ReplaceFails.js ================================================ ' ' '! !#$%&'()*+,-./ 23456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVW ^_`abcdefghijklmnopqrstuvwxyz` {|}~ abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst cdlzcdlzcdlz k,grk,grk,gr ================================================ FILE: TestCases/gitHub#55-bothQuotesInUse.js ================================================ v=0;setInterval(()=>{s="'";t='"';s='"'+s;t='"'+t;u=(v&1?s:t);u+=t+s+t;z='"'+"'"+t+s+t;console.log(u)}, 40); ================================================ FILE: TestCases/gitHub#55-quotesAsOnlyTokens.js ================================================ ` ` `! !#$%&()*+,-./` 0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\]^_abcdefghijklmnopqrstuvwxyz `{|}~` abcdabcdabcd efghefghefgh ijklijklijkl mnopmnopmnop qrstqrstqrst ================================================ FILE: TestCases/gitHub#55-sameStringInAllQuotes.js ================================================ v=0;setInterval(()=>{s="message";t='message';s=`message`+s;t='message'+t;u=(v&1?s:t);u+=t+s+t;z='message'+"message"+`message`+t+s+t;console.log(u)}, 40); ================================================ FILE: TestCases/gitHub#56-setInterval_arrowComplexValues.js ================================================ t=0;a=a.cloneNode(p=[s=5]);cc=a.getContext("2d");e=cc.getImageData(0,0,150,150);d=e.data;setInterval((w=t,x=y=z=0,aa)=>{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x{if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++x?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\]`_^` abcdefghijklmnopqrstuvwxyz {|~ abcdabcdabcd ================================================ FILE: TestCases/gitHub#63-backtick2DContext.js ================================================ a=a.cloneNode(p=[s=5]);cc=a.getContext`2d`;e=cc.getImageData(t=0,0,150,150);d=e.data;setInterval(function(){if(t%1275<1){cc.fillStyle=n=cc.createRadialGradient(225,75,25,225,75,60);n.addColorStop(x=y=f=0,"rgb("+[50,31+s%15&255,15+s%32&255]);n.addColorStop(1,"#000");cc.fillRect(150,0,150,150);for(;f<256*256;)p[f++]=Math.min(0,3*Math.cos((f&255)/20)-3*Math.cos(f/12800)-s%7+2);for(n=3e5;--n;s*=(256-s)/65)for(x+=[0,1,0,-1][s&3],y+=[0,1,0,-1][3-s&3],t=-6;++t;)for(i=0;++i-6;)p[256*(y+t&255)+(x+i&255)]+=.01}f=Math.cos(++t/200);for(y=-75;++y<75;)for(x=-(u=Math.sqrt(75*75-y*y)&255),q=4*((y+75)*150+75-u);++xdr(6,vertexAttrib1f(1,NO+=.01),3),A=s=>sS(S=cS(FN++),s)|ce(S)|aS(P,S)),!A(`${V="varying lowp vec4 C,U;\n#define R(a)mat2(cos(a),-sin(a),sin(a),cos(a))\n#define D length(vec2(length(q.xy)-2.,q.z))-.1\nvoid main(){"}lowp float t=0.,d,e,f;for(int i=0;i<99;i++){lowp vec3 p=vec3(C.xy*3.,t-3.),q=p;q.yz*=R(U.x-.8);q.xy*=R(U.x-.6);e=max(length(q.xz)-.15,abs(q.y)-2.);q=p;q.xz*=R(.8);q.yz*=R(.6);f=D;q=p;q.xz*=R(-.8);q.yz*=R(.6);f=min(f,D);d=min(e,f);t+=d;if(d<.01){d=1.5-step(length(p),1.)-float(i)/5.;break;}}gl_FragColor=vec4(d,d,d,1);}`),B,!eV(bf(B,cB())),!A(`attribute vec4 A,B;${V}gl_Position=C=A;U=B;}`)),B+82),lo(P),ug(P)) ================================================ FILE: TestCases/gitHub#64-URIError.js ================================================ c.fillText('✪🅼🅼🅼', 5, 165); ================================================ FILE: TestCases/gitHub#65-backslash_invalidEscapeSequence.js ================================================ b.innerHTML=`
🐰
🎩`;k=[];onkeydown=e=>k[e.which-37]=1;onkeyup=e=>k[e.which-37]=0;x=-400;y=-630;a=1.35;m=[0,10240,45022,59464,27080,59658,114174,0];z=1;for(i in m){for(H=17;H--;){if(m[i]>>H&1){s.innerHTML+=`
`;}else s.innerHTML+=`
${[...'🌳🌲🌵'][(i*H+z++)%3]}`;}}s.innerHTML+=`
🥕`;t=0;c=0;setInterval(e=>{t+=.3;v=x;w=y;if(k[2])a-=.05;else if(k[0])a+=.05;else if(k[1]){y+=20*Math.cos(a);x+=20*Math.sin(a)}else if(k[3]){y-=20*Math.cos(a);x-=20*Math.sin(a)}if(!(m[~~(-y/250)]>>(~~(-x/250))&1)){x=v;y=w}h.style.transform=`translateX(${-x-20}px)translateY(${-y-20}px)translateZ(${60-14*Math.sin(t)}px)rotateZ(${-a}rad)rotateX(-90deg)`;if(!c)s.style.transform=`rotateX(55deg)rotateZ(${a}rad)translateX(${x}px)translateY(${y}px)`;else{s.style.transition=`5s`;s.style.transform=`rotateX(30deg)translateX(-2200px)translateY(-2000px)translateZ(-2000px)`;a=0;}for(i in m){for(H=17;H--;){if(self[`t${i+H}`]){if(c)self[`t${i+H}`].style.transition=`5s`;self[`t${i+H}`].style.transform=`translateX(${H*250+99}px)translateY(${i*250-99}px)translateZ(160px)rotateZ(${-a}rad)rotateX(-90deg)scale(2.5)`;}}}if(x<-4100)c=1},33) ================================================ FILE: TestCases/gitHub#73-backslash_unexpandedToken.js ================================================ module.exports=function(n){function e(n,e){for(;n instanceof Array&&n[0]in e&&e[n[0]].M;)n=e[n[0]](...n.slice(1));return n}function t(n,r){for(;;){if(!(n instanceof Array))return i(n,r);if(n=e(n,r),!(n instanceof Array))return i(n,r);if("def"==n[0])return r[n[1]]=t(n[2],r);if("~"==n[0]){let e=t(n[1],r);return e.M=1,e}if("`"==n[0])return n[1];if(".-"==n[0]){let e=i(n.slice(1),r),t=e[0][e[1]];return 2 in e?e[0][e[1]]=e[2]:t}if("."==n[0]){let e=i(n.slice(1),r),t=e[0][e[1]];return t.apply(e[0],e.slice(2))}if("try"==n[0])try{return t(n[1],r)}catch(e){return t(n[2][2],i([n[2][1]],r,[e]))}else if("fn"==n[0]){let e=function(...e){return t(n[2],i(n[1],r,e))};return e.A=[n[2],r,n[1]],e}if("let"==n[0]){r=Object.create(r);for(let e in n[1])e%2&&(r[n[1][e-1]]=t(n[1][e],r));n=n[2]}else if("do"==n[0]){let e=i(n.slice(1,n.length-1),r);n=n[n.length-1]}else if("if"==n[0])n=t(n[1],r)?n[2]:n[3];else{let e=i(n,r),t=e[0];if(!t.A)return t(...e.slice(1));n=t.A[0],r=i(t.A[2],t.A[1],e.slice(1))}}}let i=function(e,i,r){return r?(i=Object.create(i),e.some((n,t)=>"&"==n?i[e[t+1]]=r.slice(t):(i[n]=r[t],0)),i):e instanceof Array?e.map((...n)=>t(n[0],i)):typeof ""==typeof e?e in i?i[e]:n.throw(e+" not found"):e};return n=Object.assign(Object.create(n),{js:eval,eval:(...e)=>t(e[0],n),"=":(...n)=>n[0]===n[1],"<":(...n)=>n[0]n[0]+n[1],"-":(...n)=>n[0]-n[1],"*":(...n)=>n[0]*n[1],"/":(...n)=>n[0]/n[1],isa:(...n)=>n[0]instanceof n[1],type:(...n)=>typeof n[0],new:(...n)=>new(n[0].bind(...n)),del:(...n)=>delete n[0][n[1]],throw:(...n)=>{throw n[0]},read:(...n)=>JSON.parse(n[0]),slurp:(...n)=>require("fs").readFileSync(n[0],"utf8"),load:(...e)=>t(JSON.parse(require("fs").readFileSync(e[0],"utf8")),n),rep:(...e)=>JSON.stringify(t(JSON.parse(e[0]),n))})} ================================================ FILE: TestCases/gitHub#83-backslash_largerOutput.js ================================================ for(b.innerHTML=`
Original source
0+ 1+ 2+ 3+ 4+ 5+ 6+ 7+ 8+ 9+ 10+ 11+ bits/original byte

Attempt method hashing and renaming for 2D canvas context WebGL canvas context AudioContext any object
Assume global variable is a (as in js1k shim)
Reassign variable names to produce consecutive character blocks, except for variables
Enable ES6 features.
Options impacting performance Encapsulate with(Math)
Refactor to run with setInterval(). Use variable for time (leave empty to assign one. Time variable should be zero on the first loop and nonzero afterwards).
RegPack v5.0.4 Score = *gain + *length + *copies     Tiebreaker =
Default settings match built-in formulas for both JS Crush and First Crush. 2/1/0 sometimes achieve better results.

Preprocessed :

Crushed : base64

RegPack'ed: base64

================================================ FILE: regPack.js ================================================ // Node.js : module shapeShifter defines ShapeShifter class (preprocessor) if (typeof require !== 'undefined') { var StringHelper = require('./stringHelper'); var ShapeShifter = require('./shapeShifter'); } function resultMessage(sourceSize, resultSize) { var message = sourceSize+'B'; var prefix = (resultSize>sourceSize?"+":""); if (sourceSize!=resultSize) { message+=' to '+resultSize+'B ('+prefix+(resultSize-sourceSize)+'B, '+prefix+(((resultSize-sourceSize)/sourceSize*1e4|0)/100)+'%)' } return message; } /** * Entry point when running RegPack from node.js, wrapper for the packer * It performs the packing, then returns the best compressed (autoextractible) string * * @param input A string containing the program to pack * @param options An object detailing the different options for the preprocessor and packer * @return A string containing the shortest compressed form of the input */ function cmdRegPack(input, options) { var originalLength = packer.getByteLength(input); var inputList = packer.runPacker(input, options); var methodCount = inputList.length; var bestMethod=0, bestStage = 0, shortestLength=1e8; for (var i=0; i0 ; --tokenCode) { if (tokenCode!=10 && tokenCode!=13 && tokenCode!=92 && tokenCode!= delimiterCode) { var token = String.fromCharCode(tokenCode); if (packedString.indexOf(token)==-1) { tokenList.push(token); } } } // Identify matches - search all string space for possible matches (all strings present more than once) var matches = {}; var found=true; // stop as soon as no substring of length t is found twice for(var t=2;found;++t) { found=false; for(i=0;i0xDFFF) && (endCode<0xD800 || endCode>0xDBFF)) { var j=i; var pattern = packedString.substr(i,t); if(!matches[pattern]) { if(~(j=packedString.indexOf(pattern,j+t))) { found=true; for(matches[pattern]=1;~j;matches[pattern]++) { j=packedString.indexOf(pattern,j+t); } } } } } } // Main loop : replace matches by tokens, one every iteration var tokensInUse = ''; for(var tokenIndex = 0; tokenIndex < tokenList.length ; ++tokenIndex) { var token = tokenList[tokenIndex]; // find the string with the best score (combination of gain, length, copies, and tiebreaker) var bestLength = 0, bestValue = 0, bestMatch = 0, bestGain = 0, bestCopies = 0; for(i in matches){ var stringLength = this.getEscapedByteLength(i); var copies=matches[i]; var gain=copies*stringLength-copies-stringLength-2; // -1 used in JS Crush performs replacement with zero gain value=options.crushGainFactor*gain+options.crushLengthFactor*stringLength+options.crushCopiesFactor*copies; if(gain>0) { if(value>bestValue||bestValue==value&&(gain>bestGain||gain==bestGain&&(options.crushTiebreakerFactor*copies>options.crushTiebreakerFactor*bestCopies))) { // copies>bestCopies JsCrush, copies 1) { // do not keep the strings already replaced by a token var offset = packedString.indexOf(newPattern); if (offset >= 0) { var matchCount = 0; while (offset >= 0) { ++matchCount; offset = packedString.indexOf(newPattern,offset+newLength); } newMatches[newPattern]=matchCount; } } } matches = newMatches; } var firstLine = true; for(i in matches){ var stringLength = this.getEscapedByteLength(i); var copies=matches[i]; var gain=copies*stringLength-copies-stringLength-2; if (gain>0) { if (firstLine) { details += "\n--- Potential gain, but not enough tokens ---\n"; firstLine = false; } var value=options.crushGainFactor*gain+options.crushLengthFactor*stringLength+options.crushCopiesFactor*copies; details+="..( ) : val="+value+", gain="+gain+", N="+copies+", str = "+i+"\n"; packerData.matchesLookup.push({token:"", string:i, originalString:i, depends:'', usedBy:'', gain:gain, copies:copies, len:stringLength, score:value, cleared:false, newOrder:9999}); } } // Implementation for #48 : show the patterns that are "almost" gains firstLine = true; for(i in matches){ var stringLength = this.getEscapedByteLength(i); var copies=matches[i]; var gain=copies*stringLength-copies-stringLength-2; var gainPlusOne=(copies+1)*stringLength-(copies+1)-stringLength-2; if (gain<=0 && gainPlusOne>0) { if (firstLine) { details += "\n--- One extra occurrence needed for a gain --\n"; firstLine = false; } value=options.crushGainFactor*gainPlusOne+options.crushLengthFactor*stringLength+options.crushCopiesFactor*copies; details+=" val="+value+", gain="+gain+"->"+gainPlusOne+" (+"+(gainPlusOne-gain)+"), N="+copies+", str = "+i+"\n"; } } var loopInitCode = ';for(i in G=', loopMemberCode = 'G[i]'; if (options.useES6) { // ES6 : use 'for .. of' construct, which iterates on values, not keys => gain six bytes loopInitCode = ';for(i of'; loopMemberCode = 'i'; } // escape the backslashes present in the code packedString = this.stringHelper.matchAndReplaceAll(packedString, false, '\\', '\\\\', '', '', 0, transform); // escape the occurrences of the string delimiter present in the code packedString = this.stringHelper.matchAndReplaceAll(packedString, false, packerData.packedStringDelimiter, '\\'+packerData.packedStringDelimiter, "", "", 0, transform); //var packedString = packedString.replace(new RegExp(packerData.packedStringDelimiter,"g"),'\\'+packerData.packedStringDelimiter); // and put everything together var unpackBlock1 = packerData.packedCodeVarName+'='+packerData.packedStringDelimiter; var unpackBlock2 = packerData.packedStringDelimiter +loopInitCode+packerData.packedStringDelimiter+tokensInUse+packerData.packedStringDelimiter +')with('+packerData.packedCodeVarName+'.split('+loopMemberCode+'))' +packerData.packedCodeVarName+'=join(pop('; var unpackBlock3 = '));'; var envMapping = [ { inLength : packedString.length, outLength : packedString.length, complete : false}, { chapter : 1, rangeIn : [0, packedString.length], rangeOut : [0, unpackBlock1.length + unpackBlock2.length + unpackBlock3.length] } ]; transform.push(envMapping); var output = unpackBlock1 + packedString + unpackBlock2 + packerData.wrappedInit + unpackBlock3 + packerData.environment + packerData.interpreterCall; return [this.getByteLength(output), output, details, transform]; }, /** * Clears a match from matchesLookup for dependencies in the PackerData * - removes the corresponding token from the use list of other matches * - sets the "cleared" flag to true * * @param packerData A PackerData structure holding the input string and setup, including the matches array * @param matchIndex index of the match to clear */ clear : function(packerData, matchIndex) { var oldToken = packerData.matchesLookup[matchIndex].token; for (var j=0;j-1) { packerData.matchesLookup[j].originalString = packerData.matchesLookup[j].originalString.split(packerData.matchesLookup[i].token).join(packerData.matchesLookup[i].originalString); } if (i!=j && packerData.matchesLookup[j].originalString.indexOf(packerData.matchesLookup[i].originalString)>-1) { packerData.matchesLookup[j].depends += packerData.matchesLookup[i].token; packerData.matchesLookup[i].usedBy += packerData.matchesLookup[j].token; } } } /** debug only for (i=0; i-1) { // do not start a block with CR nor LF, as the first character of the block // needs to be written to the regexp and those are not writable (or require escaping) if (firstInLine == 10 || firstInLine == 13) { ++firstInLine; } var lastInLine = i-1; // for the same reason, do not end a block with CR nor LF (watch out, the end of the block is the index before i) if (i==11 || i==14) { --lastInLine; } if (lastInLine>=firstInLine) { // skip if there is only CR or LF in the range var tokenCount = lastInLine-firstInLine+1; var range = this.stringHelper.writeRangeToRegexpCharClass(firstInLine, lastInLine); var containsBackslash = (firstInLine<=92 && i>92); tokenList.push({first:firstInLine, last:lastInLine, count:tokenCount, cost:range.length, oneByteTokenCount:tokenCount-(containsBackslash?1:0)}); } firstInLine = -1; } } } if (firstInLine >-1) { var range = this.stringHelper.writeRangeToRegexpCharClass(firstInLine, i-1); tokenList.push({first:firstInLine, last:i-1, count:i-firstInLine, cost:range.length, oneByteTokenCount:i-firstInLine-(firstInLine<=92?1:0)}); } // Reorder the block list. #47 introduces escaped characters, making the order a bit more complex : // - longest blocks (as in largest token count) first // - when tied for length, non escaped characters first (shortest representation in char class) // - when tied again, readable characters (32-127) first tokenList.sort(function(a,b) { return 10*b.oneByteTokenCount-b.cost+b.first/1000- (10*a.oneByteTokenCount-a.cost+a.first/1000); }); var costOneTokens = [], costTwoTokens = []; for (var tokenLine=0; tokenLine-1; ++count) { index=regPackOutput.indexOf(packerData.matchesLookup[j].originalString, index+1); } var gain = count*(packerData.matchesLookup[j].len-tokenCost)-packerData.matchesLookup[j].len-2*tokenCost; var score = options.crushGainFactor*gain+options.crushLengthFactor*packerData.matchesLookup[j].len+options.crushCopiesFactor*count; if (gain>=0) { if (score>bestScore||score==bestScore&&(gain>bestGain||gain==bestGain&&(options.crushTiebreakerFactor*count>options.crushTiebreakerFactor*bestCount))) { // R>N JsCrush, R-1) { // a string was chosen, replace it with the current token var matchedString = packerData.matchesLookup[matchIndex].originalString; packerData.matchesLookup[matchIndex].newOrder = packerData.tokenCount; var token = String.fromCharCode(tokenCode); details+=token.charCodeAt(0)+"("+token+"), gain="+bestGain+", N="+bestCount+", str = "+matchedString+"\n"; regPackOutput = this.stringHelper.matchAndReplaceAll(regPackOutput, false, matchedString, token, matchedString+token, "", 0, transform); // remove dependencies on chosen string/token this.clear(packerData, matchIndex); // define the replacement token ++packerData.tokenCount; if (packerData.tokenCount >= availableTokens.length) { tokensRemaining = false; // bail out early details+="Out of tokens\n"; } } else { // remaining strings, but no gain : skip them and end the loop for (var j=0; j= availableTokens.length) { // all available tokens in use (no replacement performed) tokenLine = tokenList.length -1 ; tokenIndex = tokenList[tokenList.length-1].count; } else { var lastTokenUsed = availableTokens[packerData.tokenCount-1]; var lineFound = false; while (!lineFound && tokenLine < tokenList.length) { // #83 : if a range starts or end in \, and it is not actually used, replace it with the previous or next character if (unusedBackslash && tokenList[tokenLine].first == 92) { // remove unused \ at the beginning of a range ++tokenList[tokenLine].first; --tokenList[tokenLine].count; } if (unusedBackslash && tokenList[tokenLine].last == 92) { // remove unused \ at the end of a range --tokenList[tokenLine].last; --tokenList[tokenLine].count; } if (lastTokenUsed >= tokenList[tokenLine].first && lastTokenUsed <= tokenList[tokenLine].last) { // no need to consider that 2-byte token \ can be in the middle of a range // since it is used last, and we would be in the case where packerData.tokenCount exceeds costOneTokens.length lineFound = true; tokenIndex = lastTokenUsed - tokenList[tokenLine].first + 1; } else { ++tokenLine; } } } // Safeguard, should never happen (i.e. last token use is not found in ranges) if (tokenLine >= tokenList.length) { details += "Exception : token out of range\nFinal check : failed"; return [-1, "", details, transform]; } // #47 introduces escaped characters (2 bytes instead of 1) as tokens // Ranges with such a character at the beginning or end thus cost one extra byte to define // If the last range used has leftover tokens (more tokens available than needed), // we use those tokens at no cost to replace the escaped ones. // for instance, last range is A-E but only used up to C, and we have range W-\\ before // by substituting D for \\, we end up with [W-[A-D] which is one byte shorter than [W-\\A-C] // #83 amends that by forcing \ to be always the last token to be considered // Since it is the last one, there are no leftovers to replace it // The only possible replaced token is thus ] // First identify if we have leftover tokens in the last range var remainingTokens = tokenList[tokenLine].count - tokenIndex; // Force the last range to its actual length - in case it ends with a \ or ] but not all tokens are used tokenList[tokenLine].last -= remainingTokens; tokenList[tokenLine].count = tokenIndex; if (remainingTokens > 0) { var tokensToReplace = []; // then look for escaped character ] (93) at the beginning or end of a range for (var i=0; i<=tokenLine; ++i) { if (tokenList[i].first == 93) { tokensToReplace.push({rangeIndex : i , atBeginning : true, count : 1}); } else if (tokenList[i].last == 93) { tokensToReplace.push({rangeIndex : i , atBeginning : false, count : 1}); } } // The only token to replace is ] (93), but we kept the code that handles multiple tokens for (var i=0; i= tokensToReplace[i].count) { // substitute as many tokens as required (1 or 2) for (var j=0; j1) { var newFirstRange = tokenList.splice(1, 1); tokenList.unshift(newFirstRange[0]); } // build the character class var tokenString = this.stringHelper.writeBlocksToRegexpCharClass(tokenList.slice(0, tokenLine+1)); // escape the backslashes in the compressed code (from original code or added as token) // #65 : do it now and not earlier so it applies on token \ as well var checkedString = this.stringHelper.matchAndReplaceAll(regPackOutput, false, '\\', '\\\\', '', '', 0, transform); // escape the occurrences of the string delimiter present in the code checkedString = this.stringHelper.matchAndReplaceAll(checkedString, false, packerData.packedStringDelimiter, '\\'+packerData.packedStringDelimiter, "", "", 0, transform); // and add the unpacking code to the compressed string var unpackBlock1 = 'for('+packerData.packedCodeVarName+'='+packerData.packedStringDelimiter; var unpackBlock2 = packerData.packedStringDelimiter+';G=/['+tokenString+']/.exec('+packerData.packedCodeVarName +');)with('+packerData.packedCodeVarName+'.split(G))'+packerData.packedCodeVarName+'=join(shift('; var unpackBlock3 = '));'; var envMapping = [ { inLength : checkedString.length, outLength : checkedString.length, complete : false}, { chapter : 1, rangeIn : [0, checkedString.length], rangeOut : [0, unpackBlock1.length + unpackBlock2.length + unpackBlock3.length] } ]; transform.push(envMapping); regPackOutput = unpackBlock1 + checkedString + unpackBlock2 + packerData.wrappedInit + unpackBlock3 + packerData.environment + packerData.interpreterCall; var resultSize = this.getByteLength(regPackOutput); // check that unpacking the string yields the original code details+="------------------------\nFinal check : "; checkedString = checkedString.replace(new RegExp('\\\\'+packerData.packedStringDelimiter,'g'), packerData.packedStringDelimiter); checkedString = checkedString.replace(/\\\\/g, '\\'); var regToken = new RegExp("["+tokenString+"]",""); for(var token="" ; token = regToken.exec(checkedString) ; ) { var k = checkedString.split(token); checkedString = k.join(k.shift()); } var success = (checkedString == packerData.contents); details+=(success ? "passed" : "failed")+".\n"; return [resultSize, regPackOutput, details, transform]; }, /** * Returns true if the character is not allowed in a RegExp char class or as a token (cannot be inserted per se, requires a sequence instead) * Forbidden characters are LF, CR, 127 * ' and " are allowed since #55, unless they are the delimiter for the packed string * * @param ascii The ASCII or Unicode value of the character * @return true if the matching character is forbidden as token, false if it is allowed */ isForbiddenCharacter : function(ascii) { return ascii==10||ascii==13||ascii==127; }, /** * Returns the number of forbidden characters in the interval [first-last] * Characters : same as in isForbiddenCharacter(), and the packed string delimiter * However, the packed string delimiter is forbidden as a token too * * @see isForbiddenCharacter * @param first : ASCII code of the first character in the interval, inclusive * @param last : ASCII code of the last character in the interval, inclusive * @param delimiterCode : ASCII code of the packed string delimiter (counts as forbidden) * @return the number of forbidden characters in the interval */ countForbiddenCharacters : function (first, last, delimiterCode) { var count=0; for (var i=first; i<=last; ++i) { count+=(this.isForbiddenCharacter(i)||i==delimiterCode)?1:0; } return count; }, /** * Third stage : build the shortest negated character class regular expression * (a char class beginning with a ^, such as [^A-D] which comprises everything but characters A, B, C and D) * @param packerData A PackerData structure holding the input string and setup * @return array [length, packed string, log] */ packToNegatedRegexpCharClass : function(packerData) { // Build a list of characters used inside the string (as ranges) // characters not in the list can be // - forbidden as tokens (LF, CR, 127) although these are allowed in the string too // - used as compression tokens // - neither used as compression tokens (if there are leftovers) nor in the string // those can be included in the RegExp without affecting the output var details = ''; var transform = []; var usedCharacters = []; var forbiddenCharacters = []; var firstInLine = -1; var availableCharactersCount = 0; for(i=1;i<128;++i) { var token = String.fromCharCode(i); if (packerData.contents.indexOf(token)>-1) { if (firstInLine ==-1) { firstInLine = i; } } else { if (firstInLine >-1) { usedCharacters.push({first:firstInLine, last:i-1, size:Math.min(i-firstInLine,3)}); firstInLine = -1; } if (this.isForbiddenCharacter(i) || i==packerData.packedStringDelimiter.charCodeAt(0)) { forbiddenCharacters.push(token); } else { ++availableCharactersCount; } } } if (firstInLine >-1) { usedCharacters.push({first:firstInLine, last:i-1, size:Math.min(i-firstInLine,3)}); } // Issue #2 : unicode characters handling var inputContainsUnicode = false; for (i=0;i127); } if (inputContainsUnicode) { // non-ASCII as a whole block. Those characters are not allowed as tokens, // and the block can be merged later to save bytes usedCharacters.push({first:128, last:65535, size:3}); } details = availableCharactersCount + " available tokens, "+packerData.tokenCount+" needed.\n" var regExpString = ""; for (i in usedCharacters) { j=usedCharacters[i]; var rangeString = this.stringHelper.writeRangeToRegexpCharClass(j.first, j.last); // Fix for issue #31 : if a token line consists in a single "-", // add it at the beginning of the character class instead of appending it if (j.size==1 && j.first==45) { // 45 is '-' regExpString=rangeString+currentCharClass; } else { regExpString+=rangeString; } } details+="initial expression : "+regExpString+"\n"; // Now, shorten the regexp by sacrificing some characters that will not be used as tokens. // The second stage yielded the actual number of tokens required. // The initial regexp lists all characters present in the string to compress. Since it is // used with an initial negation ^, it will match on all other characters. // Characters are split into used by the strings, tokens, and unused // This step iterates on the RegExp, merging ranges to reduce its length. // Characters between the ranges are included, thus lost as tokens. // For instance, [A-K] is shorter than [A-CG-K] but loses D,E,F as potential tokens // The process is repeated while there are enough tokens left. var margin = availableCharactersCount - packerData.tokenCount; var delimiterCode = packerData.packedStringDelimiter.charCodeAt(0); // #59 : start with merging all ranges with cost 0 for (blockIndex=0; blockIndex merge var gain = currentBlock.size+nextBlock.size-3; currentBlock.last=nextBlock.last; currentBlock.size=3; usedCharacters.splice(1+blockIndex, 1); // log the improvement details +="gain "+gain+" for 0, margin = "+margin+", "; var currentCharClass = this.stringHelper.writeBlocksToRegexpCharClass(usedCharacters); details += currentCharClass+"\n"; } } // #59 : now test every possibility to bridge gaps by merging ranges // the variable bridgeMap represents, in binary, the gaps that will be bridged in the current round // bit at 0 = leave gap, bit at 1 = bridge by merging neighboring ranges // If that removes more tokens than our margin allows, ignore that candidate and continue to next round // Otherwise, keep the shortest expression of all rounds var gapCount = usedCharacters.length - 1; var bestRegExpLength = 127; // length can never exceed 1 byte per character in the ASCII range var bestRegExpString = ""; var bestBlockMap = []; details += gapCount+" gaps, testing "+(1<=0 && ascii<128 && this.isCharAllowedInVariable(ascii)) { options.varsNotReassigned[ascii] = true; } } } // #74 , #96 : minification now performed inside the preprocessor, not as a hidden step in the main entry point var minifiedInput = this.minify(input); var inputData = new PackerData ('', minifiedInput); if (options.withMath) { // call module : Define environment this.defineEnvironment(inputData); } var inputList = [ inputData ]; if (options.wrapInSetInterval) { // call module : wrap with setInterval this.refactorToSetInterval(inputData, options); } else { // map the bytes of the default interpreter call "eval(_)" to the entire code var envMapping = [ { inLength : inputData.contents.length, outLength : inputData.contents.length, complete : false}, { chapter : 4, rangeIn : [0, inputData.contents.length], rangeOut : [0, inputData.interpreterCall.length] } ]; inputData.thermalMapping.push(envMapping); } // Hash and rename methods of the 2d canvas context // - method hashing only // - method and property // then store the results in the inputList if (options.hash2DContext) { for (var count=inputList.length, i=0; i -1; // all non-blanks are copied // #74 : so are all blanks between expressions or keywords // multiple blanks between expressions are shortened, only the last one is kept let doCopy = (!isBlank) || (this.isCharAllowedInVariable(previousCharCode) && this.isCharAllowedInVariable(nextCharCode)); //console.log(currentChar.charCodeAt(0)+" ("+currentChar+") "+doCopy); if (doCopy || inString) { // #96 : no change inside a string output+=currentChar; previousCharCode = currentChar.charCodeAt(0); } } if (testForSequenceEnd) { if (input.substr(i,closingSequence.length) == closingSequence) { i+= closingSequence.length-1; closingSequence = ""; inRemovedSequence = false; inString = false; } } } // Remove any semicolon located right before a block ends output = output.replace(/;}/g, "}"); return output; }, /** * Modifies the environment execution of the unpacked code, wrapping it into with(Math). * Removes all references to Math. in the input code * @param inputData (in/out) PackerData structure containing the code to refactor and setup * @return nothing. Result of refactoring is stored in parameter inputData. */ defineEnvironment : function(inputData) { inputData.environment = 'with(Math)'; var envMapping = { chapter : 2, rangeOut : [0, inputData.environment.length] }; inputData.contents = this.stringHelper.matchAndReplaceAll(inputData.contents, false, 'Math.', '', '', '', envMapping, inputData.thermalMapping); }, /** * Rewrites the input code so that it can entirely be executed inside * a setInterval() loop without prior initialization. * * Detects the function that is currently called through setInterval() * and strips it. Wraps the code before that function into a if sequence * at the beginning of the loop, that will only be run once. * * The method makes use of a "time" variable that usually controls * the flow of execution/rendering, and is increased at each loop. * It needs to be zero on the first run to trigger the initialization * sequence, then nonzero on the subsequent runs. * * Output (refactored code and log) is stored in parameter inputData. * Setup for the unpacking routine is also changed in the same object. * * @param inputData (in/out) PackerData structure containing the code to refactor and setup * @param options options set, see below for use details * @return nothing. Result of refactoring is stored in parameter inputData. * Options used are : * - timeVariableName : the variable containing time, or empty string to allocate one * - varsNotReassigned : boolean array[128], true to avoid altering variable * */ refactorToSetInterval : function(inputData, options) { var input = inputData.contents; var output = input; // initialized from input, in case we bail out early var timeVariableName = options.timeVariableName; var varsNotReassigned = options.varsNotReassigned; var details = "----------- Refactoring to run with setInterval() ---------------\n"; var timeVariableProvided = true; // implementation for #44 : match arrow function syntax (new in ES6) // regular expression matches pre-ES5 syntax : function(params){...} var loopMatch = input.match(/setInterval\(function\(([\w\d.=,]*)\){/); var functionDeclaration = "function("; if (!loopMatch) { // regular expression matches ES6 syntax : (params)=>{...} loopMatch = input.match(/setInterval\(\(([\w\d.=,]*)\)=>{/); functionDeclaration = ")=>"; } if (!loopMatch) { // regular expression matches ES6 syntax : one_param=>{...} loopMatch = input.match(/setInterval\(([\w\d.]*)(=>){/); functionDeclaration = "=>"; } if (loopMatch) { var initCode = input.substr(0, loopMatch.index); // remove any trailing comma or semicolon if (initCode[initCode.length-1]==';' || initCode[initCode.length-1]==',') { initCode = input.substr(0, initCode.length-1); } details += "First "+loopMatch.index+" bytes moved to conditional sequence.\n"; // parameters of the function passed to setInterval() : extract default values // The regex matches a variable declaration, without value assignment (no "="), // at the beginning, end, or between two commas var paramsCode = loopMatch[1]; var paramsExp = /(^|,)[A-Za-z$_][\w$_]*(,|$)/; var paramsMatch = paramsExp.exec(paramsCode); while (paramsMatch && paramsMatch[0] != "") { // if the variable is between two commas, keep one : ",k," becomes "," var keptCommas = (paramsMatch[1]+paramsMatch[2]).length>1 ? "," : ""; paramsCode = paramsCode.substr(0, paramsMatch.index)+keptCommas+paramsCode.substr(paramsMatch.index+paramsMatch[0].length); paramsMatch = paramsExp.exec(paramsCode); } // end by a semicolon if there are any initializations that will be added to the main loop code paramsCode += (paramsCode != "" ? ";" : ""); if (timeVariableName=="") { timeVariableProvided = false; timeVariableName = this.allocateNewVariable(inputData, options); details += "Using variable "+timeVariableName+" for time.\n"; } // Strip the declaration of the time variable from the init code, // as it will be defined in the unpacking routine instead. var timeDefinitionBegin = initCode.length; var timeDefinitionEnd = timeDefinitionBegin; var timeDefinitionExp = new RegExp("(^|[^\\w$])"+timeVariableName+"=","g"); var timeDefinitionMatch=timeDefinitionExp.exec(initCode); if (timeDefinitionMatch) { timeDefinitionBegin = timeDefinitionMatch.index+timeDefinitionMatch[1].length; timeDefinitionEnd = timeDefinitionBegin+2; // Check if we can strip more than "t=" depending on what comes before and after : // - Brackets means no : the declaration is used as an argument in a function // - Square brackets means no : used to define an array // - Both commas before and after : same context, inside function or array // - a leading = means no : multiple variables are defined at the same time // - anything other than "0" after is no // - other configurations are ok to remove up to the separator var canRemoveInitValue = true; var leadingChar = timeDefinitionBegin>0 ? initCode[timeDefinitionBegin-1] : ""; var trailingChar = initCode[timeDefinitionEnd]; var furtherChar = timeDefinitionEnd+10 && index0) { inString = 0; } ++index; } var finalCode = input.substr(index); var delayMatch = finalCode.match(/,([\w\d.=]*)\);?/); if (delayMatch) { finalCode = finalCode.substr(delayMatch[0].length); if (finalCode.length) { details += "Last "+finalCode.length+" bytes also moved there.\n"; finalCode = (initCode.length > 0 ? ";" : "")+finalCode; } details += "Interval of "+delayMatch[1]+ "ms pushed to unpacking routine.\n"; // wrap the initialization code into a conditional sequence : // - if(!t){/*init code*/} if the variable is used (and set) afterwards // - if(!t++){/*init code*/} if it is created only for the test // - #72 : nothing if there is no init code var wrapperCode = "", wrapperEnd = ""; if (initCode.length + finalCode.length > 0) { wrapperCode = "if(!"+timeVariableName+(timeVariableProvided?"":"++")+"){"; wrapperEnd = "}"; } var mainLoopCode = input.substr(loopMatch.index+loopMatch[0].length, index-loopMatch.index-loopMatch[0].length-1); // Redefine the "offset zero" of our transformed code, // used to hash methods/properties of contexts provided by shim inputData.initialDeclarationOffset = wrapperCode.length; output = wrapperCode + initCode + finalCode + wrapperEnd + paramsCode + mainLoopCode; inputData.interpreterCall = 'setInterval(_,'+delayMatch[1]+')'; inputData.wrappedInit = timeVariableName+'=0'; // Special case : the assignment of the time variable is done // as a parameter of setInterval() // (featured in 2012 - A rose is a rose) if (delayMatch[1].indexOf(inputData.wrappedInit) != -1) { // in this case, no need to declare the variable again inputData.wrappedInit = ""; details += timeVariableName+" initialized as parameter to setInterval, kept as is.\n"; } var functionDeclarationOffset = loopMatch.index+loopMatch[0].indexOf(functionDeclaration); // Record the change in the thermal transform var transform = [ { inLength : input.length, outLength : output.length, complete : true } ]; // "if(!t){" : mapped to "function(" transform.push ( { chapter : 0, rangeIn : [functionDeclarationOffset, functionDeclaration.length], rangeOut : [0, wrapperCode.length] }); var rangeOutBegin = wrapperCode.length; // code before the main loop, before the time variable declaration, mapped to itself transform.push ( { chapter : 0, rangeIn : [0, timeDefinitionBegin], rangeOut : [rangeOutBegin, timeDefinitionBegin] }); rangeOutBegin += timeDefinitionBegin; var initSecondHalfLength = 0; if (timeDefinitionMatch) { // code before the main loop, after the time variable declaration, mapped to itself // may omit the final "," or ";", if any, that is eliminated = mapped to nothing initSecondHalfLength = initCode.length-timeDefinitionBegin; transform.push ( { chapter : 0, rangeIn : [timeDefinitionEnd, initSecondHalfLength], rangeOut : [rangeOutBegin, initSecondHalfLength] }); rangeOutBegin += initSecondHalfLength; } if (finalCode.length) { // code after the main loop, mapped to itself transform.push ( { chapter : 0, rangeIn : [index+delayMatch[0].length, finalCode.length], rangeOut : [rangeOutBegin, finalCode.length] }); rangeOutBegin += finalCode.length; } // "}" : mapped to final "}" of the main loop, if any transform.push ( { chapter : 0, rangeIn : [index-1, 1], rangeOut : [rangeOutBegin, wrapperEnd.length] }); rangeOutBegin += wrapperEnd.length; // parameters of the loop function, mapped to themselves var paramsOffset = loopMatch.index+loopMatch[0].indexOf(loopMatch[1]); transform.push ( { chapter : 0, rangeIn : [paramsOffset, loopMatch[1].length], rangeOut : [rangeOutBegin, paramsCode.length] }); rangeOutBegin += paramsCode.length; // code in the loop function, mapped to itself transform.push ( { chapter : 0, rangeIn : [loopMatch.index+loopMatch[0].length, mainLoopCode.length], rangeOut : [rangeOutBegin, mainLoopCode.length] }); // "setInterval(" : map to original declaration var blockLength = "setInterval(".length; transform.push ( { chapter : 3, rangeIn : [loopMatch.index, blockLength], rangeOut : [0, blockLength] }); // "_" : mapped to "function(" transform.push ( { chapter : 3, rangeIn : [functionDeclarationOffset, functionDeclaration.length], rangeOut : [blockLength, 1] }); // ",nn)" : map to original declaration transform.push ( { chapter : 3, rangeIn : [index, delayMatch[0].length], rangeOut : [blockLength+1, delayMatch[1].length+2] }); // time declaration variable "t=0" : map to original declaration if any, to function declaration otherwise if (timeDefinitionMatch) { transform.push ( { chapter : 4, rangeIn : [timeDefinitionBegin, timeDefinitionEnd-timeDefinitionBegin], rangeOut : [0, inputData.wrappedInit.length] }); } else { transform.push ( { chapter : 4, rangeIn : [functionDeclarationOffset, functionDeclaration.length], rangeOut : [0, inputData.wrappedInit.length] }); } inputData.thermalMapping.push(transform); } else { // delayMatch === false details += "Unable to find delay for setInterval, module skipped.\n"; } } else { details += "setInterval() loop not found, module skipped.\n"; } details += "\n"; // output stored in inputData parameter instead of being returned inputData.contents = output; inputData.log += details; }, /** * Performs an optimal hashing and renaming of the methods/properties of a canvas 2d context. * Uses a context reference passed from a shim (if provided), plus attempts to * identify all contexts created within the code. * Returns an array containing two sub-arrays, * each in the same format as the compression methods : * [output length, output string, informations], * even if the preprocessing actually lenghtened the string. * * * @param inputData (constant) PackerData structure containing setup data and the code to preprocess * @param options (constant) options set, see below for use details * Options used are : * - contextType : type of context provided by shim : 0 for 2D, 1 for GL * - contextVariableName : the variable holding the context if provided by shim, false otherwise * - varsNotReassigned : boolean array[128], true to avoid altering variable * @return an array containing branched (and hashed) PackerData, empty if no 2d context definition is found in the code. */ preprocess2DContext : function(inputData, options) { // Obtain all context definitions (variable name and location in the code) var objectNames = [], objectOffsets = [], objectDeclarationLengths = [], searchIndex = 0; var variableName = (options.contextType==0?options.contextVariableName:false); var varsNotReassigned = options.varsNotReassigned; // Start with the preset context, if any if (variableName) { objectNames.push(variableName); objectOffsets.push(inputData.initialDeclarationOffset); objectDeclarationLengths.push(0); } // Then search for additional definitions inside the code. Keep name, declaration offset, and declaration length var input = inputData.contents; var declarations = input.match (/([\w\d.]*)=[\w\d.]*\.getContext\(?[`'"]2d[`'"]\)?/gi); if (declarations) { for (var declIndex=0; declIndex " + methodsInUse[methodIndex] + "\n"; } } } } } details += "\n"; // Choose the index variable for the hashing loop var loopVarResult = this.getMostFrequentLoopVariable(input, varsNotReassigned); var indexName = loopVarResult[0]; details += loopVarResult[1]; // operation log // Create the final hashing expression by replacing the placeholder variables var expression = this.hashFunctions[bestIndex][0].replace(/w/g, indexName); expression = expression.replace(/x/g, bestXValue); expression = expression.replace(/y/g, bestYValue); // If the input code uses mostly "" as string delimiters, use it as well in the expression instead of '' if (input.split('"').length>input.split("'").length) { expression = expression.replace(/'/g, '"'); } // Determine where in the code the hashing loop will be inserted. // (at the declaration of the last context that gets hashed). var offset = 0, loopContext = 0, shortestContext = 0; for (var contextIndex=0; contextIndex=0) { // replace only if the gain is positive offset = objectOffsets[contextIndex]; // perform the replacement at the latest context definition loopContext = contextIndex; // retrieve the context with the shortest name, will be used // as right-member of the hashing assignment if (objectNames[contextIndex].length < objectNames[shortestContext].length) { shortestContext = contextIndex; } } } var outputIntro = hashedCode.substr(0, offset); // Insert the hashing/renaming loop in the code. // If the context definition is not included (js1k shim for instance), the loop is prepended to the code and ends with ";" // otherwise the loop replaces and includes the last declaration. The code looks like : // for (i in c=a.getContext('2d'))c[...]=c[i] with a single context // for (i in c=a.getContext('2d'))c[...]=b[...]=b[i] with multiple contexts (surprising, but it works) // The ending separator is kept, unless it is a comma ",", in which case it is replaced with a semicolon ";" // (to avoid including the trailing code in the loop) var declarationLength = objectDeclarationLengths[loopContext]; var outputInitBlock1 = "for("+indexName+" in "+(declarationLength==0?objectNames[loopContext]:""); var outputInitBlock2 = (declarationLength==0?"":hashedCode.substr(offset, declarationLength)); var outputInitBlock3 = ")"; //output+="for("+indexName+" in "+(declarationLength==0?objectNames[loopContext]:hashedCode.substr(offset, declarationLength))+")"; for (var contextIndex=0; contextIndex=0) { outputInitBlock3+=objectNames[contextIndex]+"["+expression+"]="; } } outputInitBlock3+=objectNames[shortestContext]+"["+indexName+"]"; if (hashedCode[offset+declarationLength]==",") { // replace the trailing "," with ";" as explained above outputInitBlock3+=";"; ++declarationLength; } outputInitBlock3+=(declarationLength==0?";":""); var outputMain = hashedCode.substr(offset+declarationLength); var output = outputIntro + outputInitBlock1 + outputInitBlock2 + outputInitBlock3 + outputMain; // Record the renaming loop in the thermal transform var transform = [ { inLength : hashedCode.length, outLength : output.length, complete : true } ]; if (offset) { // code before the context declaration and renaming loop, kept unchanged transform.push ( { chapter : 0, rangeIn : [0, offset], rangeOut : [0, offset] }); } var rangeOutBegin = offset; if (declarationLength) { // context declaration done inside the code // "for (i in " : map to entire hashed code transform.push ( { chapter : 0, rangeIn : [offset+declarationLength, outputMain.length], rangeOut : [offset, outputInitBlock1.length] }); rangeOutBegin += outputInitBlock1.length; // "context=canvas.getContext('2d')" : map as is transform.push ( { chapter : 0, rangeIn : [offset, outputInitBlock2.length], rangeOut : [rangeOutBegin, outputInitBlock2.length] }); rangeOutBegin += outputInitBlock2.length; // ")c[...]=c[i]" : map to entire hashed code transform.push ( { chapter : 0, rangeIn : [offset+declarationLength, outputMain.length], rangeOut : [rangeOutBegin, outputInitBlock3.length] }); rangeOutBegin += outputInitBlock3.length; } else { // context declaration done beforehand (in the shim) // "for (i in c)c[...]=c[i];" : map to entire hashed code transform.push ( { chapter : 0, rangeIn : [offset, outputMain.length], rangeOut : [offset, outputInitBlock1.length+outputInitBlock2.length+outputInitBlock3.length] }); rangeOutBegin += outputInitBlock1.length+outputInitBlock2.length+outputInitBlock3.length; } // code using the hashed contexts transform.push( { chapter : 0, rangeIn : [offset+declarationLength, outputMain.length], rangeOut : [rangeOutBegin, outputMain.length] }); inputData.thermalMapping.push(transform); // output stored in inputData parameter inputData.contents = output; inputData.log = details; return true; }, /** * Identifies the optimal hashing function (the one returning the shortest result) * then renames all the properties with their respective hash, and preprends the hashing code. * * The hashing loop looks like : for(i in c)c[i[0]+[i[6]]=i; * The new properties in c contain the full name of actual properties and methods * meaning that later one may call c[c.fc](...) instead of c.fillRect(...) * or c[c.fy] instead of c.fillStyle * Unlike renameObjectMethods(), this works on properties and methods alike. * * Replacement is performed at the last object assignment(graphic or audio context), * or at the beginning for shim context, hence the offset parameter. * * If there are several contexts, only one hash is used. It is applied to * all or only some of the contexts, depending on the computed gain. * The algorithm will not define different hashes for the multiple * contexts. The rationale behind this is the assumption that the lesser * gain from using the same hash for all will be offset by the better * compression - as the repeated hashing pattern will be picked up by the * packer. * * Returns an array in the same format as the compression methods : [output length, output string, informations], * * @param inputData (in/out) PackerData structure containing the code to refactor and setup * @param objectNames : array containing variable names of context objects, whose methods to rename in the source string * @param objectOffsets : array, in the same order, of character offset to the beginning of the object declaration. Zero if defined outside (shim) * @param objectDeclarationLengths : array, in the same order, of lengths of the object declaration, starting at offset. Zero if defined outside (shim) * @param referenceProperties : an array of strings containing property names for the appropriate context type * @param varsNotReassigned : boolean array[128], true to keep name of variable * @return the result of the renaming as an array [output length, output string, informations] */ hashObjectProperties : function(inputData, objectNames, objectOffsets, objectDeclarationLengths, referenceProperties, varsNotReassigned) { var input = inputData.contents; var details = inputData.log; var propertiesInUseByContext=[]; for (var contextIndex=0; contextIndex" : w); } for (w in reverseLookup) { forwardLookup[reverseLookup[w]]=w; // keep only the property names with no collisions } var allScores = [], totalScore = 0; for (var contextIndex=0; contextIndexbestTotalScore) { bestTotalScore = totalScore; bestScoreByContext = allScores; bestIndex = functionIndex; bestXValue = xValue; bestYValue = yValue; } } } } // bail out early if no gain if (bestTotalScore < 0) { details += "Best hash loses "+(-bestTotalScore)+" bytes, skipping.\n"; return [this.getByteLength(input), input, details]; } // best hash function (based on byte gain) found. Apply it var reverseLookup = [], forwardLookup = []; for (var index=0; index" : w); } for (w in reverseLookup) { forwardLookup[reverseLookup[w]]=w; } var hashedCode = input; // Tell the user what is being replaced, and what is ignored var renamedList = "", notRenamedList = ""; for (var contextIndex=0; contextIndex=0) { renamedList += (renamedList.length>0?", " : "") + objectNames[contextIndex]; } else { notRenamedList += (notRenamedList.length>0?", " : "") + objectNames[contextIndex]; } } if (notRenamedList.length>0) { details += "No renaming for "+notRenamedList+"\n"; } if (renamedList.length>0) { details += "Renamed properties for "+renamedList+"\n"; } // Determine the context with the shortest name // It will be used to store the hashes // (at the declaration of the last context that gets hashed). var shortestContext = 0; for (var contextIndex=0; contextIndex=0) { // replace only if the gain is positive var propertiesInUse = propertiesInUseByContext[contextIndex]; for (var propertyIndex=0; propertyIndex " + propertiesInUse[propertyIndex] + "\n"; } } } } details += "\n"; // Choose the index variable for the hashing loop var loopVarResult = this.getMostFrequentLoopVariable(input, varsNotReassigned); var indexName = loopVarResult[0]; details += loopVarResult[1]; // operation log // Create the final hashing expression by replacing the placeholder variables var expression = this.hashFunctions[bestIndex][0].replace(/w/g, indexName); expression = expression.replace(/x/g, bestXValue); expression = expression.replace(/y/g, bestYValue); // If the input code uses mostly "" as string delimiters, use it as well in the expression instead of '' if (input.split('"').length>input.split("'").length) { expression = expression.replace(/'/g, '"'); } // Determine where in the code the hashing loop will be inserted. // (at the declaration of the last context that gets hashed). var offset = 0, loopContext = 0; for (var contextIndex=0; contextIndex=0) { // replace only if the gain is positive offset = objectOffsets[contextIndex]; // perform the replacement at the latest context definition loopContext = contextIndex; } } var outputIntro = hashedCode.substr(0, offset); // Insert the hashing/renaming loop in the code. // If the context definition is not included (js1k shim for instance), the loop is prepended to the code and ends with ";" // otherwise the loop replaces and includes the last declaration. The code looks like : // for (i in c=a.getContext('2d'))c[...]=i with a single context // for (i in c=a.getContext('2d'))c[...]=b[...]=i with multiple contexts // The ending separator is kept, unless it is a comma ",", in which case it is replaced with a semicolon ";" // (to avoid including the trailing code in the loop) var declarationLength = objectDeclarationLengths[loopContext]; var outputInitBlock1 = "for("+indexName+" in "+(declarationLength==0?objectNames[loopContext]:""); var outputInitBlock2 = (declarationLength==0?"":hashedCode.substr(offset, declarationLength)); var outputInitBlock3 = ")"; //output+="for("+indexName+" in "+(declarationLength==0?objectNames[loopContext]:hashedCode.substr(offset, declarationLength))+")"; outputInitBlock3+=objectNames[shortestContext]+"["+expression+"]="+indexName; if (hashedCode[offset+declarationLength]==",") { // replace the trailing "," with ";" as explained above outputInitBlock3+=";"; ++declarationLength; } outputInitBlock3+=(declarationLength==0?";":""); var outputMain = hashedCode.substr(offset+declarationLength); var output = outputIntro + outputInitBlock1 + outputInitBlock2 + outputInitBlock3 + outputMain; // Record the renaming loop in the thermal transform var transform = [ { inLength : hashedCode.length, outLength : output.length, complete : true } ]; if (offset) { // code before the context declaration and renaming loop, kept unchanged transform.push ( { chapter : 0, rangeIn : [0, offset], rangeOut : [0, offset] }); } var rangeOutBegin = offset; if (declarationLength) { // context declaration done inside the code // "for (i in " : map to entire hashed code transform.push ( { chapter : 0, rangeIn : [offset+declarationLength, outputMain.length], rangeOut : [offset, outputInitBlock1.length] }); rangeOutBegin += outputInitBlock1.length; // "context=canvas.getContext('2d')" : map as is transform.push ( { chapter : 0, rangeIn : [offset, outputInitBlock2.length], rangeOut : [rangeOutBegin, outputInitBlock2.length] }); rangeOutBegin += outputInitBlock2.length; // ")c[...]=i" : map to entire hashed code transform.push ( { chapter : 0, rangeIn : [offset+declarationLength, outputMain.length], rangeOut : [rangeOutBegin, outputInitBlock3.length] }); rangeOutBegin += outputInitBlock3.length; } else { // context declaration done beforehand (in the shim) // "for (i in c)c[...]=i;" : map to entire hashed code transform.push ( { chapter : 0, rangeIn : [offset, outputMain.length], rangeOut : [offset, outputInitBlock1.length+outputInitBlock2.length+outputInitBlock3.length] }); rangeOutBegin += outputInitBlock1.length+outputInitBlock2.length+outputInitBlock3.length; } // code using the hashed contexts transform.push( { chapter : 0, rangeIn : [offset+declarationLength, outputMain.length], rangeOut : [rangeOutBegin, outputMain.length] }); inputData.thermalMapping.push(transform); // output stored in inputData parameter inputData.contents = output; inputData.log = details; }, /** * Returns the total byte length of a string * 1 for ASCII char * 3 for Unicode (UTF-8) * Issue #5 : final size when featuing unicode characters * * @param inString the string to measure * @return the UTF-8 length of the string, in bytes */ getByteLength : function (inString) { return encodeURI(inString).replace(/%../g,'i').length; }, /** * Returns true if the character code is allowed in the name of a variable. * Allowed codes are 36($), 48-57(0-9), 65-90(A-Z), 95(_), 97-122(a-z) * * @param charCode ASCII code of the character (variables with Unicode > 127 are not accepted) * @return true if the character is allowed in the name of a variable, false otherwie */ isCharAllowedInVariable : function (charCode) { return (charCode>64 && charCode<91) || (charCode>96 && charCode<123) || (charCode>47 && charCode<58) || charCode==36 || charCode==95; }, /** * Returns true if the character code is a digit * @param charCode ASCII or Unicode value of the character * @return true for digits (0 to 9 = ASCII 48 to 57), false otherwise */ isDigit : function (charCode) { return (charCode>47 && charCode<58); }, /** * Identifies and returns the one-letter variable that is the * most common occurrence as a loop variable in a for(..;..;..) loop * * If no loop is found in the code, returns "i" as default. * Protected variables (from param varsNotReassigned) are not counted * as they should not be reassigned (issue #9), as the goal is to * create another for loop using the same variabled. * * Called by both hashing methods to assign a name to the * renaming loop, in order to benefit most from compression. * @see renameObjectMethods * @see hashObjectProperties * * @param input the code to parse for loop variables * @param varsNotReassigned (from options) boolean array[128], true to avoid altering variable * @return an array [ variable name (String), log ] */ getMostFrequentLoopVariable : function(input, varsNotReassigned) { // Choose the index variable for the added hashing code // The algorithm counts every instance of "for(*" with individual letters replacing the star // then chooses the letter with the most matches, in order to benefit most from compression var log ="Loop variables :\n"; var indexMatches = new Array(128) ; // #58 : only choose legal variable names, not digits var loopIndexRegExp = /for\(([A-Za-z_$])/g; var loopIndexResult=loopIndexRegExp.exec(input); while (loopIndexResult) { // get a set with a unique entry for each property var code = loopIndexResult[1].charCodeAt(0); if (!varsNotReassigned[code]) { // issue #9 : skip protected variable indexMatches[code] = (indexMatches[code]||0)+1; } loopIndexResult=loopIndexRegExp.exec(input); } var indexName="i"; // default name var maxMatches = 0; for (var i=0; i<128; ++i) { if (indexMatches[i]>maxMatches) { maxMatches = indexMatches[i]; indexName = String.fromCharCode(i); } } for (var i=0; i<128; ++i) { if (indexMatches[i]) { log += String.fromCharCode(i)+" *"+indexMatches[i]+(indexName == String.fromCharCode(i)?" <-- selected":"")+"\n"; } } if (maxMatches == 0) { log += "No relevant loop found, defaulting to "+indexName+"\n"; } log += "\n"; return [ indexName, log ]; }, /** * Defines and returns a packer-friendly name for a new variable. * * It first lists characters used in keywords but not in existing variables. * If none is found, it takes the first character not assigned to a variable. * If none is available, it returns a two-letter variable. * * @param inputData (constant) PackerData structure containing the input code * @param options options set, used by discriminateKeywordsAndVariables() * @return the name of the new variable, as a string * @see discriminateKeywordsAndVariables */ allocateNewVariable : function(inputData, options) { var keywordsAndVariables = this.discriminateKeywordsAndVariables(inputData, options); var keywordChars = keywordsAndVariables[0]; var variableChars = keywordsAndVariables[1]; var availableChars = keywordsAndVariables[2]; // first, characters already used by functions, keywords .. for (var i=33; i<128; ++i) { if (availableChars[i]) { return String.fromCharCode(i); } } // then, one-letter names not used by variables for (var i=33; i<128; ++i) { if (!variableChars[i] && this.isCharAllowedInVariable(i) && !this.isDigit(i)) { return String.fromCharCode(i); } } // if still not, try two-letter names var input = inputData.contents; for (var i=97; i<122; ++i) { for (var j=97; j<122; ++j) { name = String.fromCharCode(i,j); if (input.indexOf(name)==-1) { return name; } } } return "__"; }, /** * Identify the characters used in user variable names * and those used in keywords. Unicode characters are not supported. * * The result can be used for renaming variables or to * allocate a new variable name * @see reassignVariableNames * @see allocateNewVariable * * @param inputData PackerData structure containing the setup and the code to process * @param options options set, see below for use details * Options used are : * - varsNotReassigned : boolean array[128], true to avoid altering variable * @return array [ keywords, variables, available for new variables, variables only ], each is a boolean [128] * - keywords : true if the character is present in keywords, false otherwise * - variables : true if the character is already used as a variable, false otherwise * - available : true if the character is present in keywords but not used as a variable, false otherwise */ discriminateKeywordsAndVariables : function(inputData, options) { var varsNotReassigned = options.varsNotReassigned; var variableChars = [], keywordChars = [], availableChars = [], variableCharsOnly = []; var previousChar = 0; var letterCount = 0; var isKeyword = false; var input = inputData.contents; for (var i=0; i<128; ++i) { variableChars[i] = keywordChars[i] = availableChars[i] = variableCharsOnly[i] = false; } // Identify characters used in the code : // - those used only in keywords, method names. // - those used as one-letter variable or function names only (and candidates to renaming) // - those used for both variable names and within keywords var stringIndex = 0, templateLiteralIndex = 0; for (var offset=0; offset1) { isKeyword=true; keywordChars[previousChar]=true; keywordChars[currentChar]=true; } else { if (previousChar == 46) { // character . isKeyword=true; keywordChars[currentChar]=true; } } } } else { // only consider one-letter variables or functions. // Do not include digits, they are not permitted as one-letter variable names. // Do not include in variables if preceded by a dot, which would indicate member variable or function if (letterCount==1 && !this.isDigit(previousChar) && !isKeyword) { variableChars[previousChar]=true; } letterCount=0; isKeyword=false; } previousChar=currentChar; } } // Identify as available all characters used in keywords but not variables // And those that should be reassigned, if the options is set // #86 : factored here as available variable names are used in multiple places for (var i=33; i<128; ++i) { availableChars[i] = keywordChars[i] && !variableChars[i] && this.isCharAllowedInVariable(i) && !this.isDigit(i); variableCharsOnly[i] = variableChars[i] && !keywordChars[i] && !varsNotReassigned[i]; } return [ keywordChars, variableChars, availableChars, variableCharsOnly ]; }, /** * Renames one-letter variables in the code, in order to group characters in consecutive blocks as much as possible. * This will leave larger empty blocks for tokens, so that the character class that represents them * in the final regular expression will be shorter : a block is represented by three characters (begin, * dash, end), no matter now many are comprised in between. * This operation does not change the length of the code nor its inner workings. * * The method : * - lists all the one-letter variables in the code * - identifies those using a character that is not otherwise present in classes or keywords * (meaning that renaming them will actually free the character to use as a token) * - identifies all the characters in use by classes or language keywords * - reassign those to variables, if available (not used by other variables) * - if there are remaining variables in need of a name, fill gaps in the ASCII table, starting with the shortest ones. * (for instance, if a,c,d,e,g,h are in use, it will assign b and f, since [a-h] is shorter than [ac-egh] * * @param inputData (in/out) PackerData structure containing the setup and the code to process * @param options options set, see below for use details * @return nothing. Result of refactoring is stored in parameter inputData. * Options used are : * - varsNotReassigned : boolean array[128], true to avoid altering variable */ reassignVariableNames : function (inputData, options) { var varsNotReassigned = options.varsNotReassigned; var keywordsAndVariables = this.discriminateKeywordsAndVariables(inputData, options); var keywordChars = keywordsAndVariables[0]; var variableChars = keywordsAndVariables[1]; var availableChars = keywordsAndVariables[2]; var variableCharsOnly = keywordsAndVariables[3]; var input = inputData.contents; var details = "----------- Renaming variables to optimize RegExp blocks --------\n"; details += "All variables : "; var availableCharList = ""; var formerVariableCharList = ""; for (var i=32; i<128; ++i) { if (variableChars[i]) { details+=String.fromCharCode(i); } // Identify as available all characters used in keywords but not variables if (availableChars[i]) { availableCharList+=String.fromCharCode(i); } // List all variables that can be reassigned a new name. // This excludes those with a one-letter name already used in a keyword (no gain in renaming them) // and those explicitely excluded from the process by the user. if (variableCharsOnly[i]) { formerVariableCharList+=String.fromCharCode(i); } } var detailsSub1 = availableCharList.length ? availableCharList : "(none)"; var detailsSub2 = formerVariableCharList.length ? formerVariableCharList : "(none)"; details +="\n"; details += "in keywords only : " + detailsSub1 + "\nin variables only : " + detailsSub2 + "\n\n"; // Prepare to rename variables // If we have more variables to rename (characters used as variable names only) // than characters available (used in keywords only, but not as variables - yet), // we need to allocate more characters, among those not in use : // - either those not used at all in the code // - or those used by variables only (which means not renaming the variable by that name) // // The algorithm attempts to reduce the block count (i.e. chooses characters in between two blocks, that are thus merged) // so that the regular expression for tokens can be as short as possible. if (availableCharList.length < formerVariableCharList.length) { var lettersNeeded = formerVariableCharList.length - availableCharList.length; // identify blocs of unused characters var unusedBlocks = []; var blockStartsAt = -1; for (var i=1; i<128; ++i) { if (!keywordChars[i] && !varsNotReassigned[i]) { // not present in code, or used for naming variables only : add to candidate characters // variables not reassigned are not included in the pool (we do not want to rename another variable to that) if (blockStartsAt==-1) { blockStartsAt = i; } } else { // present in code, ends block if started if (blockStartsAt!=-1) { unusedBlocks.push( {first:blockStartsAt, nextToLast:i}); blockStartsAt = -1; } } } // Fix for #94 : add the lask block if it was initialized if (blockStartsAt!=-1) { unusedBlocks.push( {first:blockStartsAt, nextToLast:i}); } // There will always be enough characters, since we count those we are trying to eliminate // In the worst case, variables will be renamed to themselves, with no loss. // Sort the blocks, shortest to longest. // Fix for #29 : added tiebreaker (ASCII order) to get a consistent result // (weight of 1/1000 so it does not interfere with the main comparison) unusedBlocks.sort( function(a,b) { return (a.nextToLast-a.first)-(b.nextToLast-b.first)+.001*(a.first-b.first); } ); detailsSub1 = "Adding letters : "; detailsSub2 = "Not renaming : "; var blockIndex = 0; while (lettersNeeded) { for (var i=unusedBlocks[blockIndex].first; lettersNeeded>0 && i-1) { // variable name already in use : do not rename it formerVariableCharList = formerVariableCharList.substr(0,indexInFormerList) +formerVariableCharList.substr(indexInFormerList+1); detailsSub2+=variableName; } else { detailsSub1+=variableName; availableCharList+=variableName; } --lettersNeeded; } } ++blockIndex; } details+=detailsSub1+"\n"+detailsSub2+"\n"; } details += formerVariableCharList.length?"Renaming variables : \n":"No variables to rename.\n"; var output = input; // Perform the replacement inside all relevant strings for (var i=0; i "+availableCharList[i]+"\n"; } // output stored in inputData parameter instead of being returned inputData.contents = output; inputData.log += details; }, /** * Recognize all strings and template literals inside the input code * * Offset to beginning and end are stored into a table, along with * - delimiters used : ", ' or `(since ES6) * - other delimiters present inside the string * - if the string definition and allocation is standalone, and could thus be extracted * * Fields from the PackerData are cleared before they are filled, making the function safe to call multiple times * * @param inputData (in/out) PackerData structure containing the setup and the code to process * @param silent Boolean, true to keep logs untouched, false to add strings to the logs * @return nothing. Result is stored inside parameter inputData (containedStrings and containedTemplateLiterals) */ identifyStrings : function (inputData, silent) { var details = "\nStrings present in the code :\n"; var inString = false; var currentString = false; var currentTemplateLiteral = false; var input = inputData.contents; var escaped = false; var insideTemplateLiteral = 0; var currentChar = 0; inputData.containedStrings = []; inputData.containedTemplateLiterals = []; for (var i=0; i0) { // 125 } if (currentString.delimiter==96) { // 96 ` // #82 : inside a template literal, backticks do not terminate the string --insideTemplateLiteral; if (!insideTemplateLiteral) { currentTemplateLiteral.end=i; inputData.containedTemplateLiterals.push(currentTemplateLiteral); } } } escaped = (currentChar==92 && !escaped); } if (inputData.containedTemplateLiterals.length) { details += "\nTemplate literals present in the code :\n"; } for (var i=0; iinputData.containedStrings[i].end)) { allStringDelimiters.push("`"); } for (var stringDelimiter of allStringDelimiters) { var stringGain = 0; var stringDelimCode = stringDelimiter.charCodeAt(0); stringGain = inputData.containedStrings[i].characterCount[inputData.containedStrings[i].delimiter]; stringGain += inputData.containedStrings[i].delimiter==delimCode ? 2 : 0; stringGain -= inputData.containedStrings[i].characterCount[stringDelimCode]; stringGain -= stringDelimCode==delimCode ? 2 : 0; // give a slight malus when changing the delimiter of a string // so if the cost is tied, the solution that changes the least strings is preferred stringGain -= (inputData.containedStrings[i].delimiter == stringDelimCode ? 0 : 0.01); if (stringGain > bestStringGain) { bestNewQuote = stringDelimiter; bestStringGain = stringGain; } } cost -= bestStringGain; } newStringQuotes.push(bestNewQuote); } details += " "+delimiter+" : cost = "+cost+"\n"; if (cost < bestCost) { bestDelimiter = delimiter; bestCost = cost; bestNewStringQuotes = newStringQuotes.slice(); } } inputData.packedStringDelimiter = bestDelimiter; // perform replacement in strings // we cannot use stringHelper.matchAndReplaceAll() as only some instances of the string are replaced in the code var currentTransform = []; var newInput = "", offset = 0; for (var i=0; i "+newDelimiter+"\n"; inputData.containedStrings[i].begin = newInput.length; inputData.containedStrings[i].delimiter = newDelimiter; var replacementString = quote + rawString + quote; stringMapping.rangeOut[1] = replacementString.length; // map to actual length newInput += replacementString; inputData.containedStrings[i].end = newInput.length-1; } offset = currentString.end+1; currentTransform.push(stringMapping); } newInput += inputData.contents.substr(offset); let intervalMapping = { // transform : iso mapping of the space at the end chapter : 0, rangeIn : [offset, inputData.contents.length-offset], rangeOut: [newInput.length, inputData.contents.length-offset] }; currentTransform.push(intervalMapping); inputData.contents = newInput; inputData.log+=details+"\n"; } } // Node.js exports (for packer) if (typeof require !== 'undefined') { module.exports = ShapeShifter; } ================================================ FILE: stringHelper.js ================================================ /** * StringHelper provides utility functions to operator on strings and regular expressions. * * It is stateless and implements the singleton pattern. * * JS singleton implementation based on * http://tassedecafe.org/fr/implementer-design-pattern-singleton-javascript-1023 */ var StringHelper = (function() { var constructor = function() { /** * Count bytes in a string's UTF-8 representation. * Code by 200_success at http://codereview.stackexchange.com/questions/37512/count-byte-length-of-string * * @param normal_val : input string * @return (int) string byte length */ this.getByteLength = function (normal_val) { // Force string type normal_val = String(normal_val); var byteLen = 0; for (var i = 0; i < normal_val.length; i++) { var c = normal_val.charCodeAt(i); byteLen += c < (1 << 7) ? 1 : c < (1 << 11) ? 2 : c < (1 << 16) ? 3 : c < (1 << 21) ? 4 : c < (1 << 26) ? 5 : c < (1 << 31) ? 6 : Number.NaN; } return byteLen; } /** * Count bytes in a character's UTF-8 representation inside a string * Code similar to getByteLength() except for character \ thar must be escaped and thus costs 2 * @see getByteLength * @param unicode : character's Unicode valude * @return (int) character byte length */ this.getCharacterLength = function (unicode) { var byteLen = unicode < (1 << 7) ? 1 : unicode < (1 << 11) ? 2 : unicode < (1 << 16) ? 3 : unicode < (1 << 21) ? 4 : unicode < (1 << 26) ? 5 : unicode < (1 << 31) ? 6 : Number.NaN; byteLen += (unicode==92 ? 1 : 0); return byteLen; } /** * Encode a string to base64 * Replacement for btoa() which does not handle correctly characters beyond 0xff * * Code from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding * See also http://stackoverflow.com/questions/246801/how-can-you-encode-a-string-to-base64-in-javascript * * @param str String to encode * @return base64 representation of the string */ this.unicodeToBase64 = function(str) { return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { return String.fromCharCode('0x' + p1); })); } /** * Decode a string from base64 * Replaces atob() which does not handle correctly characters beyond 0xff * * Code from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding * * @param str base64 string to decode * @return original decoded string */ this.base64ToUnicode = function(str) { return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } /** * Returns whether the contents of the input at the given offset is a string or actual code * Actual code means either : * - outside of a string * - inside a string AND inside a template literal * * The provided PackerData must have been run through identifyStrings() first. * * @param offset byte offset within the input contents * @param inputData (constant) PackerData containing the input string and the string identification */ this.isActualCodeAt = function(offset, inputData) { var stringIndex = 0, templateLiteralIndex = 0; while (stringIndex < inputData.containedStrings.length && inputData.containedStrings[stringIndex].end < offset) { ++stringIndex; } while (templateLiteralIndex < inputData.containedTemplateLiterals.length && inputData.containedTemplateLiterals[templateLiteralIndex].end < offset) { ++templateLiteralIndex; } var insideString = (stringIndex < inputData.containedStrings.length && inputData.containedStrings[stringIndex].begin < offset && offset < inputData.containedStrings[stringIndex].end); var insideTemplateLiteral = (templateLiteralIndex < inputData.containedTemplateLiterals.length && inputData.containedTemplateLiterals[templateLiteralIndex].begin < offset && offset < inputData.containedTemplateLiterals[templateLiteralIndex].end); return (!insideString) || insideTemplateLiteral; } /** * Replace all instances of a substring, and record the changes in a transform function * Use instead of String.replace(/.../g) to get the mapping function needed for the heatwave view. * * Specific version of matchAndReplaceFirstAndAll where all occurrences are replaced with the same text * @see matchAndReplaceFirstAndAll * * @param input input string before replacements (unmodified) * @param matchExp regular expression to search for (false to match originalText as is) * @param originalText substring to replace within the regex match * @param replacementText substring to substitute to all instances of originalText * @param prefix string to prepend to the output, mapped to all matches (use "" if none) * @param suffix string to append to the output, mapped to all matches (use "" if none) * @param extraMapping string from another chapter, mapped to all matches, not added here (false if none) * @param thermalMap array of all successive mapping functions (modified) * @return the string with all replacements performed */ this.matchAndReplaceAll = function(input, matchExp, originalText, replacementText, prefix, suffix, extraMapping, thermalMap) { return this.matchAndReplaceFirstAndAll(input, matchExp, originalText, replacementText, replacementText, prefix, suffix, extraMapping, thermalMap); } /** * Replace all instances of a substring, and record the changes in a transform function * Use instead of String.replace(/.../g) to get the mapping function needed for the heatwave view. * * The matching operation is either done with a string search (set matchExp to false) * or with a regular expression that is first matched, then the originalText is * searched and replaced inside the regex match. * * The crusher and packer can prepend and append a dictionary entry to the string. * In the mapping, this prefix or suffix is mapped to all replaced strings. * * The first match has a different replacement string than the subsequent ones * This is intended to define a dictionary entry or a variable allocation in the first one. * In this case, the extra characters (the cost of the allocation) are mapped to all replaced strings. * * @param input input string before replacements (unmodified) * @param matchExp regular expression to search for (false to match originalText as is) * @param originalText substring to replace within the regex match * @param firstReplacementText substring to substitute to the first instance of originalText * @param otherReplacementsText substring to substitute to all but the first instance of originalText * @param prefix string to prepend to the output, mapped to all matches (use "" if none) * @param suffix string to append to the output, mapped to all matches (use "" if none) * @param extraMapping string from another chapter, mapped to all matches, not added here (false if none) * @param thermalMap array of all successive mapping functions (modified) * @return the string with all replacements performed */ this.matchAndReplaceFirstAndAll = function(input, matchExp, originalText, firstReplacementText, otherReplacementsText, prefix, suffix, extraMapping, thermalMap) { var mappingFunction = []; var allRangesIn = []; var output = prefix; var inputPointer = 0; var originalTextLength = originalText.length; var replacementTextLength = otherReplacementsText.length; var initialAllocationLength = otherReplacementsText.length - firstReplacementText.length; var firstReplacementMapping = false; var replacedCopies = 0; var offset = -1; if (matchExp) { let nextMatch = matchExp.exec(input); if (nextMatch) { let offsetInMatch = nextMatch[0].indexOf(originalText); offset = nextMatch.index + offsetInMatch; } } else { offset = input.indexOf(originalText, inputPointer); } while (offset >= 0) { if (offset>inputPointer) { // there is an interval between two replaced blocks. Register it into the mapping let intervalMapping = { chapter : 0, rangeIn : [inputPointer, offset-inputPointer], rangeOut: [output.length, offset-inputPointer] }; mappingFunction.push(intervalMapping); output+= input.substring(inputPointer, offset); } // register the replaced text into the mapping // #86 : always use the length of the 2nd+ replacement, // as the extra from the first one, if any, corresponds to the allocation // and will be split on all matches on a dedicated mapping let matchMapping = { chapter : 0, rangeIn : [offset, originalTextLength], rangeOut: [output.length, replacementTextLength] }; mappingFunction.push(matchMapping); if (replacedCopies == 0 && initialAllocationLength > 0) { // #86 : mapping of the allocation (extra length from 1st replacement), later split on all matches firstReplacementMapping = {rangeOut : [output.length+replacementTextLength, initialAllocationLength] }; } output+= replacedCopies==0 ? firstReplacementText : otherReplacementsText; ++replacedCopies; allRangesIn.push(offset, originalTextLength); inputPointer = offset+originalTextLength; if (matchExp) { let nextMatch = matchExp.exec(input); if (nextMatch) { let offsetInMatch = nextMatch[0].indexOf(originalText); offset = nextMatch.index + offsetInMatch; } else { offset = -1; } } else { offset = input.indexOf(originalText, inputPointer); } } // text remaining at the end if (inputPointer < input.length) { let intervalMapping = { rangeIn : [inputPointer, input.length-inputPointer], rangeOut: [output.length, input.length-inputPointer] }; mappingFunction.push(intervalMapping); output+= input.substring(inputPointer); } // Map prefix and suffix (if any) to all replaced blocks if (prefix != "") { let prefixMapping = { rangeIn : allRangesIn, rangeOut : [0, prefix.length] }; mappingFunction.push(prefixMapping); } if (suffix != "") { let suffixMapping = { rangeIn : allRangesIn, rangeOut : [output.length, suffix.length] }; mappingFunction.push(suffixMapping); } if (extraMapping) { extraMapping.rangeIn = allRangesIn; mappingFunction.push(extraMapping); } // #86 : Map allocation from first replacement text to all matches if (firstReplacementMapping) { firstReplacementMapping.rangeIn = allRangesIn; mappingFunction.push(firstReplacementMapping); } output+= suffix; mappingFunction.unshift({ inLength : input.length, outLength : output.length, complete:true}); thermalMap.push(mappingFunction); return output; } /** * Returns the character matching the provided unicode value * as it should be displayed in a character class in a Regexp : * - ] and \ needs escaping * - characters above 256 are \u.... * - characters between 128 and 255 are \x.. * - others (even below 32) are raw * @input charCode unicode value of the character to encode * @return formatted representation of the character for a RegExp char class */ this.writeCharToRegexpCharClass = function(charCode) { var output = ""; if (charCode>255) { output = "\\u"+(charCode<4096?"0":"")+charCode.toString(16); } else if (charCode>127) { output = "\\x"+charCode.toString(16); } else { output = (this.needsEscapingInCharClass(charCode)?"\\":"")+String.fromCharCode(charCode); } return output; } /** * Express a block as a range for a character class in a RegExp * @param first character code for the first character in range * @param last character code for the last character in range * @return a string representing the range inside a character class */ this.writeRangeToRegexpCharClass = function(first, last) { var length = last-first+1; var output = length > 0 ? this.writeCharToRegexpCharClass(first) : ""; output += (length>2 ? "-" : ""); if (length>1) { output += this.writeCharToRegexpCharClass(last); } return output; } /** * Express an array of blocks(ranges) as a character class in a RegExp * by iteratively calling writeRangeToRegexpCharClass on each range, then concatenating the result * @see writeRangeToRegexpCharClass * * @param allRanges array of objects {first:..., last:..., size:...} to express as a character class * @return a string representing the character class, without the encapsulating [ ] */ this.writeBlocksToRegexpCharClass = function(allRanges) { var currentCharClass = ""; for (var blockIndex = 0 ; blockIndex < allRanges.length ; ++blockIndex) { let oneRange = allRanges[blockIndex]; let rangeString = this.writeRangeToRegexpCharClass(oneRange.first, oneRange.last); // Fix for issue #31 : if a token line begins with "-", // add it at the beginning of the character class instead of appending it if (oneRange.first==45) { // 45 is '-' currentCharClass=rangeString+currentCharClass; } else { currentCharClass+=rangeString; } } return currentCharClass; } /** * Returns true if the character requires a backlash as a prefix in the character class of the * RegExp to be interpreted as part of the expression * Character : \ (used to escape others) * Character : ] (would close the character class otherwise) * @param ascii character code (ASCII / Unicode) * @return true if a backslash is needed, false otherwise */ this.needsEscapingInCharClass = function(ascii) { return ascii==92||ascii==93; } } var instance = null; return new function() { this.getInstance = function() { if (instance == null) { instance = new constructor(); instance.constructor = null; } return instance; } } })(); // Node.js init if (typeof require !== 'undefined') { module.exports = StringHelper; } ================================================ FILE: tests/allTests.js ================================================ var testAudioContextCreate = require("./testAudioContextCreate"); var testWebGLContextCreate = require("./testWebGLContextCreate"); var testStringHelper = require("./testStringHelper"); var testPackingConsistency = require("./testPackingConsistency"); var testIssue0002_UnicodeSupport = require("./testIssue0002_UnicodeSupport"); var testIssue0009_HashLoopVariable = require("./testIssue0009_HashLoopVariable"); var testIssue0017_MultipleContexts = require("./testIssue0017_MultipleContexts"); var testIssue0019_setInterval = require("./testIssue0019_setInterval"); var testIssue0030_webGLContextCreate = require("./testIssue0030_webGLContextCreate"); var testIssue0031_hyphenInRegex = require("./testIssue0031_hyphenInRegex"); var testIssue0042_patternViewer = require("./testIssue0042_patternViewer"); var testIssue0044_setIntervalArrowFunction = require("./testIssue0044_setIntervalArrowFunction"); var testIssue0045_closingBracket = require("./testIssue0045_closingBracket"); var testIssue0047_EscapeInCharClass = require("./testIssue0047_EscapeInCharClass"); var testIssue0050_unicodeSurrogate = require("./testIssue0050_unicodeSurrogate"); var testIssue0055_stringDelimiters = require("./testIssue0055_stringDelimiters"); var testIssue0056_setIntervalDefaultParams = require("./testIssue0056_setIntervalDefaultParams"); var testIssue0057_replacementInString = require("./testIssue0057_replacementInString"); var testIssue0058_numberAsLoopVariable = require("./testIssue0058_numberAsLoopVariable"); var testIssue0059_negatedRangeMerge = require("./testIssue0059_negatedRangeMerge"); var testIssue0063_backtickFunctionParam = require("./testIssue0063_backtickFunctionParam"); var testIssue0064_utf8EncodeURI = require("./testIssue0064_utf8EncodeURI"); var testIssue0065_invalidEscapeSequence = require("./testIssue0065_invalidEscapeSequence"); var testIssue0072_setIntervalNoInitCode = require("./testIssue0072_setIntervalNoInitCode"); var testIssue0074_keepWhiteSpaceSeparator = require("./testIssue0074_keepWhiteSpaceSeparator"); var testIssue0076_listVariablesInString = require("./testIssue0076_listVariablesInString"); var testIssue0079_CandXMLComments = require("./testIssue0079_CandXMLComments"); var testIssue0082_backticksInTemplateLiterals = require("./testIssue0082_backticksInTemplateLiterals"); var testIssue0083_backslashToken = require("./testIssue0083_backslashToken"); var testIssue0085_backslashSequenceLength = require("./testIssue0085_backslashSequenceLength"); var testIssue0087_firstCharacterInPattern = require("./testIssue0087_firstCharacterInPattern"); var testIssue0088_setIntervalAllocateVariable = require("./testIssue0088_setIntervalAllocateVariable"); var testIssue0089_emptyThermalMapping = require("./testIssue0089_emptyThermalMapping"); var testIssue0094_missingVariableBlock = require("./testIssue0094_missingVariableBlock"); var testIssue0096_multiLineMinification = require("./testIssue0096_multiLineMinification"); // Execute all tests in sequence // Recommendation : put new tests at the very beginning while debugging // then push them down the list afterwards testStringHelper(); testPackingConsistency(); testAudioContextCreate(); testWebGLContextCreate(); testIssue0002_UnicodeSupport(); testIssue0009_HashLoopVariable(); testIssue0017_MultipleContexts(); testIssue0019_setInterval(); testIssue0030_webGLContextCreate(); testIssue0031_hyphenInRegex(); testIssue0042_patternViewer(); testIssue0044_setIntervalArrowFunction(); testIssue0045_closingBracket(); testIssue0047_EscapeInCharClass(); testIssue0050_unicodeSurrogate(); testIssue0055_stringDelimiters(); testIssue0056_setIntervalDefaultParams(); testIssue0057_replacementInString(); testIssue0058_numberAsLoopVariable(); testIssue0059_negatedRangeMerge(); testIssue0063_backtickFunctionParam(); testIssue0064_utf8EncodeURI(); testIssue0065_invalidEscapeSequence(); testIssue0072_setIntervalNoInitCode(); testIssue0074_keepWhiteSpaceSeparator(); testIssue0076_listVariablesInString(); testIssue0079_CandXMLComments(); testIssue0082_backticksInTemplateLiterals(); testIssue0083_backslashToken(); testIssue0085_backslashSequenceLength(); testIssue0087_firstCharacterInPattern(); testIssue0088_setIntervalAllocateVariable(); testIssue0089_emptyThermalMapping(); testIssue0094_missingVariableBlock(); testIssue0096_multiLineMinification(); ================================================ FILE: tests/documentMock.js ================================================ /** * Test mock for document (not present in node.js) * This instance simply stores the result in a string * * Used for tests involving HTML rendering, such as PatternViewer or ThermalViewer * */ function DocumentMock() { this.message = ""; } DocumentMock.prototype = { createElement : function(type) { return new DivMock(); }, createTextNode : function(contents) { this.message += "[" + contents + "]"; return contents; } } function DivMock() { } DivMock.prototype = { setAttribute : function (a,b) { }, appendChild : function(element) { } } module.exports = DocumentMock; ================================================ FILE: tests/runBenchmark.js ================================================ var regPack = require("../regPack") var fs = require('fs'); var sources = [ { fileName:"2010 - Christmas Tree.js", options:{contextVariableName : '' } }, { fileName:"2012 - A rose is a rose.js", options:{contextVariableName : 'a', wrapInSetInterval : true, timeVariableName : 'T' } }, { fileName:"2012 - Autumn Evening.js", options:{contextVariableName : 'a', wrapInSetInterval : true } }, { fileName:"2012 - Mine(Love)craft.js", options:{contextVariableName : 'a', wrapInSetInterval : true, timeVariableName : 'T' } }, { fileName:"2013 - 3D City tour.js", options:{contextVariableName : 'a' } }, { fileName:"2013 - Color Factors.js", options:{contextVariableName : 'a', wrapInSetInterval : true } }, { fileName:"2013 - Comanche.js", options:{contextVariableName : 'a', wrapInSetInterval : true } }, { fileName:"2013 - Furbee.js", options:{contextVariableName : 'a' } }, { fileName:"2013 - Pointillism.js", options:{contextVariableName : 'a', wrapInSetInterval : true, timeVariableName : 'J' } }, { fileName:"2013 - Space Time Fracture.js", options:{contextVariableName : 'a' } }, { fileName:"2013 - StrangeCrystals II.js", options:{contextVariableName : 'a', wrapInSetInterval : true, timeVariableName : 'H' } }, { fileName:"2013 - Synth Sphere.js", options:{contextVariableName : 'a', wrapInSetInterval : true } }, { fileName:"2013 - Winter Wrap up.js", options:{contextVariableName : 'a' } }, { fileName:"2014 - Dragon Drop.js", options:{} }, { fileName:"2014 - Flappy Dragon Classic.js", options:{} }, { fileName:"2014 - Highway at night.js", options:{ wrapInSetInterval : true, timeVariableName : 'I' } }, { fileName:"2014 - Minecraft.js", options:{ wrapInSetInterval : true } }, { fileName:"2015 - Defender.js", options:{ varsNotReassigned : 'abcV'} }, { fileName:"2015 - Mysterious Monorail.js", options:{} }, { fileName:"2015 - Impossible road.js", options:{} }, { fileName:"2016 - Romanesco 2.0.js", options:{ contextVariableName : 'g', contextType : 1} }, { fileName:"2016 - Voxeling.js", options:{ wrapInSetInterval : true, timeVariableName : 'e', varsNotReassigned : 'abc'} }, { fileName:"2016 - Firewatch.js", options:{ wrapInSetInterval : true, timeVariableName : 's'} }, { fileName:"jscrush.js", options: {contextVariableName : 'a', varsNotReassigned : 'b_' } } ]; var defaultOptions = { withMath : false, hash2DContext : true, hashWebGLContext : true, hashAudioContext : true, contextVariableName : 'c', contextType : parseInt(0), reassignVars : true, varsNotReassigned : 'abc', crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var t0 = new Date(); sources.forEach (function(referenceItem) { var options = {}; for (key in defaultOptions) { options[key] = defaultOptions[key]; } for (key in referenceItem.options) { options[key] = referenceItem.options[key]; } var inputData = fs.readFileSync("../Benchmark/"+referenceItem.fileName, { encoding:"utf8"}); var bestVal = regPack.cmdRegPack(inputData, options); referenceItem.finalSize = regPack.packer.getByteLength(bestVal); }); var tf = new Date(); console.log(" "); console.log("BENCHMARK RESULTS :"); console.log("Total time = "+((tf-t0)/1000)+" s"); console.log("-----------------"); sources.forEach (function(referenceItem) { console.log(referenceItem.fileName+" : " + referenceItem.finalSize); }); ================================================ FILE: tests/testAudioContextCreate.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("AudioContext tests : start"); testAssignInIfThenElse(); testCreateToDifferentVariablesFirst(); testCreateToDifferentVariablesSecond(); testAssignInConditionalExpression(); console.log("AudioContext tests : done"); } /** * Creation of either AudioContext or webkitAudioContext in the then/else * statements of the same test, both stored in the same variable. * * Associated test file : audioContext_create1.js */ function testAssignInIfThenElse() { var input = fs.readFileSync("../TestCases/audioContext_create1.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : true, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : creation of AudioContext recognized // Audio-hashed environment added to input lines assert.equal(result.length, 2); assert.equal(result[1].name, " Audio"); } /** * Creation of either AudioContext or webkitAudioContext assigned to different variables. * AudioContext tested first. * * Associated test file : audioContext_create2.js */ function testCreateToDifferentVariablesFirst() { var input = fs.readFileSync("../TestCases/audioContext_create2.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : true, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : creation of AudioContext recognized // Audio-hashed environment added to input lines assert.equal(result.length, 2); assert.equal(result[1].name, " Audio"); } /** * Creation of either AudioContext or webkitAudioContext assigned to different variables. * webkitAudioContext tested first. * * Associated test file : audioContext_create3.js */ function testCreateToDifferentVariablesSecond() { var input = fs.readFileSync("../TestCases/audioContext_create3.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : true, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : creation of AudioContext recognized // Audio-hashed environment added to input lines assert.equal(result.length, 2); assert.equal(result[1].name, " Audio"); } /** * Creation of either AudioContext or webkitAudioContext in the same conditional expression * * Associated test file : audioContext_create4.js */ function testAssignInConditionalExpression() { var input = fs.readFileSync("../TestCases/audioContext_create4.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : true, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : creation of AudioContext recognized // Audio-hashed environment added to input lines assert.equal(result.length, 2); assert.equal(result[1].name, " Audio"); } module.exports = runTests; ================================================ FILE: tests/testIssue0002_UnicodeSupport.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0002 - Unicode tests : start"); testUnicodeSupport(); console.log("Issue #0002 - Unicode tests : done"); } /** * Github issue #2 - Accept unicode characters * Make sure the Unicode characters are explicitely filtered out * by the RegExp in the negated char class * * Associated test file : gitHub#2-unicode.js */ function testUnicodeSupport() { var input = fs.readFileSync("../TestCases/gitHub#2-unicode.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : internal check successful, // and the unicode characters are excluded from the token range assert.notEqual(result[0].result[2][1].indexOf("uffff]"), -1); assert.notEqual(result[0].result[2][2].indexOf("Final check : passed"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0009_HashLoopVariable.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0009 - Loop variable : start"); testHashLoopVariable(); console.log("Issue #0009 - Loop variable : done"); } /** * Github issue #9 - Hash loop variable * Make sure the variable chosen for the loop is not among the protected ones * Erroneous output spotted : "for(c in c)..." * * Associated test file : gitHub#9-hashloop.js */ function testHashLoopVariable() { var input = fs.readFileSync("../TestCases/gitHub#9-hashloop.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : true, hashWebGLContext : false, hashAudioContext : false, contextVariableName : "c", contextType : parseInt(0), reassignVars : false, varsNotReassigned : ['a', 'b', 'c'], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : loop variable is not c // (candidates are c r z with equal score, tiebreaker is alphabetical order) // variable protection kicks in and c is ignored upon chhosing the name of the variable assert.notEqual(result[1].contents.substr(0,10), "for(c in c"); } module.exports = runTests; ================================================ FILE: tests/testIssue0017_MultipleContexts.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0017 - Multiple contexts : start"); testMultipleContexts(); console.log("Issue #0017 - Multiple contexts : done"); } /** * Github issue #17 - Support for multiple contexts of the same type * Make sure all contexts of a given type are hashed at the same time * Erroneous behavior : hashing only the first context, but using renamed methods for all of them * * Associated test file : gitHub#17-multipleContexts.js */ function testMultipleContexts() { var input = fs.readFileSync("../TestCases/gitHub#17-multipleContexts.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : true, hashWebGLContext : false, hashAudioContext : false, contextVariableName : "c", contextType : parseInt(0), reassignVars : false, varsNotReassigned : ['a', 'b', 'c'], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : methods for both c and cc are hashed // when performed on 2D methods (not 2D properties which are not concerned with the bug) assert.notEqual(result[1].contents.indexOf("c[i[0]+i[6]]=cc[i[0]+i[6]]=c[i]"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0019_setInterval.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0019 - setInterval() : start"); testTimeVariableDeclaredAlone(); testTimeVariableDeclaredAtBegin1(); testTimeVariableDeclaredAtBegin2(); testTimeVariableDeclaredAtEnd1(); testTimeVariableDeclaredAtEnd2(); testTimeVariableDeclarationChainedFirst(); testTimeVariableDeclarationChainedSecond(); testTimeVariableDeclarationInArrayFirst(); testTimeVariableDeclarationInArraySecond(); testTimeVariableDeclarationInFunctionParameterFirst(); testTimeVariableDeclarationInFunctionParameterSecond(); testTimeVariableDeclarationInFunctionParameterLast(); console.log("Issue #0019 - setInterval() : done"); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and assigned alone, in the middle of other code (...;t=0;...) * * Associated test file : gitHub#19-setInterval_declarationAlone.js */ function testTimeVariableDeclaredAlone() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationAlone.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and assigned alone, at the beginning of the code, with a semicolon afterwards (t=0;...) * * Associated test file : gitHub#19-setInterval_declarationAtBegin1.js */ function testTimeVariableDeclaredAtBegin1() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationAtBegin1.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and assigned alone, at the beginning of the code, with a comma afterwards (t=0,...) * * Associated test file : gitHub#19-setInterval_declarationAtBegin2.js */ function testTimeVariableDeclaredAtBegin2() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationAtBegin2.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and assigned alone, at the beginning of the code, with a comma afterwards (t=0,...) * * Associated test file : gitHub#19-setInterval_declarationAtBegin2.js */ function testTimeVariableDeclaredAtBegin2() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationAtBegin2.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and assigned at the end of the init code, * right before the setInterval(), with a semicolon before (;t=0;setInterval...) * * Associated test file : gitHub#19-setInterval_declarationAtEnd1.js */ function testTimeVariableDeclaredAtEnd1() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationAtEnd1.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and assigned at the end of the init code, * right before the setInterval(), with a comma before (,t=0;setInterval...) * * Associated test file : gitHub#19-setInterval_declarationAtEnd2.js */ function testTimeVariableDeclaredAtEnd2() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationAtEnd2.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared in the same statement as another variable, * t coming first (t=u=0) * * Associated test file : gitHub#19-setInterval_declarationChained1.js */ function testTimeVariableDeclarationChainedFirst() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationChained1.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared in the same statement as another variable, * t coming second (u=t=0) * * Associated test file : gitHub#19-setInterval_declarationChained2.js */ function testTimeVariableDeclarationChainedSecond() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationChained2.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared in an array definition, as first member (p=[t=0,...]) * * Associated test file : gitHub#19-setInterval_declarationInArray1.js */ function testTimeVariableDeclarationInArrayFirst() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationInArray1.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared in an array definition, as second member (p=[n,t=0,...]) * * Associated test file : gitHub#19-setInterval_declarationInArray2.js */ function testTimeVariableDeclarationInArraySecond() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationInArray2.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and initialized as the first parameter passed to a function * f(t=0,...) * * Associated test file : gitHub#19-gitHub#19-setInterval_declarationInFunction1.js */ function testTimeVariableDeclarationInFunctionParameterFirst() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationInFunction1.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and initialized as a parameter passed to a function * (not the first nor the last one) : f(..., t=0,...) * * Associated test file : gitHub#19-gitHub#19-setInterval_declarationInFunction2.js */ function testTimeVariableDeclarationInFunctionParameterSecond() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationInFunction2.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #19 - use setInterval() to evaluate the unpacked code * Time variable t declared and initialized as the last parameter passed to a function * f(..., t=0) * * Associated test file : gitHub#19-gitHub#19-setInterval_declarationInFunction3.js */ function testTimeVariableDeclarationInFunctionParameterLast() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationInFunction3.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0030_webGLContextCreate.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0030 - WebGL create with ! : start"); testCreateExclamationMarkInOptions(); console.log("Issue #0030 - WebGL create with ! : done"); } /** * Github issue #30 - [description] * Creation of a GL context with "!" in the options string * * Associated test file : gitHub#30-webglContext_create_charset.js */ function testCreateExclamationMarkInOptions() { var input = fs.readFileSync("../TestCases/gitHub#30-webglContext_create_charset.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : true, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : creation of WebGL Context recognized // WebGL-hashed environment added to input lines assert.equal(result.length, 4); assert.notEqual(result[1].name.indexOf("WebGL"), -1); assert.notEqual(result[2].name.indexOf("WebGL"), -1); assert.notEqual(result[3].name.indexOf("WebGL"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0031_hyphenInRegex.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0031 - Hyphen in Regex : start"); testDirectSingleHyphen(); testDirectHyphenBeginsBlock(); testDirectHyphenEndsBlock(); testNegatedSingleHyphen(); testNegatedHyphenBeginsBlock(); testNegatedHyphenEndBlock(); console.log("Issue #0031 - Hyphen in Regex : done"); } /** * Github issue #31 - Single "-" character misinterpreted as range in RegExp * Packing generates a "-" in the regex in a block of 1 (single in the block) * * Associated test file : gitHub#31-direct-singleHyphen.js */ function testDirectSingleHyphen() { var input = fs.readFileSync("../TestCases/gitHub#31-direct-singleHyphen.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); } /** * Github issue #31 - Single "-" character misinterpreted as range in RegExp * Packing generates a "-" as the beginning of a block : [--0] (encompassing - . / 0) * * Associated test file : gitHub#31-direct-hyphenBeginsBlock.js */ function testDirectHyphenBeginsBlock() { var input = fs.readFileSync("../TestCases/gitHub#31-direct-hyphenBeginsBlock.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); } /** * Github issue #31 - Single "-" character misinterpreted as range in RegExp * Packing generates a "-" as the end of a block : [*--] (encompassing * + , -) * * Associated test file : gitHub#31-direct-hyphenEndsBlock.js */ function testDirectHyphenEndsBlock() { var input = fs.readFileSync("../TestCases/gitHub#31-direct-hyphenEndsBlock.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); } /** * Github issue #31 - Single "-" character misinterpreted as range in RegExp * Negated char class contains a "-" as the beginning of a block : [^--z] (encompassing everything but - to z) * * Associated test file : gitHub#31-negated-singleHyphen.js */ function testNegatedSingleHyphen() { var input = fs.readFileSync("../TestCases/gitHub#31-negated-singleHyphen.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[2][2].indexOf("Final check : passed"), -1); } /** * Github issue #31 - Single "-" character misinterpreted as range in RegExp * Negated char class contains a "-" as the beginning of a block : [^--z] (encompassing everything but - to z) * * Associated test file : gitHub#31-negated-hyphenBeginsBlock.js */ function testNegatedHyphenBeginsBlock() { var input = fs.readFileSync("../TestCases/gitHub#31-negated-hyphenBeginsBlock.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[2][2].indexOf("Final check : passed"), -1); } /** * Github issue #31 - Single "-" character misinterpreted as range in RegExp * Negated char class contains a "-" as the end of a block : [^C--] (encompassing everything but C (a control character) to -) * * Associated test file : gitHub#31-negated-hyphenEndsBlock.js */ function testNegatedHyphenEndBlock() { var input = fs.readFileSync("../TestCases/gitHub#31-negated-hyphenEndsBlock.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[2][2].indexOf("Final check : passed"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0042_patternViewer.js ================================================ var PatternViewer = require("../patternViewer") var fs = require("fs"); var DocumentMock = require("./documentMock"); var assert = require("assert"); function runTests() { console.log("Issue #0042 - Pattern Viewer : start"); testLastBlockBeforeEnd(); testLastBlockAtEnd(); console.log("Issue #0042 - Pattern Viewer : done"); } /** * Github issue #42 - Visualization misses patterns that end at the last character * Single pattern not including the end of the block (regression test) */ function testLastBlockBeforeEnd() { document = new DocumentMock(); var patternViewer = new PatternViewer(); var code = "abcdefghijklmnopqrstuvwxyz"; var matches = [ { token : "A", originalString : "defg" } ]; var result = patternViewer.render(code, matches); assert.equal(document.message, "[abc][defg][hijklmnopqrstuvwxyz]"); } /** * Github issue #42 - Visualization misses patterns that end at the last character * Single pattern featuring the last characters of the block */ function testLastBlockAtEnd() { document = new DocumentMock(); var patternViewer = new PatternViewer(); var code = "abcdefghijklmnopqrstuvwxyz"; var matches = [ { token : "A", originalString : "wxyz" } ]; var result = patternViewer.render(code, matches); assert.equal(document.message,"[abcdefghijklmnopqrstuv][wxyz]"); } module.exports = runTests; ================================================ FILE: tests/testIssue0044_setIntervalArrowFunction.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0044 - setInterval() with arrow function : start"); testMultipleParametersMultipleParams(); testSingleParameter(); testNoParameter(); console.log("Issue #0044 - setInterval() with arrow function : done"); } /** * Github issue #44 - arrow function support for module "refactor to setInterval()" * Multiple parameters in arrow function, none is initialized there * * Associated test file : gitHub#44-setInterval_arrowFunctionMultiParam.js */ function testMultipleParametersMultipleParams() { var input = fs.readFileSync("../TestCases/gitHub#44-setInterval_arrowFunctionMultiParam.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #44 - arrow function support for module "refactor to setInterval()" * Multiple parameters in arrow function, variable is initialized there * * Associated test file : gitHub#44-setInterval_arrowFunctionMultiParamInit.js */ function testMultipleParametersWithInit() { var input = fs.readFileSync("../TestCases/gitHub#44-setInterval_arrowFunctionMultiParamInit.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #44 - arrow function support for module "refactor to setInterval()" * Single parameter function with no parenthesis * * Associated test file : gitHub#44-setInterval_arrowFunctionSingleParam.js */ function testSingleParameter() { var input = fs.readFileSync("../TestCases/gitHub#44-setInterval_arrowFunctionSingleParam.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } /** * Github issue #44 - arrow function support for module "refactor to setInterval()" * Function with no parameter * * Associated test file : gitHub#44-setInterval_arrowFunctionNoParam.js */ function testNoParameter() { var input = fs.readFileSync("../TestCases/gitHub#44-setInterval_arrowFunctionNoParam.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("setInterval"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0045_closingBracket.js ================================================ var RegPack = require("../regPack") var assert = require("assert"); function runTests() { console.log("Issue #0045 - ] in character class : start"); testBracketBeginsBlock(); console.log("Issue #0045 - ] in character class : done"); } function testBracketBeginsBlock() { var input = "=-=A==1234567=-=A-=A-1234567890AB=-A=-A=67890AB"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And it should not contain ] in the character class assert.equal(result[0].result[1][1].indexOf("[]"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0047_EscapeInCharClass.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0047 - \\ and ] in character class : start"); testRangeSorting(); testEscapedCharacterWithoutReplacement(); testEscapedCharacterWithReplacement(); testEscapedCharacterReplacementFails(); console.log("Issue #0047 - \\ and ] in character class : done"); } /** * GitHub issue #47 - Use \ and ] (which need escaping) as tokens in packer * Range sorting consistency test (case met during development : last character had a value of NaN) * Ranges available for tokens are @ , B-E , L-P , ~ * * Associated test file : gitHub#47-packer_rangesBeyond.js */ function testRangeSorting() { var input = fs.readFileSync("../TestCases/gitHub#47-packer_rangesBeyond.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the blocks are L-P and B-E (used up to C), in that order assert.notEqual(result[0].result[1][1].indexOf("=/[L-PBC]/"), -1); } /** * GitHub issue #47 - Use \ and ] (which need escaping) as tokens in packer * Test ranges starting or ending with escaped characters, with no replacement tokens (none left) * * Associated test file : gitHub#47-packer_to92NoReplace.js * Associated test file : gitHub#47-packer_to93NoReplace.js * Associated test file : gitHub#47-packer_from92NoReplace.js * Associated test file : gitHub#47-packer_from93NoReplace.js */ function testEscapedCharacterWithoutReplacement() { var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var input = fs.readFileSync("../TestCases/gitHub#47-packer_to92NoReplace.js", { encoding:"utf8"}); var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block ends in \, no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[Y-\\\\]/"), -1); input = fs.readFileSync("../TestCases/gitHub#47-packer_to93NoReplace.js", { encoding:"utf8"}); result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block ends in ], no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[Y-\\]]/"), -1); input = fs.readFileSync("../TestCases/gitHub#47-packer_from92NoReplace.js", { encoding:"utf8"}); result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block begins with \, no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[\\\\-`]/"), -1); input = fs.readFileSync("../TestCases/gitHub#47-packer_from93NoReplace.js", { encoding:"utf8"}); result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block begins with ], no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[\\]-`]/"), -1); } /** * GitHub issue #47 - Use \ and ] (which need escaping) as tokens in packer * Test ranges starting or ending with escaped characters * with tokens left on the subsequent ranges to perform replacement * * Associated test file : gitHub#47-packer_to92AndReplace.js * Associated test file : gitHub#47-packer_to93AndReplace.js * Associated test file : gitHub#47-packer_from92AndReplace.js * Associated test file : gitHub#47-packer_from93AndReplace.js */ function testEscapedCharacterWithReplacement() { var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var input = fs.readFileSync("../TestCases/gitHub#47-packer_to92AndReplace.js", { encoding:"utf8"}); var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // Ranges Y-\\ 0-2, 2 unused, \\ gets replaced with 2 assert.notEqual(result[0].result[1][1].indexOf("=/[X-[0-2]/"), -1); input = fs.readFileSync("../TestCases/gitHub#47-packer_to93AndReplace.js", { encoding:"utf8"}); result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block ends in ], no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[X-[0-2]/"), -1); input = fs.readFileSync("../TestCases/gitHub#47-packer_from92AndReplace.js", { encoding:"utf8"}); result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block begins with \, no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[0-3^-`]/"), -1); input = fs.readFileSync("../TestCases/gitHub#47-packer_from93AndReplace.js", { encoding:"utf8"}); result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block begins with ], no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[0-2^-a]/"), -1); } /** * GitHub issue #47 - Use \ and ] (which need escaping) as tokens in packer * Test ranges starting or ending with escaped characters * with tokens left on the subsequent ranges to perform replacement, * but not enough (1 where 2 are needed) -> no replacement is performed * * Associated test file : gitHub#47-packer_to93ReplaceFails.js * Associated test file : gitHub#47-packer_from92ReplaceFails.js */ function testEscapedCharacterReplacementFails() { var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var input = fs.readFileSync("../TestCases/gitHub#47-packer_to93ReplaceFails.js", { encoding:"utf8"}); var result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // Ranges Y-\\ 0-2, 2 unused, \\ gets replaced with 2 assert.notEqual(result[0].result[1][1].indexOf("=/[X-\\]01]/"), -1); input = fs.readFileSync("../TestCases/gitHub#47-packer_from92ReplaceFails.js", { encoding:"utf8"}); result = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly assert.notEqual(result[0].result[1][2].indexOf("Final check : passed"), -1); // And the only block ends in ], no tokens are available for replacement assert.notEqual(result[0].result[1][1].indexOf("=/[\\]-`0-2]/"), -1); } module.exports = runTests ================================================ FILE: tests/testIssue0050_unicodeSurrogate.js ================================================ var RegPack = require("../regPack") var assert = require("assert"); function runTests() { console.log("Issue #0050 - Unicode surrogate byte length : start"); testByteLength(); testSurrogatePacking(); console.log("Issue #0050 - Unicode surrogate byte length : done"); } /** * Github issue #50 - Support for characters in the astral plane * First, make sure that the astral characters (composed of two 16-bit codes, * first one in [0xD800, 0xDBFF] and second one in [0xDC00, DFFF]) are correctly read * */ function testByteLength() { // standard ASCII var input = "0123456789abcdefghijklmnopqrstuvwxyz"; assert.equal(36, RegPack.packer.getByteLength(input)); // 2-byte UTF-8 input = ""; assert.equal(3, RegPack.packer.getByteLength(input)); // 4-byte UTF-8 with surrogates input = "\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25"; assert.equal(12, RegPack.packer.getByteLength(input)); input = "🔥🔥🔥"; assert.equal(12, RegPack.packer.getByteLength(input)); } /** * Github issue #50 - Support for characters in the astral plane * Then, check that the crusher does not attempt to break the input in between the two surrogate characters, * since a string starting with the second one would yield a malformed URI * */ function testSurrogatePacking() { // 4-byte UTF-8 with surrogates input = "\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : no exception thrown before, internal check successful, // and the unicode characters are excluded from the token range assert(RegPack.packer.getByteLength(result[0].result[2][1]) > 0); assert.notEqual(result[0].result[2][1].indexOf("uffff]"), -1); assert.notEqual(result[0].result[2][2].indexOf("Final check : passed"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0055_stringDelimiters.js ================================================ var RegPack = require("../regPack"); var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0055 - string delimiters as tokens : start"); testUseBackticksForPackedString(); testBothQuotesUsedNoES6(); testQuoteAsToken(); testQuoteReplacement(); console.log("Issue #0055 - string delimiters as tokens : done"); } /** * Github issue #55 - Harmonize strings delimiters inside the code, to free " or ' as compression token * Input code uses both " and ', test that packed string is defined with ` * * Associated test file : gitHub#55-bothQuotesInUse.js */ function testUseBackticksForPackedString() { var input = fs.readFileSync("../TestCases/gitHub#55-bothQuotesInUse.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.runPacker(input, options); // Expected result : each stage delimits the packed string with ` assert.equal(result[0].result[0][1].substr(0, 3), "_=`"); assert.equal(result[0].result[1][1].substr(0, 7), "for(_=`"); assert.equal(result[0].result[2][1].substr(0, 7), "for(_=`"); } /** * Github issue #55 - Harmonize strings delimiters inside the code, to free " or ' as compression token * Input code uses both " and ', but ES6 (and thus backtick use) disabled * Same input file as testUseBackticksForPackedString() * * Associated test file : gitHub#55-bothQuotesInUse.js */ function testBothQuotesUsedNoES6() { var input = fs.readFileSync("../TestCases/gitHub#55-bothQuotesInUse.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : false }; var result = RegPack.packer.runPacker(input, options); // Expected result : each stage delimits the packed string with " (` is not available) assert.equal(result[0].result[0][1].substr(0, 3), '_="'); assert.equal(result[0].result[1][1].substr(0, 7), 'for(_="'); assert.equal(result[0].result[2][1].substr(0, 7), 'for(_="'); } /** * Github issue #55 - Harmonize strings delimiters inside the code, to free " or ' as compression token * Input code uses neither ' nor ", and all other characters are used * Test that either ' or " is used as string delimiter * * Associated test file : gitHub#55-quotesAsOnlyTokens.js */ function testQuoteAsToken() { var input = fs.readFileSync("../TestCases/gitHub#55-quotesAsOnlyTokens.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.runPacker(input, options); // Expected result : one of " and ' is the delimiter, the other one the token var delimiterCode = result[0].result[0][1].charCodeAt(2+result[0].result[0][1].indexOf("_=")); var tokenCode = result[0].result[0][1].charCodeAt(9+result[0].result[0][1].indexOf("for(i of")); assert.equal(Math.min(delimiterCode, tokenCode), 34); assert.equal(Math.max(delimiterCode, tokenCode), 39); } /** * Github issue #55 - Harmonize strings delimiters inside the code, to free " or ' as compression token * Input code has multiple copies of the same string, each time with different delimiters * Test that the delimiter is changed for some strings, so that one is freed for the packed code * * Associated test file : gitHub#55-sameStringInAllQuotes.js */ function testQuoteReplacement() { var input = fs.readFileSync("../TestCases/gitHub#55-sameStringInAllQuotes.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.runPacker(input, options); // Expected result : a quote is used only as delimiter var delimiter = result[0].result[0][1][2+result[0].result[0][1].indexOf("_=")]; var escapedDelimiter = "\\"+delimiter; for (var stage=0; stage<3; ++stage) { // the only instances of the delimiter are around the string + around the tokens (for crusher stage) var delimiterCount = result[0].result[stage][1].match(new RegExp(delimiter, "g")).length; assert.equal(delimiterCount, [4, 2, 2][stage]); // and the escaped delimiter is not present inside the string assert.equal(result[0].result[stage][1].indexOf(escapedDelimiter), -1); } } module.exports = runTests; ================================================ FILE: tests/testIssue0056_setIntervalDefaultParams.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0056 - setInterval() with default params : start"); testNoAssignment(); testSingleVariableAssignment(); testOneAssignment(); testMultipleAssignments(); testComplexAssignments(); console.log("Issue #0056 - setInterval() with default params : done"); } /** * Github issue #56 - default parameter values support for module "refactor to setInterval()" * No default value (regression test) * * Associated test file : gitHub#56-setInterval_arrowNoValue.js * Associated test file : gitHub#56-setInterval_standardNoValue.js */ function testNoAssignment() { var inputArrow = fs.readFileSync("../TestCases/gitHub#56-setInterval_arrowNoValue.js", { encoding:"utf8"}); var inputStandard = fs.readFileSync("../TestCases/gitHub#56-setInterval_standardNoValue.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var resultArrow = RegPack.packer.preprocessor.preprocess(inputArrow, options); var resultStandard = RegPack.packer.preprocessor.preprocess(inputStandard, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call // Initialization of variable to default value is pushed to the main code assert.equal(resultArrow[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultArrow[0].wrappedInit, "t=0"); assert.equal(resultArrow[0].contents.indexOf("setInterval"), -1); assert.equal(resultArrow[0].contents.indexOf("x,y,z"), -1); // make sure the variable list is discarded assert.equal(resultArrow[0].contents.indexOf("z;"), -1); // make sure the last variable is discarded assert.equal(resultStandard[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultStandard[0].wrappedInit, "t=0"); assert.equal(resultStandard[0].contents.indexOf("setInterval"), -1); assert.equal(resultStandard[0].contents, resultArrow[0].contents); } /** * Github issue #56 - default parameter values support for module "refactor to setInterval()" * Single parameter with default value * * Associated test file : gitHub#56-setInterval_arrowSingleValue.js * Associated test file : gitHub#56-setInterval_standardSingleValue.js */ function testSingleVariableAssignment() { var inputArrow = fs.readFileSync("../TestCases/gitHub#56-setInterval_arrowSingleValue.js", { encoding:"utf8"}); var inputStandard = fs.readFileSync("../TestCases/gitHub#56-setInterval_standardSingleValue.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var resultArrow = RegPack.packer.preprocessor.preprocess(inputArrow, options); var resultStandard = RegPack.packer.preprocessor.preprocess(inputStandard, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call // Initialization of variable to default value is pushed at the beginning of the main loop assert.equal(resultArrow[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultArrow[0].wrappedInit, "t=0"); assert.equal(resultArrow[0].contents.indexOf("setInterval"), -1); assert.notEqual(resultArrow[0].contents.indexOf("x=0;if"), -1); assert.equal(resultStandard[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultStandard[0].wrappedInit, "t=0"); assert.equal(resultStandard[0].contents.indexOf("setInterval"), -1); assert.equal(resultStandard[0].contents, resultArrow[0].contents); } /** * Github issue #56 - default parameter values support for module "refactor to setInterval()" * Multiple parameters, only one with default value * * Associated test file : gitHub#56-setInterval_arrowOneValue.js * Associated test file : gitHub#56-setInterval_standardOneValue.js */ function testOneAssignment() { var inputArrow = fs.readFileSync("../TestCases/gitHub#56-setInterval_arrowOneValue.js", { encoding:"utf8"}); var inputStandard = fs.readFileSync("../TestCases/gitHub#56-setInterval_standardOneValue.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var resultArrow = RegPack.packer.preprocessor.preprocess(inputArrow, options); var resultStandard = RegPack.packer.preprocessor.preprocess(inputStandard, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call // Initialization of variable to default value is pushed at the beginning of the main loop assert.equal(resultArrow[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultArrow[0].wrappedInit, "t=0"); assert.equal(resultArrow[0].contents.indexOf("setInterval"), -1); assert.notEqual(resultArrow[0].contents.indexOf("z=0;if"), -1); assert.equal(resultStandard[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultStandard[0].wrappedInit, "t=0"); assert.equal(resultStandard[0].contents.indexOf("setInterval"), -1); assert.equal(resultStandard[0].contents, resultArrow[0].contents); } /** * Github issue #56 - default parameter values support for module "refactor to setInterval()" * Multiple parameters, several ones with default value * * Associated test file : gitHub#56-setInterval_arrowMultipleValues.js * Associated test file : gitHub#56-setInterval_standardMultipleValues.js */ function testMultipleAssignments() { var inputArrow = fs.readFileSync("../TestCases/gitHub#56-setInterval_arrowMultipleValues.js", { encoding:"utf8"}); var inputStandard = fs.readFileSync("../TestCases/gitHub#56-setInterval_standardMultipleValues.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var resultArrow = RegPack.packer.preprocessor.preprocess(inputArrow, options); var resultStandard = RegPack.packer.preprocessor.preprocess(inputStandard, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call // Initialization of variable to default values is pushed at the beginning of the main loop assert.equal(resultArrow[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultArrow[0].wrappedInit, "t=0"); assert.equal(resultArrow[0].contents.indexOf("setInterval"), -1); assert.notEqual(resultArrow[0].contents.indexOf("x=2,y=1,z=0;if"), -1); assert.equal(resultStandard[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultStandard[0].wrappedInit, "t=0"); assert.equal(resultStandard[0].contents.indexOf("setInterval"), -1); assert.equal(resultStandard[0].contents, resultArrow[0].contents); } /** * Github issue #56 - default parameter values support for module "refactor to setInterval()" * Multiple parameters, including a complex assignment of several variables in a row (only the first one being a parameter) * * Associated test file : gitHub#56-setInterval_arrowComplexValues.js * Associated test file : gitHub#56-setInterval_standardComplexValues.js */ function testComplexAssignments() { var inputArrow = fs.readFileSync("../TestCases/gitHub#56-setInterval_arrowComplexValues.js", { encoding:"utf8"}); var inputStandard = fs.readFileSync("../TestCases/gitHub#56-setInterval_standardComplexValues.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var resultArrow = RegPack.packer.preprocessor.preprocess(inputArrow, options); var resultStandard = RegPack.packer.preprocessor.preprocess(inputStandard, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call // Initialization of variable to default values is pushed at the beginning of the main loop assert.equal(resultArrow[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultArrow[0].wrappedInit, "t=0"); assert.equal(resultArrow[0].contents.indexOf("setInterval"), -1); assert.notEqual(resultArrow[0].contents.indexOf("w=t,x=y=z=0;if"), -1); assert.equal(resultStandard[0].interpreterCall, "setInterval(_,33)"); assert.equal(resultStandard[0].wrappedInit, "t=0"); assert.equal(resultStandard[0].contents.indexOf("setInterval"), -1); assert.equal(resultStandard[0].contents, resultArrow[0].contents); } module.exports = runTests; ================================================ FILE: tests/testIssue0057_replacementInString.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0057 - Renaming of variable $ in string : start"); testLettersOnly(); console.log("Issue #0057 - Renaming of variable $ in string : done"); } /** * Github issue #57 - collisiion between ES6 string substitution expression ${...} and variable $ * RegPack incorrectly replaces the $ in ${i} * * Associated test file : gitHub#57-templateLiteralOneVariable.js */ function testLettersOnly() { var input = fs.readFileSync("../TestCases/gitHub#57-templateLiteralOneVariable.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : true, hashWebGLContext : true, hashAudioContext : true, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : "", crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : k is the most frequent loop index, and thus selected var preprocessed = result[0].contents; // $ in ${ is not replaced assert.notEqual(preprocessed.indexOf("c.fillStyle=`hsl(256,25%,${"), -1); // T in ${... T ...} is replaced assert.equal(preprocessed.indexOf("c.fillStyle=`hsl(256,25%,${Math.max(0,100-T)"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0058_numberAsLoopVariable.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0058 - Number as loop variable : start"); testLettersOnly(); testProtectedAsMostFrequent(); testNumberAsMostFrequent(); console.log("Issue #0058 - Number as loop variable : done"); } /** * Github issue #58 - Digit selected to use as a loop variable * Default case with letters only, selecting the most frequent * * Associated test file : gitHub#58-lettersOnly.js */ function testLettersOnly() { var input = fs.readFileSync("../TestCases/gitHub#58-lettersOnly.js", { encoding:"utf8"}); var protectedVars=[]; for (i=0;i<128;++i) { protectedVars.push(i>96&&i<100); // true for a, b, c } var result = RegPack.packer.preprocessor.getMostFrequentLoopVariable(input, protectedVars); // Expected result : k is the most frequent loop index, and thus selected assert.equal(result[0], "k"); } /** * Github issue #58 - Digit selected to use as a loop variable * Case with letters only, where the most frequent variable is protected * * Associated test file : gitHub#58-lettersOnly.js */ function testProtectedAsMostFrequent() { var input = fs.readFileSync("../TestCases/gitHub#58-lettersOnly.js", { encoding:"utf8"}); var protectedVars=[]; for (i=0;i<128;++i) { protectedVars.push((i>96&&i<100)||i==107); // true for a, b, c, k } var result = RegPack.packer.preprocessor.getMostFrequentLoopVariable(input, protectedVars); // Expected result : k is the most frequent loop index, however it is protected // The function returns the second-most frequent which is p assert.equal(result[0], "p"); } /** * Github issue #58 - Digit selected to use as a loop variable * Case where the most frequent "letter" is actually a digit * * Associated test file : gitHub#58-numberAsMostFrequent.js */ function testNumberAsMostFrequent() { var input = fs.readFileSync("../TestCases/gitHub#58-numberAsMostFrequent.js", { encoding:"utf8"}); var protectedVars=[]; for (i=0;i<128;++i) { protectedVars.push(i>96&&i<100); // true for a, b, c } var result = RegPack.packer.preprocessor.getMostFrequentLoopVariable(input, protectedVars); // Expected result : 2 is the most frequent in expressions "for(*" // but not allowed as a variable name. // The function returns the most frequent legal variable name which is p assert.equal(result[0], "p"); } module.exports = runTests; ================================================ FILE: tests/testIssue0059_negatedRangeMerge.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0059 - exhaustive merge in negated char class : start"); testRangeMerge1vs3(); console.log("Issue #0059 - exhaustive merge in negated char class : done"); } /** * GitHub issue #59 - when merging range to shorten the negated char class, * the original algorithm ends on a suboptimal solution * with 4 credits left, it chooses an option which gains 1 for 1 instead of one that gains 3 for 4 * * Associated test file : gitHub#59-negatedRangeMerge-1vs3.js */ function testRangeMerge1vs3() { var input = fs.readFileSync("../TestCases/gitHub#59-negatedRangeMerge-1vs3.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, hashAllObjects : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var output = RegPack.packer.runPacker(input, options); // Expected result : the range 0-3 is merged (gain 3, cost 4), leaving only } as a token // Incorrect result : the range } is merged (gain 1, cost 1), leaving 0-3 as tokens, 0 is used var negatedCharClassModuleLog = output[0].result[2][2]; var rangePos = negatedCharClassModuleLog.indexOf(", str = abcd"); var tokenLog = negatedCharClassModuleLog.substr(rangePos-6, 6); assert.equal(tokenLog, "125(})"); } module.exports = runTests ================================================ FILE: tests/testIssue0063_backtickFunctionParam.js ================================================ var RegPack = require("../regPack"); var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0063 - getContext`param` : start"); testBacktick2DContext(); testBacktickWebGLContext(); console.log("Issue #0063 - getContext`param` : done"); } /** * Github issue #63 - Backtick use as wrapper for single function param : foo`x` instead of foo("x") * Identify 2d context created by getContext`2d` * * Associated test file : gitHub#63-backtick2DContext.js */ function testBacktick2DContext() { var input = fs.readFileSync("../TestCases/gitHub#63-backtick2DContext.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : true, hashWebGLContext : true, hashAudioContext : true, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : "abcdg", crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the 2d context is recognized assert.equal(result.length, 3); assert.notEqual(result[1].name.indexOf("2D"), -1); assert.notEqual(result[2].name.indexOf("2D"), -1); } function testBacktickWebGLContext() { var input = fs.readFileSync("../TestCases/gitHub#63-backtickWebGLContext.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : true, hashWebGLContext : true, hashAudioContext : true, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : "abcdg", crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the WebGL context is recognized assert.equal(result.length, 4); assert.notEqual(result[1].name.indexOf("WebGL"), -1); assert.notEqual(result[2].name.indexOf("WebGL"), -1); assert.notEqual(result[3].name.indexOf("WebGL"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0064_utf8EncodeURI.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0064 - EncodeURI in UTF-8 : start"); testEncodeURI(); console.log("Issue #0064 - EncodeURI in UTF-8 : done"); } /** * Github issue #64 - Accept unicode characters * Make sure the Unicode characters are explicitely filtered out * by the RegExp in the negated char class * * Associated test file : gitHub#64-URIError.js */ function testEncodeURI() { var input = fs.readFileSync("../TestCases/gitHub#64-URIError.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : internal check successful, // and the unicode characters are excluded from the token range assert.notEqual(result[0].result[2][1].indexOf("uffff]"), -1); assert.notEqual(result[0].result[2][2].indexOf("Final check : passed"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0065_invalidEscapeSequence.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0065 - invalid escape sequence using \\ as token : start"); testInvalidEscapeSequence(); testIncorrectReplacement(); console.log("Issue #0065 - invalid escape sequence using \\ as token : done"); } /** * Assert that all backslashes \ are doubled inside the input string * @packedCode string to check (packed by crusher or packer) */ function assertAllBackslashesDoubled(packedCode) { // extract the packed string var callFunction = "eval("; var packedStringVarOffset = packedCode.lastIndexOf(callFunction); if (packedStringVarOffset == -1) { callFunction = "setInterval("; packedStringVarOffset= packedCode.lastIndexOf(callFunction); } if (packedStringVarOffset == -1) { // no unpacking routine found : code is not packed assert(false); } packedStringVarOffset+=callFunction.length; var packedStringVar = packedCode[packedStringVarOffset]; // look for packed string : var packedStringBegin = packedCode.indexOf(packedStringVar+"="); var packedStringDelimiter = packedCode[packedStringBegin+2]; var packedStringEnd = packedCode.lastIndexOf(packedStringDelimiter); var packedString = packedCode.substring(packedStringBegin+3, packedStringEnd); var index = packedString.indexOf("\\"); while (index>0) { assert(packedString.charCodeAt(index)==92); assert(packedString.charCodeAt(index+1)==92); index = packedString.indexOf("\\", index+2); } } /** * GitHub issue #65 - backslash \ as token incorrectly escaped * Initial example filed with issue #65 * Backslash interpreted as the beginning of an escape sequence * * Associated test file : gitHub#65-backslash_invalidEscapeSequence.js */ function testInvalidEscapeSequence() { var input = fs.readFileSync("../TestCases/gitHub#65-backslash_invalidEscapeSequence.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var output = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly and matches the original code assert.notEqual(output[0].result[1][2].indexOf("Final check : passed"), -1); assert.notEqual(output[0].result[2][2].indexOf("Final check : passed"), -1); // Expected result : the output from each stage has backslashes escaped assertAllBackslashesDoubled(output[0].result[0][1]); assertAllBackslashesDoubled(output[0].result[1][1]); assertAllBackslashesDoubled(output[0].result[2][1]); } /** * GitHub issue #65 - backslash \ as token incorrectly escaped * Initial example filed with issue #73 * Not all occurrences of backslash token are expanded * * Associated test file : gitHub#73-backslash_unexpandedToken.js */ function testIncorrectReplacement() { var input = fs.readFileSync("../TestCases/gitHub#73-backslash_unexpandedToken.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var output = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly and matches the original code assert.notEqual(output[0].result[1][2].indexOf("Final check : passed"), -1); assert.notEqual(output[0].result[2][2].indexOf("Final check : passed"), -1); // Expected result : the output from each stage has backslashes escaped assertAllBackslashesDoubled(output[0].result[0][1]); assertAllBackslashesDoubled(output[0].result[1][1]); assertAllBackslashesDoubled(output[0].result[2][1]); } module.exports = runTests ================================================ FILE: tests/testIssue0072_setIntervalNoInitCode.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0072 - setInterval() with empty initialization block : start"); testInitCodeAtEnd(); testNoInitCode(); console.log("Issue #0072 - setInterval() with empty initialization block : done"); } /** * Github issue #72 - do not generate an empty initialization block if there is no init code * Make sure the block is created if the init code is at the end * * Associated test file : inlined */ function testInitCodeAtEnd() { var input = "t=0;setInterval(function(){t?play():init();t++},33);d=c.getImageData(0,0,640,320)"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // The initialization block is present assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.notEqual(result[0].contents.indexOf("if(!t){"), -1); } /** * Github issue #72 - do not generate an empty initialization block if there is no init code * Make sure the block is not added if there is no init code at all - neither at the beginning nor at the end * * Associated test file : inlined */ function testNoInitCode() { var input = "t=0;setInterval(function(){t?play():init();t++},33)"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "t", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // The initialization block is missing assert.equal(result[0].interpreterCall, "setInterval(_,33)"); assert.equal(result[0].wrappedInit, "t=0"); assert.equal(result[0].contents.indexOf("if(!t){"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0074_keepWhiteSpaceSeparator.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0074 - Incorrect white space removal : start"); testWhiteSpaceAsSeparator(); testMultipleWhiteSpaces(); console.log("Issue #0074 - Incorrect white space removal : done"); } /** * Github issue #74 - Do not remove white spaces that are the only separator * Check that there is a proper separator * Erroneous behavior : create incorrect code with no separator between two instructions * * Associated test file : http://codepen.io/cantelope/pen/pRXaae */ function testWhiteSpaceAsSeparator() { var input = `function Q(x,y,z,S,D){T={};T.B=[];T.S=S;T.H=D;b={};b.M=x;b.N=y;b.A=z;b.K=b.G=PI-.01;b.l=25;b.C=b.M+sin(b.K)*sin(b.G)*25 b.q=b.N+cos(b.G)*25;b.n=b.A+cos(b.K)*sin(b.G)*25;b.D=1;T.B.push(b);H(T.B[0],S,D,PI/4-cos(F/35)*PI/4.1);return T}`; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : "c", contextType : parseInt(0), reassignVars : false, varsNotReassigned : ['a', 'b', 'c'], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var result = RegPack.packer.runPacker(input, options); // Expected result : the preprocessed text has not been modified, newline was kept assert.equal(result[0].contents, input); } /** * Github issue #74 - Do not remove white spaces that are the only separator * Check that several white spaces in a row are reduced to only one * Erroneous behavior : delete all white spaces, or keep more than one */ function testMultipleWhiteSpaces() { var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : "c", contextType : parseInt(0), reassignVars : false, varsNotReassigned : ['a', 'b', 'c'], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var inputWithSpaces = "let a=0; let b=0; let c=0"; var result = RegPack.packer.runPacker(inputWithSpaces, options); // Expected result : only one space where relevant assert.equal(result[0].contents, "let a=0;let b=0;let c=0"); var inputWithTabs = "let\ta=0; let\t\tb=0; let\t\t\tc=0"; result = RegPack.packer.runPacker(inputWithTabs, options); // Expected result : only one tab where relevant assert.equal(result[0].contents, "let\ta=0;let\tb=0;let\tc=0"); } module.exports = runTests; ================================================ FILE: tests/testIssue0076_listVariablesInString.js ================================================ var RegPack = require("../regPack"); var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0076 - text from string read as variables : start"); testTemplateLiteralInterpretedAsDollarVariable(); console.log("Issue #0076 - text from string read as variables : done"); } /** * Github issue #76 - Reassign variable names : still considering (but not replacing) text in strings * The $ defining the template literal ${..} was recognized as a variable * and assigned a replacement. The replacement was not performed inside strings, but this was still the loss of a variable. * * Associated test input : same as for #82 */ function testTemplateLiteralInterpretedAsDollarVariable() { var input = "p=c.getImageData(0,0,512,512);d=p.data;for(j=0;j<512;++j){c.fillStyle=`#${`200`,`300`,`fff`,`000`][j&3]}`;d[j*4]=0;d[j*4+1]=255;d[j*4+2]=j>>1;d[j*4+3]=0;c.beginPath();c.moveTo(0,j);c.lineTo(511,j);c.stroke();}"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : 'abc', crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : one template literal inside the string assert.equal(result[0].containedTemplateLiterals.length, 1); // Expected result : the variable $ is not renamed (and there is no variable $) // Buggy result : another name is reassigned to $ assert.equal(result[0].log.indexOf("$ =>"), -1); // Expected result : $ listed as keyword var keywordsOffset = result[0].log.indexOf("in keywords only"); assert.notEqual(keywordsOffset, -1); assert.notEqual(result[0].log.indexOf("$", keywordsOffset), -1); // Expected result : ${ still present in the string assert.notEqual(result[0].contents.indexOf("${"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0079_CandXMLComments.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0079 - Minify C and XML comments : start"); testCCommentMinification(); testCppCommentMinification(); testXMLCommentMinification(); console.log("Issue #0079 - Minify C and XML comments : done"); } /** * Github issue #79 - Minify C and XML comments * Check that minification removed the C comments, both single and multiline * Erroneous behavior : keep the comment or remove only part of it * */ function testCCommentMinification() { var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : "c", contextType : parseInt(0), reassignVars : false, varsNotReassigned : ['a', 'b', 'c'], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var inputWithSingleLineComment = "if(i<0 /* || i>5 */)"; var result = RegPack.packer.runPacker(inputWithSingleLineComment, options); // Expected result : the commented code is gone assert.equal(result[0].contents, "if(i<0)"); var inputWithMultiLineComment = `/* * create an array and initialize it with fifty zeroes */ let j=Array(50).fill(0));` result = RegPack.packer.runPacker(inputWithMultiLineComment, options); // Expected result : the multiline comment is gone assert.equal(result[0].contents, "let j=Array(50).fill(0));"); } /** * Github issue #79 - Minify C and XML comments * Check that minification removed the oneliner C++ comment, and only the comment * Erroneous behavior : remove the whole line, or keep the comment */ function testCppCommentMinification() { var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : "c", contextType : parseInt(0), reassignVars : false, varsNotReassigned : ['a', 'b', 'c'], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var inputWithSingleLineComment = `var t=0; t=2; // change displayed value alert(t)`; var result = RegPack.packer.runPacker(inputWithSingleLineComment, options); // Expected result : the comment is gone, but the remainder of the line is still there assert.equal(result[0].contents, "var t=0;t=2;alert(t)"); } /** * Github issue #79 - Minify C and XML comments * Check that minification removed the XML comments, both single and multiline * Erroneous behavior : keep the comment or remove only part of it * */ function testXMLCommentMinification() { var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : "c", contextType : parseInt(0), reassignVars : false, varsNotReassigned : ['a', 'b', 'c'], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "" }; var inputWithSingleLineComment = "if(i<0 )"; var result = RegPack.packer.runPacker(inputWithSingleLineComment, options); // Expected result : the commented code is gone assert.equal(result[0].contents, "if(i<0)"); var inputWithMultiLineComment = ` let j=Array(50).fill(0));` result = RegPack.packer.runPacker(inputWithMultiLineComment, options); // Expected result : the multiline comment is gone assert.equal(result[0].contents, "let j=Array(50).fill(0));"); } module.exports = runTests; ================================================ FILE: tests/testIssue0082_backticksInTemplateLiterals.js ================================================ var RegPack = require("../regPack"); var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0082 - backticks in template literals : start"); testUseBackticksForPackedString(); testBackticksEscapingInTemplateLiteral(); console.log("Issue #0082 - backticks in template literals : done"); } /** * Github issue #82 - Backticks in template literal incorrectly read as end of string * An expression is defined with the construct ` ${variable+`other string`}` * The backticks inside the template literal ${} do not mark the end of the `-delimited string * */ function testUseBackticksForPackedString() { var input = "p=c.getImageData(0,0,512,512);d=p.data;for(j=0;j<512;++j){c.fillStyle=`#${`200`,`300`,`fff`,`000`][j&3]}`;d[j*4]=0;d[j*4+1]=255;d[j*4+2]=j>>1;d[j*4+3]=0;c.beginPath();c.moveTo(0,j);c.lineTo(511,j);c.stroke();}"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : only one string is found within the input, not five assert.equal(result[0].containedStrings.length, 1); // Expected result : the variable j is renamed throughout the input, not a single copy remains // Buggy result : in "[j&3]", j is not renamed assert.equal(result[0].contents.indexOf("j"), -1); } /** * Github issue #82 - Backticks in template literal incorrectly read as end of string * After fixing the main issue, the string is considered as a whole (good) * however the backticks inside are escaped \` (bad) * * Since the string's delimiter do not change (to preserve the template literal) * escaping the backticks is not needed */ function testBackticksEscapingInTemplateLiteral() { var input = "p=c.getImageData(0,0,512,512);d=p.data;for(j=0;j<512;++j){c.fillStyle=`#${`200`,`300`,`fff`,`000`][j&3]}`;d[j*4]=0;d[j*4+1]=255;d[j*4+2]=j>>1;d[j*4+3]=0;c.beginPath();c.moveTo(0,j);c.lineTo(511,j);c.stroke();}"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : true, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : no escaped backticks inside the preprocessed string assert.equal(result[0].contents.indexOf("\\`"), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0083_backslashToken.js ================================================ var RegPack = require("../regPack"); var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0083 - backslash token makes output larger : start"); testBackslashToken(); testCharacter9AloneInRange(); console.log("Issue #0083 - backslash token makes output larger : done"); } /** * Github issue #83 - Don't use "\" as a token if avoiding it makes the output smaller * \ costs 2 whereas other tokens cost 1 * The cost computation does not account for that extra cost * * Compare the result of packing the same string with the last character as only difference : * - one ends with a closing bracket ) and thus has \ as available token * - the second one ends with \ instead to remove it from token space * */ function testBackslashToken() { var input = fs.readFileSync("../TestCases/gitHub#83-backslash_largerOutput.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(2), crushLengthFactor : parseFloat(1), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var resultWithBackspaceToken = RegPack.packer.runPacker(input+")", options); var resultWithoutBackspaceToken = RegPack.packer.runPacker(input+"\\", options); // With the extra token available, compression should be at least as good (lower final size) as without it assert(resultWithBackspaceToken[0].result[1][0] <= resultWithoutBackspaceToken[0].result[1][0]); } /** * Github issue #83 - Don't use "\" as a token if avoiding it makes the output smaller * * The first version of the code that passed all existing tests * caused a CR (char code 10) to be added to the character class in the benchmark Flappy Dragon. * * Make sure this artefact is gone on a similar sample that as a lone 9 (TAB) in the last token range */ function testCharacter9AloneInRange() { var input = fs.readFileSync("../TestCases/gitHub#83-packer_9Alone.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var result = RegPack.packer.runPacker(input, options); // Make sure no CR has made it into the character class of the resulting string var CRcharacter = String.fromCharCode(10); assert.equal(result[0].result[1][1].indexOf(CRcharacter), -1); } module.exports = runTests; ================================================ FILE: tests/testIssue0085_backslashSequenceLength.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0085 - \\ not counted as length 2 : start"); testBackslashSequence(); testFlappyDragon(); console.log("Issue #0085 - \\ not counted as length 2 : done"); } /** * GitHub issue #85 - backslash \ counted as length 1 instead of 2 * resulting in \\\\ sequence reckoned not to be worth packing * * Example written for issue #85 * * Associated test file : gitHub#85-backslashSequence.js */ function testBackslashSequence() { var input = fs.readFileSync("../TestCases/gitHub#85-backslashSequence.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var output = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly and matches the original code assert.notEqual(output[0].result[1][2].indexOf("Final check : passed"), -1); assert.notEqual(output[0].result[2][2].indexOf("Final check : passed"), -1); // Expected result : the sequence \\\\ is packed assert.notEqual(output[0].result[0][2].indexOf("str = \\\\"), -1); assert.notEqual(output[0].result[1][2].indexOf("str = \\\\"), -1); assert.notEqual(output[0].result[2][2].indexOf("str = \\\\"), -1); } /** * GitHub issue #85 - backslash \ counted as length 1 instead of 2 * resulting in \\\\ sequence reckoned not to be worth packing * * Original example filed with issue #85 * * Associated test file : gitHub#85-flappyDragon.js */ function testFlappyDragon() { var input = fs.readFileSync("../TestCases/gitHub#85-flappyDragon.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var output = RegPack.packer.runPacker(input, options); // Expected result : the regular expression decodes correctly and matches the original code assert.notEqual(output[0].result[1][2].indexOf("Final check : passed"), -1); assert.notEqual(output[0].result[2][2].indexOf("Final check : passed"), -1); // Expected result : the sequence \\\\ is packed assert.notEqual(output[0].result[0][2].indexOf("str = \\\\"), -1); assert.notEqual(output[0].result[1][2].indexOf("str = \\\\"), -1); assert.notEqual(output[0].result[2][2].indexOf("str = \\\\"), -1); } module.exports = runTests ================================================ FILE: tests/testIssue0087_firstCharacterInPattern.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0087 - first character part of a pattern : start"); testFirstCharacterInCrusher(); console.log("Issue #0087 - first character part of a pattern : done"); } /** * GitHub issue #87 - crusher ignores first character while searching for patterns * As a consequence, the pattern starts with the 2nd character instead * * */ function testFirstCharacterInCrusher() { var input = "0123456789a0123456789b"; var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : false, timeVariableName : "", useES6 : true }; var output = RegPack.packer.runPacker(input, options); // Expected result : the first pattern is the string "0123456789", not "123456789" assert.equal(output[0].matchesLookup[0].originalString, "0123456789"); } module.exports = runTests ================================================ FILE: tests/testIssue0088_setIntervalAllocateVariable.js ================================================ var RegPack = require("../regPack") var fs = require("fs"); var assert = require("assert"); function runTests() { console.log("Issue #0088 - variable allocation for setInterval() : start"); testVariableAllocation(); console.log("Issue #0088 - variable allocation for setInterval() : done"); } /** * GitHub issue #88 - allocateNewVariable() crashes after being given incorrect parameters * Crash happens when the module "Refactor with setInterval()" is called without being given a time variable * (meaning it has to allocate and declare its own variable) * * Reusing test case from issue #19, with different parameters * * Associated test file : gitHub#19-setInterval_declarationAlone.js */ function testVariableAllocation() { var input = fs.readFileSync("../TestCases/gitHub#19-setInterval_declarationAlone.js", { encoding:"utf8"}); var options = { withMath : false, hash2DContext : false, hashWebGLContext : false, hashAudioContext : false, contextVariableName : false, contextType : parseInt(0), reassignVars : false, varsNotReassigned : [], crushGainFactor : parseFloat(1), crushLengthFactor : parseFloat(0), crushCopiesFactor : parseFloat(0), crushTiebreakerFactor : parseInt(1), wrapInSetInterval : true, timeVariableName : "", useES6 : true }; var output = RegPack.packer.preprocessor.preprocess(input, options); // Expected result : the encapsulation in setInterval is performed // References to "setInterval" are removed from the main code and pushed to the encapsulating call // And a time variable is allocated (and it does not crash) assert.equal(output[0].interpreterCall, "setInterval(_,33)"); assert.equal(output[0].wrappedInit.substr(1,2), "=0"); } module.exports = runTests ================================================ FILE: tests/testIssue0089_emptyThermalMapping.js ================================================ var RegPack = require("../regPack") var ThermalViewer = require("../thermalViewer") var fs = require("fs"); var DocumentMock = require("./documentMock"); var assert = require("assert"); function runTests() { console.log("Issue #0089 - thermal view with empty mapping : start"); testEmptyThermalMapping(); console.log("Issue #0089 - thermal view with empty mapping : done"); } /** * GitHub issue #89 - the development page showing all steps crashes with the mapping is empty * (no hashing, no reallocation) * * Crash happens in the ThermalViewer which tries to access the last element with the mapping * without performing bounds checking before * */ function testEmptyThermalMapping() { var input = "0123456789abcdef"; var thermalMapping = []; document = new DocumentMock(); var thermalViewer = new ThermalViewer(); var output = thermalViewer.render(input, thermalMapping); // Expected result : the thermalViewer returns without crashing // result is a
 with one text block
	assert.equal(document.message, "[0123456789abcdef]");
}



module.exports = runTests

================================================
FILE: tests/testIssue0094_missingVariableBlock.js
================================================
var RegPack = require("../regPack")
var fs = require("fs");
var assert = require("assert");

function runTests() {
	console.log("Issue #0094 - missing last block in candidate variable names : start");
	testRenamingWithNoSpare();
	console.log("Issue #0094 - missing last block in candidate variable names : done");
}


/**
 * GitHub issue #94 - reassignVariableNames() crashes after trying to read beyond array bounds
 * Crash happens when a candidate variable (usually z) is present in the last block of unused characters
 * and there are no spare variable names.
 */
function testRenamingWithNoSpare() {
	var input = '$=A=B=C=D=E=F=G=H=I=J=K=L=M=N=O=P=Q=R=S=T=U=V=W=X=Y=Z=_=a=b=c=d=e=f=g=h=i=j=k=l=m==n=o=p=q=r=s=t=u=v=w=x=y=z="ACFIMNPRSTVabcdefghiklmnopqrstuvwxy"';
	var options = {
		withMath : false,
		hash2DContext : false,
		hashWebGLContext : false,
		hashAudioContext : false,
		contextVariableName : false,
		contextType : parseInt(0),
		reassignVars : true,
		varsNotReassigned : [],
		crushGainFactor : parseFloat(1),
		crushLengthFactor : parseFloat(0),
		crushCopiesFactor : parseFloat(0),
		crushTiebreakerFactor : parseInt(1),
		wrapInSetInterval : false,
		timeVariableName : "",
		useES6 : true
	};
	
	var output = RegPack.packer.preprocessor.preprocess(input, options);

	// Expected result : variable reassignment is performed
	// However, since there are no spares, each variable is reassigned to itself
	// z has a match (being z itself) and the operation does not crash
	assert.equal(output[0].contents, input);
	
}



module.exports = runTests

================================================
FILE: tests/testIssue0096_multiLineMinification.js
================================================
var RegPack = require("../regPack")
var fs = require("fs");
var assert = require("assert");

function runTests() {
	console.log("Issue #0096 - Multi-line minification : start");
	testSingleLineMinification();
	testMultiLineMinification();
	console.log("Issue #0096 - Multi-line minification : done");
}


/**
 * Github issue #96 - Do not remove newlines or trailing blanks in template literals
 * Make sure that blanks are left inside strings. Test on single line string
 * Erroneous behavior : removing blanks inside the string
 */
function testSingleLineMinification() {
	var input = `var c=0, d="this is a single line with spaces", e='this is another string with spaces', f=255`;
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : false,
			hashAudioContext : false,
			contextVariableName : "c",
			contextType : parseInt(0),
			reassignVars : false,
			varsNotReassigned : ['a', 'b', 'c'],
			crushGainFactor : parseFloat(1),
			crushLengthFactor : parseFloat(0),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.runPacker(input, options);
	
	// Expected result : the preprocessed text contains the original strings untouched, the spaces are still present
	assert.notEqual(result[0].contents.indexOf('="this is a single line with spaces"'), -1);
	assert.notEqual(result[0].contents.indexOf("='this is another string with spaces'"), -1);
	// make sure that other spaces are removed
	assert.equal(result[0].contents.substr(0, 10), "var c=0,d=");
}

/**
 * Github issue #96 - Do not remove newlines or trailing blanks in template literals
 * Make sure that blanks are left inside strings. Test on multiline strings defined as template literals (since ES6)
 * Erroneous behavior : removing blanks or newlines (CR) inside the string
 */
function testMultiLineMinification() {
	var input = `var c=0, d=\`this is a multi line string
used as an example.\`, e=255` ;
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : false,
			hashAudioContext : false,
			contextVariableName : "c",
			contextType : parseInt(0),
			reassignVars : false,
			varsNotReassigned : ['a', 'b', 'c'],
			crushGainFactor : parseFloat(1),
			crushLengthFactor : parseFloat(0),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.runPacker(input, options);
	
	// Expected result : the preprocessed text contains the original string untouched, the CR is still present
	assert.notEqual(result[0].contents.indexOf('this is a multi line string\n'), -1);
	// make sure that other spaces are removed
	assert.equal(result[0].contents.substr(0, 10), "var c=0,d=");
}

module.exports = runTests;


================================================
FILE: tests/testPackingConsistency.js
================================================
var RegPack = require("../regPack");
var fs = require("fs");
var assert = require("assert");


function runTests() {
	console.log("Packing consistency tests : start");
	testConsistency("../TestCases/gitHub#9-hashloop.js");
	testConsistency("../TestCases/gitHub#17-multipleContexts.js");
	testConsistency("../TestCases/gitHub#19-setInterval_declarationAlone.js");
	testConsistency("../TestCases/gitHub#30-webglContext_create_charset.js");
	testConsistency("../TestCases/gitHub#31-direct-hyphenBeginsBlock.js");
	testConsistency("../TestCases/gitHub#44-setInterval_arrowFunctionMultiParam.js");
	testConsistency("../TestCases/hash_using_length.js");
	console.log("Packing consistency tests : done");
}

/**
 * Unpacks a compressed string, independently of the wrapper (crusher or packer)
 */
function unpack(packedCode) {
	var callFunction = "eval(";
	var packedStringVarOffset = packedCode.lastIndexOf(callFunction);
	if (packedStringVarOffset == -1) {
		callFunction = "setInterval(";
		packedStringVarOffset= packedCode.lastIndexOf(callFunction);
	}
	if (packedStringVarOffset == -1) {
		// no unpacking routine found : code is not packed
		return packedCode;
	}
	packedStringVarOffset+=callFunction.length;
	var packedStringVar = packedCode[packedStringVarOffset];
	
	// look for packed string : 
	var packedStringBegin = packedCode.indexOf(packedStringVar+"=");
	var packedStringDelimiter = packedCode[packedStringBegin+2];
	var packedStringEnd = packedCode.lastIndexOf(packedStringDelimiter);
	
	var packedString = packedCode.substring(packedStringBegin+3, packedStringEnd);
	var originalString = packedString.replace(new RegExp("\\\\"+packedStringDelimiter, "g"), packedStringDelimiter)
									 .replace(/\\\\/g,'\\');

	
	var beginRegPack = packedCode.indexOf("=/", packedStringEnd);
	if (beginRegPack>0) {	// RegPack marker identified
		var endRegPack = packedCode.indexOf("/.exec", beginRegPack);
		var tokenString = packedCode.substring(beginRegPack+2, endRegPack);
		var regToken = new RegExp(tokenString,"");
		for(var token="" ; token = regToken.exec(originalString) ; ) {
			var k = originalString.split(token);
			originalString = k.join(k.shift());
		}
	} else {
		var tokensEnd = packedStringEnd;
		var tokensBegin = packedCode.lastIndexOf(packedStringDelimiter, tokensEnd-1);
		packedStringEnd = packedCode.lastIndexOf(packedStringDelimiter, tokensBegin-1);
		
		if (packedStringEnd > 0) {	// JSCrush / FirstCrush marker identified
			packedString = packedCode.substring(packedStringBegin+3, packedStringEnd);
			originalString = packedString.replace(new RegExp("\\\\"+packedStringDelimiter, "g"), packedStringDelimiter)
										 .replace(/\\\\/g,'\\');
			
			var tokenString= packedCode.substring(tokensBegin+1, tokensEnd);
			for (var i in tokenString) {
				var k = originalString.split(tokenString[i])
				originalString = k.join(k.pop());
			}
		} 
	}
	return originalString;

}

/**
 * Improved assert that two strings match
 * In case of mismatch, show the first difference
 */
function assertEqualStrings(actual, expected) {
	assert.equal(actual.length, expected.length);
	var delta=0;
	for (var i=0; i10) {
				break;
			}
		}
	}
	assert.equal(actual, expected);
}


/**
 * This test runs in an input through the crusher and packer stages,
 * then unpacks it and compares with the original. The two must match.
 */
function testConsistency(inputFile) {
	var input = fs.readFileSync(inputFile, { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : false,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : false,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(1),
			crushLengthFactor : parseFloat(0),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : "",
			useES6 : true
		};
	var output = RegPack.packer.runPacker(input, options);
	
	// Expected result : the compressed result at each stage matches the input
	// References to "setInterval" are removed from the main code and pushed to the encapsulating call
	assertEqualStrings(unpack(output[0].result[0][1]), output[0].contents);
	//console.log("Packed = "+output[0].result[1][1]);

	assertEqualStrings(unpack(output[0].result[1][1]), output[0].contents);
	assertEqualStrings(unpack(output[0].result[2][1]), output[0].contents);
}

module.exports = runTests;

================================================
FILE: tests/testStringHelper.js
================================================
var StringHelper = require("../stringHelper")
var PackerData = require("../packerData");
var fs = require("fs");
var assert = require("assert");


function runTests() {
	console.log("StringHelper tests : start");
	testGetByteLength();
	testBase64();
	testWriteRangeToRegexpCharClass();
	testIsActualCodeAt();
	console.log("StringHelper tests : done");
}

// basic implementation of btoa(), present in browser but not in node
btoa=function(input) {
	var output="";
	var code="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	for (i=0; i>2];
		output+=code[((input.charCodeAt(i)&3)<<4)+(i+1>4 : 0)];
		output+=i+1>=input.length ? "=" : code[((input.charCodeAt(i+1)&15)<<2) + (i+2>6 : 0)];
		output+=i+2>=input.length ? "=" : code[input.charCodeAt(i+2)&63];
	}
	return output;
}

// basic implementation of atob(), present in browser but not in node
atob=function(input) {
	var output="";
	var code="=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	for (i=0; i>4));
		output+=encoded2<0 ? "" : String.fromCharCode(((encoded1&15)<<4)+((encoded2&60)>>2));
		output+=encoded3<0 ? "" : String.fromCharCode(((encoded2&3)<<6)+encoded3);
	}
	return output;
}
/**
 * Unit test for StringHelper.getByteLength()
 */
function testGetByteLength() {
	var stringHelper = StringHelper.getInstance();
	assert.equal(stringHelper.getByteLength("e"), 1);
	assert.equal(stringHelper.getByteLength(""), 1);
	assert.equal(stringHelper.getByteLength("~"), 1);
	assert.equal(stringHelper.getByteLength("\x80"), 2);
	assert.equal(stringHelper.getByteLength("\xfc"), 2);
	assert.equal(stringHelper.getByteLength("\u0200"), 2);
	assert.equal(stringHelper.getByteLength("\u02ff"), 2);
	assert.equal(stringHelper.getByteLength("\u2000"), 3);
}

/**
 * Unit test for StringHelper.unicodeToBase64()
 *               StringHelper.base64ToUnicode()
 */ 
function testBase64() {
	var stringHelper = StringHelper.getInstance();
	assert.equal(stringHelper.unicodeToBase64("M"), "TQ==");
	assert.equal(stringHelper.unicodeToBase64("Ma"), "TWE=");
	assert.equal(stringHelper.unicodeToBase64("Man"), "TWFu");
	assert.equal(stringHelper.unicodeToBase64("abc123!?$*&()'-=@~"), "YWJjMTIzIT8kKiYoKSctPUB+");
	assert.equal(stringHelper.unicodeToBase64("This is the data, in the clear."), "VGhpcyBpcyB0aGUgZGF0YSwgaW4gdGhlIGNsZWFyLg==");
	assert.equal(stringHelper.unicodeToBase64("\n"), "Cg==");
	assert.equal(stringHelper.unicodeToBase64("\u0227"), "yKc=");
	assert.equal(stringHelper.unicodeToBase64("Base 64 \u2014 Mozilla Developer Network"), "QmFzZSA2NCDigJQgTW96aWxsYSBEZXZlbG9wZXIgTmV0d29yaw==");
	assert.equal(stringHelper.unicodeToBase64("\u2713 \xE0 la mode"), "4pyTIMOgIGxhIG1vZGU=");
	assert.equal(stringHelper.unicodeToBase64("\uD83D\uDD25"), "8J+UpQ==");

	assert.equal(stringHelper.base64ToUnicode("TQ=="), "M");
	assert.equal(stringHelper.base64ToUnicode("TWE="), "Ma");
	assert.equal(stringHelper.base64ToUnicode("TWFu"), "Man");
	assert.equal(stringHelper.base64ToUnicode("YWJjMTIzIT8kKiYoKSctPUB+"), "abc123!?$*&()'-=@~");
	assert.equal(stringHelper.base64ToUnicode("VGhpcyBpcyB0aGUgZGF0YSwgaW4gdGhlIGNsZWFyLg=="), "This is the data, in the clear.");
	assert.equal(stringHelper.base64ToUnicode("Cg=="), "\n");
	assert.equal(stringHelper.base64ToUnicode("yKc="), "\u0227");
	assert.equal(stringHelper.base64ToUnicode("QmFzZSA2NCDigJQgTW96aWxsYSBEZXZlbG9wZXIgTmV0d29yaw=="), "Base 64 \u2014 Mozilla Developer Network");
	assert.equal(stringHelper.base64ToUnicode("4pyTIMOgIGxhIG1vZGU="), "\u2713 \xE0 la mode");
	assert.equal(stringHelper.base64ToUnicode("8J+UpQ=="), "\uD83D\uDD25");
	
}

/**
 * Unit test for StringHelper.writeBlocksToRegexpCharClass()
 *               StringHelper.writeRangeToRegexpCharClass()
 *               StringHelper.writeCharToRegexpCharClass()
 *               StringHelper.needsEscapingInCharClass()
 */
function testWriteRangeToRegexpCharClass () {
	var stringHelper = StringHelper.getInstance();
	assert.equal (stringHelper.writeRangeToRegexpCharClass(48, 57), "0-9");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(65, 90), "A-Z");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(65, 66), "AB");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(65, 65), "A");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(45, 45), "-");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(45, 48), "--0");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(32, 35), " -#");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(32, 34), ' -"');
	assert.equal (stringHelper.writeRangeToRegexpCharClass(90, 95), "Z-_");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(91, 95), "[-_");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(90, 93), "Z-\\]");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(90, 92), "Z-\\\\");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(91, 92), "[\\\\");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(92, 97), "\\\\-a");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(93, 98), "\\]-b");
	assert.equal (stringHelper.writeRangeToRegexpCharClass(92, 93), "\\\\\\]");	
	assert.equal (stringHelper.writeRangeToRegexpCharClass(126, 128), "~-\\x80");	
	assert.equal (stringHelper.writeRangeToRegexpCharClass(130, 146), "\\x82-\\x92");	
	assert.equal (stringHelper.writeRangeToRegexpCharClass(254, 256), "\\xfe-\\u0100");	
	assert.equal (stringHelper.writeRangeToRegexpCharClass(512, 767), "\\u0200-\\u02ff");	
	assert.equal (stringHelper.writeRangeToRegexpCharClass(110, 109), "");	
	assert.equal (stringHelper.writeRangeToRegexpCharClass(35, 33), "");	
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:48, last:57}]), "0-9");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:65, last:90}]), "A-Z");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:48, last:57}, {first:65, last:90}]), "0-9A-Z");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:48, last:57}, {first:65, last:65}]), "0-9A");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:48, last:57}, {first:65, last:65}, {first:67, last:67}]), "0-9AC");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:48, last:57}, {first:65, last:65}, {first:45, last:45}]), "-0-9A");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:91, last:95}, {first:45, last:45}, {first:65, last:90}]), "-[-_A-Z");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:91, last:95}, {first:45, last:46}, {first:65, last:90}]), "-.[-_A-Z");
	assert.equal (stringHelper.writeBlocksToRegexpCharClass([{first:91, last:95}, {first:45, last:48}, {first:65, last:90}]), "--0[-_A-Z");
}

/**
 * Unit test for StringHelper.isActualCodeAt()
 */
function testIsActualCodeAt() {
	var packerData = new PackerData();
	var stringHelper = StringHelper.getInstance();

	// empty string analysis : everything is code
	assert.equal (stringHelper.isActualCodeAt(0, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(20, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(400, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(8000, packerData), true);
	
	// two strings, the first one contains a template literal
	packerData.containedStrings = [ {begin : 20, end : 100}, {begin : 200, end : 300} ];
	packerData.containedTemplateLiterals = [ {begin : 40, end : 70} ];
	
	assert.equal (stringHelper.isActualCodeAt(0, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(19, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(21, packerData), false);
	assert.equal (stringHelper.isActualCodeAt(39, packerData), false);
	assert.equal (stringHelper.isActualCodeAt(41, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(69, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(71, packerData), false);
	assert.equal (stringHelper.isActualCodeAt(99, packerData), false);
	assert.equal (stringHelper.isActualCodeAt(101, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(199, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(201, packerData), false);
	assert.equal (stringHelper.isActualCodeAt(299, packerData), false);
	assert.equal (stringHelper.isActualCodeAt(301, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(400, packerData), true);
	assert.equal (stringHelper.isActualCodeAt(8000, packerData), true);
}

module.exports = runTests;

================================================
FILE: tests/testWebGLContextCreate.js
================================================
var RegPack = require("../regPack")
var fs = require("fs");
var assert = require("assert");


function runTests() {
	console.log("WebGL Context tests : start");
	testCreateExperimentalWebGL();
	testCreateWebGL();
	testCreateBothFirst();
	testCreateBothSecond();
	testCreateOptionsFirst();
	testCreateOptionsSecond();
	testHashCollisions();
	console.log("WebGL Context tests : done");
}


/**
 * Creation of a GL context as "experimental-webgl" only
 *
 * Associated test file : webglContext_create1.js
 */
function testCreateExperimentalWebGL() {
	var input = fs.readFileSync("../TestCases/webglContext_create1.js", { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : true,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : true,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(2),
			crushLengthFactor : parseFloat(1),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.preprocessor.preprocess(input, options);
	
	// Expected result : creation of WebGL Context recognized
	// WebGL-hashed environment added to input lines
	
	assert.equal(result.length, 4);
	assert.notEqual(result[1].name.indexOf("WebGL"), -1);
	assert.notEqual(result[2].name.indexOf("WebGL"), -1);
	assert.notEqual(result[3].name.indexOf("WebGL"), -1);
}


/**
 * Creation of a GL context as "webgl" only
 *
 * Associated test file : webglContext_create2.js
 */
function testCreateWebGL() {
	var input = fs.readFileSync("../TestCases/webglContext_create2.js", { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : true,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : true,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(2),
			crushLengthFactor : parseFloat(1),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.preprocessor.preprocess(input, options);
	
	// Expected result : creation of WebGL Context recognized
	// WebGL-hashed environment added to input lines
	
	assert.equal(result.length, 4);
	assert.notEqual(result[1].name.indexOf("WebGL"), -1);
	assert.notEqual(result[2].name.indexOf("WebGL"), -1);
	assert.notEqual(result[3].name.indexOf("WebGL"), -1);
}

/**
 * Creation of a GL context as either "webgl" or "experimental-webgl" in the same conditional expression
 * (experimental-webgl mentioned first)
 *
 * Associated test file : webglContext_create3.js
 */
function testCreateBothFirst() {
	var input = fs.readFileSync("../TestCases/webglContext_create3.js", { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : true,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : true,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(2),
			crushLengthFactor : parseFloat(1),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.preprocessor.preprocess(input, options);
	
	// Expected result : creation of WebGL Context recognized
	// WebGL-hashed environment added to input lines
	
	assert.equal(result.length, 4);
	assert.notEqual(result[1].name.indexOf("WebGL"), -1);
	assert.notEqual(result[2].name.indexOf("WebGL"), -1);
	assert.notEqual(result[3].name.indexOf("WebGL"), -1);
}


/**
 * Creation of a GL context as either "webgl" or "experimental-webgl" in the same conditional expression
 * (webgl mentioned first)
 *
 * Associated test file : webglContext_create4.js
 */
function testCreateBothSecond() {
	var input = fs.readFileSync("../TestCases/webglContext_create4.js", { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : true,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : true,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(2),
			crushLengthFactor : parseFloat(1),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.preprocessor.preprocess(input, options);
	
	// Expected result : creation of WebGL Context recognized
	// WebGL-hashed environment added to input lines
	
	assert.equal(result.length, 4);
	assert.notEqual(result[1].name.indexOf("WebGL"), -1);
	assert.notEqual(result[2].name.indexOf("WebGL"), -1);
	assert.notEqual(result[3].name.indexOf("WebGL"), -1);
}

/**
 * Creation of a GL context as either "webgl" or "experimental-webgl" in the same conditional expression
 * along with options stored in a variable defined earlier.
 *
 * Associated test file : webglContext_create5.js
 */
function testCreateOptionsFirst() {
	var input = fs.readFileSync("../TestCases/webglContext_create5.js", { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : true,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : true,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(2),
			crushLengthFactor : parseFloat(1),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.preprocessor.preprocess(input, options);
	
	// Expected result : creation of WebGL Context recognized
	// WebGL-hashed environment added to input lines
	
	assert.equal(result.length, 4);
	assert.notEqual(result[1].name.indexOf("WebGL"), -1);
	assert.notEqual(result[2].name.indexOf("WebGL"), -1);
	assert.notEqual(result[3].name.indexOf("WebGL"), -1);
}


/**
 * Creation of a GL context as either "webgl" or "experimental-webgl" in the same conditional expression
 * along with options defined in the same line
 *
 * Associated test file : webglContext_create6.js
 */
function testCreateOptionsSecond() {
	var input = fs.readFileSync("../TestCases/webglContext_create6.js", { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : true,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : true,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(2),
			crushLengthFactor : parseFloat(1),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.preprocessor.preprocess(input, options);
	
	// Expected result : creation of WebGL Context recognized
	// WebGL-hashed environment added to input lines
	
	assert.equal(result.length, 4);
	assert.notEqual(result[1].name.indexOf("WebGL"), -1);
	assert.notEqual(result[2].name.indexOf("WebGL"), -1);
	assert.notEqual(result[3].name.indexOf("WebGL"), -1);
}


/**
 * Detection of hash collisions between two methods, one containing entierly the name of the other
 * Example tested with enable() and enableVertexAttribArray()
 *
 * Associated test file : webglContext_substringHash.js
 */
function testHashCollisions() {
	var input = fs.readFileSync("../TestCases/webglContext_substringHash.js", { encoding:"utf8"});
	var options = {
			withMath : false,
			hash2DContext : false,
			hashWebGLContext : true,
			hashAudioContext : false,
			contextVariableName : false,
			contextType : parseInt(0),
			reassignVars : true,
			varsNotReassigned : [],
			crushGainFactor : parseFloat(2),
			crushLengthFactor : parseFloat(1),
			crushCopiesFactor : parseFloat(0),
			crushTiebreakerFactor : parseInt(1),
			wrapInSetInterval : false,
			timeVariableName : ""
		};
	var result = RegPack.packer.preprocessor.preprocess(input, options);
	
	// Expected result : hashed value for both enable() and enableVertexAttribArray() is different
	// upon hashing methods
	var hash1Match = result[1].contents.match(/gl\.(\w*)\(gl.DEPTH_TEST/);
	var hash1Value = hash1Match[1];
	var hash2Match = result[1].contents.match(/gl\.(\w*)\(\);/);
	var hash2Value = hash2Match[1];
	assert.notEqual(hash1Value, hash2Value);
	
	// and same result upon hashing properties
	hash1Match = result[3].contents.match(/gl\[gl\.(\w*)\]\(gl\[gl.(\w*)\]\);/);
	hash1Value = hash1Match[1];
	hash2Match = result[3].contents.match(/gl\[gl\.(\w*)\]\(\);/);
	hash2Value = hash2Match[1];
	assert.notEqual(hash1Value, hash2Value);
}

module.exports = runTests;

================================================
FILE: thermalViewer.js
================================================
/**
 * @constructor
 * The ThermalViewer class renders the original code into a DHTML view,
 * using background color to show the final (packed) size of each character
 *
 *
 * @param name The name of the branch, summing the operations performed
 * @param dataString The input string to pack
 */
function ThermalViewer ()
{
}


ThermalViewer.prototype = {

	/**
	 * Produces an HTML render of the patterns used by the packer.
	 * The rendered 
is not added to the page HTML. * * @param unpackedCode The original unpacked code (before preprocessing) * @param thermalMap Array listing all the mappings for the successive replacements * @return A
object showing the varying compression rates in the code * */ render : function(unpackedCode, thermalMap) { var output = document.createElement("pre"); output.setAttribute("class","topLevel"); // #89 : if the mapping is empty, assume identity // (no compression / replacement, every character stored as is) var finalSize = unpackedCode.length; if (thermalMap.length > 0) { finalSize = thermalMap[thermalMap.length-1][0].outLength; } // transform the successive mappings to a heatmap // start from the final size : 8 bits for each character of the output var currentHeatMap = new Array(finalSize).fill(8); for (let stageIndex=thermalMap.length-1; stageIndex>=0; --stageIndex) { let currentMapping = thermalMap[stageIndex]; var nextHeatMap = currentHeatMap; if (currentMapping[0].complete) { // the transform covers every byte of the string, hence the map is recomputer from scratch // as opposed to extra chapters that are only added to the current cost map nextHeatMap = new Array(currentMapping[0].inLength).fill(0); } for (let blockIndex=1; blockIndex 0) { this.addTextBlock(output, currentColorValue, unpackedCode.substring(startOffset, offset)); } startOffset = offset; currentColorValue = color; } } if (startOffset or
 where the new block will be appended as a child
	 * @param colorClass background color index, integer [0-10] 
	 * @param text the text to display
	 */
	addTextBlock : function(parentNode, colorClass, text) {
		var newSpan = document.createElement("span");
		newSpan.appendChild(document.createTextNode(text));
		newSpan.setAttribute("class", "thermal"+Math.min(10, colorClass));
		parentNode.appendChild(newSpan);
	}
}


// Node.js exports (for non-regression tests only)
if (typeof require !== 'undefined') {
	module.exports = ThermalViewer;
}