{********************************************************************************** uPreBasicos Unidad con las definiciones y funciones básicas para el tratamiento de los errores y contextos del pre-procesador PreSQL. Un contexto es una abstracción de datos materializada en una estructura que permite almacenar texto, generalmente destinado a ser preprocesado por el PreSQL. En el contexto se tiende considera siempre, el salto de línea como si fuera un sólo caracter porque se lee con una sola llamada a las funciones VerCar() y CogCar(). En el tratamiento del contexto, se asume que todas las líneas, excepto la última, tienen un caracter FIN_LIN como delimitador final. Esta unidad se ha adaptado del código Visual Basic del PreSQL 1.3. Adaptado Por Tito Hinostroza 23/08/2013 *********************************************************************************** } unit uPreBasicos; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Fgl; Const FIN_LIN = #13; //Fin de línea FIN_CON = #0; //Fin de contexto //Tipos de contextos TC_ARC = 0 ; //contexto de tipo archivo TC_TXT = 1 ; //contexto de tipo texto Type //Tipo de operando TPTipOper = (TIP_DES, //tipo desconocido TIP_NUM, //tipo número TIP_CAD); //tipo cadena //Categoría para clasificar a los operandos TPCatOper = (COP_VACIO, //Operando nulo // COP_VARPUNTO, //Variable Punto COP_DEFINIC, //es una variable (definición) COP_FUNCION, //Es una función COP_CONST, //Es una constante COP_EXPRESION); //Es resultado de una expresión //Tipo expresión. Se usa para manejo de evaluación aritmética. { Texpre } Texpre = object //Tipo expresión txt: String; //Texto de la expresión tip: TPTipOper; //Tipo de dato que devuelve la expresión cat: TPCatOper; //Categoría de expresión // uop: String ; //último operador que se ejecutó de la expresión private fTxt: String; //Valor numérico de la expresión fNum: Single; //Valor numérico de la expresión procedure FijTxt(txt0: string); function LeeTxt: string; procedure FijNum(n0: single); function LeeNum: single; function LeeFec: TDateTime; public def : pointer; //referencia a la definición (en caso de que lo sea) property valTxt: string read LeeTxt write FijTxt; property valNum: single read LeeNum write FijNum; property valFec: TDateTime read LeeFec; End; //ID para categorizar a los tokens TtkTokenKind = (tkComment, tkIdentifier, tkKey, tkNull, tkNumber, tkSpace, tkString, tkUnknown, tkMacro); TContexto = class; {Posición dentro de un contexto. A diferecnia de "Tcontexto", es un registro y siempre guardará una copia permanente. Además no guarda el texto del contexto} TPosCont = record arc : String ; //Nombre de archivo fil : LongInt ; //Fila col : Integer ; //Columna nlin : LongInt ; //Número de líneas fCon : TContexto; //Referencia al Contexto End; { TPError } {Define al objeto PError, el que se usa para tratar los errores del preprocesador. Solo se espera que haya uno de estos objetos, por eso se ha declarado como OBJECT} TPError = object private numER : Integer; //codigo de error arcER : String; //nombre de archivo que origino el error fil : integer; //número de línea del error col : integer; //número de columna del error public cadError : String; //cadena de error NombPrograma: string; //Usado para poner en el encabezado del mensaje procedure IniError; procedure GenError(num: Integer; msje : String; archivo: String = ''; nlin: integer = 0); procedure GenError(msje: String; posCon: TPosCont); function GenTxtError: string; // procedure MosError; function ArcError: string; function nLinError: longint; Function nColError: longint; function HayError: boolean; end; { TContexto } {Estructura que define a un objeto contexto. Se usa tanto para leer la entrada como para escribir en la salida.} TContexto = class tip : integer; arc : String; //nombre de archivo fil : LongInt; //fila actual col : Integer; //columna actual nlin : LongInt; //Número de líneas del Contexto lin : TStringList; {Líneas de texto. Se almacena en TStringList porque es rápida la carga desde un archivo y porque es compatible con el almacenamiento en el Control Editor.} constructor Create; destructor Destroy; override; //Métodos de lectura Function IniCont:Boolean; Function FinCont:Boolean; Function VerCar:Char; Function CogCar:Char; Function VerCarAnt: Char; Function VerCarSig: Char; Function CapBlancos:Boolean; //Métodos de escritura procedure CurPosIni; procedure CurPosFin; procedure PonSalto; //Agrega Salto de línea procedure SacLinea; //quita la última línea procedure PonCar(c: char); //Agrega caracter procedure PonCad(s: String); //Agrega cadena procedure SacCar; //Quita un caracter //Métodos de llenado/lectura function LeeCad: string; //Lee el contenido del contexto procedure FijCad(cad : string); //Fija el contenido del contexto con cadena procedure FijArc(arc0: string); //Fija el contenido del contexto con archivo End; //Define una lista de Contextos TListaCont = specialize TFPGObjectList; { TPPro } TPPro = object private Function LeePosContAct: TPosCont; procedure FijPosContAct(pc:TPosCont); function LeeCadSal: String; procedure FijCadSal(cad: string); public constructor Create; destructor Destroy; //override; procedure Iniciar; //Prepara la secuencia de preprocesamiento //rutinas de entrada procedure NuevoContexEntTxt(txt: string; arc0: String); procedure NuevoContexEntArc(arc0: String); procedure QuitaContexEnt; //quita contexto de entrada actual //rutinas basicas de lectura Function IniCont:Boolean; Function FinCont:Boolean; Function VerCar: Char; Function VerCarN(numcar:Integer): String; Function CogCar:Char; // Function VerCarAnt:Char; Function CapBlancos:Boolean; function Capturar(cap: String): Boolean; property PosAct: TPosCont read LeePosContAct write FijPosContAct; //rutinas avanzadas de lectura Function VerIdentificador:String; Function VerIdentifM:String; //VE identificador en mayúscula Function VerPalabra():String; Function VerElemento():String; Function CogIdentificador:String; function CogIdentificador(var ide, ideM: string; var uc: char): boolean; function CogIdentificador(var ideM: string): boolean; Function cogPalabra():String; Function CogElemento():String; Function CogNumero:Single; Function CogNumero(var n:Single):boolean; Function CogCadena:String; Function CogCadena(var s: string):boolean; Function coger_ruta():String; function TipoSigToken: TtkTokenKind; //Devuelve el tipo del siguiente token Function VerSiEsComentario():Boolean; Function CogerHastaFinLinea():String; Function CogerHastaComent():String; Function VerHastaFinLinea():String; function CogerComent: boolean; function CogerLista(lista: TStringList; delims: string): string; function cogOperador: String; //coge operador function jerOp(oper: String): Integer; //jerarquía de operador function Evaluar(Op1: Texpre; opr: String; Op2: Texpre): Texpre; //rutinas de salida procedure NuevoContexSal; //Crea nuevo contexto de salida actual procedure QuitaContexSal; //quita contexto de salida actual procedure PonCar(c: char); //Agrega caracter procedure Escribe(palabra: string); procedure EscribeSalto; procedure SacCar; procedure GenArchivo(ArcSal0: string); //Genera archivo de salida function TextSalida: string; //Devuelve el texto preprocesado property cadenaSal: String read LeeCadSal write FijCadSal; //Cadena se salida del contexto actual private ConsE: TListaCont; //Lista de contextos de entrada ConsS: TListaCont; //Lista de contextos de salida //Variables del Contexto actual cEnt : TContexto; //referencia al contexto de entrada actual cSal : TContexto; //referencia al contexto de salida actual end; var PErr : TPError; //Objeto de Error PPro : TPPro; //Objeto Pre-Procesador //////////////////////////////////////////////////////////////////////////////////////////// implementation //caracteres iniciales válidos para idntificador { TODO : Debe desaparecer porque debe poder identificarse el token en la función "TipoSiguienteToken" } const CAR_INI_IDENT = ['$','a'..'z','A'..'Z','_']; //No se incluye 'ñ' //caracteres válidos para idntificador const CAR_IDENT = ['$','a'..'z','A'..'Z','_','0'..'9']; //No se incluye 'ñ' //caracteres válidos para idntificador const CAR_VAL_PALABRA = ['$','a'..'z','A'..'Z','_','0'..'9','.']; //¿'ñ'? { Texpre } procedure Texpre.FijTxt(txt0: string); //Fija valor de texto de un operando begin tip := TIP_CAD; //se fija como cadeana, de otra forma no podría recibir este valor fTxt := txt0; end; function Texpre.LeeTxt: string; //Lee la variable como texto begin if tip = TIP_CAD then //si ya es texto, la lectura es directa Result := ftxt else if tip = TIP_NUM then //Si es numérico, se hace una transformación //siempre se podrá transformar Result := FloatToStr(fNum) //pero no deja de ser numérico else Result := ''; end; procedure Texpre.FijNum(n0: single); begin tip := TIP_NUM; //se fija como número, de otra forma no podría recibir este valor fNum := n0; end; function Texpre.LeeNum: single; begin if tip = TIP_CAD then begin //si es texto, se hace una transformación //puede que no se pueda transformar if not TryStrToFloat(trim(ftxt), Result) then //pero no deja de ser texto PErr.GenError( 1, 'Número inválido.') end else if tip = TIP_NUM then //Si ya es numérico, la lectura es directa Result := fNum else Result := 0; end; function Texpre.LeeFec: TDateTime; begin if tip = TIP_CAD then begin //si es texto, se hace una transformación //puede que no se pueda transformar ftxt := trim(ftxt); if not TryStrToDateTime(ftxt, Result) then //pero no deja de ser texto PErr.GenError( 1, 'Fecha inválida.') end else if tip = TIP_NUM then //Si ya es numérico, la lectura es directa Result := fNum else Result := 0; end; { TPPro } constructor TPPro.Create; begin //Crea lista de Contextos ConsE := TListaCont.Create(true); //crea contenedor de Contextos, con control de objetos. cEnt := nil; ConsS := TListaCont.Create(true); //crea contenedor de Contextos con control. cSal := nil; end; destructor TPPro.Destroy; begin //Limpia lista de Contextos ConsE.Free; //Limpia Contextos de salida ConsS.Free; end; procedure TPPro.Iniciar; //Inicia la maquinaria de manejo de Contextos begin ConsE.Clear; //elimina todos los Contextos de entrada ConsS.Clear; //elimina end; procedure TPPro.NuevoContexEntTxt(txt: string; arc0: String); //Crea un Contexto a partir de una cadena. //Fija el Contexto Actual "cEnt" como el Contexto creado. begin cEnt := TContexto.Create; //crea Contexto ConsE.Add(cEnt); //Registra Contexto cEnt.FijCad(txt); //inicia con texto cEnt.arc := arc0; {Se guarda el nombre del archivo actual, solo para poder procesar las funciones $NOM_ACTUAL y $DIR_ACTUAL} cEnt.CurPosIni; //posiciona al inicio end; procedure TPPro.NuevoContexEntArc(arc0: String); //Crea un Contexto a partir de un archivo. Devuelve el manejador del Contexto //Fija el Contexto Actual "cEnt" como el Contexto creado. begin If not FileExists(arc0) Then begin //ve si existe PErr.GenError( 1, 'No se encuentra archivo: ' + arc0); Exit; end; cEnt := TContexto.Create; //crea Contexto ConsE.Add(cEnt); //Registra Contexto cEnt.FijArc(arc0); //inicia con archivo cEnt.CurPosIni; //posiciona al inicio end; procedure TPPro.QuitaContexEnt; //Elimina el contexto de entrada actual. Deja apuntando al anterior en la misma posición. begin if ConsE.Count = 0 then exit; //no sep uede quitar más ConsE.Delete(ConsE.Count-1); if ConsE.Count = 0 then cEnt := nil else //apunta al último CEnt := ConsE[ConsE.Count-1]; end; function TPPro.LeePosContAct: TPosCont; //Devuelve Contexto actual y su posición begin Result.fCon := cEnt; if cEnt = nil then begin //aún no hay Contexto definido Result.fil := 1; Result.col := 1; Result.arc := ''; Result.nlin := 0; end else begin Result.fil := cEnt.fil; Result.col := cEnt.col; Result.arc := cEnt.arc; Result.nlin := cEnt.nlin; end; End; procedure TPPro.FijPosContAct(pc:TPosCont); //Fija Contexto actual y su posición begin cEnt := pc.fCon; if cEnt = nil then begin //no tiene un Contexto actual // filAct := 1; // colAct := 1; // cEnt.arc := ''; // nlin := 0; end else begin cEnt.fil := pc.fil; cEnt.col := pc.col; cEnt.arc := pc.arc; cEnt.nlin := pc.nlin; end; End; function TPPro.LeeCadSal: String; //Devuelve la cadena equivalente del contexto de salida actual begin Result := cSal.LeeCad; end; procedure TPPro.FijCadSal(cad: string); //Fija la cadena equivalente del contexto de salida actual begin cSal.FijCad(cad); end; //******************************************************************************** //Funciones Básicas de acceso al Contexto actual. Todo acceso al Contexto actual //debe hacerse a través de estas funciones por seguridad. //******************************************************************************** function TPPro.IniCont: Boolean; //Devuelve verdadero si se está al inicio del Contexto actual (fila 1, columna 1) begin Result := cEnt.IniCont; End; function TPPro.FinCont: Boolean; //Devuelve verdadero si se ha pasado del final del Contexto actual begin Result := cEnt.FinCont; End; function TPPro.VerCar: Char; //Devuelve el caracter actual a partir de la posición actual, del Contexto actual. //Si no hay texto en el Contexto actual o si se ha llegado al final del //texto, devuelve FIN_CON. //Si está al final de una línea devuelve siempre "FIN_LIN" begin Result := cEnt.VerCar; End; function TPPro.VerCarN(numcar: Integer): String; //Devuelve los N caracteres a partir de la posición actual, del Contexto actual. //Si no hay texto en el Contexto actual o si se ha llegado al final del //texto, devuelve FIN_CON. //Si está al final de una línea devuelve siempre "FIN_LIN" var linact:String; begin If FinCont Then Exit(FIN_CON); linact := cEnt.lin[cEnt.fil-1]; //línea actual If cEnt.col = Length(linact) + 1 Then begin //Se está al fin de la línea. Se considera que cada línea //tiene un salto de línea al final, excepto la última línea. //En este caso siempre se devuelve FIN_LIN Result := FIN_LIN end Else //No se está al fin de la línea Result := copy(linact, cEnt.col, numcar); End; function TPPro.CogCar: Char; //Devuelve el caracter actual del Contexto actual e incrementa //el puntero a la siguiente posición. //La siguiente posición del fin de una línea es el caracter "salto de //línea", que son en realidad dos caracteres. begin Result :=cEnt.CogCar; End; {Function TPPro.VerCarAnt: Char; //echa un vistazo al caracter anterior del Contexto //Si no hay caracter anterior, devuelve cadena vacía Var linact:String; begin Result := #0; If cEnt.FinCont Then Exit; //Realmente debería devolver el caracter final If IniCont Then Exit; //No hay caracter anterior linact := cEnt.lin[cEnt.fil-1]; //línea actual If cEnt.col = 1 Then //Está al inicio de una línea Result := FIN_LIN //devuelve el salto anterior Else Result := linact[cEnt.col-1]; End;} function TPPro.CapBlancos: Boolean; //Coge los blancos iniciales del contexto de entrada. //Si no encuentra algun blanco al inicio, devuelve falso begin Result := cEnt.CapBlancos; End; function TPPro.Capturar(cap: String): Boolean; //coge la cadena dada ignorando los blancos iniciales. Var i:Integer; begin Result := False; cEnt.CapBlancos; //quita blancos iniciales i := 1; While Not cEnt.FinCont And (i <= Length(cap)) do begin If cEnt.VerCar = cap[i] Then begin cEnt.CogCar; i := i + 1; end Else Exit; //fallo en algun caracter End; If i > Length(cap) Then //encontró toda la cadena Capturar := True; End; //******************************************************************************** //Funciones de mayor nivel para acceso al Contexto actual. //******************************************************************************** function TPPro.VerIdentificador: String; //devuelve una palabra correspondiente a un identificador //empieza a buscar desde el principio Var col0:Integer; begin col0 := cEnt.col; VerIdentificador := CogIdentificador; cEnt.col := col0; End; function TPPro.VerIdentifM: String; //Devuleve el dientificador en mayúscula begin Result := UpCase(VerIdentificador); end; function TPPro.VerPalabra: String; //devuelve una palabra correspondiente a un identificador //empieza a buscar desde el principio Var ncolTmp:Integer; begin ncolTmp := cEnt.col; VerPalabra := cogPalabra; cEnt.col := ncolTmp; End; function TPPro.VerElemento: String; //devuelve una palabra correspondiente a un identificador //empieza a buscar desde el principio Var fil0, col0: integer; begin col0 := cEnt.col; //guarda todo el contexto, porque "CogElemento", puede cambiar de línea fil0 := cEnt.fil; VerElemento := CogElemento; cEnt.col := col0; //recupera cEnt.fil := fil0; End; function TPPro.CogIdentificador: String; //Coge una palabra correspondiente a un identificador desde la posicion actual del //contexto. Var temp:String; car1, car: char; begin Result := ''; //no hay identificador inicialmente //CapBlancos; //no debe eliminar espacios temp := ''; car1 := VerCar; //lee caracter inicial If FinCont Then Exit; //Fin de Contexto If Not (car1 in CAR_INI_IDENT) Then //primer caracter valido Exit; //no es identificador temp += CogCar; //acumula //busca hasta encontar fin de identificador car := VerCar; While car in CAR_IDENT do begin if (car = '$') then begin //verifica regla de nombre de identif. //verifica si es delimitador o inicio de otro identificador if (car1='$') then //es delimitador temp += CogCar; //acumula break; end; temp += CogCar; //acumula car := VerCar; end; //se llego al final del archivo Result := temp //copia hasta el final End; function TPPro.CogIdentificador(var ide, ideM: string; var uc:char): boolean; {Versión que lee el identificador normal y en mayúscula. Si no encuentra ningún identificador devuelve FALSE. Devuelve además el último caracter leido antes del identificador (uc).} begin if TipoSigToken = tkIdentifier then begin uc := cEnt.VerCarAnt; //lee caracter anterior ide := CogIdentificador(); ideM := UpCase(ide); //en mayúscula exit(true); //sale con TRUE end else //no hay identificador exit(false); end; function TPPro.CogIdentificador(var ideM: string): boolean; //Versión sencilal que devuelve el identificador en mayúscula begin if TipoSigToken = tkIdentifier then begin ideM := UpCase(CogIdentificador()); //en mayúscula exit(true); //sale con TRUE end else //no hay identificador exit(false); end; function TPPro.cogPalabra: String; //coge una palabra completa (alfanumerico y punto decimal) //desde la posicion donde se encuentra el archivo Var temp:String; car:char; begin cogPalabra := ''; //no hay identificador inicialmente CapBlancos; //quita blancos iniciales temp := ''; car := VerCar; If car = '' Then Exit; If Not (car in CAR_VAL_PALABRA) Then //primer caracter valido Exit; //no es identificador temp := temp + CogCar; //acumula //busca hasta encontrar fin de identificador While VerCar <> '' do begin car := VerCar; If car in CAR_VAL_PALABRA Then begin CogCar; //toma el caracter temp += car; //acumula end Else begin cogPalabra := temp; //copia el identificador Exit; End; end; //se llego al final del archivo cogPalabra := temp; //copia hasta el final End; function TPPro.CogElemento: String; {Toma un elemento de una cadena. El elemento puede ser un identificador, un símbolo o una frase. Los elementos se separan por caracteres "blancos". Por ejemplo, la cadena: casa 1 'nueva casa' Tiene 3 elementos: "casa", "1" y "nueva casa".} Var temp:String; car:char; begin CapBlancos; //quita blancos iniciales If cEnt.VerCar = '''' Then //Inicio de cadena Result := CogCadena Else begin temp := ''; While cEnt.VerCar <> '' do begin car := cEnt.VerCar; If not (car in [' ',#9,FIN_LIN, FIN_CON]) Then begin cEnt.CogCar; //toma el caracter temp += car; //acumula end Else begin Result := temp; //copia el identificador Exit; End; end; //se llego al final del archivo Result := temp; //copia hasta el final End; End; function TPPro.CogNumero: Single; {Coge una cifra numerica, del contexto actual, desde la posicón actual. Primero elimina los blancos. Si no encuentra algún caracter numérico al inicio, o el signo menos, sale } begin CogNumero(Result); End; function TPPro.CogNumero(var n: Single): boolean; Var car:char; temp:String; begin Result := false ; //no hay numero CapBlancos; car := cEnt.VerCar; If Not (car in ['0'..'9','.','-']) Then //primer caracter no valido Exit; //no es numero if (car in ['.','-']) and not (cEnt.VerCarSig in ['0'..'9']) then Exit; //no es válido temp := cEnt.CogCar; //acumula primer dígito //busca hasta encontar fin de identificador While cEnt.VerCar in ['0'..'9','.'] do begin car := cEnt.CogCar; //toma el caracter temp += car; //acumula end; //se llego al final del número n := StrToFloat(temp); //copia hasta el final Result := true; //indica que hubo número end; function TPPro.CogCadena: String; begin CogCadena(Result); End; function TPPro.CogCadena(var s: string): boolean; {Coge una constante de tipo cadena (entre apóstrofos) desde la posicion donde se encuentra el archivo, hasta el delimitador o fin de línea. Si no encuentra una cadena, devuelve FALSE} Var car : char; begin PErr.IniError; Result := false; //no hay cadena CapBlancos; //quita blancos iniciales s := ''; car := cEnt.VerCar; If car <> '''' Then //primer caracter no valido Exit; //no es constante cadena cEnt.CogCar; //toma el caracter Result := true; //indica que se encontró cadena //busca hasta encontar fin de identificador While not(cEnt.VerCar in [FIN_LIN, FIN_CON]) do begin car := cEnt.CogCar; If car <> '''' Then begin s += car; //acumula end Else begin Exit; End; end; //se llego al final del archivo PErr.GenError('No se encontro fin de cadena', PosAct); end; function TPPro.coger_ruta: String; //Coge una cadena que representa la ruta de un archivo (con o sin apóstrofos) //desde la posicion donde se encuentra el archivo Var temp:String; car:String; begin PErr.IniError; coger_ruta := ''; //no hay cadena CapBlancos; //quita blancos iniciales temp := ''; car := VerCar; If car = '' Then Exit; If car = '''' Then begin //ruta en formato de cadena temp := CogCadena; If PErr.HayError Then Exit; coger_ruta := temp; Exit; //no es constante cadena end Else begin //busca hasta encontar blanco (espacio o salto de línea, o tab) While Not FinCont And Not (VerCar in [' ',#9,FIN_LIN]) do temp += CogCar; //acumula Result := temp; //copia //se llego al final del archivo // GenError 1, "No se encontro fin de cadena", ArcActual, filAct End; End; function TPPro.TipoSigToken: TtkTokenKind; //Identifica el token que inicia en la posición actual. begin case VerCar of '$','a'..'z','A'..'Z','_': Result := tkIdentifier; else Result := tkUnknown; end; end; function TPPro.VerSiEsComentario: Boolean; //devuelve verdad si la posicion actual del archivo de entrada corresponde //al inicio de un comentario. No Filtra blancos iniciales ni salta lineas var cad: string[2]; begin VerSiEsComentario := False; cad := VerCarN(2); Result := (cad = '--') or (cad = '/*'); End; function TPPro.CogerHastaFinLinea: String; //coge una cadena correspondiente a los caracteres desde el punto actual hasta el fin de la linea Var temp:String; begin temp := ''; While Not cEnt.FinCont And (cEnt.VerCar <> FIN_LIN) do temp += cEnt.CogCar; cEnt.CogCar; //Coge el fin de línea //se llego al final del archivo o al fin de linea Result := temp; //copia hasta el final End; function TPPro.CogerHastaComent: String; //Coge texto hasta encontrar el inicio de un comentario o un salto de línea var linact : string; i,j, min: integer; begin linact := cEnt.lin[cEnt.fil-1]; //línea actual //busca posiicón de comentario min := length(linact)+1; //valor inicial i := Pos('--',linAct); if i<>0 then min := i; //primer valor j := Pos('/*',linAct); if j < cEnt.col then j := 0; //comentarios anteriores (/* ... */), se ignoran. if j<>0 then //hay otro if j < min then min := j; //compara valor //toma los caracteres necesarios Result := Copy(linAct,cEnt.col,min-cEnt.col); //copia hasta el final cEnt.col := min; //pone kasta posición leida if min = length(linact) + 1 then CogCar; //Coge el fin de línea end; function TPPro.CogerComent: boolean; {Coge un comantario, de tipo /* .. */. Debe llamarse cuando se ha detectado el inicio de este comentario. Puede coger varias líneas.} begin Result := false; while not FinCont do begin if cEnt.CogCar = '*' then begin //puede ser delimitador final if cEnt.VerCar = '/' then begin cEnt.CogCar; //toma el delimitador Result := true; exit; //sale end; end; end; end; function TPPro.CogerLista(lista: TStringList; delims: string): string; {Estrae una lista de elementos del contexto actual, hasta encontrar uno de los delimitadores indicados en "delims". Si termina por encontrar un delimitador, devuelve el delimitador encontrado (siempre en mayúscula). Los delimitadores deben indicarse separados por coma, sin esapcios entre ellos} var tmp: string; l_delims : TstringList; begin Result:=''; l_delims := TStringList.Create; //crea lista l_delims.Delimiter:=','; l_delims.DelimitedText:=delims; //descompone lista { --VERSIÓN SIN COGER ELEMENTO tmp := VerElemento; //toma primero While Not FinCont do begin If l_delims.IndexOf(tmp) <> -1 Then //busca delimitador begin Result:= tmp; break end; //encontro delimitador, sale. lista.add(CogElemento); //agrega elemento tmp := VerElemento; //toma siguiente end;} repeat tmp := CogElemento; //toma siguiente if tmp = '' then break; //es fin de contexto, porque CogElemento toma cualquier cosa If l_delims.IndexOf(tmp) <> -1 Then begin //busca delimitador Result:= Upcase(tmp); break; //encontro delimitador, sale. end; lista.add(tmp); //agrega elemento until Cent.FinCont; //aquí puede haber llegado por fin de contexto o por haber encontrado //algún delimitador. l_delims.Free; end; function TPPro.VerHastaFinLinea: String; //Devuelve una cadena correspondiente a los caracteres desde el punto actual hasta el fin de //la linea. Empieza a buscar desde el principio. Var nfilTmp:LongInt; ncolTmp:Integer; begin nfilTmp := cEnt.fil; ncolTmp := cEnt.col; VerHastaFinLinea := CogerHastaFinLinea; cEnt.fil := nfilTmp; //devuelve el numero de linea inicial cEnt.col := ncolTmp; End; //******************************************************************************** //Funciones para manejo de la salida //******************************************************************************** procedure TPPro.NuevoContexSal; //Crea un nuevo contexto de salida y pone cursor al inicio. begin //Crea Contexto de salida cSal := TContexto.Create; ConsS.Add(cSal); //Registra Contexto cSal.FijCad(''); //Iniicia cadena y posiciona cursor al final end; procedure TPPro.QuitaContexSal; //Elimina el contexto de salida actual. Deja apuntando al anterior en la misma posición. begin if ConsS.Count = 0 then exit; //no sep uede quitar más ConsS.Delete(ConsS.Count-1); if ConsS.Count = 0 then cSal := nil else //apunta al último CSal := ConsS[ConsS.Count-1]; end; procedure TPPro.PonCar(c: char); begin cSal.PonCar(c); end; procedure TPPro.Escribe(palabra: string); //Escribe un palabra en el archivo de salida. Este debe ser el único punto //de acceso al archivo de salida. begin cSal.PonCad(palabra); end; procedure TPPro.EscribeSalto; //Escribe un salto de línea en el archivo de salida. begin cSal.PonSalto; end; procedure TPPro.SacCar; //Quita un caracter del dispositivo de salida, borrando la información previamente escrita. begin cSal.SacCar; end; procedure TPPro.GenArchivo(ArcSal0: string); //Genera el archivo de salida begin if ArcSal0 = '' then exit; //protección cSal.lin.SaveToFile(ArcSal0); end; function TPPro.TextSalida: string; begin Result := cSal.lin.Text; end; function TPPro.cogOperador: String; {Coge un operador en la posición del contexto actual. Si no encuentra devuelve cadena vacía y no coge caracteres, salvo espacios iniciales.} begin cogOperador := ''; CapBlancos; //quita blancos iniciales Case VerCar of //completa con operador de más caracteres '+': begin Result := CogCar; // If VerCar = '+' Then begin CogCar; Result := '++' end; // If VerCar = '=' Then begin CogCar; Result := '+=' end; end; '-': begin Result := CogCar; // If VerCar() = '-' Then begin CogCar; Result := '--' end; // If VerCar() = '=' Then begin CogCar; Result := '-=' end; end; '*': begin Result := CogCar; // If VerCar() = '=' Then begin CogCar; Result := '*=' end; end; '/': begin Result := CogCar; // If VerCar() = '=' Then begin CogCar; Result := '/=' end; end; // '=': begin // Result := CogCar; // If VerCar() = '=' Then begin CogCar; Result := '==' end; // If VerCar() = '<' Then begin CogCar; Result := '=<' end; //operador 'menor' // If VerCar() = '>' Then begin CogCar; Result := '=>' end; //operador 'mayor' // end; // '>': begin // Result := CogCar; // If VerCar() = '=' Then begin CogCar; Result := '>=' end; // If VerCar() = '>' Then begin CogCar; Result := '>>' end; // If VerCar() = '+' Then begin CogCar; Result := '>+' end; // If VerCar() = '-' Then begin CogCar; Result := '>-' end; // end; // '<': begin // Result := CogCar; // If VerCar() = '=' Then begin CogCar; Result := '<=' end; // If VerCar() = '>' Then begin CogCar; Result := '<>' end; // If VerCar() = '<' Then begin CogCar; Result := '<<' end; // end; // '|': begin // Result := CogCar; // If VerCar() = '|' Then begin CogCar; Result := '||' end; //OR // If VerCar() = '!' Then begin CogCar; Result := '|!' end; //XOR // end; // '~': begin //operador LIKE // Result := CogCar; // end; // '&': begin // Result := CogCar; // If VerCar = '&' Then begin CogCar; Result := '&&' end; //AND // end; End; End; function TPPro.jerOp(oper: String): Integer; //Devuelve la jerarquía de un operador ver documentación técnica. begin Case oper of // '>>', '<<', '>+', '>-': jerOp = 1: Exit Function // '=': jerOp := 2; // '&&', '||', '!', '|!': jerOp := 3; // '==', '<>', '>', '>=', '<', '<=', '~': jerOp := 4; '+', '-'{, '|', '&'}: jerOp := 5; '*', '/'{, '\', '%'}: jerOp := 6; // '=<', '=>': jerOp := 7; // '^', '++', '--', '+=', '-=', '*=', '/=': jerOp := 8; Else jerOp := 0; End; End; function TPPro.Evaluar(Op1: Texpre; opr: String; Op2: Texpre): Texpre; //Devuelve el resultado y tipo de una operación begin PErr.IniError; Evaluar.cat := COP_EXPRESION; //ahora es expresión por defecto Case opr of '': begin //Sin operador. Y se supone sin Op2 //no hay nada que hacer, ya está en la pila Evaluar := Op1; end; { '=': begin //Asignación If Op1.cat = COP_DEFINIC Then begin //Asignación a una variable // Evaluar.val := Op2.val; // Evaluar.tip := Op2.tip; Op1 := Op2; tDefi Op1.def:=; Evaluar:= Op1 end Else Perr.GenError('Sólo se puede asignar valor a una variable', PosAct); end;} '+': begin Evaluar.valNum := Op1.valNum + Op2.valNum; //Fuerza a Evaluar.tip := TIP_NUM end; '-': begin Evaluar.valNum := Op1.valNum - Op2.valNum; end; '*': begin Evaluar.valNum := Op1.valNum * Op2.valNum; end; '/': begin If Op2.valNum = 0 Then Perr.GenError('No se puede dividir por cero.', PosAct) Else begin //error Evaluar.valNum := Op1.valNum / Op2.valNum; End; end; { '\': begin If val(Op2.val) = 0 Then Perr.GenError('No se puede dividir por cero.', PosAct); Else begin //error Evaluar.val := val(Op1.val) \ val(Op2.val); Evaluar.tip := TIP_NUM; End; end; '%': begin If val(Op2.val) = 0 Then Perr.GenError('No se puede dividir por cero.', PosAct); Else begin //error Evaluar.val := val(Op1.val) Mod val(Op2.val); Evaluar.tip := TIP_NUM; End; end; '^': begin If val(Op2.val) = 0 And val(Op2.val) = 0 Then Perr.GenError('No se puede Evaluar 0^0', PosAct); Else begin //error Evaluar.val := val(Op1.val) ^ val(Op2.val); Evaluar.tip := TIP_NUM; End; end; '++': begin //mono-operando, sólo Op1 Op1.val := val(Op1.val) + 1 //incrementa Evaluar.val := Op1.val; Evaluar.tip := TIP_NUM; end; '--': begin //mono-operando Op1.val := val(Op1.val) - 1 //decrementa Evaluar.val := Op1.val; Evaluar.tip := TIP_NUM; //operadores de comparación end; '==': begin If Op1.val := Op2.val Then Evaluar.val := 1 Else //error Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '<>': begin If Op1.val <> Op2.val Then Evaluar.val := 1 Else //error Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '>': begin If Op1.val > Op2.val Then Evaluar.val := 1 Else //error Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '<': begin If Op1.val < Op2.val Then Evaluar.val := 1 Else //error Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '>=': begin If Op1.val >= Op2.val Then Evaluar.val := 1 Else //error Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '<=': begin If Op1.val <= Op2.val Then Evaluar.val := 1 Else //error Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '|': begin //concatenación de cadenas Evaluar.val := Op1.val & Op2.val Evaluar.tip := TIP_CAD end; '~': begin //comparación de cadenas If (Op1.val Like Op2.val) Then Evaluar.val := 1 Else //no cuadra Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '&&': begin //And lógico If (val(Op1.val) = 1 And val(Op2.val) = 1) Then Evaluar.val := 1 Else //no cuadra Evaluar.val := 0 Evaluar.tip := TIP_NUM end; '||': begin If (val(Op1.val) = 0 And val(Op2.val) = 0) Then Evaluar.val := 0 Else //no cuadra Evaluar.val := 1 Evaluar.tip := TIP_NUM end; '!': begin If val(Op1.val) = 1 Then Evaluar.val := 0 Else //no cuadra Evaluar.val := 1 Evaluar.tip := TIP_NUM end;} Else begin Perr.GenError('No se reconoce operador: ' + opr, PosAct); Exit; End; end; //Completa campos de evaluar Evaluar.txt := Op1.txt + opr + Op2.txt; //texto de la expresión // Evaluar.uop := opr; //última operación ejecutada End; { TPError } procedure TPError.IniError; begin numER := 0; cadError := ''; arcER := ''; fil := 0; col := 0; end; procedure TPError.GenError(num: Integer; msje: String; archivo: String; nlin: integer = 0); //Genera un error begin numER := num; cadError := msje; arcER := archivo; fil := nlin; end; procedure TPError.GenError(msje: String; posCon: TPosCont); //Genera un error en la posición indicada begin numER := 1; cadError := msje; arcER := posCon.arc; fil := posCon.fil; col := posCon.col; end; function TPError.GenTxtError: string; //Genera una cadena con el mensaje de error de acuerdo al nivel de detalle que tenga. begin Result :=cadError; If arcER <> '' Then begin //agrega información de archivo Result += LineEnding + arcER; end; If fil <> 0 Then begin //Hay número de línea // Result := Pchar('[' + arcER + ']: ' + cadError + ' Línea: ' + IntToStr(fil); Result += LineEnding + '(' +IntToStr(fil) + ',' + IntToStr(col) + ') '; end; end; {procedure TPError.MosError; //Muestra un mensaje de error begin writeln(TxtError); No debe ser dependiente del tipo de Aplicación end;} function TPError.ArcError: string; //Devuelve el nombre del archivo de error begin ArcError := arcER; end; function TPError.nLinError: longint; //Devuelve el número de línea del error begin nLinError := fil; end; function TPError.nColError: longint; //Devuelve el número de línea del error begin nColError := col; end; function TPError.HayError: boolean; begin HayError := numER <> 0; end; { TContexto } //******************************************************************************** //Funciones Básicas para administración de los Contextos //******************************************************************************** constructor TContexto.Create; begin inherited; //solo se pone por seguridad, ya que no es necesario. lin := TStringList.Create; //crea lista de cadenas para almacenar el texto nlin := 0; CurPosFin; //inicia fil y col end; destructor TContexto.Destroy; begin lin.Free; //libera lista inherited Destroy; end; function TContexto.IniCont: Boolean; //Devuelve verdadero si se está al inicio del Contexto (fila 1, columna 1) begin Result := (fil = 1) And (col = 1); end; function TContexto.FinCont: Boolean; //Devuelve verdadero si se ha pasado del final del Contexto actual begin //Protección a Contexto vacío If nlin = 0 Then begin Result := True; Exit; End; //Verifica optimizando verificando primero la condición más probable If fil < nlin Then Result := False Else If fil > nlin Then Result := True Else If fil = nlin Then begin //Verifica si estamos en la línea final. //OJO, en la línea final no existe un salto de línea adicional If col >= Length(lin[fil-1]) + 1 Then Result := True Else Result := False End; end; function TContexto.VerCar: Char; //Devuelve el caracter actual begin If FinCont Then Exit(FIN_CON); If col = Length(lin[fil-1]) + 1 Then begin //Se está al fin de la línea. Se considera que cada línea //tiene un salto de línea al final, excepto la última línea. //En este caso siempre se devuelve FIN_LIN Result := FIN_LIN end Else //No se está al fin de la línea Result := lin[fil-1][col]; end; function TContexto.CogCar: Char; //Lee un caracter del contexto y avanza el cursor una posición. begin If FinCont Then Exit(FIN_CON); If col >= Length(lin[fil-1]) + 1 Then begin //Se está al fin de la línea. Trabaja igual que VerCar(). Result := FIN_LIN; col := 1; fil := fil + 1; //Pasa a siguiente fila, puede ser que se //haya pasado la cantidad de líneas disponibles end Else begin //No se está al fin de la línea Result := lin[fil-1][col]; inc(col); End; end; function TContexto.VerCarAnt: Char; //echa un vistazo al caracter anterior del Contexto //Si no hay caracter anterior, devuelve cadena vacía Var linact:String; begin Result := #0; If IniCont Then Exit; //No hay caracter anterior linact := lin[fil-1]; //línea actual If col = 1 Then //Está al inicio de una línea Result := FIN_LIN //devuelve el salto anterior Else Result := linact[col-1]; end; function TContexto.VerCarSig: Char; //Devuelve el catacter siguiente al actual. OJO: Solo mira la línea actual. begin If FinCont Then Exit(FIN_CON); If col >= Length(lin[fil-1]) Then begin Result := FIN_LIN end Else //No se está al fin de la línea Result := lin[fil-1][col+1]; end; function TContexto.CapBlancos: Boolean; //Coge los blancos iniciales del contexto de entrada. //Si no encuentra algun blanco al inicio, devuelve falso begin Result := False; if not (VerCar in [' ', FIN_LIN, #9]) then exit; //no hay blancos repeat CogCar until FinCont or not (VerCar in [' ', FIN_LIN, #9]); end; procedure TContexto.CurPosIni; //Mueve la posición al inicio del contenido. begin if lin.Count = 0 then begin fil := 0; col := 0; end else begin fil := 1; col := 1; //posiciona al inicio end; end; procedure TContexto.CurPosFin; //Mueve la posición al final del contenido. begin if lin.Count = 0 then begin fil := 0; col := 0; end else begin fil := lin.Count; col := length(lin[fil-1])+1; //posiciona al final end; end; procedure TContexto.PonSalto; //Escribe un salto de línea en el contexto begin lin.Add(''); fil := lin.Count; //actualiza filas col := 1; //posiciona en primera columna end; procedure TContexto.SacLinea; //Saca la última línea del contexto. Debe haber por lo menos una línea begin if lin.Count = 0 then exit; lin.Delete(lin.Count-1); //elimina última línea CurPosFin; //actualiza posición de cursor end; procedure TContexto.PonCar(c: char); //Escribe un caracter en el contexto. Debe haber por lo menos una línea begin if lin.Count = 0 then exit; //sin datos if c = FIN_LIN then //caracter de salto de línea PonSalto else begin //caracter normal lin[lin.Count-1] := lin[lin.Count-1] + c; //agrega a línea actual inc(col); //actualiza columna end; end; procedure TContexto.PonCad(s: String); //Escribe una cadena (sin saltos) en el contexto. Debe haber por lo menos una línea begin if lin.Count = 0 then exit; //sin datos lin[lin.Count-1] := lin[lin.Count-1] + s; //agrega a línea actual col += length(s); //actualiza columna end; procedure TContexto.SacCar; //Quita un caracter del contexto var n:integer; begin n := lin.Count; if n = 0 then exit; //sin datos if (n = 1) and (length(lin[0])=0) then exit; //sin datos //hay datos if col = 1 then //al inicio de línea SacLinea else begin lin[n-1] := copy(lin[n-1],1,col-2); //recorta CurPosFin; //actualiza posición de cursor end; end; function TContexto.LeeCad: string; //Devuelve el contenido del contexto en una cadena. begin Result := lin.text; end; procedure TContexto.FijCad(cad: string); //Fija el contenido del contexto con una cadena. begin tip := TC_TXT; //indica que contenido es Texto if cad='' then begin //cadena vacía, crea una línea vacía lin.Clear; lin.Add(''); nlin := 1; //actualiza número de líneas end else begin lin.Text := cad; nlin := lin.Count; //actualiza número de líneas end; CurPosFin; //actualiza posición de cursor arc := ''; //No se incluye información de archivo end; procedure TContexto.FijArc(arc0: string); //Fija el contenido del contexto con un archivo begin tip := TC_TXT; //indica que contenido es Texto lin.LoadFromFile(arc0); nlin := lin.Count; //actualiza número de líneas CurPosFin; //actualiza posición de cursor arc := arc0; //No se incluye información de archivo end; initialization PPro.Create; finalization PPro.Destroy; end.