Add Javascript Compressor, and new XObject base util class (will replace Object.js)
authorAlan Knowles <alan@akkbhome.com>
Thu, 29 Apr 2010 22:59:07 +0000 (06:59 +0800)
committerAlan Knowles <alan@akkbhome.com>
Thu, 29 Apr 2010 23:00:17 +0000 (07:00 +0800)
pack.js         - pack and rewrite JS files - similar to YUI Compressor
XObject.js      - replacement for Object.js - more generic, no overwriting built in types etc.
ScopeParser.js  - full scope parser

14 files changed:
File.js
JSDOC/CompressWhite.js [new file with mode: 0644]
JSDOC/Identifier.js [new file with mode: 0644]
JSDOC/Packer.js [new file with mode: 0644]
JSDOC/Scope.js [new file with mode: 0644]
JSDOC/ScopeParser.js [new file with mode: 0644]
JSDOC/Token.js
JSDOC/TokenReader.js
JSDOC/TokenStream.js
XObject.js [new file with mode: 0644]
docs.js
docs/class.html
docs/index.html
pack.js [new file with mode: 0755]

diff --git a/File.js b/File.js
index f0da9ff..e788e59 100755 (executable)
--- a/File.js
+++ b/File.js
@@ -4,6 +4,20 @@ Gio = imports.gi.Gio;
 
 imports['String.js'].load(String);
 
+/**
+* @namespace File
+* 
+* Library to wrap GLib and Gio basic File related methods
+* 
+* usage:
+* 
+* File = import.File.File;
+* 
+* var contents = File.read("/tmp/test.txt");
+*
+* 
+* 
+*/
 var File = {
 
     SEPARATOR : '/',
@@ -74,14 +88,44 @@ var File = {
         var can = f.resolve_relative_path('');
         return can.get_path();
     },
-
+    /**
+     * write
+     * @arg path {String} File to write to
+     * @arg string {String} Contents of file.
+     * 
+     */
     write : function (path, string) {
         var f = Gio.file_new_for_path(String(path));
         var data_out = new Gio.DataOutputStream({base_stream:f.replace(null, false, Gio.FileCreateFlags.NONE, null)});
         data_out.put_string(string, null);
         data_out.close(null);
     },
-
+    /**
+     * append
+     * @arg path {String} File to write to
+     * @arg string {String} string to append to file.
+     * 
+     */
+    append : function (path, string) {
+        var f = Gio.file_new_for_path(String(path));
+        var data_out = new Gio.DataOutputStream({
+                base_stream:f.append_to(Gio.FileCreateFlags.NONE, null)
+        });
+        data_out.put_string(string, null);
+        data_out.close(null);
+    },
+    /**
+     * remove 
+     * Delete a file.
+     * @arg path {String} File to remove
+     * 
+     * 
+     */
+    remove : function (path)
+    {
+        var f = Gio.file_new_for_path(String(path));
+        return f['delete']();
+    },
     // copy files recursively from fromDir, silently ignore them if they already exist in toDir
     silentRecursiveCopy : function (fromDir, toDir) {
         var filesToCopy = File.recursiveListing(fromDir);
diff --git a/JSDOC/CompressWhite.js b/JSDOC/CompressWhite.js
new file mode 100644 (file)
index 0000000..6b08e16
--- /dev/null
@@ -0,0 +1,284 @@
+ // <script type="text/javascript">
+/**
+ * 
+ * pack a javascript file, and return a shorter version!
+ * 
+ * a bit picky at present with ; and crlf reading...
+ * @arg ts {TokenStream} 
+   @arg packer {Packer} 
+ */
+CompressWhite =  function (ts, packer)
+{
+    
+    ts.rewind();
+    //var str = File.read(fn);
+    var rep_var = 1;
+    
+    while (true) {
+        var tok = ts.next();
+        if (!tok) {
+            break;
+        }
+        if (tok.type == "WHIT") {
+            continue;
+            //if (tok._isDoc) {
+            //    continue;
+            //}
+            // just spaces, not \n!
+            //if (tok.data.indexOf("\n") < 0) {
+            //    continue;
+           // }
+            
+            
+        }
+        if (tok.data == "}")  {
+            
+            if (ts.lookTok(1).type == 'NAME' && ts.look(1,true).name == "NEWLINE") {
+            
+                ts.look(0).outData = ts.look(0).data+"\n";
+            }
+            // restore.. 
+            
+            continue;
+        }
+        // add semi-colon's where linebreaks are used... - not foolproof yet.!
+        if (tok.type == "NAME")  {
+            //var tokident = ts.look(-1).data + tok.data + ts.look(1).data +  ts.look(2).data;
+            // a = new function() {} 
+            if (ts.lookTok(1).data == '=' && ts.lookTok(2).name == 'NEW'  && 
+                ts.lookTok(3).name == 'FUNCTION') {
+                // freeze time.. 
+                var cu = ts.cursor;
+                
+                ts.balance("(");
+                
+                
+                ts.balance("{");
+                // if next is not ';' -> make it so...
+                
+                if (ts.lookTok(1).data != ';'  && ts.lookTok(1).data != '}' && ts.lookTok(1,true).name == "NEWLINE") {
+                    ts.look(0).outData = ts.cur().data +";";
+                }
+                // restore.. 
+                ts.cursor = cu;
+                continue;
+            }
+            // a = function() { ...
+               
+            if (ts.lookTok(1).data == '=' &&  ts.lookTok(2).name == "FUNCTION") {
+                // freeze time.. 
+                //println("got = function() ");
+                var cu = ts.cursor;
+                
+                ts.balance("(");
+                ts.balance("{");
+                // if next is not ';' -> make it so...
+                // although this var a=function(){},v,c; causes 
+                if (ts.lookTok(1).data != ';' && ts.lookTok(1).data != '}' && ts.lookTok(1,true).name == "NEWLINE") {
+                    ts.look(0).outData = ts.look(0).data+";";
+                }
+                // restore.. 
+                ts.cursor = cu;
+                continue;
+            }
+            // next item is a name..
+            if ((ts.lookTok(1).type == 'NAME' || ts.lookTok(1).type == 'KEYW' ) &&  ts.look(1,true).name == "NEWLINE") {
+                // preserve linebraek
+                ts.look(0).outData = ts.look(0).data+"\n";
+            }
+            // method call followed by name..
+            if (ts.lookTok(1).data == "(")  {
+                var cu = ts.cursor;
+                
+                ts.balance("(");
+                 // although this var a=function(){},v,c; causes 
+                
+                if (ts.lookTok(1).type == 'NAME' && ts.look(1,true).name == "NEWLINE") {
+                
+                    ts.look(0).outData = ts.look(0).data+"\n";
+                }
+                // restore.. 
+                ts.cursor = cu;
+                continue;
+            }
+            
+            
+            // function a () { ... };
+                /*
+            if (ts.look(-1).isTypeN(Script.TOKfunction) &&  ts.look(1).isTypeN(Script.TOKlparen)) {
+                // freeze time.. 
+                //println("got = function() ");
+                var cu = ts.cursor;
+                
+                ts.balance("lparen");
+                ts.balance("lbrace");
+                // if next is not ';' -> make it so...
+                // although this var a=function(){},v,c; causes 
+                if (!ts.look(1).isData(';') && !ts.look(1).isData('}') && ts.look(1,true).isLineBreak()) {
+                    ts.cur().outData = ts.cur().data+";";
+                }
+                // restore.. 
+                ts.cursor = cu;
+                continue;
+            }
+            */
+            
+            // a = { ....
+                
+            if (ts.lookTok(1).data == '=' &&  ts.lookTok(2).data == '{') {
+                // freeze time.. 
+                //println("----------*** 3 *** --------------");
+                var cu = ts.cursor;
+                
+                if (!ts.balance("{") ){
+                    throw "could not find end lbrace!!!";
+                }
+                // if next is not ';' -> make it so...
+
+                if (ts.lookTok(1).data != ';' && ts.lookTok(1).data != '}' && ts.look(1,true).name=="NEWLINE") {
+                    ts.look(0).outData = ts.look(0).data +";";
+                }
+                // restore.. 
+                ts.cursor = cu;
+                continue;
+            }
+            
+            // any more??
+        }
+        
+        
+        
+         
+        //println("got Token: " + tok.type);
+        
+        
+        
+        switch(tok.data.toUpperCase()) {
+            // things that need space appending
+            case "FUNCTION":
+            case "BREAK":
+            case "CONTINUE":
+                // if next item is a identifier..
+                if (ts.lookTok(1).type == "NAME" || ts.lookTok(1).data.match(/^[a-z]+$/i) ) { // as include is a keyword for us!!
+                   tok.outData =  tok.data + " ";
+                }
+                continue;
+                
+                
+            case "RETURN": // if next item is not a semi; (or }
+                if (ts.lookTok(1).data == ';' || ts.lookTok(1).data == '}') {
+                    continue;
+                }
+                tok.outData =  tok.data + " ";
+                
+                continue;
+            
+                
+            case "ELSE": // if next item is not a semi; (or }
+                if (!ts.lookTok(1).name == "IF") {
+                    continue;
+                }
+                
+                tok.outData =  tok.data + " ";
+                continue;
+            
+            case "++": // if previous was a plus or next is a + add a space..
+            case "--": // if previous was a - or next is a - add a space..
+            
+                var p = (tok.data == "--" ? '-' : '+'); 
+            
+                if (ts.lookTok(1).data == p) {
+                    tok.outData =  tok.data + " ";
+                }
+                if (ts.lookTok(-1).data == p) {
+                    tok.outData =  " " +  tok.data;
+                    
+                }
+                continue;
+            
+            case "IN": // before and after?? 
+            case "INSTANCEOF":
+                
+                tok.outData = " " + tok.data + " ";
+                continue;
+            
+            case "VAR": // always after..
+            case "NEW":
+            case "DELETE":
+            case "THROW":
+            case "CASE":
+            
+            case "VOID":
+                tok.outData =  tok.data + " ";
+                
+                continue
+                
+            case "TYPEOF": // what about typeof(
+                if (ts.lookTok(1).data != '(') {
+                    tok.outData =  tok.data + " ";
+                }
+                continue;
+             case ";":
+                //remove semicolon before brace -- 
+                //if(ts.look(1).isTypeN(Script.TOKrbrace)) {
+                //    tok.outData = '';
+               // }
+                continue;
+           
+            default:
+                continue;
+        }
+    }
+    
+    ts.rewind();
+    
+    // NOW OUTPUT THE THING.
+    //var f = new File(minfile, File.NEW);
+    
+    var out = '';
+    var outoff = 0;
+    out.length = ts.slen; // prealloc.
+    out = '';
+    while (true) {
+        var tok = ts.nextTok();
+           
+        if (!tok) {
+            break;
+        }
+        
+        
+        if (tok.type == "NAME"  && tok.identifier && tok.identifier.mungedValue && tok.identifier.mungedValue.length) {
+            //f.write(tok.identifier.mungedValue);
+            out += tok.identifier.mungedValue;
+            continue;
+        }
+        
+        // at this point we can apply a text translation kit...
+        
+        if ((tok.type == 'STRN') && (tok.name== 'DOUBLE_QUOTE')) {
+            if (packer && packer.stringHandler) {
+                out += packer.stringHandler(tok);
+                continue;
+            }
+        }
+     
+        out += tok.outData !== false ? tok.outData : tok.data;
+        
+        if ((tok.outData == ';') && (out.length - outoff > 255)) {
+            outoff = out.length;
+            out += "\n";
+        }
+    }
+    //f.close();
+    /*
+    // remove the last ';' !!!
+    if (out.substring(out.length-1) == ';') {
+        return out.substring(0,out.length-1);
+       }
+    */
+    return out;
+    
+}
+    
+    
\ No newline at end of file
diff --git a/JSDOC/Identifier.js b/JSDOC/Identifier.js
new file mode 100644 (file)
index 0000000..7cc82bd
--- /dev/null
@@ -0,0 +1,25 @@
+//<Script type="text/javascript">
+
+/**
+ * @class  Identifier
+ * holds details about identifiers and their replacement values
+ * used by the packer..
+ * 
+ */
+
+
+function Identifier(name, scope) {
+   // print("NEW IDENT: " + name);
+    this.name = name;
+    this.scope = scope;
+    this.identifiers = {};
+    
+}
+Identifier.prototype = {
+    name: '',
+    refcount: 1,
+    mungedValue : '', // should be at least 1?!?!
+    scope : false,  // script of fn scope..
+    toMunge : true
+};
diff --git a/JSDOC/Packer.js b/JSDOC/Packer.js
new file mode 100644 (file)
index 0000000..d04e630
--- /dev/null
@@ -0,0 +1,449 @@
+// <script type="text/javascript">
+XObject         = imports.XObject.XObject;
+File            = imports.File.File;
+
+TextStream      = imports['JSDOC/TextStream.js'].TextStream;
+TokenReader     = imports['JSDOC/TokenReader.js'].TokenReader;
+ScopeParser     = imports['JSDOC/ScopeParser.js'].ScopeParser;
+TokenStream     = imports['JSDOC/TokenStream.js'].TokenStream;
+CompressWhite   = imports['JSDOC/CompressWhite.js'].CompressWhite;
+
+GLib = imports.gi.GLib;
+/**
+ * @namespace JSDOC
+ * @class  Packer
+ * Create a new packer
+ * 
+ * Use with pack.js 
+ * 
+ * 
+ * Usage:
+ * <code>
+ *
+Packer = imports['JSDOC/Packer.js'].Packer;
+var x = new  Packer({
+    
+    files : [ "/location/of/file1.js", "/location/of/file2.js", ... ],
+    target : "/tmp/output.js",
+    debugTarget : "/tmp/output.debug.js", // merged file without compression.
+    translateJSON: "/tmp/translate.json",
+    
+    
+);
+x.packFiles(
+    "/location/of/temp_batch_dir", 
+    "/location/of/output-compacted-file.js",
+    "/location/of/output-debug-merged-file.js"
+);
+    
+ *</code> 
+ *
+ * Notes for improving compacting:
+ *  if you add a jsdoc comment 
+ * <code>
+ * /**
+ *   eval:var:avarname
+ *   eval:var:bvarname
+ *   ....
+ * </code>
+ * directly before an eval statement, it will compress all the code around the eval, 
+ * and not rename the variables 'avarname'
+ * 
+ * Dont try running this on a merged uncompressed large file - it's used to be horrifically slow. not sure about now..
+ * Best to use lot's of small classes, and use it to merge, as it will cache the compaction
+ * 
+ * 
+ * 
+ * Notes for translation
+ *  - translation relies on you using double quotes for strings if they need translating
+ *  - single quoted strings are ignored.
+ * 
+ * Generation of indexFiles
+ *   - translateIndex = the indexfile
+ * 
+ * 
+ * 
+ * 
+
+ */
+Packer = function(cfg)
+{
+    
+    XObject.extend(this, cfg);
+    
+    if (this.srcfile) {
+        this.loadSourceFile();
+    }
+    
+    if (!this.files) {
+        throw "No Files";
+    }
+    
+    
+    this.timer =  new Date() * 1;
+    this.packAll();
+    
+}
+Packer.prototype = {
+    /**
+     * @prop srcfiles {String} file containing a list of files/or classes to use.
+     */
+    srcfiles : false,
+    
+    /**
+     * @prop files {Array} list of files to compress (must be full path)
+     */
+    files : false,
+    /**
+     * @prop target {String} target to write files to - must be full path.
+     */
+    target : '',
+    /**
+     * @prop debugTarget {String} target to write files debug version to (uncompacted)- must be full path.
+     */
+    debugTarget : '', // merged file without compression.
+    /**
+     * @prop tmpDir {String} (optional) where to put the temporary files. 
+     *      if you set this, then files will not be cleaned up
+     */
+    tmpDir : '/tmp',
+    
+    translateJSON : '', // json based list of strings in all files.
+   
+    /**
+     * @prop cleanup {Boolean} (optional) clean up temp files after done - 
+     *    Defaults to false if you set tmpDir, otherwise true.
+     */
+    cleanup : true,  
+    
+    /**
+     * @prop prefix {String} (optional) prefix of directory to be stripped of when
+     *    Calculating md5 of filename 
+     */
+    prefix : '',  
+    out : '', // if no target is specified - then this will contain the result
+    
+    
+    loadSourceFile : function()
+    {
+        var lines = File.read(this.srcfile).split("\n");
+        var _this = this;
+        lines.forEach(function(f) {
+            
+            if (/^\s*\//.test(f) || !/[a-z]+/i.test(f)) { // skip comments..
+                return;
+            }
+            if (/\.js$/.test(f)) {
+                _this.files.push( f);
+                // js file..
+                return;
+            }
+            
+            //println("ADD"+ f.replace(/\./g, '/'));
+            var add = f.replace(/\./g, '/').replace(/\s+/g,'')+'.js';
+            if (_this.files.indexOf(f) > -1) {
+                return;
+            }
+            _this.files.push( add );
+            
+        })
+    },
+    
+    
+    packAll : function()  // do the packing (run from constructor)
+    {
+        
+        //this.transOrigFile= bpath + '/../lang.en.js'; // needs better naming...
+        //File.write(this.transfile, "");
+        if (this.target) {
+            File.write(this.target, "");
+        }
+        
+        if (this.debugTarget) {
+            File.write(this.debugTarget, "");
+        }
+        
+        for(var i=0; i < this.files.length; i++)  {
+            var file = this.files[i];
+            
+            print("reading " +file );
+            if (!File.isFile(file)) {
+                print("SKIP (is not a file) " + file);
+                continue;
+            }
+           
+            if (this.debugTarget) {
+                File.append(this.debugTarget, File.read(file));
+            }
+            // it's a good idea to check with 0 compression to see if the code can parse!!
+            
+            // debug file..
+            //File.append(dout, str +"\n"); 
+            
+       
+            
+            var minfile = this.tmpDir + '/' +file.replace(/\//g, '.');
+            
+            
+            // let's see if we have a min file already?
+            // this might happen if tmpDir is set .. 
+            if (true && File.exists(minfile)) {
+                var mt = File.mtime(minfile);
+                var ot = File.mtime(file);
+                print("compare : " + mt + "=>" + ot);
+                if (mt >= ot) {
+                    continue;
+                    /*
+                    // then the min'files time is > than original..
+                    var str = File.read(minfile);
+                    print("using MIN FILE  "+ minfile);
+                    if (str.length) {
+                        File.append(outpath, str + "\n");
+                    }
+                    
+                    continue;
+                    */
+                }
+                
+            }
+            
+            print("COMPRESSING ");
+            //var codeComp = pack(str, 10, 0, 0);
+            if (File.exists(minfile)) {
+                File.remove(minfile);
+            }
+            var str = File.read(file);
+            var str = this.pack(str, file, minfile);
+            if (str.length) {
+                File.write(minfile, str);
+            }
+            
+             
+          
+        }  
+        if (this.translateJSON) {
+            
+               
+            print("MERGING LANGUAGE");
+            var out = "if (typeof(_T) == 'undefined') { _T={};}\n"
+            if (this.target) {
+                File.write(this.target, out);
+            } else {
+                this.out += out;
+            }
+            
+            
+            
+            File.write(this.translateJSON, "");
+            for(var i=0; i < this.files.length; i++)  {
+                var file = this.files[i];
+                var transfile= this.tmpDir + '/' +file.replace(/\//g, '.') +'.lang.trans';
+                var transmd5 = this.tmpDir  + '/' +file.replace(/\//g, '.') +'.lang';
+                if (File.exists(transmd5)) {
+                    var str = File.read(transmd5);
+                    if (str.length) {
+                        if (this.target) {
+                            File.append(this.target, str + "\n");
+                        } else {
+                            this.out += str + "\n";
+                        }
+                        
+                    }
+                    if (this.cleanup) {
+                        File.remove(transmd5);
+                    }
+                }
+                if (File.exists(transfile)) {
+                    var str = File.read(transfile);
+                    if (str.length) {
+                        File.append(this.translateJSON, str);
+                    }
+                    if (this.cleanup) {
+                        File.remove(transfile);
+                    }
+                }
+                
+               
+            }
+        }
+        
+        print("MERGING SOURCE");
+        
+        for(var i=0; i < this.files.length; i++)  {
+            var file = this.files[i];
+            var minfile = this.tmpDir + '/' + file.replace(/\//g, '.');
+            
+            
+            if (!File.exists(minfile)) {
+                continue;
+            }
+            var str = File.read(minfile);
+            print("using MIN FILE  "+ minfile);
+            if (str.length) {
+                if (this.target) {
+                    File.append(this.target, str + "\n");   
+                } else {
+                    this.out += str + "\n";
+                }
+                
+            }
+            if (this.cleanup) {
+                File.remove(minfile);
+            }
+            
+        }
+        
+         
+    
+    
+    },
+    /**
+     * Core packing routine  for a file
+     * 
+     * @param str - str source text..
+     * @param fn - filename (for reference?)
+     * @param minfile - min file location...
+     * 
+     */
+    
+    pack : function (str,fn,minfile)
+    {
+    
+        var tr = new  TokenReader(  { keepDocs :true, keepWhite : true,  keepComments : true, sepIdents : true });
+        this.timerPrint("START" + fn);
+        
+        // we can load translation map here...
+        
+        var toks = tr.tokenize(new TextStream(str)); // dont merge xxx + . + yyyy etc.
+        
+        // at this point we can write a language file...
+        if (this.translateJSON) {
+            
+            this.writeTranslateFile(fn, minfile, toks);
+        }
+        
+        this.activeFile = fn;
+        
+        // and replace if we are generating a different language..
+        
+        this.timerPrint("Tokenized");
+        //return;//
+        var sp = new ScopeParser(new TokenStream(toks));
+        this.timerPrint("Converted to Parser");
+        sp.packer = this;
+        sp.buildSymbolTree();
+        this.timerPrint("Built Sym tree");
+        sp.mungeSymboltree();
+        this.timerPrint("Munged Sym tree");
+        print(sp.warnings.join("\n"));
+        var out = CompressWhite(sp.ts, this);
+        this.timerPrint("Compressed");
+        return out;
+        
+        
+         
+    },
+    
+    timerPrint: function (str) {
+        var ntime = new Date() * 1;
+        var tdif =  ntime -this.timer;
+        this.timer = ntime;
+        print('['+tdif+']'+str);
+    },
+    
+    /**
+     * 
+     * Translation concept...
+     * -> replace text strings with _T....
+     * -> this file will need inserting at the start of the application....
+     * -> we need to generate 2 files, 
+     * -> a reference used to do the translation, and the _T file..
+     * 
+     */
+    
+    writeTranslateFile : function(fn, minfile, toks) 
+    {
+        
+        var map = {};
+        var _this = this;
+        toks.forEach(function (t) {
+            if (t.type == 'STRN' && t.name == 'DOUBLE_QUOTE') {
+                var sval = t.data.substring(1,t.data.length-1);
+                var ffn = fn.substring(_this.prefix.length);
+                map[sval] = _this.md5(ffn + '-' + sval);
+            }
+        })
+        
+        var transfile = minfile + '.lang.trans';
+        var transmd5 = minfile + '.lang';
+        print("writeTranslateFile "  + transfile);
+        var i = 0;
+        var v = '';
+        if (File.exists(transfile)) {
+            File.remove(transfile);
+        }
+        if (File.exists(transmd5)) {
+            File.remove(transmd5);
+        }
+        for(v in map) { i++; break };
+        if (!i ) {
+            return; // no strings in file...
+        }
+        var ffn = fn.substring(this.prefix.length);
+         
+         
+        File.write(transfile, "\n'" + ffn  + "' : {");
+        var l = '';
+        var _tout = {}
+         
+        File.write(transmd5, '');
+        for(v in map) {
+            File.append(transfile, l + "\n\t \"" + v + '" : "' + v + '"');
+            l = ',';
+            // strings are raw... - as the where encoded to start with!!!
+            File.append(transmd5, '_T["' + this.md5(ffn + '-' + v) + '"]="'+v+"\";\n");
+        }
+        File.append(transfile, "\n},"); // always one trailing..
+        
+         
+    },
+    md5 : function (string)
+    {
+        
+        return GLib.compute_checksum_for_string(GLib.ChecksumType.MD5, string, string.length);
+        
+    },
+    stringHandler : function(tok)
+    {
+        //print("STRING HANDLER");
+       // callback when outputing compressed file, 
+       var data = tok.data;
+        if (!this.translateJSON) {
+         //   print("TURNED OFF");
+            return data;
+        }
+        if (tok.name == 'SINGLE_QUOTE') {
+            return data;
+        }
+        
+        var sval = data.substring(1,data.length-1);
+        // we do not clean up... quoting here!??!!?!?!?!?
+        
+        
+        // blank with tabs or spaces..
+        //if (!sval.replace(new RegExp("(\\\\n|\\\\t| )+",'g'), '').length) {
+       //     return tok.outData;
+       // }
+        
+        var sval = tok.data.substring(1,data.length-1);
+        var fn = this.activeFile.substring(this.prefix.length);
+        
+        
+        return '_T["' + this.md5(fn + '-' + sval) + '"]';
+        
+        
+    }
+    
+    
+};
diff --git a/JSDOC/Scope.js b/JSDOC/Scope.js
new file mode 100644 (file)
index 0000000..84e86da
--- /dev/null
@@ -0,0 +1,316 @@
+//<Script type="text/javascript">
+
+/**
+* Scope stuff
+* 
+* // FIXME - I need this to do next() without doccomments..
+*/
+
+Identifier = imports['JSDOC/Identifier.js'].Identifier
+XObject = imports.XObject.XObject; 
+
+
+function Scope(braceN, parent, startTokN, lastIdent)
+{
+    if (lastIdent.length) {
+       //  println("NEW SCOPE: " + lastIdent);
+    }
+    
+    this.braceN = braceN
+    this.parent = parent;
+    this.id = startTokN;
+    this.identifiers = { };
+    this.subScopes = [];
+    this.hints = { };
+    this.ident = lastIdent;
+    
+    
+    //println("ADD SCOPE(" + this.id + ") TO "+ (parent ? this.parent.id : 'TOP') + "<BR/>");
+    
+    if (parent) {
+        this.parent.subScopes.push(this);
+    } 
+    
+}
+
+
+
+
+
+
+
+Scope.prototype = {
+    
+    id : 0,
+    braceN : -1,
+    parent : false,
+    subScopes : false,
+    identifiers : false,  // map of identifiers to {Identifier} objects
+    hints: false, 
+    mungeM : true, 
+    ident: '',
+    
+    munged : false,
+    protectedVars : {}, // only used by to parent..
+    declareIdentifier : function(symbol, token) {
+        
+        //println("ADD IDENT(" + this.id + "):<B>" + symbol+"</B><BR/>");
+        
+        if (typeof(this.identifiers[symbol])== 'undefined') {
+            
+            this.identifiers[symbol] =  new Identifier(symbol, this);
+            
+        }
+        if (typeof(token) != 'undefined') { // shoudl this happen?
+            token.identifier = this.identifiers[symbol];
+            
+        }
+        if (this.braceN < 0) {
+                // then it's global... 
+                this.identifiers[symbol].toMunge  = false;
+        }
+        this.addToParentScope(symbol);
+        return this.identifiers[symbol];
+    },
+    getIdentifier : function(symbol) {
+        return (typeof(this.identifiers[symbol])== 'undefined') ? false : this.identifiers[symbol];
+    },
+    
+    addHint : function(varName, varType) {
+        this.hint[varName] = varType;
+    },
+    preventMunging : function() {
+        this.mungeM = false;
+    },
+
+    usedsymcache : false,
+    
+    getUsedSymbols : function() {
+        
+        var result = [];
+       // if (this.usedsymcache !== false) {
+        //    return this.usedsymcache;
+        //}
+        
+        var idents = this.identifiers;
+        for(var i in idents) { 
+            //println('<b>'+i+'</b>='+typeof(idents[i]) +'<br/>');
+            var identifier = this.identifiers[i];
+            var mungedValue = identifier.mungedValue
+            if (!mungedValue.length) {
+                //println(identifier.toSource());
+                mungedValue = identifier.name;
+            }
+            result.push(mungedValue);
+        }
+        //println("Symbols for ("+ this.id +"): <B>" + result.join(',') + "</B><BR/>");
+        //this.usedsymcache = result;
+        return result;
+    },
+
+    getAllUsedSymbols :function() {
+        var result = this.getUsedSymbols();
+        var scope = this.parent;
+        while (scope !== false) {
+            //println("addused:"+scope.id);
+            result = result.concat(scope.getUsedSymbols());
+            scope = scope.parent;
+        }
+         //println("Done - addused");
+        return result;
+    },
+    /** - we need to register short vairalbes so they never get munged into.. */
+    addToParentScope: function(ident) 
+    {
+        if (ident.length > 2) {
+            return;
+        }
+        var scope = this.parent;
+        while (scope !== false) {
+            //println("addused:"+scope.id);
+            if (!scope.parent) {
+                scope.protectedVars[ident] = true;
+            }
+            scope = scope.parent;
+        }
+        
+    },
+    isProtectedVar: function(ident)
+    {
+        if (ident.length > 2) {
+            return false;
+        }
+        var scope = this.parent;
+        while (scope !== false) {
+            //println("addused:"+scope.id);
+            if (!scope.parent) {
+                if (typeof(scope.protectedVars[ident])  != 'undefined') return true;
+            }
+            scope = scope.parent;
+        }
+        return false;
+    },
+    
+    /**
+     * set's all the munged values on the identifiers.
+     * 
+     * 
+     */
+
+    munge :function() 
+    {
+
+        if (!this.mungeM) {
+            // Stop right here if this scope was flagged as unsafe for munging.
+           // println("MUNGE: SKIP -  Scope" + this.id+"</BR>");
+            return;
+        }
+        if (this.munged) {
+            return;
+        }
+        
+
+        
+        
+        var pickFromSet = 1;
+
+        // Do not munge symbols in the global scope!
+        if (this.parent) {
+            
+            var all = [];
+            for (var ii in this.identifiers) {
+                all.push(ii);
+            }
+            //print("MUNGE: " + all.join(', '));
+            
+            //println("MUNGE: Building FreeSyms:" + this.id+"</BR>");
+            
+            var freeSymbols = [];
+            var sy = this.getAllUsedSymbols();
+            
+            var addSyms=function(batch)
+            {
+                for(var i =0;i<batch.length;i++) {
+                    if (sy.indexOf(batch[i]) > -1) {
+                        continue;
+                    }
+                    freeSymbols.push(batch[i]);
+                }
+            }
+             
+            addSyms(Scope.ones); 
+             
+            var repsym = '';
+            //println(freeSymbols.toSource());
+            
+            //println("MUNGE: Replacing " + this.id+"</BR>");
+            for (var i in  this.identifiers) {
+                
+                // is the identifer in the global scope!?!!?
+                
+                
+                if (!this.identifiers[i].toMunge) {
+                    //print("SKIP toMunge==false : " + i)
+                    continue;
+                }
+                
+                if (this.isProtectedVar(i)) {
+                    //print("SKIP PROTECTED: " + i)
+                    continue; // 
+                }
+                
+                
+                
+                //if (this.identifiers[i].constructor !=  Identifier) {
+                //    print("SKIP NOT IDENTIFIER : " + i)
+                //    continue;
+               // }
+               // println("IDENT:" +i+'</BR>');
+                
+                if (!repsym.length) {
+                    if (!freeSymbols.length) {
+                        addSyms(JSDOC.Scope.twos); 
+                    }
+                    repsym = freeSymbols.shift(); // pop off beginngin???
+                }
+                
+                var identifier = this.identifiers[i]; 
+                //println(typeof(identifier.name));
+                var mungedValue = identifier.name; 
+                
+                //println([     repsym,mungedValue ]);
+                
+                if (this.mungeM && repsym.length < mungedValue.length) {
+                    //print("REPLACE:"+ mungedValue +" with " + repsym );    
+                    mungedValue = repsym;
+                    repsym = '';
+                }
+                
+                identifier.mungedValue =  mungedValue;
+            }
+            //println("MUNGE: Done " + this.id+"</BR>");
+        }
+        this.munged = true;
+        //println("Doing sub scopes");
+        for (var j = 0; j < this.subScopes.length; j++) {
+            var ss = this.subScopes[j];
+            ss.munge();
+        }
+    }
+
+};
+
+
+
+
+
+XObject.extend(Scope, {
+    
+    builtin : ["NaN","top"],
+    skips : [  'as', 'is', 'do', 'if', 'in', 'for', 'int', 'new', 'try', 'use', 'var', "NaN","top"],
+     
+    ones : [],
+    twos : [],
+    threes : [],
+    init : function () {
+        /* cache it later?
+        if (File.exists('/tmp/var_list_ones.js')) {
+            eval("JSDOC.Scope.ones = " + File.read('/tmp/var_list_ones.js'));
+            eval("JSDOC.Scope.twos = " + File.read('/tmp/var_twos_ones.js'));
+            eval("JSDOC.Scope.threes = " + File.read('/tmp/var_threes_ones.js'));
+        }
+        */
+        this.ones = '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'.split(',');
+        var a = this.ones;
+        var n = a.concat( '0,1,2,3,4,5,6,7,8,9'.split(','));
+        for(var i = 0; i < a.length; i++) {
+            for(var j = 0; j < n.length; j++) {
+                var tw = a[i] + n[j];
+                if (this.skips.indexOf(tw) < 0) {
+                    this.twos.push(tw);
+                }
+                    
+                /*
+                for(var k = 0; k < n.length; k++) {
+                    var thr = a[i] + n[j] + n[k];
+                    //println("thr="+ thr + ":iOf="+this.skips.indexOf(thr) );
+                    if (this.skips.indexOf(thr)  < 0) {
+                        //println("+"+thr);
+                        this.threes.push(thr);
+                       }
+                    
+                }
+                */
+            }
+        }
+        //println("done creating var list");
+        //println("threes="+ this.threes.toSource());
+        //throw "DONE";
+        
+       
+    }
+})
+// init the scope constants..
+Scope.init();
\ No newline at end of file
diff --git a/JSDOC/ScopeParser.js b/JSDOC/ScopeParser.js
new file mode 100644 (file)
index 0000000..1b315b0
--- /dev/null
@@ -0,0 +1,859 @@
+//<Script type="text/javascript">
+
+Scope = imports['JSDOC/Scope.js'].Scope;
+
+/**
+* Scope stuff
+* 
+* // FIXME - I need this to do next() without doccomments..
+*/
+
+ScopeParser = function(ts) {
+    this.ts = ts; // {TokenStream}
+    this.warnings = [];
+    this.scopes = [];
+    this.indexedScopes = {};
+    this.timer = new Date() * 1;
+    this.debug = false;
+}
+
+// list of keywords that should not be used in object literals.
+ScopeParser.idents = [
+       "break",         
+        "case",                 
+        "continue",    
+        "default",     
+        "delete",      
+        "do",           
+       "else",         
+       "export",       
+       "false",        
+       "for",          
+       "function",     
+       "if",           
+       "import",       
+       "in",           
+       "new",          
+       "null",         
+       "return",       
+       "switch",       
+       "this",         
+       "true",         
+       "typeof",       
+       "var",          
+       "void",         
+       "while",        
+       "with",         
+
+       "catch",        
+       "class",        
+       "const",        
+       "debugger",     
+       "enum",         
+       "extends",      
+       "finally",      
+       "super",        
+        "throw",        
+        "try",         
+
+        "abstract",    
+        "boolean",     
+        "byte",                
+        "char",                
+        "double",      
+        "final",       
+        "float",       
+        "goto",                
+        "implements", 
+        "instanceof",
+        "int",          
+        "interface",    
+        "long",                 
+        "native",      
+        "package",     
+        "private",     
+        "protected",    
+        "public",       
+        "short",       
+        "static",      
+        "synchronized",         
+        "throws",       
+        "transient",    
+               "include",       
+               "undefined"
+];
+
+
+ScopeParser.prototype = {
+    timer: 0,
+    timerPrint: function (str) {
+        var ntime = new Date() * 1;
+        var tdif =  ntime -this.timer;
+        this.timer = ntime;
+        var pref = '';
+        if (tdif > 100) { //slower ones..
+            pref = '***';
+        }
+        println(pref+'['+tdif+']'+str);
+        
+    },
+    warn: function(s) {
+        //print('****************' + s);
+        this.warnings.push(s);
+        //println("WARNING:" + htmlescape(s) + "<BR>");
+    },
+    // defaults should not be initialized here =- otherwise they get duped on new, rather than initalized..
+    warnings : false,
+    ts : false,
+    scopes : false,
+    global : false,
+    mode : "", //"BUILDING_SYMBOL_TREE",
+    braceNesting : 0,
+    indexedScopes : false,
+    munge: true,
+
+
+
+
+
+    buildSymbolTree : function()
+    {
+        //println("<PRE>");
+        
+        this.ts.rewind();
+        this.braceNesting = 0;
+        this.scopes = [];
+        
+        
+        
+        
+        this.globalScope = new  Scope(-1, false, -1, '');
+        indexedScopes = { 0 : this.globalScope };
+        
+        this.mode = 'BUILDING_SYMBOL_TREE';
+        this.parseScope(this.globalScope);
+        
+        //print("---------------END PASS 1 ---------------- ");
+        
+    },
+    mungeSymboltree : function()
+    {
+
+        if (!this.munge) {
+            return;
+        }
+
+        // One problem with obfuscation resides in the use of undeclared
+        // and un-namespaced global symbols that are 3 characters or less
+        // in length. Here is an example:
+        //
+        //     var declaredGlobalVar;
+        //
+        //     function declaredGlobalFn() {
+        //         var localvar;
+        //         localvar = abc; // abc is an undeclared global symbol
+        //     }
+        //
+        // In the example above, there is a slim chance that localvar may be
+        // munged to 'abc', conflicting with the undeclared global symbol
+        // abc, creating a potential bug. The following code detects such
+        // global symbols. This must be done AFTER the entire file has been
+        // parsed, and BEFORE munging the symbol tree. Note that declaring
+        // extra symbols in the global scope won't hurt.
+        //
+        // Note: Since we go through all the tokens to do this, we also use
+        // the opportunity to count how many times each identifier is used.
+
+        this.ts.rewind();
+        this.braceNesting = 0;
+        this.scopes= [];
+        this.mode = 'PASS2_SYMBOL_TREE';
+        
+        //println("MUNGING?");
+        
+        this.parseScope(this.globalScope);
+        this.globalScope.munge();
+    },
+
+
+    log : function(str)
+    {
+        print ("                    ".substring(0, this.braceNesting*2) + str);
+        
+        //println("<B>LOG:</B>" + htmlescape(str) + "<BR/>\n");
+    },
+    logR : function(str)
+    {
+            //println("<B>LOG:</B>" + str + "<BR/>");
+    },
+
+
+
+
+
+
+    parseScope : function(scope) // parse a token stream..
+    {
+        //this.timerPrint("parseScope EnterScope"); 
+        //this.log(">>> ENTER SCOPE" + this.scopes.length);
+        var symbol;
+        var token;
+        
+        var identifier;
+
+        var expressionBraceNesting = this.braceNesting;
+        
+        var parensNesting = 0;
+        
+        var isObjectLitAr = [ false ];
+        var isInObjectLitAr;
+        this.scopes.push(scope);
+        token = this.ts.lookTok(1);
+        while (token) {
+          //  this.timerPrint("parseScope AFTER lookT: " + token.toString()); 
+             
+            //this.log(token.data);
+            if (token.type == 'NAME') {
+            //    print('*' + token.data);
+            }
+            switch(token.type + '.' + token.name) {
+                case "KEYW.VAR":
+                case "KEYW.CONST": // not really relivant as it's only mozzy that does this.
+                    
+                    //this.log("parseScope GOT VAR/CONST : " + token.toString()); 
+                    while (true) {
+                        token = this.ts.nextTok();
+                        !this.debug|| print( token.toString());
+                      
+                        if (!token) { // can return false at EOF!
+                            break;
+                        }
+                        if (token.name == "VAR" || token.data == ',') { // kludge..
+                            continue;
+                        }
+                        //this.logR("parseScope GOT VAR  : <B>" + token.toString() + "</B>"); 
+                        if (token.type !="NAME") {
+                            for(var i = Math.max(this.ts.cursor-10,0); i < this.ts.cursor+1; i++) {
+                                print(this.ts.tokens[i].toString());
+                            }
+                            
+                            print( "var without ident");
+                            Seed.quit()
+                        }
+                        
+
+                        if (this.mode == "BUILDING_SYMBOL_TREE") {
+                            identifier = scope.getIdentifier(token.data) ;
+                            
+                            if (identifier == false) {
+                                scope.declareIdentifier(token.data, token);
+                            } else {
+                                token.identifier = identifier;
+                                this.warn("(SCOPE) The variable " + token.data  + ' (line:' + token.line + ")  has already been declared in the same scope...");
+                            }
+                        }
+
+                        token = this.ts.nextTok();
+                        !this.debug|| print(token.toString());
+                        /*
+                        assert token.getType() == Token.SEMI ||
+                                token.getType() == Token.ASSIGN ||
+                                token.getType() == Token.COMMA ||
+                                token.getType() == Token.IN;
+                        */
+                        if (token.name == "IN") {
+                            break;
+                        } else {
+                            //var bn = this.braceNesting;
+                            this.parseExpression();
+                            //this.braceNesting = bn;
+                            //this.logR("parseScope DONE  : <B>ParseExpression</B> - tok is:" + this.ts.lookT(0).toString()); 
+                            
+                            token = this.ts.lookTok(1);
+                            !this.debug|| print("AFTER EXP: " + token.toString());
+                            if (token.data == ';') {
+                                break;
+                            }
+                        }
+                    }
+                    break;
+                case "KEYW.FUNCTION":
+                    //println("<i>"+token.data+"</i>");
+                     var bn = this.braceNesting;
+                    this.parseFunctionDeclaration();
+                     this.braceNesting = bn;
+                    break;
+
+                case "PUNC.LEFT_CURLY": // {
+                    //println("<i>"+token.data+"</i>");
+                    isObjectLitAr.push(false);
+                    this.braceNesting++;
+                    
+                    //print(">>>>>> OBJLIT PUSH(false)" + this.braceNesting);
+                    break;
+
+                case "PUNC.RIGHT_CURLY": // }
+                    //println("<i>"+token.data+"</i>");
+                    this.braceNesting--;
+                    isObjectLitAr.pop();
+                    //print(">>>>>> OBJLIT POP"+ this.braceNesting);
+                        //assert braceNesting >= scope.getBra ceNesting();
+                    
+                    if (this.braceNesting < expressionBraceNesting) {
+                        var ls = this.scopes.pop();
+                        ls.getUsedSymbols();
+                        // eat symbol if we are currently at { 
+                        if (this.ts.look(0).data == '{') {
+                            this.ts.nextTok();
+                        }
+                        
+                        //print("<<<<<<<EXIT SCOPE" +this.scopes.length);
+                        return;
+                    }
+                    break;
+
+                case "KEYW.WITH":
+                    //println("<i>"+token.data+"</i>");   
+                    if (this.mode == "BUILDING_SYMBOL_TREE") {
+                        // Inside a 'with' block, it is impossible to figure out
+                        // statically whether a symbol is a local variable or an
+                        // object member. As a consequence, the only thing we can
+                        // do is turn the obfuscation off for the highest scope
+                        // containing the 'with' block.
+                        this.protectScopeFromObfuscation(scope);
+                        this.warn("Using 'with' is not recommended." + (this.munge ? " Moreover, using 'with' reduces the level of compression!" : ""), true);
+                    }
+                    break;
+
+                case "KEYW.CATCH":
+                    //println("<i>"+token.data+"</i>");
+                    this.parseCatch();
+                    break;
+                /*
+                case Token.SPECIALCOMMENT:
+                        if (mode == BUILDING_SYMBOL_TREE) {
+                            protectScopeFromObfuscation(scope);
+                            this.warn("Using JScript conditional comments is not recommended." + (munge ? " Moreover, using JScript conditional comments reduces the level of compression." : ""), true);
+                        }
+                        break;
+                */
+                
+                case "STRN.DOUBLE_QUOTE": // used for object lit detection..
+                case "STRN.SINGLE_QUOTE":
+                    //println("<i>"+token.data+"</i>");
+                    if (this.ts.lookTok(-1).data == '{' && this.ts.lookTok(1).data == ':') {
+                        // then we are in an object lit.. -> we need to flag the brace as such...
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                        //print(">>>>>> OBJLIT REPUSH(true)");
+                    }
+                    isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    
+                    if (isInObjectLitAr &&  this.ts.lookTok(1).data == ':' &&
+                        ( this.ts.lookTok(-1).data == '{'  ||  this.ts.lookTok(-1).data == ':' )) {
+                        // see if we can replace..
+                        // remove the quotes..
+                        // should do a bit more checking!!!! (what about wierd char's in the string..
+                        var str = token.data.substring(1,token.data.length-1);
+                        if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
+                            token.outData = str;
+                        }
+                        
+                         
+                        
+                    }
+                    
+                    
+                    
+                    break;
+                
+                case "NAME.NAME":
+                
+                    //print("DEAL WITH NAME:");
+                    // got identifier..
+                    
+                    // look for  { ** : <- indicates obj literal.. ** this could occur with numbers ..
+                    if ((this.ts.lookTok(-1).data == "{") && (this.ts.lookTok(1).data == ":")) {
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                        //print(">>>>>> OBJLIT REPUSH(true)");
+                        //println("<i>"+token.data+"</i>");
+                        break;
+                    }
+                   // print("DEAL WITH obj lit:");
+                    isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    
+                    if (isInObjectLitAr && (this.ts.lookTok(1).data == ":") && (this.ts.lookTok(-1).data == ",")) {
+                        // skip, it's an object lit key..
+                        //println("<i>"+token.data+"</i>");
+                        break;
+                    }
+                    
+                    
+                    // skip anyting with "." before it..!!
+                     
+                    if (this.ts.lookTok(-1).data == ".") {
+                        // skip, it's an object prop.
+                        //println("<i>"+token.data+"</i>");
+                        break;
+                    }
+                    symbol = token.data;
+                    if (this.mode == 'PASS2_SYMBOL_TREE') {
+                        
+                        //println("GOT IDENT: -2 : " + this.ts.lookT(-2).toString() + " <BR> ..... -1 :  " +  this.ts.lookT(-1).toString() + " <BR> "); 
+                        
+                        //print ("MUNGE?" + symbol);
+                        
+                        //println("GOT IDENT: <B>" + symbol + "</B><BR/>");
+                             
+                            //println("GOT IDENT (2): <B>" + symbol + "</B><BR/>");
+                        identifier = this.getIdentifier(symbol, scope);
+                        
+                        if (identifier == false) {
+// BUG!find out where builtin is defined...
+                            if (symbol.length <= 3 &&  Scope.builtin.indexOf(symbol) < 0) {
+                                // Here, we found an undeclared and un-namespaced symbol that is
+                                // 3 characters or less in length. Declare it in the global scope.
+                                // We don't need to declare longer symbols since they won't cause
+                                // any conflict with other munged symbols.
+                                this.globalScope.declareIdentifier(symbol, token);
+                                this.warn("Found an undeclared symbol: " + symbol + ' (line:' + token.line + ')', true);
+                            }
+                            
+                            //println("GOT IDENT IGNORE(3): <B>" + symbol + "</B><BR/>");
+                        } else {
+                            token.identifier = identifier;
+                            identifier.refcount++;
+                        }
+                    }   
+                    
+                    break;
+                    //println("<B>SID</B>");
+                default:
+                    if (token.type != 'KEYW') {
+                        break;
+                    }
+                   // print("Check eval:");
+                
+                    symbol = token.data;
+                    
+                     if (this.mode == 'BUILDING_SYMBOL_TREE') {
+
+                        if (symbol == "eval") {
+                            // look back one and see if we can find a comment!!!
+                            if (this.ts.look(-1).type == "COMM") {
+                                // look for eval:var:noreplace\n
+                                var _t = this;
+                                this.ts.look(-1).data.replace(/eval:var:([a-z_]+)/ig, function(m, a) {
+                                    
+                                    var hi = _t.getIdentifier(a, scope);
+                                   // println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
+                                    if (hi) {
+                                     //   println("PROTECT "+a+" from munge");
+                                        hi.toMunge = false;
+                                    }
+                                    
+                                });
+                                
+                                
+                            } else {
+                                
+                            
+                                this.protectScopeFromObfuscation(scope);
+                                this.warn("Using 'eval' is not recommended. (use  eval:var:noreplace in comments to optimize) " + (this.munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
+                            }
+
+                        }
+
+                    }
+                    break;
+                
+                
+            } // end switch
+            
+            
+            //this.timerPrint("parseScope TOK : " + token.toString()); 
+            token = this.ts.nextTok();
+            //if (this.ts.nextT()) break;
+            
+        }
+        //print("<<<<<<<EXIT SCOPE ERR?" +this.scopes.length);
+    },
+
+
+    parseExpression : function() {
+
+        // Parse the expression until we encounter a comma or a semi-colon
+        // in the same brace nesting, bracket nesting and paren nesting.
+        // Parse functions if any...
+        //println("<i>EXP</i><BR/>");
+        !this.debug || print("PARSE EXPR");
+        var symbol;
+        var token;
+        var currentScope;
+        var identifier;
+
+        var expressionBraceNesting = this.braceNesting;
+        var bracketNesting = 0;
+        var parensNesting = 0;
+        var isInObjectLitAr;
+        var isObjectLitAr = [ false ];
+        while (token = this.ts.lookTok()) {
+     
+
+            
+            currentScope = this.scopes[this.scopes.length-1];
+            
+            //println("<i>"+token.data+"</i>");
+            //this.log("EXP:" + token.data);
+            switch (token.type) {
+                case 'PUNC':
+                    switch(token.data) {
+                         
+                        case ';':
+                        case ',':
+                            if (this.braceNesting == expressionBraceNesting &&
+                                    bracketNesting == 0 &&
+                                    parensNesting == 0) {
+                                
+                                return;
+                            }
+                            break;
+
+                       
+
+                        case '{': //Token.LC:
+                            isObjectLitAr.push(false);
+                            
+                            this.braceNesting++;
+                            ///print(">>>>> EXP PUSH(false)"+this.braceNesting);
+                            break;
+
+                        case '}': //Token.RC:
+                            this.braceNesting--;
+                            isObjectLitAr.pop();
+                            //print(">>>>> EXP POP" + this.braceNesting);    
+                           // assert braceNesting >= expressionBraceNesting;
+                            break;
+
+                        case '[': //Token.LB:
+                            bracketNesting++;
+                            break;
+
+                        case ']': //Token.RB:
+                            bracketNesting--;
+                            break;
+
+                        case '(': //Token.LP:
+                            parensNesting++;
+                            break;
+
+                        case ')': //Token.RP:
+                            parensNesting--;
+                            break;
+                    }
+                    break;
+                    
+                case 'STRN': // used for object lit detection..
+                    if (this.ts.lookTok(-1).data == "{" && this.ts.lookTok(1).data == ":" ) {
+                        // then we are in an object lit.. -> we need to flag the brace as such...
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                        //print(">>>>> EXP PUSH(true)");
+                    }
+                    
+                    
+                     
+                    isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    if (isInObjectLitAr &&  this.ts.lookTok(1).data == ":"  &&
+                        ( this.ts.lookTok(-1).data == "{"  ||  this.ts.lookTok(-1).data == "," )) {
+                        // see if we can replace..
+                        // remove the quotes..
+                        var str = token.data.substring(1,token.data.length-1);
+                        if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
+                            token.outData = str;
+                        }
+                        
+                         
+                        
+                    }
+                    
+                    break;
+                
+                      
+             
+                case 'NAME':
+               
+                    symbol = token.data;
+                  
+                    if (this.ts.look(0).data == "{"  && this.ts.lookTok(2).data == ":") {
+                        // then we are in an object lit.. -> we need to flag the brace as such...
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                         //print(">>>>> EXP  PUSH(true)");
+                        break;
+                    }
+                    isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    if (isInObjectLitAr && this.ts.lookTok(0).data == "," && this.ts.lookTok(2).data == ":") {
+                        break;
+                    }
+                    //print(this.ts.lookTok(0).data);
+                    if (this.ts.lookTok(0).data == ".") {
+                        //skip '.'
+                        break;
+                    }
+                    
+                     if (this.mode == 'PASS2_SYMBOL_TREE') {
+
+                        identifier = this.getIdentifier(symbol, currentScope);
+                        //println("<B>??</B>");
+                        if (identifier == false) {
+
+                            if (symbol.length <= 3 &&  Scope.builtin.indexOf(symbol) < 0) {
+                                // Here, we found an undeclared and un-namespaced symbol that is
+                                // 3 characters or less in length. Declare it in the global scope.
+                                // We don't need to declare longer symbols since they won't cause
+                                // any conflict with other munged symbols.
+                                this.globalScope.declareIdentifier(symbol, token);
+                                this.warn("Found an undeclared symbol: " + symbol + ' (line:' + token.line + ')', true);
+                            } else {
+                                //println("undeclared")
+                            }
+                            
+                            
+                        } else {
+                            //println("<B>++</B>");
+                            token.identifier = identifier;
+                            identifier.refcount++;
+                        }
+                        
+                    }
+                    break;
+                    
+                    
+                    
+                    
+                    //println("<B>EID</B>");
+                 case 'KEYW':   
+                 
+                    if (token.name == "FUNCTION") {
+                        
+                        this.parseFunctionDeclaration();
+                        break;
+                    }
+               
+                    
+             
+                    symbol = token.data;
+                    if (this.mode == 'BUILDING_SYMBOL_TREE') {
+
+                        if (symbol == "eval") {
+                            if (this.ts.look(-1).type == 'COMM') {
+                                // look for eval:var:noreplace\n
+                                var _t = this;
+                                this.ts.look(-1).data.replace(/eval:var:([a-z]+)/ig, function(m, a) {
+                                    var hi = _t.getIdentifier(a, currentScope);
+                                   //println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
+                                    if (hi) {
+                                      //  println("PROTECT "+a+" from munge");
+                                        hi.toMunge = false;
+                                    }
+                                    
+                                    
+                                });
+                                
+                            } else {
+                                this.protectScopeFromObfuscation(currentScope);
+                                this.warn("Using 'eval' is not recommended." + (this.munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
+                            }
+                            
+
+                        }
+                        break;
+                    } 
+                   
+            }
+            if (!this.ts.nextTok()) break;
+        }
+    },
+
+
+    parseCatch : function() {
+
+        var symbol;
+        var token;
+        var currentScope;
+        var identifier;
+
+        //token = getToken(-1);
+        //assert token.getType() == Token.CATCH;
+        token = this.ts.nextTok();
+        //assert token.getType() == Token.LP; (
+        token = this.ts.nextTok();
+        //assert token.getType() == Token.NAME;
+        
+        symbol = token.data;
+        currentScope = this.scopes[this.scopes.length-1];
+
+        if (this.mode == 'BUILDING_SYMBOL_TREE') {
+            // We must declare the exception identifier in the containing function
+            // scope to avoid errors related to the obfuscation process. No need to
+            // display a warning if the symbol was already declared here...
+            currentScope.declareIdentifier(symbol, token);
+        } else {
+            //?? why inc the refcount?? - that should be set when building the tree???
+            identifier = this.getIdentifier(symbol, currentScope);
+            identifier.refcount++;
+        }
+
+        token = this.ts.nextTok();
+        //assert token.getType() == Token.RP; // )
+    },
+    
+    parseFunctionDeclaration : function() 
+    {
+       // print("PARSE FUNCTION");
+        var symbol;
+        var token;
+        var currentScope  = false; 
+        var fnScope = false;
+        var identifier;
+        //this.logR("<B>PARSING FUNCTION</B>");
+        currentScope = this.scopes[this.scopes.length-1];
+
+        token = this.ts.nextTok();
+        if (token.type == "NAME") {
+            if (this.mode == 'BUILDING_SYMBOL_TREE') {
+                // Get the name of the function and declare it in the current scope.
+                symbol = token.data;
+                if (currentScope.getIdentifier(symbol) != false) {
+                    this.warn("The function " + symbol + " has already been declared in the same scope...", true);
+                }
+                currentScope.declareIdentifier(symbol,token);
+            }
+            token =  this.ts.nextTok();
+        }
+
+        //assert token.getType() == Token.LP;
+        if (this.mode == 'BUILDING_SYMBOL_TREE') {
+            fnScope = new Scope(this.braceNesting, currentScope, token.n, '');
+            
+            //println("STORING SCOPE" + this.ts.cursor);
+            
+            this.indexedScopes[this.ts.cursor] = fnScope;
+            
+        } else {
+            //qln("FETCHING SCOPE" + this.ts.cursor);
+            fnScope = this.indexedScopes[this.ts.cursor];
+          
+        }
+        
+        // Parse function arguments.
+        var argpos = 0;
+        while (this.ts.lookTok().data != ')') { //(token = consumeToken()).getType() != Token.RP) {
+            token = this.ts.nextTok();
+           // print ("FUNC ARGS: " + token.toString())
+            //assert token.getType() == Token.NAME ||
+            //        token.getType() == Token.COMMA;
+            if (token.type == 'NAME' && this.mode == 'BUILDING_SYMBOL_TREE') {
+                symbol = token.data;
+                identifier = fnScope.declareIdentifier(symbol,token);
+                if (symbol == "$super" && argpos == 0) {
+                    // Exception for Prototype 1.6...
+                    identifier.preventMunging();
+                }
+                argpos++;
+            }
+        }
+
+        token = this.ts.nextTok();
+        // assert token.getType() == Token.LC;
+        this.braceNesting++;
+
+        token = this.ts.nextTok();
+        if (token.type == "STRN" && this.ts.lookTok(1).data == ';') {
+            /*
+            
+            NOT SUPPORTED YET!?!!?!
+            
+            // This is a hint. Hints are empty statements that look like
+            // "localvar1:nomunge, localvar2:nomunge"; They allow developers
+            // to prevent specific symbols from getting obfuscated (some heretic
+            // implementations, such as Prototype 1.6, require specific variable
+            // names, such as $super for example, in order to work appropriately.
+            // Note: right now, only "nomunge" is supported in the right hand side
+            // of a hint. However, in the future, the right hand side may contain
+            // other values.
+            consumeToken();
+            String hints = token.getValue();
+            // Remove the leading and trailing quotes...
+            hints = hints.substring(1, hints.length() - 1).trim();
+            StringTokenizer st1 = new StringTokenizer(hints, ",");
+            while (st1.hasMoreTokens()) {
+                String hint = st1.nextToken();
+                int idx = hint.indexOf(':');
+                if (idx <= 0 || idx >= hint.length() - 1) {
+                    if (mode == BUILDING_SYMBOL_TREE) {
+                        // No need to report the error twice, hence the test...
+                        this.warn("Invalid hint syntax: " + hint, true);
+                    }
+                    break;
+                }
+                String variableName = hint.substring(0, idx).trim();
+                String variableType = hint.substring(idx + 1).trim();
+                if (mode == BUILDING_SYMBOL_TREE) {
+                    fnScope.addHint(variableName, variableType);
+                } else if (mode == CHECKING_SYMBOL_TREE) {
+                    identifier = fnScope.getIdentifier(variableName);
+                    if (identifier != null) {
+                        if (variableType.equals("nomunge")) {
+                            identifier.preventMunging();
+                        } else {
+                            this.warn("Unsupported hint value: " + hint, true);
+                        }
+                    } else {
+                        this.warn("Hint refers to an unknown identifier: " + hint, true);
+                    }
+                }
+            }
+            */
+        }
+
+        this.parseScope(fnScope);
+        // now pop it off the stack!!!
+       
+        
+        
+    },
+    
+    protectScopeFromObfuscation : function(scope) {
+            //assert scope != null;
+        
+        if (scope == this.globalScope) {
+            // The global scope does not get obfuscated,
+            // so we don't need to worry about it...
+            return;
+        }
+
+        // Find the highest local scope containing the specified scope.
+        while (scope && scope.parent != this.globalScope) {
+            scope = scope.parent;
+        }
+
+        //assert scope.getParentScope() == globalScope;
+        scope.preventMunging();
+    },
+    
+    getIdentifier: function(symbol, scope) {
+        var identifier;
+        while (scope != false) {
+            identifier = scope.getIdentifier(symbol);
+            //println("ScopeParser.getIdentgetUsedSymbols("+symbol+")=" + scope.getUsedSymbols().join(','));
+            if (identifier) {
+                return identifier;
+            }
+            scope = scope.parent;
+        }
+        return false;
+    }
+};
\ No newline at end of file
index cad3547..0486c68 100644 (file)
@@ -4,18 +4,41 @@ imports['Object.js'].load(Object);
 JSDOC   = imports['JSDOC.js'].JSDOC;
 console = imports['console.js'].console;
 /**
-       @constructor
+ *     @class Token
+ * 
+ *  @prop data {String} raw value of token
+ *  @prop type {String} type of token
+ *     TOKN  (unknown)          - name is UNKNOWN_TOKEN
+ *     KEYW  (keyword)          - name is upper case version of keyword
+ *     NAME  (name/identifier)  - name is NAME
+ *     COMM  (comment)          - name is MULTI_LINE_COMM, JSDOC, SINGLE_LINE_COMM
+ *     PUNC  (puctuation)       - name is String description of punctionan (eg LEFTPARAM)
+ *     WHIT  (white space)      - name is SPACE,NEWLINE
+ *     STRN  (string)           - name is DOBULE_QUOTE, SINGLE_QUOTE
+ *     NUMB  (number)           - name is OCTAL,DECIMAL,HEC_DEC
+ *     REGX   (reg.expression)  - name is REGX
+ *  @prop name {String} see type details above
+ *  @prop indentifier {Identifier} identifier class if relivant
+ * 
 */
 
 Token = Object.define(
-    function(data, type, name) {
+    function(data, type, name, line) {
         this.data = data;
         this.type = type;
         this.name = name;
-        this.prefix = '';
+        this.line = line;
+        this.prefix = '';    
+        this.outData = false; // used by packer/scopeparser
+        this.identifier = false; // used by scope
     }, 
     Object, 
     {
+         toString: function()
+        {
+            return 'line:' + this.line + ', type:' + this.type + ', name:' + this.name + ', data:' + this.data;
+        },
+        
         
         toRaw : function(lvl)
         {
index 6f5d959..4d795cf 100644 (file)
@@ -1,22 +1,24 @@
 //<script type="text/javascript">
 
-imports['Object.js'].load(Object);
+//imports['Object.js'].load(Object);
+XObject = imports.XObject.XObject;
 console = imports['console.js'].console;
 
 JSDOC   = imports['JSDOC.js'].JSDOC;
 Token   = imports['JSDOC/Token.js'].Token;
-Lang    = imports['JSDOC/Token.js'].Lang;
+Lang    = imports['JSDOC/Lang.js'].Lang;
 
 /**
        @class Search a {@link JSDOC.TextStream} for language tokens.
 */
-TokenReader = Object.define(
+TokenReader = XObject.define(
     function(o) {
         
         this.keepDocs = true;
         this.keepWhite = false;
         this.keepComments = false;
-        Roo.apply(this, o || {});
+        this.sepIdents = false; // seperate '.' in identifiers..
+        XObject.extend(this, o || {});
         
     },
     Object,
@@ -29,6 +31,7 @@ TokenReader = Object.define(
 
 
         tokenize : function(/**JSDOC.TextStream*/stream) {
+            this.line =1;
             var tokens = [];
             /**@ignore*/ tokens.last    = function() { return tokens[tokens.length-1]; }
             /**@ignore*/ tokens.lastSym = function() {
@@ -50,7 +53,7 @@ TokenReader = Object.define(
                 if (this.read_word(stream, tokens))      continue;
                 
                 // if execution reaches here then an error has happened
-                tokens.push(new Token(stream.next(), "TOKN", "UNKNOWN_TOKEN"));
+                tokens.push(new Token(stream.next(), "TOKN", "UNKNOWN_TOKEN", this.line));
             }
             
             
@@ -72,9 +75,25 @@ TokenReader = Object.define(
             }
             else {
                 var name;
-                if ((name = Lang.keyword(found))) tokens.push(new Token(found, "KEYW", name));
-                else tokens.push(new Token(found, "NAME", "NAME"));
+                if ((name = Lang.keyword(found))) {
+                    tokens.push(new Token(found, "KEYW", name, this.line));
+                    return true;
+                }
+                if (!this.sepIdents || found.indexOf('.') < 0 ) {
+                    tokens.push(new Token(found, "NAME", "NAME", this.line));
+                    return true;
+                }
+                var n = found.split('.');
+                var p = false;
+                n.forEach(function(nm) {
+                    if (p) {
+                        tokens.push(new Token('.', "PUNC", "DOT", this.line));
+                    }
+                    p=true;
+                    tokens.push(new Token(nm, "NAME", "NAME", this.line));
+                });
                 return true;
+                
             }
         },
 
@@ -92,7 +111,7 @@ TokenReader = Object.define(
                 return false;
             }
             else {
-                tokens.push(new Token(found, "PUNC", Lang.punc(found)));
+                tokens.push(new Token(found, "PUNC", Lang.punc(found), this.line));
                 return true;
             }
         },
@@ -112,7 +131,7 @@ TokenReader = Object.define(
             }
             else {
                 if (this.collapseWhite) found = " ";
-                if (this.keepWhite) tokens.push(new Token(found, "WHIT", "SPACE"));
+                if (this.keepWhite) tokens.push(new Token(found, "WHIT", "SPACE", this.line));
                 return true;
             }
         },
@@ -124,6 +143,7 @@ TokenReader = Object.define(
             var found = "";
             
             while (!stream.look().eof && Lang.isNewline(stream.look())) {
+                this.line++;
                 found += stream.next();
             }
             
@@ -132,7 +152,7 @@ TokenReader = Object.define(
             }
             else {
                 if (this.collapseWhite) found = "\n";
-                if (this.keepWhite) tokens.push(new Token(found, "WHIT", "NEWLINE"));
+                if (this.keepWhite) tokens.push(new Token(found, "WHIT", "NEWLINE", this.line));
                 return true;
             }
         },
@@ -143,14 +163,16 @@ TokenReader = Object.define(
         read_mlcomment : function(/**JSDOC.TokenStream*/stream, tokens) {
             if (stream.look() == "/" && stream.look(1) == "*") {
                 var found = stream.next(2);
-                
+                var c = '';
                 while (!stream.look().eof && !(stream.look(-1) == "/" && stream.look(-2) == "*")) {
-                    found += stream.next();
+                    c = stream.next();
+                    if (c == "\n") this.line++;
+                    found += c;
                 }
                 
                 // to start doclet we allow /** or /*** but not /**/ or /****
-                if (/^\/\*\*([^\/]|\*[^*])/.test(found) && this.keepDocs) tokens.push(new Token(found, "COMM", "JSDOC"));
-                else if (this.keepComments) tokens.push(new Token(found, "COMM", "MULTI_LINE_COMM"));
+                if (/^\/\*\*([^\/]|\*[^*])/.test(found) && this.keepDocs) tokens.push(new Token(found, "COMM", "JSDOC", this.line));
+                else if (this.keepComments) tokens.push(new Token(found, "COMM", "MULTI_LINE_COMM", this.line));
                 return true;
             }
             return false;
@@ -172,8 +194,9 @@ TokenReader = Object.define(
                 }
                 
                 if (this.keepComments) {
-                    tokens.push(new Token(found, "COMM", "SINGLE_LINE_COMM"));
+                    tokens.push(new Token(found, "COMM", "SINGLE_LINE_COMM", this.line));
                 }
+                this.line++;
                 return true;
             }
             return false;
@@ -201,7 +224,7 @@ TokenReader = Object.define(
                     }
                     else if (stream.look() == "\"") {
                         string += stream.next();
-                        tokens.push(new Token(string, "STRN", "DOUBLE_QUOTE"));
+                        tokens.push(new Token(string, "STRN", "DOUBLE_QUOTE", this.line));
                         return true;
                     }
                     else {
@@ -226,7 +249,7 @@ TokenReader = Object.define(
                     }
                     else if (stream.look() == "'") {
                         string += stream.next();
-                        tokens.push(new Token(string, "STRN", "SINGLE_QUOTE"));
+                        tokens.push(new Token(string, "STRN", "SINGLE_QUOTE", this.line));
                         return true;
                     }
                     else {
@@ -255,8 +278,8 @@ TokenReader = Object.define(
                 return false;
             }
             else {
-                if (/^0[0-7]/.test(found)) tokens.push(new Token(found, "NUMB", "OCTAL"));
-                else tokens.push(new Token(found, "NUMB", "DECIMAL"));
+                if (/^0[0-7]/.test(found)) tokens.push(new Token(found, "NUMB", "OCTAL", this.line));
+                else tokens.push(new Token(found, "NUMB", "DECIMAL", this.line));
                 return true;
             }
         },
@@ -293,7 +316,7 @@ TokenReader = Object.define(
             
             while (!stream.look().eof) {
                 if (Lang.isHexDec(found) && !Lang.isHexDec(found+stream.look())) { // done
-                    tokens.push(new Token(found, "NUMB", "HEX_DEC"));
+                    tokens.push(new Token(found, "NUMB", "HEX_DEC", this.line));
                     return true;
                 }
                 else {
@@ -338,7 +361,7 @@ TokenReader = Object.define(
                             regex += stream.next();
                         }
                         
-                        tokens.push(new Token(regex, "REGX", "REGX"));
+                        tokens.push(new Token(regex, "REGX", "REGX", this.line));
                         return true;
                     }
                     else {
index cc3f6af..b5385a6 100644 (file)
@@ -21,6 +21,7 @@ TokenStream = Object.define(
     },
     Object,
     {
+        cursor : -1, // where are we in the stream.
         
         rewind : function() {
             this.cursor = -1;
@@ -33,7 +34,9 @@ TokenStream = Object.define(
             if (typeof n == "undefined") n = 0;
 
             if (considerWhitespace == true) {
-                if (this.cursor+n < 0 || this.cursor+n > this.tokens.length) return {};
+                if (this.cursor+n < 0 || this.cursor+n > (this.tokens.length -1)) {
+                    return new Token("", "VOID", "START_OF_STREAM");
+                }
                 return this.tokens[this.cursor+n];
             }
             else {
@@ -91,8 +94,14 @@ TokenStream = Object.define(
             var i = this.cursor;
 
             while (true) {
-                if (i < 0) return false;
-                else if (i > this.tokens.length) return false;
+               // print(i);
+                if (i < 0) {
+                    if (n > -1) {
+                        i = 0; continue;
+                    }
+                    return   new Token("", "VOID", "END_OF_STREAM");
+                }
+                else if (i > this.tokens.length) return  new Token("", "VOID", "END_OF_STREAM");
 
                 if (i != this.cursor && (this.tokens[i] === undefined || this.tokens[i].is("WHIT") || this.tokens[i].is("COMM"))) {
                     if (n < 0) i--; else i++;
@@ -105,7 +114,7 @@ TokenStream = Object.define(
                 count++;
                 (n < 0)? i-- : i++;
             }
-
+        // should never get here..
             return false; // because null isn't an object and caller always expects an object;
             
         },
@@ -133,7 +142,9 @@ TokenStream = Object.define(
         },
         // what about comments after 'function'...
         // is this used ???
-
+        nextTok  : function() {
+            return this.nextNonSpace();
+        },
         nextNonSpace : function ()
         {
             
@@ -149,9 +160,14 @@ TokenStream = Object.define(
             }
         },
         /**
-            @type JSDOC.Token[]
-        */
+         *    @type JSDOC.Token[]
+         * @param start {String}  token name or data (eg. '{'
+         * @param stop {String} (Optional) token name or data (eg. '}'
+         */
         balance : function(/**String*/start, /**String*/stop) {
+            
+            start = typeof(Lang.matching(start)) == 'undefined' ? Lang.punc(start) : start;
+            
             if (!stop) stop = Lang.matching(start);
             
             var depth = 0;
@@ -218,10 +234,16 @@ TokenStream = Object.define(
         arrayToString : function(ar) {
             console.log(typeof(ar));
             var ret = [];
-            Roo.each(ar, function(e) {
+            ar.forEach(function(e) {
                 ret.push(e.data);
             })
             return ret.join('');
+        },
+        dump: function()
+        {
+            this.tokens.forEach(function(t) {
+                print(t.toString());
+            });
         }
 });
     
\ No newline at end of file
diff --git a/XObject.js b/XObject.js
new file mode 100644 (file)
index 0000000..424db53
--- /dev/null
@@ -0,0 +1,439 @@
+//<script type="text/javascript">
+
+/**
+ * XObject
+ * Yet another attempt to create a usable object construction library for seed..
+ * 
+ * Why is this useful?
+ * A) It turns rather messy code into a tree structure, making it easy to find code relating to 
+ *    an interface element
+ * B) In theory it should be gjs/Seed compatible..
+ * C) It provides getElementById style lookups for elements.
+ * D) It provides classic OO constructors for Javascript (extend/define)
+ * E) It does not modify any buildin prototypes.. 
+ *
+ * Extend this.. to use it's wonderful features..
+ * 
+ * normal usage:
+ * XObject = imports.XObject.XObject;
+ * 
+ * Xyz = new XObject({
+ *     xtype: Gtk.Window,
+ *     id : 'window',
+ *     items : [
+ *     
+ *     ]
+ *  });
+ *  Xyz.init(); // create and show.
+ * 
+ * 
+ * 
+ * @arg xtype {String|Function} constructor or string.
+ * @arg id {String}  (optional) id for registry
+ * @arg xns {String|Object}   (optional) namespace eg. Gtk or 'Gtk' - used with xtype.
+ * @arg items {Array}   (optional) list of child elements which will be constructed.. using XObject
+ * @arg listeners {Object}   (optional) map Gobject signals to functions
+ * @arg pack {Function|String|Array}   (optional) how this object gets added to it's parent
+ * @arg el {Object}   (optional) premade GObject
+ * 
+ *  --- needs a xdebug option!
+ * 
+ * 
+ * He's some questions.
+ * - should we generate ID's for all elements? (if so we probably need to garbage collect)
+ * - should we have a special property to use as the constructor / gobject.properties rather
+ *   than sending all basic types to this?
+ * 
+ * 
+ */
+
+function XObject (cfg) {
+    // first apply cfg if set.
+    this.config = cfg;
+    if (cfg.init) {
+        this.init = cfg.init; // override!
+    }
+    
+    
+}
+
+
+
+XObject.prototype = {
+    /**
+     * @property el {GObject} the Gtk / etc. element.
+     */
+    el : false, 
+    /*
+     * @property items {Array} list of sub elements
+     */
+    /**
+     * @property parent {XObject} parent Element
+     */
+     
+     /**
+     * @property config {Object} the construction configuration.
+     */
+     /**
+      * @method init
+      * Initializes the Element (el) hooks up all the listeners
+      * and packs the children.
+      * you can override this, in child objects, then 
+      * do this to do thi initaliztion.
+      * 
+      * XObject.prototype.init.call(this); 
+      * 
+      */ 
+    init : function()
+    {
+        var cfg = this.config;
+    
+        print("new xobj?"  + XObject.keys(cfg).join(','));
+        //print(cfg);
+        o =  {};
+        
+        cfg.items = cfg.items || [];
+        
+        XObject.extend(o, cfg); // copy everything into o.
+        
+        o.pack = typeof(o.pack) == 'undefined' ? 'add' : o.pack;
+        
+        XObject.extend(this, o);
+
+        // remove items.
+        
+        this.listeners = this.listeners || {}; 
+        this.items = [];
+        
+        // remove objects/functions from o, so they can be sent to the contructor.
+        for (var i in o) {
+            if ((typeof(o[i]) == 'object') || 
+                (typeof(o[i]) == 'function') || 
+                i == 'pack' ||
+                i == 'id' ||
+                i == 'xtype' ||
+                i == 'xdebug' ||
+                i == 'xns'
+            ) {
+                delete o[i];
+            }
+        }
+        
+        // do we need to call 'beforeInit here?'
+         
+        // handle include?
+        //if ((this.xtype == 'Include')) {
+        //    o = this.pre_registry[cls];
+        //}
+        var isSeed = typeof(Seed) != 'undefined';
+         
+        // xtype= Gtk.Menu ?? what about c_new stuff?
+        print(this.xtype);
+        if (typeof(this.xtype) == 'function') {
+            print("func?"  + XObject.keys(o).join(','));
+            this.el = this.el ||   this.xtype(o);
+        }
+        if (typeof(this.xtype) == 'object') {
+            print("obj?"  + XObject.keys(o).join(','));
+            this.el = this.el ||  new this.xtype(o);
+        }
+        //print(this.el);
+        if (!this.el && o.xns) {
+            
+            var NS = imports.gi[o.xns];
+            if (!NS) {
+                Seed.print('Invalid xns: ' + o.xns);
+            }
+            constructor = NS[o.xtype];
+            if (!constructor) {
+                Seed.print('Invalid xtype: ' + o.xns + '.' + o.xtype);
+            }
+            this.el  =   isSeed ? new constructor(o) : new constructor();
+            
+        }
+        // always overlay props..
+        for (var i in o) {
+            this.el[i] = o[i];
+        }
+        // register it!
+        //if (o.xnsid  && o.id) {
+         //   XObject.registry = XObject.registry || { };
+         //   XObject.registry[o.xnsid] = XObject.registry[o.xnsid] || {}; 
+         //   XObject.registry[o.xnsid][o.id] = this;
+        //}
+        
+        cfg.items.forEach(this.addItem, this);
+        
+        for (var i in this.listeners) {
+            this.addListener(i, this.listeners[i]);
+        }
+        // delete this.listeners ?
+        
+        
+        // do we need to call 'init here?'
+    },
+      
+     
+     /**
+      * @method addItem
+      * Adds an item to the object using a new XObject
+      * uses pack property to determine how to add it.
+      * @arg cfg {Object} same as XObject constructor.
+      */
+    addItem : function(o) {
+        
+         
+        var item = (o.constructor == XObject) ? o : new XObject(o);
+        item.init();
+        item.parent = this;
+        this.items.push(item);
+        
+        if (item.pack===false) {  // no 
+            return;
+        }
+        if (typeof(item.pack) == 'function') {
+            // parent, child
+            item.pack.apply(o, [ o , o.items[i] ]);
+            item.parent = this;
+            return;
+        }
+        var args = [];
+        var pack_m  = false;
+        if (typeof(item.pack) == 'string') {
+            pack_m = item.pack;
+        } else {
+            pack_m = item.pack.shift();
+            args = item.pack;
+        }
+        
+        // handle error.
+        if (pack_m && typeof(this.el[pack_m]) == 'undefined') {
+            Seed.print('pack method not available : ' + this.xtype + '.' +  pack_m);
+            return;
+        }
+        
+        
+        //Seed.print('Pack ' + this.el + '.'+ pack_m + '(' + item.el + ')');
+
+        args.unshift(item.el);
+        print('[' + args.join(',') +']');
+        //Seed.print('args: ' + args.length);
+        if (pack_m) {
+            this.el[pack_m].apply(this.el, args);
+        }
+        
+       
+        
+    },
+    /**
+      * @method addListener
+      * Connects a method to a signal. (gjs/Seed aware)
+      * 
+      * @arg sig  {String} name of signal
+      * @arg fn  {Function} handler.
+      */
+    addListener  : function(sig, fn) 
+    {
+        Seed.print("Add signal " + sig);
+        var _li = XObject.createDelegate(fn,this);
+        // private listeners that are not copied to GTk.
+        
+        if (typeof(Seed) != 'undefined') {
+          //   Seed.print(typeof(_li));
+            this.el.signal[sig].connect(_li);
+        } else {
+            this.el.connect( sig, _li);
+        }
+             
+        
+    },
+     /**
+      * @method get
+      * Finds an object in the child elements using xid of object.
+      * prefix with '.' to look up the tree.. multiple '..' to look further up..
+      * 
+      * @arg name  {String} name of signal
+      * @return   {XObject|false} the object if found.
+      */
+    get : function(xid)
+    {
+        var ret=  false;
+        if (xid[0] == '.') {
+            return this.parent.get(xid.substring(1));
+        }
+        
+        
+        this.items.forEach(function(ch) {
+            if (ch.id == xid) {
+                ret = ch;
+                return true;
+            }
+        })
+        if (ret) {
+            return ret;
+        }
+        // iterate children.
+        this.items.forEach(function(ch) {
+            ret = ch.get(xid);
+            if (ret) {
+                return true;
+            }
+        })
+        return ret;
+    }
+      
+      
+} 
+         
+        
+/**
+ * Copies all the properties of config to obj.
+ *
+ * Pretty much the same as JQuery/Prototype..
+ * @param {Object} obj The receiver of the properties
+ * @param {Object} config The source of the properties
+ * @param {Object} defaults A different object that will also be applied for default values
+ * @return {Object} returns obj
+ * @member XObject extend
+ */
+
+
+XObject.extend = function(o, c, defaults){
+    if(defaults){
+        // no "this" reference for friendly out of scope calls
+        XObject.extend(o, defaults);
+    }
+    if(o && c && typeof c == 'object'){
+        for(var p in c){
+            o[p] = c[p];
+        }
+    }
+    return o;
+};
+
+XObject.extend(XObject,
+{
+    /**
+     * Copies all the properties of config to obj, if the do not exist.
+     * @param {Object} obj The receiver of the properties
+     * @param {Object} config The source of the properties
+     * @return {Object} returns obj
+     * @member Object extendIf
+     */
+
+
+    extendIf : function(o, c){
+
+        if(!o || !c || typeof c != 'object'){
+            return o;
+        }
+        for(var p in c){
+            if (typeof(o[p]) != 'undefined') {
+                continue;
+            }
+            o[p] = c[p];
+        }
+        return o;
+    },
+
+
+    /**
+     * Extends one class with another class and optionally overrides members with the passed literal. This class
+     * also adds the function "override()" to the class that can be used to override
+     * members on an instance.
+     *
+     * usage:
+     * MyObject = Object.define(
+     *     function(...) {
+     *          ....
+     *     },
+     *     parentClass, // or Object
+     *     {
+     *        ... methods and properties.
+     *     }
+     * });
+     * @param {Function} constructor The class inheriting the functionality
+     * @param {Object} superclass The class being extended
+     * @param {Object} overrides (optional) A literal with members
+     * @return {Function} constructor (eg. class
+     * @method define
+     */
+    define : function(){
+        // inline overrides
+        var io = function(o){
+            for(var m in o){
+                this[m] = o[m];
+            }
+        };
+        return function(sb, sp, overrides) {
+            if (typeof(sp) == 'undefined') {
+                // error condition - try and dump..
+                throw "Missing superclass: when applying: " + sb
+            }
+
+            var F = function(){}, sbp, spp = sp.prototype;
+            F.prototype = spp;
+            sbp = sb.prototype = new F();
+            sbp.constructor=sb;
+            sb.superclass=spp;
+
+            // extends Object.
+            if(spp.constructor == Object.prototype.constructor){
+                spp.constructor=sp;
+            }
+            
+            sb.override = function(o){
+                Object.extend(sb.prototype, o);
+            };
+            sbp.override = io;
+            XObject.extend(sb.prototype, overrides);
+            return sb;
+        };
+    }(),
+
+         
+    /**
+     * returns a list of keys of the object.
+     * @param {Object} obj object to inspect
+     * @return {Array} returns list of kyes
+     * @member XObject keys
+     */
+    keys : function(o)
+    {
+        var ret = [];
+        for(var i in o) {
+            ret.push(i);
+        }
+        return ret;
+    },
+      
+    /**
+     * @member XObject createDelegate
+     * creates a delage metdhod
+     * @param {Function} method to wrap
+     * @param {Object} scope 
+     * @param {Array} args to add
+     * @param {Boolean|Number} append arguments or replace after N arguments.
+     * @return {Function} returns the delegate
+     */
+
+    createDelegate : function(method, obj, args, appendArgs){
+        
+        return function() {
+            var callArgs = args || arguments;
+            if(appendArgs === true){
+                callArgs = Array.prototype.slice.call(arguments, 0);
+                callArgs = callArgs.concat(args);
+            }else if(typeof appendArgs == "number"){
+                callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
+                    var applyArgs = [appendArgs, 0].concat(args); // create method call params
+                    Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
+                }
+                return method.apply(obj || window, callArgs);
+            };
+    }
+    
+});
\ No newline at end of file
diff --git a/docs.js b/docs.js
index 10f1f11..9755ebe 100644 (file)
--- a/docs.js
+++ b/docs.js
@@ -37,16 +37,16 @@ if (typeof(Seed.argv[3]) == 'string') {
     ns_list = Seed.argv[3].split(',');
 }
  
-ns_list = ns_list .sort();
-var cls_list = [];
+ns_list = ns_list.sort();
+
 var cls_template = new JSDOC.Template(__script_path__ + '/docs/class.html');
 var cls_ix_template = new JSDOC.Template(__script_path__ + '/docs/class_ix.html');
 var reference_template = new JSDOC.Template(__script_path__ + '/docs/references.html');
+var ns_idx = [];
 ns_list.map(function(ns_name) 
 {
     var  core = imports.gi[ns_name];
-   
+    var idx = { name: ns_name}; 
     console.log("START:" + ns_name);
    
     var ns = Introspect.ns(ns_name);
@@ -54,10 +54,6 @@ ns_list.map(function(ns_name)
 
     Gio.simple_write(outputdir + '/'+ ns_name +  '.html', cls_template.process(ns));
     
-    cls_list.push({
-        alias : ns_name 
-    });
-    
     // left bar index of elements in namespace...
     Gio.simple_write(outputdir + '/_ix_'+ ns_name +  '.shtml', cls_ix_template.process(ns));
      
@@ -71,6 +67,7 @@ ns_list.map(function(ns_name)
         
     };
     for (var i in actions) {
+        idx[i]= ns[i].length;
         ns[i].map( function(n) {
             Gio.simple_write(outputdir + '/'+ ns_name + '.' + n + '.html', 
                 cls_template.process(
@@ -84,7 +81,7 @@ ns_list.map(function(ns_name)
             console.log(ns_name + '.' +n);
         }); 
     }
-    
+    ns_idx.push(idx);
       
 });
 
@@ -119,7 +116,7 @@ for (var i in Introspect.references) {
 }
 
 var ix_template = new JSDOC.Template(__script_path__ + '/docs/index.html');
-Gio.simple_write(outputdir + '/index.html', ix_template.process(ns_list));
+Gio.simple_write(outputdir + '/index.html', ix_template.process(ns_idx));
 File.silentRecursiveCopy(__script_path__ + '/docs/resources/', outputdir);
 
 
index 33f5218..4d0f5e7 100644 (file)
             <td class="hd-info">{+data.ns+} = imports.gi.{+data.ns+}; </td>
        </tr>
 </if>
-    
+     <tr>
+            <td class="label">Note:</td>
+            <td class="hd-info"><b style="color:red">This documentation is generated from the HEAD of most 
+            libraries (and some experimental libs at times) - and may not be exactly the same as your
+            installed .gir files - you may want to download the generator and install on your machine 
+            to find out what you have.<b/></td>
+        </tr>
         <tr>
             <td class="label">C documentation:</td>
             <td class="hd-info">{+new Link().toGnome(data.alias)+}</td>
index aae2b1a..0a852cf 100755 (executable)
     </div>
        </div>
 
+ <h2>GObject Libraries</h2>
   <for each="thisClass" in="data">
+    {!
+    if (!thisClass.objects) { continue; }
+    !}
     <div>
-       <h2 class="classTitle">{+ (new Link().toSymbol(thisClass)) +}</h2>
+       <h2 class="classTitle">{+ (new Link().toSymbol(thisClass.name)) +}</h2>
     </div>
+  </for>
  
+ <h2>Non - GObject Libraries</h2>
+  <for each="thisClass" in="data">
+    {!
+    if (thisClass.objects) { continue; }
+    !}
+    <div>
+       <h2 class="classTitle">{+ (new Link().toSymbol(thisClass.name)) +}</h2>
+    </div>
   </for>
  
 </div>
  </body>
  </html>
diff --git a/pack.js b/pack.js
new file mode 100755 (executable)
index 0000000..3a17a4c
--- /dev/null
+++ b/pack.js
@@ -0,0 +1,83 @@
+#!/usr/bin/seed
+//<script type="text/javascript">
+/**
+ * packer command line
+ * 
+ * -o Output
+ * -O Output debug file here.
+ * -t Translate json file.
+ * -w Cache / working dir.
+ * -f File to read with a list of source paths / or class names.
+ * -C no cleanup (use with -w if you need are using a cache directory.)
+ * -p prefix for translation md5 generator (directory that files are in, and is removed 
+ *    from path when generating an md5 for the translated name.
+ *
+ * compresses files listed as arguments and outputs result
+ */
+
+Packer          = imports['JSDOC/Packer.js'].Packer;
+File = imports.File.File;
+  
+
+var args = Array.prototype.slice.call(Seed.argv);
+args.shift(); //seed
+args.shift(); // pack.js
+var cfg = {
+    files : [],
+    target : false,
+}
+
+
+for(var i =0; i < args.length;i++) {
+    if (args[i] == '-o') {
+        cfg.target = args[i+1];
+        i++;
+        continue;
+    }
+    if (args[i] == '-O') {
+        cfg.debugTarget = args[i+1];
+        i++;
+        continue;
+    }
+    if (args[i] == '-t') {
+        cfg.translateJSON = args[i+1];
+        i++;
+        continue;
+    }
+    if (args[i] == '-w') {
+        cfg.tmpDir = args[i+1];
+        i++;
+        continue;
+    }
+    if (args[i] == '-p') {
+        cfg.prefix = args[i+1];
+        i++;
+        continue;
+    }
+    if (args[i] == '-C') {
+        cfg.cleanup = false;
+        continue;
+    }
+    if (args[i] == '-f') {
+        cfg.srcfile = args[i+1];
+        i++;
+        continue;
+    }
+    if (cfg.files.indexOf(args[i]) > -1) {
+        continue; // remove dupes.
+    }
+    cfg.files.push(args[i]);
+}
+var pack;
+try {
+    pack = new Packer(cfg)
+} catch (e) {
+    print("ERROR " + e.toString());
+    throw e;
+}
+if (!pack.target) {
+    print(pack.out);
+}
+
+