{
uPreProces
Modificado por Tito Hinostroza 03/11/2014
Unidad que define la sintaxis del PreSQL 2.0. Adaptado de la versión 1.3 de VB.
Se define esta unidad para implementar el procesamiento de un texto o archivo.
No se incluyen mensajes de tipo writeln(), o ShowMessage(), para evitar hacer a
la unidad dependiente del tipo de aplicación.
}
unit uPreProces; {$mode objfpc}{$H+}

interface
uses
  Classes, SysUtils, Fgl, FileUtil, DateUtils, uPreBasicos;

procedure PreProcesar(arc: String; txt: String; var cadcon0: string);

implementation

//---------Manejo de definiciones------------
Const MAX_DEFINICIONES = 500;
Const MAX_INCLUSIONES = 50;

//Tipos de definiciones
Const TD_CON = 0;    //Definición de contexto
Const TD_DIR = 1;    //Definición directaD

Type

{ Tdefinicion }

Tdefinicion  = class   //estructura de definicion
   //Notar que esta clase accede a los objetos globales PProc y PErr
   nom: String;       //nombre de la definicion
   tip: Integer;      //Tipo de definición
   con: TPosCont;     //Ubicación donde está el texto de la definicion
   txt: String;       //Texto de la definición (Solo las de Tipo directo)
   procedure Expandir;  //espande la definición y escribe en la salida
   function ValTxt: string;  //devuelve la defición expandida
   procedure FijTxt(t: string); //fija un valor de texto a la definición
   procedure LeeElementos(elem: TStringList);  //lee definición como lista
End;

Tinclusion = class
   arc: string;  //archivo incluido
   pad: string;  //archivo padre
end;

//Define una lista de definiciones
TListaDef = specialize TFPGObjectList<Tdefinicion>;
TListaInc = specialize TFPGObjectList<Tinclusion>;
//----------Manejo de inclusiones-------------

var
  cad_con: string;    //variable interna para cadena de conexión.
  definiciones: TListaDef;   //Lista de definiciones
  inclusiones: TListaInc;  //Lista de inclusiones
  arcEnt : string;    //archivo de entrada

procedure decodificar_PARA; forward;
procedure preProcesarCad(txt: String; archivo: String); forward;
procedure preProcesarArc(archivo: String); forward;
procedure decodificar_INCLUIR(IncluirMult: Boolean = False); forward;
Function CogExpresion(jerar: Integer): Texpre; forward;
Function definido(ident: String): Tdefinicion; forward;
Function preProcFuncion(identif: String): Boolean; forward;
/////////////////////// Funciones para evaluación de expresiones ///////////////////////
Function CogOperando: Texpre;
//Coge un operando en la posición actual del contenido
var c : String;
    cad : String;
    num : single;
    exp : Texpre;
    def : Tdefinicion;
begin
    PPro.CapBlancos;   //quita blancos iniciales
    c := UpCase(PPro.VerCar);

    {If EsFuncion() Then   //Sólo funciones predefinidas
        CogOperando := CogFuncion;
    Else} If PPro.CogNumero(num) then begin
       CogOperando.txt := '#';   //indica número
       CogOperando.valNum := num;   //fija tipo a número
       CogOperando.cat := COP_CONST;
    end Else If PPro.CogCadena(cad) Then begin   //Constante cadena
       CogOperando.txt := '?';   //indica cadena
       CogOperando.valTxt := cad;    //fija tipo a número
       CogOperando.cat := COP_CONST;
    end Else If c = '(' Then begin
       PPro.CogCar;         //coge paréntesis
       exp := CogExpresion(0);
       if PErr.HayError then exit;
       CogOperando := exp;
       If PPro.Capturar(')') Then begin
           Exit;
       end Else begin
          PErr.GenError('Error en expresión. Se esperaba ")"', PPro.PosAct);
          Exit;       //error
       end;
    end else If PPro.CogIdentificador(cad) Then begin
        //puede ser definición o identificador desconocido
        def := definido(cad);
        if def = nil then begin
           PErr.GenError('Identificador desconocido: ' + cad, PPro.PosAct);
           Exit;
        end;
        //es definición, se considera como una variable
        CogOperando.txt := 'd';  //indica definición
        CogOperando.valTxt := def.ValTxt;  //expande directamente
//        CogOperando.def := def;   //guarda la referencia a la definición
        CogOperando.cat := COP_DEFINIC;
    end  Else
        Exit;  //no devuelve nada
End;
Function CogExpresion(jerar: Integer): Texpre;
//Toma una expresión completa, en la posición actual del contenido
var Op1, Op2 : Texpre;
    opr, opr2 : String;
    jerOpr, jerOpr2: Integer;
    pos1, pos2 : TPosCont;
begin
    PPro.CapBlancos;  //quita blancos iniciales
    Op1 := CogOperando;  //error
    If Op1.txt = '' Then
        Exit;
    opr := PPro.cogOperador;
    If opr = '' Then begin
        CogExpresion := Op1;
        Exit
    End;
    jerOpr := PPro.jerOp(opr);     //Hay operador, tomar su jerarquía
    //-------------------------- ¿Delimitada por jerarquía? ---------------------
    If jerOpr <= jerar Then begin  //es menor que la que sigue, expres.
        CogExpresion := Op1;  //solo devuelve el único operando que leyó
        Exit;
    End;
    While opr <> '' do begin
        pos1 := PPro.PosAct;    //Guarda por si lo necesita
        Op2 := CogOperando;
        If Op2.txt = '' Then   begin //error
           PErr.GenError('Error en expresión. Se esperaba operando.', PPro.PosAct);
           Exit;
        end;
        pos2 := PPro.PosAct;    //Guarda por si lo necesita
        opr2 := PPro.cogOperador;
        If opr2 <> '' Then begin  //Hay otro operador
            jerOpr2 := PPro.jerOp(opr2);
            //¿Delimitado por jerarquía de operador?
            If jerOpr2 <= jerar Then begin  //sigue uno de menor jerarquía, hay que salir
                PPro.PosAct := pos2;   //antes de coger el operador
                CogExpresion := PPro.Evaluar(Op1, opr, Op2);
                Exit;
            End;
            If jerOpr2 > jerOpr Then begin    //y es de mayor jerarquía, retrocede
                PPro.PosAct := pos1;        //retrocede
                Op2 := CogExpresion(jerOpr);        //evalua primero
                opr2 := PPro.cogOperador;    //actualiza el siguiente operador
            End;
        End;

        Op1 := PPro.Evaluar(Op1, opr, Op2);    //evalua resultado
        if PErr.HayError then exit;
        opr := opr2;
        jerOpr := PPro.jerOp(opr);    //actualiza operador anterior
    end;
    CogExpresion := Op1;
    CogExpresion.cat := COP_EXPRESION;
End;

Function incluido(arc0: String): Tinclusion;
//Devuelve la referencia a una inclusión. Si no esta incluido devuelve NIL.
var s: Tinclusion;
begin
   Result := nil;   //valor por defecto
   For s in inclusiones do
      If UpCase(arc0) = s.arc Then  begin  //encontro
         Result := s;
         Exit;
      End;
End;
Function definido(ident: String): Tdefinicion;
//devuelve la referencia a una definición. Si no esta definido devuelve NIL.
var s: Tdefinicion;
begin
   Result := nil;   //valor por defecto
   For s in definiciones do
      If UpCase(ident) = s.nom Then  begin  //encontró
         Result := s;
         Exit;
      End;
End;

//************************************************************************************
//**************************** MANEJO DE DEFINICIONES ********************************
Function CreaDefinicionCon(nom: String; def: Tdefinicion = nil): Tdefinicion;
//Ceea la nueva definición de contexto. El contenido debe leerse luego.
//Si se especifica el índice se sobreescriben los datos.
//Devuelve la definición creada.
begin
    If def = nil Then begin   //Se debe crear nueva definición
        If definiciones.Count >= MAX_DEFINICIONES Then begin
            PErr.GenError('Demasiadas definiciones.', PPro.PosAct);
            Exit;
        End;
        def := Tdefinicion.Create;   //crea una nueva
        definiciones.Add(def);   //la agrega a la lista
    End;
    //Se actualizan los datos
    def.nom := UpCase(nom);   //guarda mayuscula
    def.tip := TD_CON    ;   //de tipo contexto
    def.txt := '';
    //Se guarda la posición donde empieza el cuerpo, tiene que ser después de la palabra COMO
    def.con := PPro.PosAct;
    Result := def;
End;
Function CreaDefinicionDir(nom: String; txt: String; def: Tdefinicion = nil): Tdefinicion;
{Crea una definición directa. Si se especifica  "def", se sobreescriben los datos
 de la definición. Si no se especifica se genera una definición nueva.
 Devuelve la definición directa creada.}
begin
    If def = nil Then begin   //Se debe crear nueva definición
        If definiciones.Count >= MAX_DEFINICIONES Then begin
            PErr.GenError('Demasiadas definiciones.', PPro.PosAct);
            Exit;
        End;
        def := Tdefinicion.Create;  //crea una nueva
        definiciones.Add(def);     //la agrega a la lista
    End;
    //Se actualizan los datos
    def.nom := UpCase(nom);   //guarda mayuscula
    def.tip := TD_DIR ;       //de tipo directo
    def.txt := txt;
    //Se guarda la posición donde empieza el cuerpo, tiene que ser después de la palabra IGUAL
    def.con := PPro.PosAct;
    Result := def;
End;
procedure EliminaDefinicion(ind: Tdefinicion);
//Elimina una definición de la memoria. Se le debe proporcionar la referencia.
begin
    If ind = nil Then Exit;
    definiciones.Remove(ind);
End;

procedure ProcCaracter(escribir: boolean = true);
//Procesa un caracter del contexto de entrada.
//Devuelve el caracter procesado.
var uc: char;
    com: string[2];
begin
    com := PPro.VerCarN(2);
    If com = '--' Then begin  //es comentario de línea
       PPro.CogerHastaFinLinea;
       if escribir then PPro.EscribeSalto;     //Escribe el salto quitado
    end Else if com = '/*' then begin //comentario de bloque
       PPro.CogerComent;   //toma todo el comentario
    end Else begin
       uc := PPro.CogCar;   //último caracter
       if escribir then PPro.PonCar(uc);   //escribe siguiente caracter, si no hay error
    End;
End;

function ExpandirDefinido(nom: string): boolean;
{Verifica si el identificador es una definición y de ser así lo expande y devuelve
 TRUE. De otra forma devuelve FALSE.}
var def: Tdefinicion;   //numero de definicion
begin
    def := definido(nom);
    If def = nil Then begin
       Result := false;  //no lo encuentra, devuelve falso
    end else begin  //lo expande
       def.Expandir;   //si hay errores se devuelven
       Result := true;
    End;
End;
procedure BuscaFINDEFINIR(posDef: TPosCont; nomDef: string);
{Busca el delimitador FINDEFINIR en el contexto actual. Termina al encontrar el
 delimitador, o al encontrar algún error}
var
   idenM: String;
begin
   nomDef := UpCase(nomDef);   //pasa a mayúscula
   //busca final de bloque $DEFINIR o $REDEF, sin escribir en salida.
   While Not PPro.FinCont do begin
      If PPro.CogIdentificador(idenM) Then begin
         If idenM = nomDef Then begin    //verificación de llamada a la definiicón
            PErr.GenError('Llamada recursiva a "' + nomDef + '" en definición.', PPro.PosAct);
            Exit;
         End else If idenM = 'FINDEFINIR' Then begin
            PPro.CogIdentificador;   //coge el "FINDEFINIR"
            PPro.CapBlancos;      //quita blancos hasta siguiente identificador
            exit;
         end else If idenM = '$DEFINIR' Then begin     //encontró otro definir
            PErr.GenError('Se esperaba "FINDEFINIR" de definición. ', posDef);
            Exit;
         end else If idenM = '$REDEF' Then begin     //encontró otro definir
            PErr.GenError('Se esperaba "FINDEFINIR" de definición. ', posDef);
            Exit;
         End;
         //hay identificador pero no es reconocido. Lo ignora.
      end Else begin
         ProcCaracter(false);   //va leyendo sin escribir
      end;
   end;
   //se ha llegado al fin de archivo, sin encontrar delimitador
   PErr.GenError('Inesperado fin de archivo. No se encontro "FINDEFINIR" de definición. ', posDef);
end;
procedure decodificar_DEFINIR;
//Decodifica la instruccion      $DEFINIR var COMO <bloque> FINDEFINIR
var defi : String;
    temp : String;
    posDef: TPosCont;         //posición temporal del contexto
begin
    PErr.IniError;
    PPro.CapBlancos;   //coge espacios después de "$DEFINIR"
    posDef := PPro.PosAct;  //guarda posición
    defi := PPro.CogIdentificador;   //coge nombre de la variable
    If defi = '' Then begin
      PErr.GenError('Se esperaba identificador después de $DEFINIR', PPro.PosAct);
      Exit;
    end;
    If definido(defi) <> nil Then begin    //ya esta definida esa variable
        PErr.GenError('Ya esta definido el identificador ' + defi, PPro.PosAct);
        Exit;
    End;
    PPro.CapBlancos;   //quita blancos iniciales
    If PPro.VerCar = '=' Then  begin //Definición directa
        PPro.CogCar;   //coge el "="
//        If UpCase(VerIdentificador) = '$CONSULTAR' Then begin
//            temp := decodificar_CONSULTAR(True);
//            If HayError Then Exit;
//            CreaDefinicionDir(UpCase(defi), temp);
//        end Else begin   //Definición directa normal
            temp := PPro.CogerHastaComent;
            CreaDefinicionDir(defi, temp);
//        End;
        Exit;
    end Else If PPro.VerIdentifM = 'COMO' Then
        PPro.CogIdentificador
    Else begin
        PErr.GenError('Se esperaba "COMO" o "=" después de ' + defi, PPro.PosAct);
        Exit;
    End;
    //Crea la nueva definición y empieza lectura de contenido
    CreaDefinicionCon(defi);
    If PErr.HayError Then Exit;
    BuscaFINDEFINIR(posDef, defi);   //Actualiza Error
End;
procedure decodificar_REDEF;
//Decodifica la instruccion      $REDEF var COMO <bloque> FINDEFINIR
var defi : String; def: Tdefinicion;
    temp : String;
    posDef: TPosCont;         //posición temporal del contexto
begin
   PErr.IniError;
   PPro.CapBlancos;   //coge espacios después de "$DEFINIR"
   posDef := PPro.PosAct;  //guarda posición
   defi := PPro.CogIdentificador;   //coge nombre de la variable
    If defi = '' Then begin
      PErr.GenError('Se esperaba identificador después de $REDEF', PPro.PosAct);
      Exit;
    end;
    def := definido(defi);  //toma definición
    PPro.CapBlancos;     //quita blancos iniciales
    If PPro.VerCar = '=' Then  begin //Definición directa
        PPro.CogCar;   //coge el "="{ TODO : No se guarda información del archivo donde se lee esta definición  }
//        If UpCase(VerIdentificador) = '$CONSULTAR' Then begin
//            temp := decodificar_CONSULTAR(True);
//            If HayError Then Exit;
//            CreaDefinicionDir(UpCase(defi), temp);
//        end Else begin   //Definición directa normal
            temp := PPro.CogerHastaComent;
            CreaDefinicionDir(defi, temp, def);
//        End;
        Exit;
    end Else If PPro.VerIdentifM = 'COMO' Then
        PPro.CogIdentificador
    Else begin
        PErr.GenError('Se esperaba "COMO" o "=" después de ' + defi, PPro.PosAct);
        Exit;
    End;
    //Crea o actualiza la definición y empieza lectura de contenido
    CreaDefinicionCon(defi, def);
    If PErr.HayError Then Exit;
    BuscaFINDEFINIR(posDef, defi);   //Actualiza Error
End;
function procesarHastaDelim(delims: String): string;
{Procesa el contexto actual hasta encontrar uno de los delimitadores, el fin del contexto
 o se genere algún error. Si termina por encontrar alguno de los delimitadores, coge el
 identificador y devuelve el delimitador encontrado (en mayúscula). La lista de
 delimitadores se debe dar separada por comas.  La comparación con los identificadores
 se hace ignorando la caja.}
var l_delims : TstringList;
    iden, idenM: String;
    uc : char;  //último caracter
begin
    Result := '';  //inicia
    l_delims := TStringList.Create;  //crea lista
    //convierete lista de cadena en TSTringList
    l_delims.Delimiter:=',';
    l_delims.DelimitedText:=delims;
    //explora el contexto
    While Not PPRo.FinCont And Not PErr.HayError do begin
        If PPro.CogIdentificador(iden, idenM, uc) Then begin
           //-------------aqui se procesa el comando identificador
           If idenM = '$INCLUIR' Then  //palabra reservada
              decodificar_INCLUIR
//           Else If idenM = '$CONSULTAR' Then  //palabra reservada
//              decodificar_CONSULTAR
           Else If idenM = '$PARA' Then  //palabra reservada
              decodificar_PARA
           Else If preProcFuncion(idenM) Then   //preprocesa función
               //no hace nada porque ya se hizo
           else if ExpandirDefinido(idenM) then
              //no hace nada porque ya lo expandió
           else If l_delims.IndexOf(idenM) <> -1 Then begin  //busca delimitador
              If uc = ' ' Then PPro.SacCar; //si último caracter fue espacio, retrocede.
              Result:= idenM; break  end    //encontro delimitador, sale.
           else //hay identificador pero no es reconocido
              PPro.Escribe(iden);    //escribe identificador
        End Else    //no es inicio de identificador
           ProcCaracter;
    end;
    //aquí puede haber llegado por error, por fin de contexto o por haber encontrado
    //algún delimitador.
    l_delims.Free;
End;

function procesarCuerpoPARA(vari: String; con: String;
           lPar: TStringList; lCon: TStringList; n1, n2: Integer; delims: string): string;
{Procesa el cuerpo del $PARA para el caso "PRIMERO ... " O "HACER ...".
 Lee el lazao iterando en lPar y lCon, desde n1 hasta n2 hasta encontrar algúno de los
 delimitadores indicados. Devuelve el demimitador encontrado si no hubo error.}
var i: Integer;
    posinic: TPosCont;
    npa, nco: Tdefinicion;   //para manejar el reemplazo
begin
    posinic := PPro.PosAct;       //Guarda posición
    npa := nil; nco := nil;  //inicia índices
    For i := n1 To n2 do begin
        PPro.PosAct := posinic;   //Mueve al inicio
        //crea las variables para esta vuelta del lazo
        npa := CreaDefinicionDir(vari, lPar[i], npa);
        If lCon.Count > 0 Then nco := CreaDefinicionDir(con, lCon[i], nco);
        Result := procesarHastaDelim(delims);  //Guarda delimitador
        if Result <> '' then continue; //encontró el final del bloque, pasa al siguiente
        If PErr.HayError Then break;    //Hubo error, salir
        If PPro.FinCont Then begin     //Se ha llegado al fin de archivo.
            PErr.GenError('Inesperado fin de archivo. No se encontró "FINPARA" del "PARA"', posinic);
            break;
        End;
    end;
    //aquí llega por error o por haber encontrado al delimitador
    EliminaDefinicion(npa); //limpia la memoria
    EliminaDefinicion(nco); //limpia la memoria
End;

procedure decodificar_PARA;
{Decodifica la instruccion      PARA var EN var1,var2,...varn [CON cond EN cond1,cond2,...condN] HACER
                                   <bloque>
                               FINPARA}
var vari : String;       //variable "vari"
    con : String ;       // variable "con"
    lPar : TStringList;   //lista de ítems de para
    lCon : TStringList;   //lista de ítems de con
    tmp : String;

  procedure VerificSiDefLista(var lst : TStringList);
  //Verifica si la lista de elementos es un lista definición, y si lo es, la expande.
  var def: Tdefinicion;
  begin
     If (lst.Count = 1) then begin   //un solo elemento
        //puede ser definición lista
        def := definido(lst[0]);  //la busca
        if def <> nil Then begin    //es una definición lista
           lst.Clear;   //la limpia
           def.LeeElementos(lst);
           If PErr.HayError Then Exit;
           If lst.Count = 0 Then begin      //esta vacía
              PErr.GenError('La lista del $PARA no contiene elementos.', PPro.PosAct);
              Exit;
           end;
        End;
     End;
  End;

begin
  lPar := TStringList.Create;
  lCon := TStringList.Create;
  try
    PErr.IniError;
    PPro.CapBlancos;  //quita blancos después de $PARA
    //---------------------- coge la lista del "EN" -------------------------------
    vari := PPro.CogIdentificador;     //coge nombre de la variable
    If vari = '' Then begin
        PErr.GenError('Se esperaba variable después del PARA', PPro.PosAct);
        Exit;
    End;
    PPro.CapBlancos;  //quita blancos después de $PARA
    If UpCase(PPro.CogIdentificador()) <> 'EN' Then begin
        Perr.GenError('Se esperaba palabra "EN" después de ' + vari, PPro.PosAct);
        Exit;
    End;
    tmp := PPro.CogerLista(lPar,'HACER,CON,PRIMERO');  //toma lista de elementos EN ...
    If lPar.Count = 0 Then begin
        PErr.GenError('Se esperaba lista de elementos después del "EN"', PPro.PosAct);
        Exit;
    End;
    If PPro.FinCont Then begin
        PErr.GenError('Inesperado fin de archivo. Se esperaba fin de sentencia PARA..EN..HACER.', PPro.PosAct);
        Exit;
    End;
    VerificSiDefLista(lPar);
    If PErr.HayError Then Exit;
    //-------------------------- coge secuencia CON ----------------------------------------
    //CON cond EN cond1,cond2,...condN,  si es que existe
    If tmp = 'CON' Then begin
        PPro.CapBlancos;          //quita blancos
        con := PPro.CogIdentificador;     //coge nombre de la variable cond
        If con = '' Then begin
            PErr.GenError('Se esperaba variable después del CON', PPro.PosAct);
            Exit;
        End;
        PPro.CapBlancos;          //quita blancos
        If UpCase(PPro.CogIdentificador) <> 'EN' Then begin
            PErr.GenError('Se esperaba palabra "EN" después de ' + con, PPro.PosAct);
            Exit;
        End;
        tmp := PPro.CogerLista(lCon,'HACER,PRIMERO');  //toma lista de elementos EN ...
        If lCon.Count = 0 Then begin
            PErr.GenError('Se esperaba lista de elementos después de "CON ... EN"', PPro.PosAct);
            Exit;
        End;
        If PPro.FinCont Then begin
            PErr.GenError('Inesperado fin de archivo. Se esperaba fin de sentencia PARA..EN..CON..EN..HACER.', PPro.PosAct);
            Exit;
        End;
        VerificSiDefLista(lCon);
        If PErr.HayError Then Exit;
        If lCon.Count < lPar.Count Then begin
            PErr.GenError('Se esperaban ' + IntToStr(lPar.count) + ' variables en sentencia ..CON..EN..HACER.', PPro.PosAct);
            Exit;
        End;
    End;
    //-------------------------------continua con el cuerpo del HACER---------------------------------
    If tmp = 'HACER' Then begin   //tmp debe tener el último delimitador
        //Expande lazo
        procesarCuerpoPARA(vari, con, lPar, lCon, 0, lPar.Count-1,'FINPARA');
        //Puede terminar con Error
        PPro.CapBlancos;  //quita blancos hasta siguiente identificador
    end Else If tmp = 'PRIMERO' Then begin //sentencia con "PRIMERO"
        //Expande lazo
        tmp := procesarCuerpoPARA(vari, con, lPar, lCon, 0, 0,'FINPARA,HACER');
        If PErr.HayError Then Exit;   //sale
        If tmp = 'HACER' Then begin  //terminó con HACER
            //procesa sin considerar el primer elemento
            procesarCuerpoPARA(vari, con, lPar, lCon, 1, lPar.Count-1,'FINPARA');
            If PErr.HayError Then Exit;
        End;
        //se supone que terminó con FINPARA
        PPro.CapBlancos;  //quita blancos hasta siguiente identificador
    end Else begin
        PErr.GenError('Se esperaba palabra reservada "HACER"', PPro.PosAct);
        Exit;
    End;
  finally
    lPar.Free; lCon.Free;  //libera listas
  end;
End;
procedure decodificar_INCLUIR(IncluirMult: Boolean = False);
{Decodifica la instruccion      INCLUIR <archivo>
 que incluye un archivo dentro del archivo principal
 "IncluirMult" indica que se puede incluir múltiples veces el mismo
 archivo.}
var arch : String;
    inc : Tinclusion;
begin
    PErr.IniError;
    arch := PPRo.coger_ruta;
    arch := Trim(arch);
//    PPro.quitar_comentario(arch);    //quita si hay comentarios "--"
    If arch = '' Then begin
        PErr.GenError('Se esperaba nombre de archivo en INCLUIR', PPRo.PosAct);
        Exit;
    End;
    If Pos('\', arch) = 0 Then begin    //si no se especifica camino
        //le agrega camino de archivo de entrada
        If Pos('\', PPro.PosAct.arc) <> 0 Then  //Si es que tiene ruta
            arch := ExtractFilePath(PPro.PosAct.arc) + arch;
    End;
    //se verifica si ya se ha incluido el archivo
    If (incluido(arch)<>nil) And Not IncluirMult Then begin
        //Ya esta incluido el archivo
        Exit; //sale, no lo vuelve a incluir
    End;
    //Verifica si existe
    If not FIleExists(arch) Then begin
        PErr.GenError('No Existe Archivo a INCLUIR: ' + arch, PPRo.PosAct);
        Exit;
    End;
    //Finalmente lo incluye
    If inclusiones.Count > MAX_INCLUSIONES Then begin
        PErr.GenError('Demasiados Archivos Incluidos.', PPRo.PosAct);
        Exit;
    End;
    inc := Tinclusion.Create;
    inc.arc := UpCase(arch);     //guarda nombre mayuscula
    inc.pad := PPro.PosAct.arc;  //guarda mnombre de padre
    inclusiones.Add(inc);        //lo agrega

    preProcesarArc(arch);        //preprocesa
    //se ha llegado al fin de archivo incluido, puede haber habido Error.
End;

procedure decodificar_FECHA_ACTUAL;
//Decodifica la función fecha_actual(desplazamiento, formato)
var desplaz : single;       //desplazamiento de fecha
    formato, temp : String;
    nsem : byte;
    e : Texpre;
begin
    If PPro.Capturar('(') = False Then begin
        PErr.GenError('Se esperaba "(" después de la función $FECHA_ACTUAL()', PPro.PosAct);
        Exit;
    End;
    e := CogExpresion(0);
    If PErr.HayError Then Exit;

    desplaz := e.valNum;
    If PPro.Capturar(',') = False Then begin
        PErr.GenError('Se esperaba ","', PPro.PosAct);
        Exit;
    End;
    formato := PPRo.CogCadena;
    If PErr.HayError Then Exit;
    If PPro.Capturar(')') = False Then begin
        PErr.GenError('Se esperaba ")"', PPro.PosAct);
        Exit;
    End;
    //verifica si hay "ww" en formato y lo reemplaza
    temp := Format('%2d',[WeekOfTheYear(Now + desplaz)]);
    formato := StringReplace(formato, 'ww', temp, [rfReplaceAll, rfIgnoreCase]);
    //escribe fecha actual
    DateTimeToString(temp, formato, Now + desplaz );
    PPro.Escribe(temp);
End;
procedure decodificar_EXPR;
//Decodifica la función $expr(expresion)
var
    e : Texpre;
begin
    If PPro.Capturar('(') = False Then begin
       PErr.GenError('Se esperaba "(" después de la función $EXPR()', PPro.PosAct);
       Exit;
    End;
    e := CogExpresion(0);
    If PErr.HayError Then Exit;
    If PPro.Capturar(')') = False Then begin
        PErr.GenError('Se esperaba ")" después de la función $EXPR()', PPro.PosAct);
        Exit;
    End;
    PPro.Escribe(e.valTxt);
End;
procedure decodificar_FORMATO;
//Decodifica la función $formato(expresion, formato)
var e: Texpre;
//    expresion_cad: String; //expresion de cadena
    expresion_num: Single; //expresion numerica
    expresion_fec: TDateTime;
    formato : String;
    temp: string;

   function LeeFecha(e: Texpre): TDateTime;
   //procesa el caso en el que la expresión es de tipo "ww99/9999". Se supone que "e" es cadena.
   var cad: string;
       sem, ano : Integer;
   begin
      cad := e.valTxt;
      If (length(cad) = 8) and (cad[1] = 'w') and (cad[4] = '/') Then begin
         //debe ser formato de semana: "ww99/9999" verifica
         If not TryStrToInt(copy(cad, 2, 2),sem) or
            not TryStrToInt(copy(cad, 5, 4),ano) Then begin
             PErr.GenError('Error en formato de semana. El formato es: w##/####', PPro.PosAct);
             Exit;
         End;
         //devuvelve la fecha solicitada
         Result := EncodeDateWeek(ano, sem);
      end else begin  //debe ser un formato de fecha normal
         result := e.valFec;  //Puede dar error
         if Perr.HayError then begin   //precisa el error
            PErr.GenError('Fecha inválida', PPro.PosAct);
            Exit;
         end;
      end;
   end;

begin
    If PPro.Capturar('(') = False then begin
       PErr.GenError('Se esperaba "(" después de la función $FORMATO()', PPro.PosAct);
       Exit;
    End;
    //lee primer parámetro
    e := CogExpresion(0);   //puede ser constante o definición
    If PErr.HayError Then Exit;

    if e.tip = TIP_CAD then begin
       //la cadena debe ser una fecha
       //Aquí se tiene siempre una cadena
       expresion_fec := LeeFecha(e);   //toma fecha
       if PErr.HayError then exit;     //puede dar error
       If PPro.Capturar(',') = False Then begin
           PErr.GenError('Se esperaba ","', PPro.PosAct);
           Exit;
       End;
       //toma segundo parámetro
       formato := PPro.CogCadena;  //toma el formato
       If PErr.HayError Then Exit;
       If PPro.Capturar(')') = False Then begin
           PErr.GenError('Se esperaba ")"', PPro.PosAct);
           Exit;
       End;
       //verifica si hay "ww" en formato y lo reemplaza
       temp := Format('%2d',[WeekOfTheYear(expresion_fec)]);
       formato := StringReplace(formato, 'ww', temp, [rfReplaceAll, rfIgnoreCase]);
       //escribe la fecha
       DateTimeToString(temp, formato, expresion_fec);
       PPro.Escribe(temp);   //escribe fecha actual
    end else if e.tip =  TIP_NUM then begin

    end else begin
      //Solo se maneja tipos de dato de cadena (fecha) { TODO : Incluir formato para números }
      PErr.GenError('Se esperaba expresión de tipo cadena-fecha', PPro.PosAct);
      Exit;
    end;
End;

Function preProcFuncion(identif: String): Boolean;
//Hace el preprocesamiento de una función
//Si no era una función válida, devuleve falso
var temp: String;
begin
    preProcFuncion := True;   //se asume que es función
    If identif = '$FECHA_ACTUAL' Then     //funcion fecha_actual()
        decodificar_FECHA_ACTUAL
    Else If identif = '$FORMATO' Then     //funcion formato()
        decodificar_FORMATO
    Else {If identif = '$INTERVALO_SEMANA' Then    //funcion intervalo_semana()
        decodificar_INTERVALO_SEMANA
    Else If identif = '$PSQL_REINIC' Then     //funcion psql_reinic()
        decodificar_PSQL_REINIC
    Else If identif = '$COLUMNAS' Then     //funcion columna()
        decodificar_COLUMNAS
    Else If identif = '$INDICES' Then     //funcion indices()
        decodificar_INDICES
    Else If identif = '$INFOVISTA' Then     //funcion infovista()
        decodificar_INFOVISTA
    Else If identif = '$INFOTABLA' Then     //funcion infotabla()
        decodificar_INFOTABLA
    Else }If identif = '$NOM_ACTUAL' Then begin  //variable de nombre actual de archivo
        If PPRo.VerCarN(2) = '()' Then
           begin PPro.CogCar; PPro.CogCar end;   //para facilitar el uso de esta función
        temp := ChangeFileExt(ExtractFileName(arcEnt),'');  //usa siempre el archivo de entrada
        PPro.Escribe(temp);   //escribe solo el nombre de archivo
    end
    Else If identif = '$DIR_ACTUAL' Then begin   //variable de nombre actual del directorio
        If PPro.VerCarN(2) = '()' Then
           begin PPro.CogCar; PPro.CogCar end;   //para facilitar el uso de esta función
        temp := ExtractFilePath(arcEnt);   //usa siempre el archivo de entrada
        if temp = '' then exit;  //no hay ruta, sale
        If temp[length(temp)] = '\' Then temp := copy(temp, 1, Length(temp) - 1);
        PPro.Escribe(temp);     //escribe solo el camino sin "\"
    end Else If identif = '$EXPR' Then     //funcion expresión()
        decodificar_EXPR
//    Else If identif = '$LEE_CADENA' Then     //funcion expresión()
//        decodificar_LEECADENA
    Else
        //No era función
        preProcFuncion := False;

End;
{procedure decodificar_CONNECT;
//Procesa la sentecnia CONNECT para determinar la cadena de conexión que se usará en la
//consulta
begin
  If cad_con = '' Then begin  //Es la primera cadena de conexión de la consulta
     //No se escribe, sólo se lee para usarla en la llamada al SQLPLUS
     cad_con := Trim(PPro.CogerHastaComent);
     if cad_con = '' then begin
        PErr.GenError('Se esperaba cadena de conexión.', PPro.PosAct);
        exit;
     end;
     //quita punto y coma final si existe
     If cad_con[length(cad_con)] = ';' Then
        cad_con := copy(cad_con, 1, Length(cad_con) - 1);
  end else   //no es la primera cadena de conexión
     PPro.Escribe('CONNECT');    //escribe CONNECT
end;}
procedure preProcesarAct;
//Realiza el preprocesamiento del Contenido actual
var iden, idenM: String;
    uc : char;
begin
   //-----------------------------------------
   While Not PPro.FinCont do begin
      If PErr.HayError Then break;
      If PPro.CogIdentificador(iden, idenM, uc) Then begin
         //-------------aqui se procesa el identificadorencontrado
         {If idenM = 'CONNECT' Then
            decodificar_CONNECT
         Else }If idenM = '$DEFINIR' Then  //palabra reservada
            decodificar_DEFINIR
         Else If idenM = '$REDEF' Then  //palabra reservada
            decodificar_REDEF
         Else If idenM = '$INCLUIR' Then  //palabra reservada
            decodificar_INCLUIR
//           ElseIf idenM = '$CONSULTAR' Then  //palabra reservada
//               decodificar_CONSULTAR
         Else If idenM = '$PARA' Then  //palabra reservada
            decodificar_PARA
         Else If preProcFuncion(idenM) Then   //preprocesa función
            //no hace nada porque ya se hizo
         else if ExpandirDefinido(idenM) then
            //no hace nada porque ya lo expandió
         else //hay identificador pero no es reconocido
            PPro.Escribe(iden);    //escribe identificador
      end else
         ProcCaracter;
   end;
End;
procedure preProcesarCad(txt: String; archivo: String);
//Preprocesa una cadena de texto. No modifica la posición ni el contenido actual
//Escribe su salida en el dispositivo de salida actual.
var con: TPosCont;
begin
    PErr.IniError;
    con := PPro.PosAct;   //Guarda posición y contenido actual
    PPro.NuevoContexEntTxt(txt, trim(archivo));   //Crea nuevo contenido
    If PErr.HayError Then Exit;
    preProcesarAct;
    PPro.PosAct := con;   //recupera el contenido actual
End;
procedure preProcesarArc(archivo: String);
//Preprocesa un archivo de texto. No modifica la posición ni el contenido actual
//Escribe su salida en el dispositivo de salida actual.
var con: TPosCont;
begin
    PErr.IniError;
    con := PPro.PosAct;   //Guarda posición y contenido actual
    PPro.NuevoContexEntArc(Trim(archivo));   //Crea nuevo contenido
    If PErr.HayError Then  Exit;
    preProcesarAct;
    PPro.PosAct := con;   //recupera el contenido actual
    //¿¿¿Y no destruye el contexto actual???
End;
procedure InicPreproc;
//Inicia el preprocesamiento
begin
  Perr.IniError;
  //preprocesa
  cad_con := '';       //Inicia la primera cadena de conexión de la consulta
  definiciones.Clear;  //inicializa el numero de definiciones "$DEFINIR"
  inclusiones.Clear;   //inicializa el numero de inclusiones "$INCLUIR"
  PPro.Iniciar;        //Inicia contextos para trabajo
  PPro.NuevoContexSal;  //Crea contexto de salida
  //variable predefinida
  CreaDefinicionDir('$horas', '00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23');
  CreaDefinicionDir('$meses', '01 02 03 04 05 06 07 08 09 10 11 12');
end;
procedure PreProcesar(arc: String; txt: String; var cadcon0: string);
{Realiza el preprocesamiento de un archivo o un texto. Si "txt" <> "", se procesa "txt",
 de otra forma, procesa el archivo "arc". La salida preprocesada se debe extraer de
 PPro.
 Si se encuentra una sentencia de tip CONNECT en la consulta, se devolverá la cadena
 de conexión, en "cadcon0", de otra forma se devolverá una cadena vacía.
 Punto de entrada único para el preprocesamiento.}
begin
  cadcon0 := '';
  arcEnt := Arc;  //Guarda el nombre del archivo de entrada para el procesamiento.
  InicPreproc;
  If txt = '' Then     //Procesa archivo
    preProcesarArc(arcEnt)
  Else                 //Procesa cadena
    preProcesarCad(txt,arcEnt);  {indica archivo de entrada para poder procesar las funciones
                                  $Dir_actual y $Nom_actual}
  //Si hubo error, sale actualizando variables de error
  if PErr.HayError then exit;
  if cad_con <> '' then cadcon0 := cad_con;  //la cadena de conexión de la consulta prevalece
End;

{ Tdefinicion }

procedure Tdefinicion.Expandir;
var con0: TPosCont;   //Posición de contenido
    iden, idenM: String;
    uc : char;  //último caracter
begin
   If tip = TD_DIR Then begin
        //Es de tipo directo, lo expande
        PPro.Escribe(txt);
        Exit;       //Sale no más
   End;
   //Expande definición de tipo contexto
   con0 := PPro.PosAct;   //guarda contenido actual
   PPro.PosAct := con;   //fija a contenido de la definicion
   //expande hasta el bloque DEFINIDO
   If PPro.VerCar = ' ' Then PPro.CogCar;   //ignora el primer espacio si lo hay
   While Not PPro.FinCont do begin
      If PErr.HayError Then break;
      If PPro.CogIdentificador(iden, idenM, uc) Then begin
          //-------------aqui se procesa el comando encontrado
          If idenM = '$INCLUIR' Then    //palabra definida
             decodificar_INCLUIR(True)
//          ElseIf iden = '$CONSULTAR' Then     //palabra reservada
//             Call decodificar_CONSULTAR
          Else If idenM = '$PARA' Then     //palabra reservada
             decodificar_PARA
          Else If preProcFuncion(idenM) Then   //preprocesa función
              //no hace nada porque ya se hizo
          else if ExpandirDefinido(idenM) then
              //no hace nada porque ya lo expandió
          else if idenM = 'FINDEFINIR' Then begin
              If uc = ' ' Then PPro.SacCar; //si último caracter fue espacio, retrocede.
              break;    //terminó su trabajo
          end else //hay identificador pero no es reconocido
              PPro.Escribe(iden);    //escribe identificador
      end Else     //no es inicio de identificador
         ProcCaracter;
   end;
   if PPro.FinCont then begin;
     //este error no deberia producirse ya que se ha examinado anteriormente
     //el archivo y se encontro el FINDEFINIR de otra forma no se habria llegado aqui
     PErr.GenError('Sorpresa!!!. No se encontro "FINDEFINIR" del "$DEFINIR"', PPro.PosAct);
     exit;
   end;
   PPro.PosAct := con0;  //Devuelve al contenido de trabajo
end;
function Tdefinicion.ValTxt: string;
//Devuelve la definición expandida como una cadena.
begin
  If tip = TD_DIR Then
     Result := txt
  else begin   //es de contexto
     PPro.NuevoContexSal;   //crea nuevo contexto de salida
     Expandir;              //expande en nuevo contexto
     //Puede devolver error
     Result := Ppro.cadenaSal;  //copia resultado
 { TODO : Ver y corregir, por qué una definición de tipo
 COMO .. FINDEFINIR de una sola linea, se expande con un salto al final }
     PPro.QuitaContexSal;   //Elimina contexto creado
  end;
end;

procedure Tdefinicion.FijTxt(t: string);
begin
  tip := TD_DIR;   //la fuerza a definición directa, o de otra forma no tiene sentido
                   //cambiar su valor
  txt := t;        //cambia su valor
end;

procedure Tdefinicion.LeeElementos(elem: TStringList);
{Lee los elementos de la definición, y los agrega al areglo "elem". Se debe usar con
 definiciones de tipo lista. Los eleemntos están separados por blancos. }
begin
   PPro.NuevoContexEntTxt(ValTxt, con.arc); //Pone expansión de definición en nuevo contexto
   while not PPro.FinCont do begin       //explora expansión
     elem.Add(Ppro.CogElemento);
   end;
   PPro.QuitaContexEnt;   //elimina el contexto
end;

initialization
   //crea objetos
   definiciones := TListaDef.Create(true);
   inclusiones := TListaInc.Create(true);
   arcEnt := '';  //inicia archivo de entrada

finalization
   definiciones.Free;
   inclusiones.Free;
end.

