Alters

Proyecto BSearch - Núcleo

¡Buenas!

No penséis que me he olvidado de vosotros... ¡pues no!

Como prometí, vamos a ver el núcleo del buscador

¡Vamos allá!


Para ver cómo funciona el buscador vamos a ir por partes. Es un desarrollo complicado, en cuanto a que hacen falta tener varios conceptos muy claros.

veréis que he dividido el trabajo en "zonas", de las cuales cada una tiene una función específica, y raramente están interconectadas (salvo algunos casos, ya lo veremos).

Intentaré mostraros el progreso que hice tal y como lo hice (ya que antes de enseñarlo me he querido asegurar de que funciona...), es decir, las "zonas" no estarán expuestas en un órden lógico, sino en el órden en que las diseñé.

Antes de ir directos al código, voy a explicar un poco el funcionamiento que le asigné al buscador:
  •  Especificaciones
    •  Puedes buscar por proyecto, tag, título, o cualquier combinación
    •  Puedes usar lógica simple (and, or), siempre binaria
    •  Puedes anidar lógica (paréntesis)
    •  Puedes usar asignaciones (variables). De hecho, si eres un poco "hacker" puedes aprovechar el funcionamiento para crear variables interesantes.
  • Funcionamiento
    • "And" y "or" funcionan lógicamente
    • Las acotaciones (proyecto, tag, título) serán siempre en este órden
    • Los paréntesis funcionarán como tal (primero el más interno)
    • Primero se acotará, después se usará lógica

Entonces, basándonos en esto, la lógica me decía que primero debería resolver el tema de los paréntesis. ¿Porqué? Porque en las matemáticas es lo primero que se resuelve... ¿no? jejeje

La verdad es que tiene bastante sentido: primero quitamos los paréntesis y nos quedarán operaciones simples.

Para ello creé la

ZONA PARÉNTESIS

El algoritmo que diseñé para quitar los paréntesis es el siguiente:

  • Recorres la cadena de caracteres
  • Si es "(", tomas a partir del siguiente caracter
    • Mantenemos una cuenta de "(" y ")", y almacenamos hasta que se ha abierto o cerrado un paréntesis y hasta que la diferencia entre "(" y ")" sea igual o menor que cero (restando una unidad cuando encontramos ")" y añadiendo cuando encontramos "(").
    • Tomamos ese texto almacenado y pasa a ser la entrada, volvemos al principio
  • Si llegamos al final y no hay paréntesis
    • Procedemos a analizar el texto (más adelante)
    • Tomamos el texto y lo sustituímos por una "variable" creada por nosotros
    • Recuperamos el valor anterior (con la sustitución) y volvemos al inicio
  • Cuando todo el texto ha sido procesado así, terminamos
Como veis, hacemos uso de "variables", que podríamos "hackear" para usarlas en nuestro beneficio (luego lo vemos!)

Por otra parte, "recuperar valores" y "guardar valores" para mi tiene un sentido único: PILA. ¿Os acordáis de mi última aventura? Pues ese tipo de pilas. No os debería costar imaginar cómo implementar una sencilla pila... jeje.

Si hay una pila, normalmente hay una función recursiva... así que veremos también recursividad.

Voy a dejaros aquí la implementación del algoritmo, con sus comentarios:

//Variable de pila
var stck = [];

//Contador de pila
var itck = 0;

//Variable auxiliar, contiene el texto a tratar actualmente
var crnt = '';

//Variable para el contador de variable
var iplc = 0;

//Variable para la variable
var tplc = '';


/**
 * Función push
 * Almacena un valor t en la pila
 */
function push(t){
stck[itck] = t;
itck++;
}

/**
 * Función pop
 * Devuelve el siguiente valor de la pila
 * y lo elimina de ella
 */
function pop(){
itck--;
crnt = stck[itck];
stck[itck] = '';
}


/**
 * Función repl
 * Recorre la pila-array y reemplaza el texto t
 * por un valor tplc.
 *
 * La pila se recorre secuencialmente sin usar
 * las funciones "pop" y "push" para ganar
 * velocidad.
 *
 * La función "pack" la veremos más adelante
 */
function repl(t){
tplc = '$_'+iplc+'_';

for(ir = 0;ir < itck;ir++){
stck[ir] = replaceAll(stck[ir], t, tplc);
stck[ir] = replaceAll(stck[ir], '(' + tplc + ')', tplc);
        stck[ir] = replaceAll(stck[ir], '(' + tplc, tplc);
       stck[ir] = replaceAll(stck[ir], tplc + ')', tplc);
}

//pack();

iplc++;
}

/**
 * Función cleanStack
 * Elimina valores repetidos mediante
 * un algoritmo de búsqueda simplificada
 * ligeramente modificado y optimizado
 */
function cleanStack(){
for(ci = 0;ci < itck;ci++){
for(cj = (ci + 1);cj < itck;cj++){
if(stck[ci] == stck[cj]){
stck[ci] = '';
break;
}
}
}
}

/**
 * Función chckp
 * Comprueba que haya el mismo número
 * de paréntesis abiertos que cerrados
 */
function chckp(t){
hl = t.length;
hc = 0;

for(hi = 0;hi < hl;hi++){
if(t.charAt(hi) == '('){
hc++;
}else if(t.charAt(hi) == ')'){
hc--;
}
}

return hc;
}

/**
 * Función scan
 * Busca en t si existe un paréntesis
 * abierto.
 * Retorna la posición si lo encuentra
 * Retorna -1 si no lo encuentra
 */
function scan(t){
sl = t.length;
sr = -1;

for(si = 0;si < sl;si++){
if(t.charAt(si) == '('){
sr = si;
break;
}
}

return sr;
}

/**
 * Función trm
 * Dado el texto t, y la posición ti
 * extrae texto hasta que encuentra
 * un paréntesis y la cuenta de éstos
 * es igual o menor que 0.
 * Retorna el texto extraído
 */
function trm(ti, t){
tl = t.length;
tr = '';
ta = 0;
tp = false;

for(;ti < tl;ti++){
if(t.charAt(ti) == '('){
tp = true;
ta++;
}else if(t.charAt(ti) == ')'){
tp = true;
ta--;
}

if(ta <= 0){
if(tp){
return tr;
}else{
if(t.charAt(ti) != ')'){
tr += t.charAt(ti);
}
}
}else{
tr += t.charAt(ti);
}
}

return tr;
}

/**
 * Función go
 * Ejecuta el algoritmo de sustitución
 * de paréntesis
 *
 * La función kernel la veremos más
 * adelante
 */
function go(t){
crnt = t;

if(itck === 0){
push(crnt);
}

goi = scan(crnt);

if(goi >= 0){
ga = trm((goi + 1), crnt);
push(ga);
go(ga);
}else{
//kernel(crnt);
repl(crnt);
cleanStack();
pop();

while((crnt.substring(0, 2) == '$_' || crnt === '') && itck > 0){
pop();
}

if(stck[0] === '' && crnt == tplc){
return true;
}else{
go(crnt);
}
}  
}


Hasta aquí la zona de los paréntesis. Las funciones comentadas las iremos viendo, con paciencia.

La siguiente parte que desarrollé, por ser más simple, es la parte lógica. Usaremos básicamente dos operaciones: AND (&) y OR (|).

ZONA LÓGICA

Al tratarse de conjuntos (conjuntos de entradas) que pueden ser resumidos en números, la lógica se traduce en operaciones matemáticas: Intersección (AND) y Unión (OR).

Para realizar estas operaciones diseñé un par de algoritmos, tal que:
  •  OR:
    •  Se toma el conjunto B y se acopla a A
    • Se revisa en busca de valores iguales y se eliminan
    • Se compacta el conjunto y se devuelve
  • AND:
    • Se toma el conjunto B y se acopla a A
    • Desde el primer elemento de A hasta el último
      • Si en cualquiera de los elementos siguientes está el elemento que estamos buscando
        • Lo guardamos
    • Retornamos los valores guardados, previa compresión

Como antes, os dejo el código con comentarios:

/**
 * Función cleanR
 * Usando el mismo algoritmo de búsqueda
 * que cleanStack eliminamos valores
 * repetidos, y al finalizar compactamos
 * el array
 */
function cleanR(a){
ai = a.length;

for(ci = 0;ci < ai;ci++){
for(cj = (ci + 1);cj < ai;cj++){
if(a[ci] == a[cj]){
a[ci] = '';
break;
}
}
}

return compact(a);
}

/**
 * Función compact
 * Recorre el array en busca de valores
 * nulos, y los elimina
 *
 * Retorna un array limpio.
 */
function compact(a){
ai = a.length;
r = [];
ri = 0;

for(ci = 0;ci < ai;ci++){
if(a[ci] === '' || a[ci] === null){}
else{
r[ri] = a[ci];
ri++;
}
}

return r;
}

/**
 * Función and
 * Ejecuta una intersección de conjuntos
 */
function and(a, b){
al = a.length;
am = b.length;
ac = join(a, b);
ar = [];
ari = 0;

for(ai = 0;ai < al;ai++){
if(is(ac, ac[ai], al)){
ar[ari] = ac[ai];
ari++;
}
}

return cleanR(ar);
}

/**
 * Función or
 * Ejecuta una unión de conjuntos
 */
function or(a, b){
return cleanR(join(a, b));
}

/**
 * Función join
 * Une los elementos de b
 * en a
 */
function join(a, b){
jl = a.length;
jm = b.length;
 
for(ji = jl;ji < (jm + jl);ji++){
a[ji] = b[(ji - jl)];
}

return a;
}

/**
 * Función is
 * Busca si el elemento ai está en la lista
 * a, a partir de la posición i.
 *
 * Retorna "true" si lo encuentra
 * Retorna "false" si no lo encuentra
 */
function is(a, ai, i){
isl = a.length;

for(;i < isl;i++){
if(a[i] == ai){
return true;
}
}

return false;
}


Se puede apreciar que no es demasiado difícil hacer un "and" o un "or" si se sabe cómo ;)
Es más, incluso para valores de cadena, éstos se podrían pasar a un array de bytes y tratarlos como conjuntos... :^P

Bueno, la siguiente fase que implementé fue la que lógicamente va antes: la de acotar los rangos.


ZONA ACOTAR RANGOS

Para los rangos tenemos que tener en cuenta un par de cosas: primero cuáles serán los delimitadores, y segundo, las posibles combinaciones de acotaciones que se pueden hacer.

Empezamos por lo sencillo:
  • "#" indica proyecto
  • "." indica tag
  • "[" indica título

Ahora un par de "leyes":
  • No puede haber "#" después de "." o "["
  • No puede haber "." después de "["

Esto nos deja un orden de proyecto-tag-título, pudiendo omitirse cualquiera de los eslabones, pero no se puede cambiar el orden
.

La zona de rangos requiere el uso de objetos JS (aunque se podría no usarlos - de hecho son usados casi íntegramente para llevar datos), y un poco de comprensión tipo Entidad - Relación.

Para ello he elaborado el siguiente gráfico



Tras ver cómo están relacionados los elementos, vamos a ver cómo acotar los rangos:
  • Tendremos como entrada un proyecto, un tag y un título
  • Definimos dos bases vacías
  • Si el proyecto es nulo, la primera base serán todos los tag
  • Si el proyecto no es nulo, obtendremos todos los tag que estén en la lista que surja de la búsqueda de proyecto
  • Si el tag es nulo, la segunda base será una lista en la que estén todos los títulos contenidos en la primera base
  • Si el tag no es nulo, obtendremos todos los títulos de la lista que surja de buscar los tags que coinciden con la búsqueda del tag en la primera base
  • Si el título es nulo, obtendremos las entradas de la segunda base
  • Si el título no es nulo, obtendremos todas las entradas de la lista que surja de buscar los títulos que coinciden con la búsqueda del título en la segunda base.
Suena a confusión, lo se... pero es lo mejor que puedo explicarlo.

A continuación os dejo el algoritmo:

//Lista de entradas
var e = [];

//Lista de proyectos
var p = [];

//Lista de tags
var t = [];

//Lista de títulos
var l = [];

//Contador de "e"
var eii = 0;

//Contador de "p"
var pii = 0;

//Contador de "t"
var tii = 0;

//Contador de "l"
var lii = 0;

/**
 * Objeto _ent
 * Representa una entrada.
 * Contiene un id (automático),
 * un título (id), una url y una
 * lista de tags (id)
 */
var _ent = function(eid, eti, eta, eur){
this.id_ent = eid;
this.id_tit = eti;
this.url = eur;
this.tags = eta;

/**
* Función toString
* Se usa para mostrar la información
* del objeto
*/
this.toString = function(){
return 'id_ent: '+this.id_ent+' id_tit: '+this.id_tit+' url: '+this.url+' tags: '+this.tags+'<hr />';
};
};

/**
 * Objeto pro
 * Representa un proyecto.
 * Contiene un id (automático),
 * un nombre, una lista de entradas (id)
 * y una lista de tags (id)
 */
var pro = function(pid, pna, pen, pta){
this.id_pro = pid;
this.name = pna;
this.entries = pen;
this.tags = pta;

/**
* Función getLT
* Retorna una lista de posibles "match"
* para coincidencias
*/
this.getLT = function(){
return this.name.split(' ');
};

/**
* Función toString
* Se usa para mostrar la información
* del objeto
*/
this.toString = function(){
return 'id_pro: '+this.id_pro+' name: '+this.name+' entries: '+this.entries+' tags: '+this.tags+'<hr />';
};
};

/**
 * Objeto _tag
 * Representa un tag.
 * Contiene un id (automático),
 * un nombre, una lista de entradas (id)
 * y una lista de proyectos (id)
 */
var _tag = function(tid, tna, ten, tpr){
this.id_tag = tid;
this.name = tna;
this.entries = ten;
this.projects = tpr;

/**
* Función getLT
* Retorna una lista de posibles "match"
* para coincidencias
*/
this.getLT = function(){
return this.name.split(' ');
};

/**
* Función toString
* Se usa para mostrar la información
* del objeto
*/
this.toString = function(){
return 'id_tag: '+this.id_tag+' name: '+this.name+' entries: '+this.entries+' projects: '+this.projects+'<hr />';
};
};

/**
 * Objeto tit
 * Representa un título.
 * Contiene un id (automático),
 * un nombre, y una lista de
 * entradas (id)
 */
var tit = function(tid, tti, ten){
this.id_tit = tid;
this.title = tti;
this.id_ent = ten;

/**
* Función getLT
* Retorna una lista de posibles "match"
* para coincidencias
*/
this.getLT = function(){
return this.title.split(' ');
};

/**
* Función toString
* Se usa para mostrar la información
* del objeto
*/
this.toString = function(){
return 'id_tit: '+this.id_tit+' title: '+this.title+' id_ent: '+this.id_ent+'<hr />';
};
};

/**
 * Función addE
 * Añade un nuevo _ent
 */
function addE(eti, eta, eur){
e[eii] = new _ent(eii, eti, eta, eur);
eii++;
}

/**
 * Función addP
 * Añade un nuevo pro
 */
function addP(pna, pen, pta){
p[pii] = new pro(pii, pna, pen, pta);
pii++;
}

/**
 * Función addT
 * Añade un nuevo _tag
 */
function addT(tan, ten, tpr){
t[tii] = new _tag(tii, tan, ten, tpr);
tii++;
}


/**
 * Función addL
 * Añade un nuevo tit
 */
function addL(tid, tti, ten){
l[lii] = new tit(lii, tid, tti, ten);
lii++;
}

/**
 * Función getL
 * A partir de arr y el texto t
 * retorna una lista de "matches"
 * entre arr y t
 */
function getL(arr, txt){
gr = [];
gri = 0;
gl = arr.length;
gt = txt.split(' ');
gtl = gt.length;

for(gi = 0;gi < gl;gi++){
ga = arr[gi].getLT();
gal = ga.length;
 
for(gj = 0;gj < gtl;gj++){
for(gk = 0;gk < gal;gk++){
if(gt[gj] == ga[gk]){
gr[gri] = arr[gi];
gri++;
break;
}
}
}
}

return gr;
}

/**
 * Función getTag
 * A partir de la lista (arr)
 * de proyectos, retorna
 * una lista de tags
 */
function getTag(arr){
tl = arr.length;
tr = [];
tri = 0;

for(ti = 0;ti < tl;ti++){
ta = arr[ti].tags;
tal = ta.length;

for(tj = 0;tj < tal;tj++){
tr[tri] = ta[tj];
tri++;
}
}

return tr;
}

/**
 * Función getTit
 * A partir de una lista (arr)
 * de tags, retorna una
 * lista de títulos
 */
function getTit(arr){
tr = [];
tri = 0;
tl = arr.length;

for(ti = 0;ti < tl;ti++){
ta = arr[ti].entries;
tal = ta.length;

for(tj = 0;tj < tal;tj++){
tr[tri] = getTitById(getEntById(ta[tj]).id_tit);
tri++;
}

}

return tr;
}

/**
 * Función getEnt
 * A partir de una lista (arr)
 * de títulos, retorna
 * una lista de entradas
 */
function getEnt(arr){
el = arr.length;
er = [];

for(ei = 0;ei < el;ei++){
er[ei] = arr[ei].id_ent;
}

return er;
}

/**
 * Función getEntById
 * Dado un id retorna
 * una entrada
 */
function getEntById(id){
for(gi = 0;gi < eii;gi++){
if(e[gi].id_ent == id){
return e[gi];
}
}

return new _ent(null, null, [], null);
}

/**
 * Función getTagById
 * Dado un id retorna
 * un tag
 */
function getTagById(id){
for(gi = 0;gi < tii;gi++){
if(t[gi].id_tag == id){
return t[gi];
}
}

return new _tag(null, null, [], []);
}

/**
 * Función getEntById
 * Dado un id retorna
 * un título
 */
function getTitById(id){
for(gi = 0;gi < lii;gi++){
if(l[gi].id_tit == id){
return l[gi];
}
}


return new tit(null, null, null);
}

/**
 * Función get
 * Dado un proyecto,
 * un tag y un título
 * retorna una lista de
 * entradas (id) que hacen
 * "match" con las especificaciones
 */
function get(pr, ta, ti){
gb1 = [];
gb2 = [];

if(pr === null){
gb1 = t;
}else{
glt = cleanR(getTag(getL(p, pr)));
gltl = glt.length;

for(geti = 0;geti < gltl;geti++){
gb1[geti] = getTagById(glt[geti]);
}
}


if(ta === null){
gb2 = cleanR(getTit(gb1));
}else{
gb2 = cleanR(getTit(getL(gb1, ta)));
}

if(ti === null){
return cleanR(getEnt(gb2));
}else{
return cleanR(getEnt(getL(gb2, ti)));
}
}


Ahora creía que ya lo tenía todo listo, que faltaba muy poquito PERO... me dejaba una cosa en el tintero...
Esta parte será ejecutada desde "kernel", por eso nunca nos preocupamos de los paréntesis ;-)


ZONA DE ASIGNACIÓN

Esta parte no la pensé bien en un principio, y tuve que tocar bastante el esquema mental que tenía... al final lo dejé como un "aparte" que se ejecuta antes, y aunque resta un poco de eficiencia, es la única manera que se me ocurrió.

El algoritmo que diseñé hace lo siguiente:
  • Dado un texto, lo recorre en busca de "="
  • Si encuentra uno
    • Buscamos el valor de la variable
    • Buscamos el nombre de la variable
    • Eliminamos la asignación
    • Sustituimos los usos por el valor

Se ve sencillo pero tiene un par de handycaps. Primero: ¿Cómo sabemos dónde empieza una variable? Sabemos dónde acaba (en el "="), pero el inicio... Segundo: Hay veces que estará entre paréntesis la asignación, y hay veces que no...

Para la primera parte es sencillo: las variables deben empezar por "$". La segunda parte se debe controlar con algún tipo de "switch". Lo más sencillo es ver el código:

/**
 * Función tvar
 * Dado un texto t, lo recorre
 * y sustituye las variables
 * establecidas por sus valores
 */
function tvar(t){
tl = t.length;
tp = 0;

for(ti = 0;ti < tl;ti++){
if(t.charAt(ti) == "="){
if(t.charAt(ti + 1) == "("){
tp = 1;
}

t = sust(t, ti, tp);
ti = 0;
}
}

return t;
}

/**
 * Función sust
 * Busca en el texto t la variable
 * y la asignación y los susittuye
 */
function sust(t, si, p){
sp1 = gvar(t, (si - 1));
sp2 = gasi(t, (si + 1), p);
t = replaceAll(t, sp1 + "=", "");
t = replaceAll(t, sp1, sp2);

return t;
}

/**
 * Función gvar
 * A partir de una búsqueda inversa
 * en t, busca la parte de la variable
 */
function gvar(t, vi){
vr = '';

for(;vi >= 0; vi--){
vr = t.charAt(vi) + vr;

if(t.charAt(vi) == "$"){
return vr;
}
}

return '';
}

/**
 * Función gasi
 * A partir de una búsqueda en t
 * busca la parte de la asignación
 */
function gasi(t, ai, p){
al = t.length;
aa = '';

if(p == 1){
ac = 0;
ae = false;

for(;ai < al;ai++){
if(t.charAt(ai) == "("){
ae = true;
ac++;
}else if(t.charAt(ai) == ")"){
ae = true;
ac--;
}

       aa += t.charAt(ai);

if(ac <= 0 && ae){
return aa;
}
}
}else{
for(;ai < al;ai++){
if(t.charAt(ai) == "(" || t.charAt(ai) == ")"){
return aa;
}else{
aa += t.charAt(ai);
}
}
   
       return aa;
}

return '';
}


En este punto está todo "listo", pero falta un tecnicismo. A medida que sustituimos en la zona de los paréntesis, necesitaremos saber qué conjunto simbolizan...

Es decir, imaginad que tenemos #proj1.tag1[title1]|[title2], y se sustituye por $_0_. Esta variable debe apuntar al conjunto (0, 1) que representa la entrada 1 y la entrada 2. ¿Cómo hacerlo?


ZONA DE ALMACENAR

Para ello diseñé la zona de almacenar. Hubo mucho lío en esta zona y cambié varias veces los algoritmos... veréis que hay un par de variables que "surgen de la nada". Son de otra zona (ya la veremos).

Para almacenar usaremos el siempre repudiado (y que personalmente, me encanta) "eval". Podemos crear variables "On-The-Fly" con eval, y sin despeinarnos.

Particularmente, guardaremos las cadenas de texto que sustituimos, y los conjuntos que representan. Vamos a ver cómo:

/**
 * Función pack
 * Guarda los valores de crnt y kcur
 */
function pack(){
eval(tplc + ' = "' + crnt + '"');
eval(tplc + '_val = [' + kcur + ']');

kcur = '';
}


¿Os suena "tplc"?, ¿Os suena "pack"? Aquí está... es sencillo su uso ;-)

Ahora solo falta el detonante, y algo que a la vez orqueste todo...


ZONA KERNEL

Esta zona es todo, digamos "High Level". Solo incluye llamadas a otras funciones, y lleva alguna que otra función puente de llamada a otras funciones. Vamos a ver:

//Variable de texto actual
var kcur = '';

/**
 * Función start
 * Función "detonante", la que
 * se llama desde el HTML
 */
function start(t){
go(tvar(t));
}

/**
 * Función kernel
 * Con el texto dado ejecuta la lógica
 * de conjuntos
 */
function kernel(t){
kl = t.length;
ka = '';

for(ki = 0;ki < kl;ki++){
if(t.charAt(ki) == '&' || t.charAt(ki) == '|'){
ntp(ka);

ka = '';
}

ka += t.charAt(ki);
}

ntp(ka);
}

/**
 * Función ntp
 * Sobre kcur efectúa operaciones de
 * conjuntos
 */
function ntp(t){
if(kcur === ''){
kcur = compact(cmp(t));
}else{
kcur = compact(opr(t, cmp(t.substring(1))));
}
}

/**
 * Función cmp
 * A partir del texto dado, crea
 * una llamada a "get", para acotar
 * el rango de entradas
 */
function cmp(t){
if(t.substring(0, 2) == '$_' || t.substring(1, 2) == '$_'){
return eval(t + '_val;');
}

cp1 = null;
cp2 = null;
cp3 = null;
ca = '';
cn = '';
cl = t.length;

for(ci = 0;ci < cl;ci++){
ct = t.charAt(ci);

if(ct == '#' || ct == '.' || ct == '['){
if(ca !== ''){
if(cn == '#'){
cp1 = ca;
}else if(cn == '.'){
cp2 = ca;
}else if(cn == '['){
cp3 = ca;
}
}

ca = '';
cn = ct;
}else{
if(ct === '&' || ct === '|'){
return get(cp1, cp2, cp3);
}else{
                if(ct !== ']'){
 ca += ct;
                }
}
}
}

if(ca !== ''){
if(cn == '#'){
cp1 = ca;
}else if(cn == '.'){
cp2 = ca;
}else if(cn == '['){
cp3 = ca;
}
}

return get(cp1, cp2, cp3);
}

/**
 * Función opr
 * Efectúa una llamada a "and"
 * o a "or"
 */
function opr(t, c){
if(t.charAt(0) == '&'){
return and(kcur, c);
}else if(t.charAt(0) == '|'){
return or(kcur, c);
}

return null;
}


La función "detonante" será "start", y tras sucesivas llamadas a "kernel" tendremos nuestro conjunto final de entradas.

Faltaría, no obstante, algo que cargase los objetos, y algo que pasase de simples número (id_ent) a listas de entradas. Esto os lo dejo a vuestra imaginación (ya que a día de hoy aún no está esta parte), y como final os dejo algunas funciones "misc":


ZONA MISC

/**
 * Función replaceAll
 * Hace un reemplazo global
 */
function replaceAll(t, b, r){while(t.indexOf(b) != -1){t = t.replace(b, r);}return t;}

/**
 * Función g
 * Sinónimo de document.getElementById
 */
function g(id){return document.getElementById(id);}

/**
 * Función _add
 * Añade texto de debug
 */
function _add(t){g('t').innerHTML += t+'<hr />';}

Y bueno gente, aquí acaba la entrada número 99. Os espero en el especial 100 / 2 años (que aún no se qué tendrá, pero tengo una idea).

Saludos y como siempre,

¡Hasta la próxima!