diff --git a/gun.js b/gun.js index 8e1a2efd9..2328d01bf 100644 --- a/gun.js +++ b/gun.js @@ -136,12 +136,231 @@ }; })(USE, './onto'); + ;USE(function(module){ ;(function(){ + // TODO: BUG! Unbuild will make these globals... CHANGE unbuild to wrap files in a function. + // Book is a replacement for JS objects, maps, dictionaries. + var sT = setTimeout, B = sT.Book || (sT.Book = function(text){ + var b = function book(word, is){ + var has = b.all[word], p; + if(is === undefined){ return (has && has.is) || b.get(has || word) } + if(has){ + if(p = has.page){ + p.size += size(is) - size(has.is); + p.text = ''; + } + has.text = ''; + has.is = is; + return b; + } + //b.all[word] = {is: word}; return b; + return b.set(word, is); + }; + // TODO: if from text, preserve the separator symbol. + b.list = [{from: text, size: (text||'').length, substring: sub, toString: to, book: b, get: b, read: list}]; + b.page = page; + b.set = set; + b.get = get; + b.all = {}; + return b; + }), PAGE = 2**12; + + function page(word){ + var b = this, l = b.list, i = spot(word, l, b.parse), p = l[i]; + if('string' == typeof p){ l[i] = p = {size: -1, first: b.parse? b.parse(p) : p, substring: sub, toString: to, book: b, get: b, read: list} } // TODO: test, how do we arrive at this condition again? + //p.i = i; + return p; + // TODO: BUG! What if we get the page, it turns out to be too big & split, we must then RE get the page! + } + function get(word){ + if(!word){ return } + if(undefined !== word.is){ return word.is } // JS falsey values! + var b = this, has = b.all[word]; + if(has){ return has.is } + // get does an exact match, so we would have found it already, unless parseless page: + var page = b.page(word), l, has, a, i; + if(!page || !page.from){ return } // no parseless data + return got(word, page); + } + function got(word, page){ + var b = page.book, l, has, a, i; + if(l = from(page)){ has = l[got.i = i = spot(word, l, B.decode)]; } // TODO: POTENTIAL BUG! This assumes that each word on a page uses the same serializer/formatter/structure. + // parseless may return -1 from actual value, so we may need to test both. // TODO: Double check? I think this is correct. + if(has && word == has.word){ return (b.all[word] = has).is } + if('string' != typeof has){ has = l[got.i = i+=1] } + if(has && word == has.word){ return (b.all[word] = has).is } + a = slot(has) // Escape! + if(word != B.decode(a[0])){ + has = l[got.i = i+=1]; // edge case bug? + a = slot(has); // edge case bug? + if(word != B.decode(a[0])){ return } + } + has = l[i] = b.all[word] = {word: ''+word, is: B.decode(a[1]), page: page, substring: subt, toString: tot}; // TODO: convert to a JS value!!! Maybe index! TODO: BUG word needs a page!!!! TODO: Check for other types!!! + return has.is; + } + + function spot(word, sorted, parse){ parse = parse || spot.no || (spot.no = function(t){ return t }); // TODO: BUG???? Why is there substring()||0 ? // TODO: PERF!!! .toString() is +33% faster, can we combine it with the export? + var L = sorted, min = 0, page, found, l = (word=''+word).length, max = L.length, i = max/2; + while(((word < (page = (parse(L[i=i>>0])||'').substring())) || ((parse(L[i+1])||'').substring() <= word)) && i != min){ // L[i] <= word < L[i+1] + i += (page <= word)? (max - (min = i))/2 : -((max = i) - min)/2; + } + return i; + } + + function from(a, t, l){ + if('string' != typeof a.from){ return a.from } + //(l = a.from = (t = a.from||'').substring(1, t.length-1).split(t[0])).toString = join; // slot + (l = a.from = slot(t = t||a.from||'')).toString = join; + return l; + } + function list(each){ each = each || function(x){return x} + // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). + var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; + while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } + return r; + } + + function set(word, is){ + var b = this, has = b.all[word]; + if(has){ return b(word, is) } // updates to in-memory items will always match exactly. + var page = b.page(word=''+word), tmp; // before we assume this is an insert tho, we need to check + if(page && page.from){ // if it could be an update to an existing word from parseless. + b.get(word); + if(b.all[word]){ return b(word, is) } + } + // MUST be an insert: + has = b.all[word] = {word: word, is: is, page: page, substring: subt, toString: tot}; + page.first = (page.first < word)? page.first : word; + if(!page.limbo){ (page.limbo = []).toString = join } + page.limbo.push(has); + b(word, is); + page.size += size(word) + size(is); + if((b.PAGE || PAGE) < page.size){ split(page, b) } + return b; + } + + function split(p, b){ // TODO: use closest hash instead of half. + //console.time(); + // TODO: BUG???? May need to do a SORTED merge with FROM. + var i = 0, L = p.limbo, tmp; + //while(tmp = L[i++]){ } + var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; + //console.timeEnd(); + var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; + nl.toString = join; + //console.time(); + while(tmp = L[i++]){ + nl.push(tmp); + next.size += (tmp.is||'').length||1; + tmp.page = next; + } + //console.timeEnd(); + //console.time(); + p.limbo = p.limbo.slice(0, j); + p.size -= next.size; + p.sort = 0; + b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. // TODO: BUG! spot may need parse too? + //console.timeEnd(); + if(b.split){ b.split(next, p) } + } + + function slot(t){ return heal((t=t||'').substring(1, t.length-1).split(t[0]), t[0]) } B.slot = slot; // TODO: check first=last & pass `s`. + function heal(l, s){ var i, e; + if(0 > (i = l.indexOf(''))){ return l } // ~700M ops/sec on 4KB of Math.random()s, even faster if escape does exist. + if('' == l[0] && 1 == l.length){ return [] } // annoying edge cases! how much does this slow us down? + //if((c=i+2+parseInt(l[i+1])) != c){ return [] } // maybe still faster than below? + if((e=i+2+parseInt((e=l[i+1]).substring(0, e.indexOf('"'))||e)) != e){ return [] } // NaN check in JS is weird. + l[i] = l.slice(i, e).join(s||'|'); // rejoin the escaped value + return l.slice(0,i+1).concat(heal(l.slice(e), s)); // merge left with checked right. + } + + function size(t){ return (t||'').length||1 } // bits/numbers less size? Bug or feature? + function subt(i,j){ return this.word } + //function tot(){ return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'" } + function tot(){ var tmp = {}; + //if((tmp = this.page) && tmp.saving){ delete tmp.book.all[this.word]; } // TODO: BUG! Book can't know about RAD, this was from RAD, so this MIGHT be correct but we need to refactor. Make sure to add tests that will re-trigger this. + return this.text = this.text || ":"+B.encode(this.word)+":"+B.encode(this.is)+":"; + tmp[this.word] = this.is; + return this.text = this.text || B.encode(tmp,'|',':').slice(1,-1); + //return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'"; + } + function sub(i,j){ return (this.first||this.word||B.decode((from(this)||'')[0]||'')).substring(i,j) } + function to(){ return this.text = this.text || text(this) } + function join(){ return this.join('|') } + function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? + if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } + if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". + return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. + } + function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( + var j = 0, i; + while(i = l[j++]){ + if(got(i.word, p)){ + f[got.i] = i; + } else { + f.push(i); + } + } + return sort(f); + } + function sort(l){ //return l.sort(); + return l.sort(function(a,b){ + return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; + }); + } + + B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32); + switch(typeof d){ + case 'string': // text + var i = d.indexOf(s), c = 0; + while(i != -1){ c++; i = d.indexOf(s, i+1) } + return (c?s+c:'')+ '"' + d; + case 'number': return (d < 0)? ''+d : '+'+d; + case 'boolean': return d? '+' : '-'; + case 'object': if(!d){ return ' ' } // TODO: BUG!!! Nested objects don't slot correctly + var l = Object.keys(d).sort(), i = 0, t = s, k, v; + while(k = l[i++]){ t += u+B.encode(k,s,u)+u+B.encode(d[k],s,u)+u+s } + return t; + } + } + B.decode = function(t, s){ s = s || "|"; + if('string' != typeof t){ return } + switch(t){ case ' ': return null; case '-': return false; case '+': return true; } + switch(t[0]){ + case '-': case '+': return parseFloat(t); + case '"': return t.slice(1); + } + return t.slice(t.indexOf('"')+1); + } + + B.hash = function(s, c){ // via SO + if(typeof s !== 'string'){ return } + c = c || 0; // CPU schedule hashing by + if(!s.length){ return c } + for(var i=0,l=s.length,n; i>0])||'').substring())) || ((parse(L[i+1])||'').substring() <= word)) && i != min){ // L[i] <= word < L[i+1] - i += (page <= word)? (max - (min = i))/2 : -((max = i) - min)/2; - } - return i; -} - -function from(a, t, l){ - if('string' != typeof a.from){ return a.from } - //(l = a.from = (t = a.from||'').substring(1, t.length-1).split(t[0])).toString = join; // slot - (l = a.from = slot(t = t||a.from||'')).toString = join; - return l; -} -function list(each){ each = each || function(x){return x} - // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). - var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; - while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } - return r; -} - -function set(word, is){ - var b = this, has = b.all[word]; - if(has){ return b(word, is) } // updates to in-memory items will always match exactly. - var page = b.page(word=''+word), tmp; // before we assume this is an insert tho, we need to check - if(page && page.from){ // if it could be an update to an existing word from parseless. - b.get(word); - if(b.all[word]){ return b(word, is) } - } - // MUST be an insert: - has = b.all[word] = {word: word, is: is, page: page, substring: subt, toString: tot}; - page.first = (page.first < word)? page.first : word; - if(!page.limbo){ (page.limbo = []).toString = join } - page.limbo.push(has); - b(word, is); - page.size += size(word) + size(is); - if((b.PAGE || PAGE) < page.size){ split(page, b) } - return b; -} - -function split(p, b){ // TODO: use closest hash instead of half. - //console.time(); - // TODO: BUG???? May need to do a SORTED merge with FROM. - var i = 0, L = p.limbo, tmp; - //while(tmp = L[i++]){ } - var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; - //console.timeEnd(); - var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; - nl.toString = join; - //console.time(); - while(tmp = L[i++]){ - nl.push(tmp); - next.size += (tmp.is||'').length||1; - tmp.page = next; - } - //console.timeEnd(); - //console.time(); - p.limbo = p.limbo.slice(0, j); - p.size -= next.size; - p.sort = 0; - b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. // TODO: BUG! spot may need parse too? - //console.timeEnd(); - if(b.split){ b.split(next, p) } -} - -function slot(t){ return heal((t=t||'').substring(1, t.length-1).split(t[0]), t[0]) } B.slot = slot; // TODO: check first=last & pass `s`. -function heal(l, s){ var i, e; - if(0 > (i = l.indexOf(''))){ return l } // ~700M ops/sec on 4KB of Math.random()s, even faster if escape does exist. - if('' == l[0] && 1 == l.length){ return [] } // annoying edge cases! how much does this slow us down? - //if((c=i+2+parseInt(l[i+1])) != c){ return [] } // maybe still faster than below? - if((e=i+2+parseInt((e=l[i+1]).substring(0, e.indexOf('"'))||e)) != e){ return [] } // NaN check in JS is weird. - l[i] = l.slice(i, e).join(s||'|'); // rejoin the escaped value - return l.slice(0,i+1).concat(heal(l.slice(e), s)); // merge left with checked right. -} - -function size(t){ return (t||'').length||1 } // bits/numbers less size? Bug or feature? -function subt(i,j){ return this.word } -//function tot(){ return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'" } -function tot(){ var tmp = {}; - //if((tmp = this.page) && tmp.saving){ delete tmp.book.all[this.word]; } // TODO: BUG! Book can't know about RAD, this was from RAD, so this MIGHT be correct but we need to refactor. Make sure to add tests that will re-trigger this. - return this.text = this.text || ":"+B.encode(this.word)+":"+B.encode(this.is)+":"; - tmp[this.word] = this.is; - return this.text = this.text || B.encode(tmp,'|',':').slice(1,-1); - //return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'"; -} -function sub(i,j){ return (this.first||this.word||B.decode((from(this)||'')[0]||'')).substring(i,j) } -function to(){ return this.text = this.text || text(this) } -function join(){ return this.join('|') } -function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? - if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } - if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". - return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. -} -function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( - var j = 0, i; - while(i = l[j++]){ - if(got(i.word, p)){ - f[got.i] = i; - } else { - f.push(i); - } - } - return sort(f); -} -function sort(l){ //return l.sort(); - return l.sort(function(a,b){ - return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; - }); -} - -B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32); - switch(typeof d){ - case 'string': // text - var i = d.indexOf(s), c = 0; - while(i != -1){ c++; i = d.indexOf(s, i+1) } - return (c?s+c:'')+ '"' + d; - case 'number': return (d < 0)? ''+d : '+'+d; - case 'boolean': return d? '+' : '-'; - case 'object': if(!d){ return ' ' } // TODO: BUG!!! Nested objects don't slot correctly - var l = Object.keys(d).sort(), i = 0, t = s, k, v; - while(k = l[i++]){ t += u+B.encode(k,s,u)+u+B.encode(d[k],s,u)+u+s } - return t; - } -} -B.decode = function(t, s){ s = s || "|"; - if('string' != typeof t){ return } - switch(t){ case ' ': return null; case '-': return false; case '+': return true; } - switch(t[0]){ - case '-': case '+': return parseFloat(t); - case '"': return t.slice(1); - } - return t.slice(t.indexOf('"')+1); -} - -B.hash = function(s, c){ // via SO - if(typeof s !== 'string'){ return } - c = c || 0; // CPU schedule hashing by - if(!s.length){ return c } - for(var i=0,l=s.length,n; i>0])||'').substring())) || ((parse(L[i+1])||'').substring() <= word)) && i != min){ // L[i] <= word < L[i+1] + i += (page <= word)? (max - (min = i))/2 : -((max = i) - min)/2; + } + return i; +} + +function from(a, t, l){ + if('string' != typeof a.from){ return a.from } + //(l = a.from = (t = a.from||'').substring(1, t.length-1).split(t[0])).toString = join; // slot + (l = a.from = slot(t = t||a.from||'')).toString = join; + return l; +} +function list(each){ each = each || function(x){return x} + // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). + var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; + while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } + return r; +} + +function set(word, is){ + var b = this, has = b.all[word]; + if(has){ return b(word, is) } // updates to in-memory items will always match exactly. + var page = b.page(word=''+word), tmp; // before we assume this is an insert tho, we need to check + if(page && page.from){ // if it could be an update to an existing word from parseless. + b.get(word); + if(b.all[word]){ return b(word, is) } + } + // MUST be an insert: + has = b.all[word] = {word: word, is: is, page: page, substring: subt, toString: tot}; + page.first = (page.first < word)? page.first : word; + if(!page.limbo){ (page.limbo = []).toString = join } + page.limbo.push(has); + b(word, is); + page.size += size(word) + size(is); + if((b.PAGE || PAGE) < page.size){ split(page, b) } + return b; +} + +function split(p, b){ // TODO: use closest hash instead of half. + //console.time(); + // TODO: BUG???? May need to do a SORTED merge with FROM. + var i = 0, L = p.limbo, tmp; + //while(tmp = L[i++]){ } + var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; + //console.timeEnd(); + var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; + nl.toString = join; + //console.time(); + while(tmp = L[i++]){ + nl.push(tmp); + next.size += (tmp.is||'').length||1; + tmp.page = next; + } + //console.timeEnd(); + //console.time(); + p.limbo = p.limbo.slice(0, j); + p.size -= next.size; + p.sort = 0; + b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. // TODO: BUG! spot may need parse too? + //console.timeEnd(); + if(b.split){ b.split(next, p) } +} + +function slot(t){ return heal((t=t||'').substring(1, t.length-1).split(t[0]), t[0]) } B.slot = slot; // TODO: check first=last & pass `s`. +function heal(l, s){ var i, e; + if(0 > (i = l.indexOf(''))){ return l } // ~700M ops/sec on 4KB of Math.random()s, even faster if escape does exist. + if('' == l[0] && 1 == l.length){ return [] } // annoying edge cases! how much does this slow us down? + //if((c=i+2+parseInt(l[i+1])) != c){ return [] } // maybe still faster than below? + if((e=i+2+parseInt((e=l[i+1]).substring(0, e.indexOf('"'))||e)) != e){ return [] } // NaN check in JS is weird. + l[i] = l.slice(i, e).join(s||'|'); // rejoin the escaped value + return l.slice(0,i+1).concat(heal(l.slice(e), s)); // merge left with checked right. +} + +function size(t){ return (t||'').length||1 } // bits/numbers less size? Bug or feature? +function subt(i,j){ return this.word } +//function tot(){ return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'" } +function tot(){ var tmp = {}; + //if((tmp = this.page) && tmp.saving){ delete tmp.book.all[this.word]; } // TODO: BUG! Book can't know about RAD, this was from RAD, so this MIGHT be correct but we need to refactor. Make sure to add tests that will re-trigger this. + return this.text = this.text || ":"+B.encode(this.word)+":"+B.encode(this.is)+":"; + tmp[this.word] = this.is; + return this.text = this.text || B.encode(tmp,'|',':').slice(1,-1); + //return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'"; +} +function sub(i,j){ return (this.first||this.word||B.decode((from(this)||'')[0]||'')).substring(i,j) } +function to(){ return this.text = this.text || text(this) } +function join(){ return this.join('|') } +function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? + if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } + if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". + return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. +} +function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( + var j = 0, i; + while(i = l[j++]){ + if(got(i.word, p)){ + f[got.i] = i; + } else { + f.push(i); + } + } + return sort(f); +} +function sort(l){ //return l.sort(); + return l.sort(function(a,b){ + return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; + }); +} + +B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32); + switch(typeof d){ + case 'string': // text + var i = d.indexOf(s), c = 0; + while(i != -1){ c++; i = d.indexOf(s, i+1) } + return (c?s+c:'')+ '"' + d; + case 'number': return (d < 0)? ''+d : '+'+d; + case 'boolean': return d? '+' : '-'; + case 'object': if(!d){ return ' ' } // TODO: BUG!!! Nested objects don't slot correctly + var l = Object.keys(d).sort(), i = 0, t = s, k, v; + while(k = l[i++]){ t += u+B.encode(k,s,u)+u+B.encode(d[k],s,u)+u+s } + return t; + } +} +B.decode = function(t, s){ s = s || "|"; + if('string' != typeof t){ return } + switch(t){ case ' ': return null; case '-': return false; case '+': return true; } + switch(t[0]){ + case '-': case '+': return parseFloat(t); + case '"': return t.slice(1); + } + return t.slice(t.indexOf('"')+1); +} + +B.hash = function(s, c){ // via SO + if(typeof s !== 'string'){ return } + c = c || 0; // CPU schedule hashing by + if(!s.length){ return c } + for(var i=0,l=s.length,n; i + +
    +
    + + + +
    + + \ No newline at end of file diff --git a/test/rad/mocha.html b/test/rad/mocha.html index 22c7eace7..089830876 100644 --- a/test/rad/mocha.html +++ b/test/rad/mocha.html @@ -17,8 +17,7 @@ - - +