Indice:
Introduction
The function of a compiler is the one to translate a source code to an object code. In the case of hy4GL, the source code is language 4GL. The compiler takes like entrance a program writing in language 4GL and translates it to its equivalent one in language C. In a second step is used one external tool (the compiler of C) to generate code in ensablador.
Language C for several reasons has been chosen. In the first place, the objective of the project is to obtain a tool to program applications of data bases that are related to servers based on PostgreSQL, and PostgreSQL offers bookstores of functions to accede to his data bases dese language C. On the other hand, this language allows a greater portability and one independence of the platform that could not be obtained of another way.
Not to lose portability, the generated code uses bookstores of functions found in all the UNIX platforms. For generation of the user interface it has been chosen by ncurses, one bookstore of functions that offer a method to accede to terminals in way independent text of the type of used terminal. In addition, ncurses includes functions to generate and to handle menus and entrance forms, which has facilitated this section of the code generator.
hy4GL has been structured in two parts: a tool to obtain feasible programs a to divide of source code in 4GL and a bookstore of functions that will use these programs in time of execution. The run-Time bookstore includes the functions of handling of the internal battery, those of conversion of types and functions that can use the programmer to obtain information like present time/date, or the length of a chain. The 4GL compiler consists of the following parts:
. lexical analyzer (to scanner),
. parser (to parser),
. semantic analyzer and
. code generator.
All these parts are interconnected. exit of one is the entrance of the following one. In case of hy4GL, the four parts are not totally sequential: the semantic analysis is made to same time that the syntactic analysis and the generation of code.
Figure 1: Modules of the compiler
Módu those of comp ilador
Analyzed
lexicon
(to scanner)
Tico analysis semán
Analyzed r
sintác tico
(to parser)
Generated r of
Cód igo
Lexical analyzer (to scanner)
The lexical analyzer taking as entered the program written in language 4GL and divides it in tokens. To each they token is associated an attribute to him. Thus a whole number produces token TK_CONS and its attribute associate is its numerical value. The used lexical analyzer in hy4GL has been generated using tool LEX 1. One is a tool that generates programs in language C which lexical text patrons recognize. program is generated from a file in that specifies a series of tuplas (called rules) formed by regular expressions and code in C that will be executed whenever find the regular expression in the source.
The behavior of the lexical analyzer depends on the context in which we are: to to arrive at a whole number is obtained token TK_CONS normally, but if at this moment we were within a commentary, is not obtained token some. Four contexts are defined different for the operation from this lexical analyzer: an initial context, in which it finds the analyzer normally; a context for the commentaries between keys (format of Informix-4GL) and another one for the commentaries between symbols / * and * / (format of C). Exist additional context in which is the analyzer between the symbols ' c' and ' c. ' (introduction of sentences in contracted C).
The language has a series of reserved words that cannot be used like names of
variables. When the lexical analyzer finds a sequence of characters that
could be
varname, this one is looked for in a table of reserved words. If one is, lexical
analyzer gives back token corresponding to the reserved word. In opposite case,
he gives back token TK_ID (identifier) and a chain with the name like attribute.
The search in the list of reserved words is made by means of an algorithm
search dichotomizing to accelerate the process. The list of reserved words
of language 4GL and his tokens associated one is in the table of the
following page. The reserved words are not sensible to very small capital
letters and. Nevertheless,
identifiers yes are it.
1 Concretely, version 2,5,4 of GNU lexical Fast to analyzer generator (flex)
Reserved words
" abort " TK_ABORT
" to after " TK_AFTER,
" and " TK_AND
" ace " TK_AS
" AT " TK_AT
" attribute " TK_ATTR
" attributes " TK_ATTR
" before " TK_BEFORE
" begin " TK_BEGIN
" to border " TK_BORDER
" bright " TK_BRIGHT
" browse " TK_BROWE
" button " TK_BUTTON
" by " TK_BY
" call " TK_CALL
" to char " TK_CHAR
" check " TK_CHECK
" to clear " TK_CLEAR
" clipped " TK_CLIPPED
" close " TK_CLOSE
" columns " TK_COLS
" command " TK_COMMAND
" commit " TK_COMMIT
" continue " TK_CONTINUE
" create " TK_CREATE
" current " TK_CURRENT
" cursor " TK_CURSOR
" database " TK_DB
" it declares " TK_DECLARE
" it defines " TK_DEFINE
" delay " TK_DELAY
" delete " TK_DELETE
" display " TK_DISPLAY
" double " TK_DOBLE
" drop " TK_DROP
" else " TK_ELSE
" end " TK_END
" every " TK_EVERY
" exec_sql " TK_EXEC
" exit " TK_EXIT
" extern " TK_EXTERN
" fetch " TK_FETCH
" field " TK_FIELD
" finish " TK_FINISH
" first " TK_FIRST
" to footer " TK_FOOTER
" for " TK_FOR
" foreach " TK_FOREACH
" form " TK_FORM
" format " TK_FORMAT
" from " TK_FROM
" function " TK_FUNCTION
" group " TK_GROUP
" to header " TK_HEADER
" horizontal " TK_HORIZ
" if " TK_IF
" input " TK_INPUT
" insert " TK_INSERT
" into " TK_INTO
" key " TK_KEY
" label " TK_LABEL
" last " TK_LAST
" let " TK_LET
" like " TK_LIKE
" local " TK_LOCAL
" main " TK_MAIN
" menu " TK_MENU
" move " TK_MOVE
" next " TK_NEXT
" of " TK_OF
" on " TK_ON
" TK_OPEN open "
" or " TK_OR
" to order " TK_ORDER
" output " TK_OUTPUT
" to over " TK_OVER
" page " TK_PAGE
" pipe " TK_PIPE
" previous " TK_PREVIOUS
" print " TK_PRINT
" prompt " TK_PROMPT
" radio " TK_RADIO
" record " TK_RECORD
" report " TK_REPORT
" return " TK_RETURN
" returning " TK_RETURNING
" row " TK_ROW
" rows " TK_ROWS
" select " TK_SELECT
" start " TK_START
" TK_THEN then "
" title " TK_TITLE
" to " TK_TO
" to trailer " TK_FOOTER
" transaction " TK_TRANSACTION
" using " TK_USING
" validate " TK_VALIDATE
" values " TK_VALUES
" vertical " TK_VERT
" where " TK_WHERE
" while " TK_WHILE
" window " TK_WINDOW
" with " TK_WITH
Parser (to parser)
The parser is a program (or function) that receives tokens recognized by lexical analyzer and determines if they adjust or not to language 4GL. In case that thus he is, tokens and their corresponding attributes are organized forming a tree of abstract syntax. During the generation of the tree of abstract syntax some verifications are made semantic: like verifying the existence of variables, or the type of a cursor.
Like the lexical analyzer, the parser also has been generated using one
tool designed for such aim. This tool is YACC 2, that generates parser
starting off of
a grammar described in a file.
Concrete grammar
In the first place the concrete grammar of the language has been designed:
fich: file
| database file
file: declarations functions
| functions
functions: functions function
| function
function: TK_FUNCTION TK_ID ' (' ') ' actions TK_END TK_FUNCTION
| TK_MAIN actions TK_END TK_MAIN
function: TK_FUNCTION TK_ID ' (' lst_identif ') ' actions TK_END TK_FUNCTION
| TK_EXTERN TK_ID ' (' ') ' type
| report
| form_def
actions: declarations sentences
| sentences
declarations: declaration
| declarations declaration
2 In fact BISON has been used. One is the generator of parsers of GNU. BISON is compatible with the standard YACC, although contributes enough characteristics not including in YACC.Sin embargo, and to avoid problems of portability, has not been used any of these characteristics.
declaration: TK_DEFINE lst_identif type
type: TK_ID
| TK_CHAR
| TK_CHAR ' (' TK_CONS ') '
| TK_RECORD fields TK_END TK_RECORD
| TK_LIKE TK_ID '. ' TK_ID
| TK_RECORD TK_LIKE TK_ID '. ' ' * '
fields: fields field
| field
field: TK_ID type
sentences: error
| sentences
| sentences sentences
it sentences: fuente_c
| exit_program
| allocation
| call
| return
| reading
| to write
| opventana
| bloquecontrol
| TK_DELAY expression
| SQL
| menu
| exitmenu
| sent_browse
| sent_report
| sent_form
exit_program: TK_EXIT TK_ID
fuente_c: TK_FUENTE_C
| fuente_c TK_FUENTE_C
opventana: TK_OPEN TK_WINDOW TK_ID TK_AT expression ', ' expression
TK_WITH expression TK_ROWS ', ' tit_ventana expression TK_COLS
| TK_OPEN TK_WINDOW TK_ID TK_AT expression ', ' expression
TK_WITH expression TK_ROWS ', ' expression TK_COLS
Tit_ventana TK_ATTR atributos_vent
| TK_CLOSE TK_WINDOW TK_ID
| TK_MOVE TK_WINDOW TK_ID TK_TO expression ', ' expression
| TK_CURRENT TK_WINDOW TK_ID
| TK_CLEAR TK_WINDOW
tit_ventana:
| TK_TITLE expression
atributos_vent: atributos_vent ', ' atributo_vent
| atributo_vent
| ' (' atributos_vent ') '
atributo_vent: color
| color TK_OVER color
| TK_BORDER
| TK_DOBLE TK_BORDER
| TK_BRIGHT
color: TK_ID
call: TK_ID ' (' listaexpresiones ') '
| TK_ID ' (' ') '
called | TK_CALL
to write: TK_DISPLAY lista_expr_disp
lista_expr_disp: expr_disp
| lista_expr_disp ', ' expr_disp
expr_disp: listaexpresiones
| listaexpresiones TK_AT expression ', ' expression
| listaexpresiones TK_ATTR atributos_vent
| listaexpresiones TK_AT expression ', ' expression
TK_ATTR atributos_vent
reading: TK_PROMPT TK_FOR identif
| TK_PROMPT TK_FOR TK_CHAR identif
| TK_PROMPT expression TK_FOR identif
| TK_PROMPT expression TK_FOR TK_CHAR identif
return: TK_RETURN listaexpresiones
| TK_RETURN
allocation: TK_LET lst_identif TK_IGUAL listaexpresiones
| call TK_RETURNING lst_identif
listaexpresiones: expression
| listaexpresiones ', ' expression
| ' (' listaexpresiones ') '
| TK_ID '. ' ' * '
| listaexpresiones ', ' TK_ID '. ' ' * '
expression: identif
| expression ' [ ' expression ' ] '
| expression ' [ ' expression ', ' expression ' ] '
| TK_REAL
| TK_CONS
| TK_CADENA
| bin aria
| unaria
| '(' expresion ')'
| llamada
| expresion TK_USING TK_CADENA
| expresion TK_CLIPPED
lst_identif : lst_identif ',' identif
| identif
| lst_identif ',' TK_ID '.' '*'
| TK_ID '.' '*'
identif : TK_ID
| TK_ID '.' TK_ID
binaria : expresion '+' expresion
| expresion '-' expresion
| expresion '*' expresion
| expresion '/' expresion
| expresion TK_AND expresion
| expresion TK_OR expresion
| expresion '>' expresion
| expresion TK_MAYORIGUAL expresion
| expresion TK_MENORIGUAL expresion
| expresion ' <' expresion
| expresion TK_DISTINTO expresion
| expresion TK_IGUAL expresion
unaria : '-' expresion
| '!' expresion
bloquecontrol : blqif
| blqwhile
| cont_while
| exit_while
| blqfor
| cont_for
| exit_for
| foreach
| cont_foreach
| exit_foreach
foreach : TK_FOREACH TK_ID TK_INTO lst_identif sentencias TK_END TK_FOREACH
cont_foreach : TK_CONTINUE TK_FOREACH
exit_foreach : TK_EXIT TK_FOREACH
blqif : TK_IF expresion then sentencias TK_END TK_IF
| TK_IF expresion then sentencias TK_ELSE sentencias TK_END TK_IF
then :
| TK_THEN
blqwhile : TK_WHILE expresion sentencias TK_END TK_WHILE
cont_while : TK_CONTINUE TK_WHILE
exit_while : TK_EXIT TK_WHILE
blqfor : TK_FOR identif TK_IGUAL expresion TK_TO expresion
sentencias TK_END TK_FOR
cont_for : TK_CONTINUE TK_FOR
exit_for : TK_EXIT TK_FOR
sql : database
| declare
| select
| insert
| delete
| transacc_begin
| transacc_commit
| transacc_abort
| open_cursor
| close_cursor
| fetch_cursor
| exec_sql
database : TK_DB TK_ID
declare : TK_DECLARE TK_ID decl_local TK_CURSOR TK_FOR select
| TK_DECLARE TK_ID decl_local TK_CURSOR TK_FOR expresion
decl_local :
| TK_LOCAL
open_cursor : TK_OPEN TK_CURSOR TK_ID
close_cursor : TK_CLOSE TK_CURSOR TK_ID
fetch_cursor : TK_FETCH fetch_dir TK_ID TK_INTO lst_identif
fetch_dir :
| TK_NEXT
| TK_PREVIOUS
exec_sql : TK_EXEC expresion into
select : TK_SELECT lst_campos into TK_FROM lst_tablas where orden
delete : TK_DELETE TK_FROM TK_ID where
lst_campos : campo_tabla
| lst_campos ',' campo_tabla
campo_tabla : '*'
| TK_ID
| TK_ID '(' lst_campos ')'
into :
| TK_INTO lst_identif
where :
| TK_WHERE expresion
orden :
| TK_ORDER TK_BY lst_campos
lst_tablas : TK_ID
| lst_tablas ',' TK_ID
insert : TK_INSERT TK_INTO TK_ID TK_VALUES '(' listaexpresiones ')'
transacc_begin : TK_BEGIN TK_TRANSACTION
transacc_abort : TK_ABORT TK_TRANSACTION
transacc_commit : TK_COMMIT TK_TRANSACTION
menu : TK_MENU expresion menuformat menuattr menuops TK_END TK_MENU
menu : TK_MENU menuformat menuattr menuops TK_END TK_MENU
menuops : menuops menuop
| menuop
menuop : TK_COMMAND expresion ',' expresion sentencias
| TK_COMMAND expresion sentencias
| TK_ON TK_KEY '(' TK_ID ')' sentencias
exitmenu : TK_EXIT TK_MENU
menuattr : TK_ATTR atributos_vent ';' atributos_vent
| TK_ATTR atributos_vent
|
menuformat :
| TK_HORIZ
| TK_VERT
| TK_VERT TK_HORIZ
| TK_HORIZ TK_VERT
sent_browse : browse
| browse_exit
browse_exit : TK_EXIT TK_BROWSE
browse : TK_BROWSE TK_ID TK_INTO lst_identif
browse_donde
browse_titulo
TK_DISPLAY expresion
browseops TK_END TK_BROWSE
browse_donde :
| TK_FROM expresion ',' expresion TK_TO expresion ',' expresion
browse_titulo :
| TK_TITLE expresion
browseops : browseop
| browseops browseop
browseop : TK_ON TK_KEY '(' TK_ID ')' sentencias
| TK_ON TK_KEY '(' TK_CADENA ')' sentencias
| TK_ON TK_KEY '(' TK_CONS ')' sentencias
sent_report : start_r
| output_r
| finish_r
| rpt_print
start_r : TK_START TK_REPORT TK_ID
output_r : TK_OUTPUT TK_TO TK_REPORT TK_ID '(' listaexpresiones ')'
finish_r : TK_FINISH TK_REPORT TK_ID
rpt_print : TK_PRINT expresion
report : TK_REPORT TK_ID '(' lst_identif ')' declaraciones
rpt_salida rpt_formato TK_END TK_REPORT
rpt_salida : TK_OUTPUT rpt_formato_pg
rpt_formato_pg :
| rpt_item_fmt
| rpt_formato_pg rpt_item_fmt
rpt_item_fmt : TK_REPORT TK_TO TK_CADENA
| TK_REPORT TK_TO TK_PIPE TK_CADENA
| TK_PAGE TK_ID TK_CONS
rpt_formato : TK_FORMAT rpt_first_pg_hd rpt_pg_hd
rpt_before_gr rpt_every rpt_after_gr rpt_pg_ft rpt_last_rw
rpt_first_pg_hd :
| TK_FIRST TK_PAGE TK_HEADER sentencias
rpt_pg_hd :
| TK_PAGE TK_HEADER sentencias
rpt_before_gr :
| rpt_item_before_gr
| rpt_before_gr rpt_item_before_gr
rpt_item_before_gr : TK_BEFORE TK_GROUP TK_OF lst_identif sentencias
rpt_every :
| TK_ON TK_EVERY TK_ROW sentencias
rpt_after_gr :
| rpt_item_after_gr
| rpt_after_gr rpt_item_after_gr
rpt_item_after_gr : TK_AFTER TK_GROUP TK_OF lst_identif sentencias
rpt_pg_ft :
| TK_PAGE TK_FOOTER sentencias
rpt_last_rw :
| TK_ON TK_LAST TK_ROW sentencias
sent_form : form_create
| form_display
| form_dispByName
| form_inputByName
| form_exit
| form_validate
| form_current
form_create : TK_CREATE TK_FORM TK_ID
form_display : TK_DISPLAY TK_FORM TK_ID
form_dispByName : TK_DISPLAY TK_BY TK_ID lst_identif
form_inputByName : TK_INPUT TK_BY TK_ID lst_identif
| TK_INPUT TK_BY TK_ID lst_identif form_onkeys
form_afterfields TK_END TK_INPUT
| TK_INPUT TK_BY TK_ID lst_identif form_afterfields TK_END TK_INPUT
| TK_INPUT TK_BY TK_ID lst_identif form_onkeys TK_END TK_INPUT
form_onkeys : form_onkey
| form_onkeys form_onkey
form_afterfields : form_afterfield
| form_afterfields form_afterfield
form_onkey : TK_ON TK_KEY '(' TK_ID ')' sentencias
| TK_ON TK_KEY '(' TK_CADENA ')' sentencias
| TK_ON TK_KEY '(' TK_CONS ')' sentencias
| TK_ON TK_BUTTON TK_ID sentencias
form_afterfield : TK_AFTER TK_FIELD TK_ID sentencias
form_exit : TK_EXIT TK_INPUT
form_validate : TK_VALIDATE TK_FIELD
form_current : TK_CURRENT TK_FIELD TK_ID
| TK_NEXT TK_FIELD TK_ID
form_def : TK_FORM TK_ID form_elementos TK_END TK_FORM
form_elementos : form_elemento
| form_elementos form_elemento
form_elemento :
| TK_INPUT TK_ID TK_FROM expresion ',' expresion
TK_TO expresion ',' expresion form_attribs
| TK_BUTTON TK_ID TK_AT expresion ',' expresion form_attribs
| TK_RADIO TK_ID TK_AT expresion ',' expresion form_attribs
| TK_CHECK TK_ID TK_AT expresion ',' expresion form_attribs
form_attribs :
| TK_ATTR atributos_vent
Árbol de sintaxis abstracta y tabla de símbolos
Para la generación del árbol de sintaxis abstracta se ha usado la HESA 3 . Se
trata de un programa que genera una serie de estructuras y funciones en C
que facilitan la organización de los tokens y atributos formando el árbol
de sintaxis abstracta. La HESA toma como entrada un fichero en el que se
especifica la sintaxis abstracta del lenguaje. En nuestro caso, la sintaxis es
la siguiente:
%{
#include "global.h"
%}
%union {cadena nombre;
double valor;
}
%type <nombre> Identificador
%type <valor> Constante
%%
Fichero :: var: Variables; fun: Funciones
Funciones :: Funcion *
Funcion :: nom: Identificador; par: ListaId; cod: Acciones; tip: Tipo
ListaId :: Identificador *
Rect :: y,x,yy,xx: Expresion
Tipo :: tip: Identificador; tam: Constante; cam: Variables
Acciones :: var: Variables; cod: Sentencias
Variables :: Variable *
Variable :: nom: Identificador; tip: Tipo
Sentencias :: Sentencia *
Sentencia :: FuenteC | ExitProgram | Asignacion | Llamada | Retorno | Bloque
| Leer | Escribir | Sql | OpVentana | Delay | OpMenu | OpReport
| OpForm | OpBrowse
FuenteC:: Identificador *
OpMenu:: Menu | ExitMenu | MenuAttr
Menu :: nom: Identificador; exp: Expresion; fmt: Constante;
att: MenuAttr; opc: MenuOps
MenuOps :: MenuOp *
MenuOp :: nom: Expresion; desc: Expresion; cod: Sentencias; key: Constante
MenuAttr :: fore,back: AttsVentana
3 HESA: Herramienta de Especificación de Sintaxis Abstracta, del Dpto de
Lenguajes y Sistemas Informáticos de la Universidad de Sevilla.
OpForm:: Form | FormDisplay | FormCreate |
OpFormDispByName | OpFormInputByName | FormExit |
FormCurrentField | FormValidateField
FormDisplay:: nom: Identificador
FormCreate:: nom: Identificador
Form:: nom: Identificador; campos: FormCampos
FormCampos:: FormCampo *
FormCampo:: tipo: FormTipoCampo; nom: Identificador;
y,x,yy,xx: Expresion; attr: AttsVentana
FormTipoCampo:: FormButton | FormInput | FormLabel | FormCheck
FormDispByName:: campos: ListaId
FormInputByName:: campos: ListaId; opc: FormOnKeys; after: FormAfterFields
FormOnKeys :: FormOnKey *
FormOnKey :: nom: Identificador; cod: Sentencias
FormAfterFields :: FormAfterField *
FormAfterField :: nom: Identificador; cod: Sentencias
FormCurrentField :: nom: Identificador
OpBrowse:: Browse | BrowseExit
Browse :: nom: Identificador; cur: Identificador; vars: ListaId;
donde: Rect; titulo: Expresion; exp: Expresion; opc: BrowseOps
BrowseOps :: BrowseOp *
BrowseOp :: nom: Identificador; cod: Sentencias
OpReport :: StartReport | FinishReport | PrintReport
Report :: nom: Identificador; par: ListaId; var: Variables;
out: RptOutput; fmt: FormatoRpt
StartReport :: nom: Identificador
FinishReport :: nom: Identificador
PrintReport :: exp: Expresion
RptOutput:: Identificador *
FormatoRpt:: fph,ph:Sentencias; bg: RptGrupos; ev: Sentencias;
ag: RptGrupos; pf,lr: Sentencias
RptGrupos:: RptGrupo *
RptGrupo:: ids: ListaId; cod: Sentencias
Sql :: Database | Select | Insert | Update | Delete | Declare | ExecSQL
Database :: nom: Identificador
Declare :: nom: Identificador; local: Constante; query: Select;
es_select: Constante
Select :: cam: ListaId; into: ListaId; from: ListaId;
where: Condicion; order: ListaId; asc: Constante
ExecSQL :: comando: Expresion; into: ListaId
Insert :: tabla: Identificador; valores: ListaId
Delete :: tabla: Identificador; where: Condicion
Transacc :: op: Identificador
OpenCursor :: nom: Identificador
CloseCursor :: nom: Identificador
FetchCursor :: nom: Identificador; next: Constante; vars: ListaId
Delay :: tiempo: Expresion
OpVentana :: AbVentana | CieVentana | MovVentana | ClearWindow
AbVentana :: nom: Identificador; y, x, h, w: Constante;
attr: AttsVentana; tit: Expresion
CieVentana :: nom: Identificador
MovVentana :: nom: Identificador; y, x: Expresion
CurVentana :: nom: Identificador
AttsVentana :: AtVentana *
AtVentana :: nom: Identificador; val: Constante
Leer :: esc: Expresion; var: Variable; cha: Constante
Escribir :: lst: Displays
Displays :: Display *
Display :: y,x: Expresion; exp: Expresiones; attr: AttsVentana
Llamada :: nom: Identificador; arg: Expresiones
Llamaproc :: nom: Identificador; arg: Expresiones
Retorno :: exp: Expresiones
Asignacion :: des: ListaId; fue: Expresiones
Bloque :: While | ContinueWhile | ExitWhile | For | ContinueFor | ExitFor |
If | Foreach | ContinueForeach | ExitForeach
While :: con: Condicion ; cod: Sentencias
For :: cont: Id; desde, hasta: Expresion; cod: Sentencias
If :: con: Condicion ; codsi, codelse: Sentencias
Foreach :: nom: Identificador; vars: ListaId; cod: Sentencias
Condicion :: termi, termd: Expresion; op: Logica
Expresiones :: Expresion *
Expresion :: Id | Const | Binaria | Unaria | Llamada |
Cadena | Subcadena | Using | Clipped
Id :: nom: Identificador
Subcadena :: exp: Expresion; desde, hasta: Expresion
Using :: exp: Expresion; mascara: Identificador
Clipped :: exp: Expresion
Const :: val: Constante; tip: Identificador
Cadena :: val: Identificador
Unaria :: term: Expresion; op: Opunar
Opunar :: Menos | Negacion
Binaria :: termi,termd: Expresion; op: Opbina
Opbina :: Aritmetica | Logica
Aritmetica :: Suma | Resta | Multiplica | Divide | Pot
Logica :: Or | And | Menor | Mayor | Igual | Distinto
| Not | Menorigual | Mayorigual
A partir de este fichero, la HESA genera un conjunto de funciones y
estructuras que permiten tanto la generación del árbol de sintaxis
abstracta como su recorrido posterior durante la fase de generación de código.
Estas funciones se han usado para atribuir la gramática concreta que se
detalló anteriormente. De esta manera, el árbol de sintaxis abstracta se
va generando a medida que se van reconociendo las estructuras del lenguaje
en el fichero fuente.
Debido a su extensión no se incluye en este manual el fuente de la gramática
atribuida. Se puede consultar en el archivo 'sintax.y'.
Para el desarrollo de este proyecto no se ha implementado una tabla de símbolos
como tal. Toda la información sobre las variables, su ámbito y su tipo
se obtiene directamente del árbol de sintáxis abstracta. Cuando una
sentencia hace referencia a una veriable, ésta se busca en primer lugar
en la rama del árbol de sintaxis abstracta que corresponde con las
declaraciones de variables locales a la función en la que nos encontramos.
Si no se encuentra en esta rama, se busca en la rama que corresponde a las
declaraciones de variables globales. Y por último se intenta encontrar en la
lista de variables externas.
En el siguiente fragmento de la sintaxis atribuida se puede observar cómo
durante el análisis sintáctico se hacen comprobaciones semánticas, como
no permitir que una variable se declare más de una vez:
declaracion: TK_DEFINE lst_identif tipo
{
Arbol una,todas;
int i;
char *nomvar;
todas=NULL;
for (i=1;i <=ListaId_longitud($2);i++)
{
nomvar=Identificador_nombre(Id_nom(ListaId_elemento($2,i)));
if (Busca_Var(nomvar,lst_locales))
{
yyerror("Variable %s ya declarada",nomvar);
}
una=Variable(Asigna_nombre(nomvar),$3);
todas=Variables(todas,una);
}
$$=todas;
}
;
Cada identificador incluido en la lista de identificadores es buscado en la
lista de variables locales. La variable lst_locales es una variable global
que apunta en cada momento a la raíz del árbol con las declaraciones de
variables locales de la función que se está procesando. En caso de encontrarse
una variable ya declarada con el mismo nombre, se produce un error. La función
Busca_Var recorre el árbol que se le indique como segundo parámetro, y
devuelve un puntero al
nodo en el que se encuentre el identificador que se esté buscando (primer parámetro).
En caso de no encontrarlo, devuelve NULL.
Figura 2. Busqueda de variables en el árbol de sintáxis abstracta.
/* Funcion que busca un simbolo en un arbol */
/* Si lo encuentra, devuelve un nodo con su desc. Si no, devuelve NULL */
Arbol Busca_Var(char *id,Arbol ptr)
{
Arbol aux=NULL, encontrado=NULL;
int i,cuantas;
cuantas=Variables_longitud(ptr);
for (i=1;i <=cuantas;i++)
{
aux=Variables_elemento(ptr,i);
if (!Constructor(aux))
break;
if (!strcmp(Identificador_nombre(Variable_nom(aux)),id))
{
encontrado=aux;
break;
}
}
return encontrado;
}
Generación de Código
La última tarea que realiza el compilador es la generación del código en
lenguaje C a partir del árbol de sintaxis abstracta. Este código en
lenguaje C será posteriormente compilado usando un compilador de C para
obtener el ejecutable final. Se ha prestado especial atención en hacer
que el fuente generado se legible. Se incluyen varios comentarios que
indican la sentencia original a la que corresponden los bloques de código C.
Además, el código se genera indentado para mejorar su legibilidad.
En primer lugar se genera siempre una parte fija, que incluye un
comentario con el nombre del fuente original y la versión del compilador.
Se incluyen los archivos de cabecera necesarios para compilar el programa
en C y se generan las declaraciones de variables internas usadas por las
librerías de run-time. A partir de ese momento, se comienza a recorrer el árbol
de sintaxis abstracta que se ha generado durante la fase de análisis sintáctico.
Variables globales.
El primer nodo del árbol de sintaxis abstracta corresponde a la lista de variables globales del programa. La misma función con la que se generan las variables globales es la siguiente: void genVariables(Arbol vars, char *prefijo)
{
Arbol aux;
int i,cuantas;
cuantas=Variables_longitud(vars);
for (i=0;i <cuantas;i++)
{
aux=Variables_elemento(vars,i+1);
if (!Constructor(aux))
break;
genVariable(aux,prefijo);
}
}
La función recorre todos los elementos de la lista de variables, llamando a la función genVariable para cada nodo. La función genVariable será la que realmente genere el código para cada variable:
void genVariable(Arbol var, char *prefijo)
{
cadena tipo,nombre;
int tamanno;
strcpy(nombre, Identificador_nombre(Variable_nom(var)));
strcpy(tipo, Identificador_nombre(Tipo_tip(Variable_tip(var))));
tamanno=Constante_valor(Tipo_tam(Variable_tip(var)));
if (!strcmp(tipo,"WINDOW"))
genera(indentacion,"%s _WINDOW *%s=NULL;\n",prefijo,nombre);
else if (!strcmp(tipo,"FORM"))
{
genera(indentacion,"%s _4GLFORM %s=NULL;\n",prefijo,nombre);
genera(indentacion,"%s _4GLFORM _GenForm%s();\n",prefijo,nombre);
}
else if (!strcmp(tipo,"CURSOR"))
genera(indentacion,"%s _CURSOR _C%s;\n",prefijo,nombre);
else if (!strcmp(tipo,"LCURSOR"))
genera(indentacion,"%s _CURSOR _C%s;\n",prefijo,nombre);
else if (!strcmp(tipo,"MENU"))
{
genera(indentacion,"%s _MENU %s;\n",prefijo,nombre);
genera(indentacion,"%s ITEM *%s_items[%d];\n",prefijo,nombre,tamanno);
}
else if (!strcmp(tipo,"BROWSE"))
genera(indentacion,"%s _BROWSE %s;\n",prefijo,nombre);
else if (!strcmp(tipo,"REPORT"))
{
genera(indentacion,"/* Funciones del report %s */\n",nombre);
genera(indentacion,"%s int _RPT_%s_inic;\n",prefijo,nombre);
genera(indentacion,"void _Fn_STARTRPT_%s();\n",nombre);
genera(indentacion,"void _Fn_OutputRpt_%s();\n",nombre);
genera(indentacion,"void _Fn_RPT_%s();\n",nombre);
genera(indentacion,"/*---*/\n");
}
else if (!strcmp(tipo,"int"))
genera(indentacion,"%s int %s;\n",prefijo,nombre);
else if (!strcmp(tipo,"float"))
genera(indentacion,"%s double %s;\n",prefijo,nombre);
else if (!strcmp(tipo,"date"))
genera(indentacion,"%s _4GLDATE %s;\n",prefijo,nombre);
else if (!strcmp(tipo,"time"))
genera(indentacion,"%s _4GLTIME %s;\n",prefijo,nombre);
else if (!strcmp(tipo,"datetime"))
genera(indentacion,"%s _4GLDATETIME %s;\n",prefijo,nombre);
else if (!strcmp(tipo,"interval"))
genera(indentacion,"%s _4GLINTERVAL %s;\n",prefijo,nombre);
else if (!strcmp(tipo,"char"))
genera(indentacion,"%s char %s[%d];\n",prefijo,nombre,tamanno+1);
else if (!strcmp(tipo,"record"))
genRecord(var);
else
genera(indentacion,"%s %s %s;\n",prefijo,tipo,nombre);
}
La función comprueba el tipo de datos de la variable, y genera una declaración en C para ese tipo de datos. Aquellos tipos que existen en C (char, int, float) se declaran directamente, mientras que los tipos añadidos por el lenguaje 4GL (date, time, datetime, interval) y las variables de control necesarias para el manejo de menús, consultas SQL, formularios, ventanas, etc. se declaran como tipos definidos en el archivo de cabecera hy4gl.h Si la variable que se está generando es de tipo reistro, recorrerá la lista de campos, generando un registro de C con la estructura necesaria:
void genRecord(Arbol var)
{
Arbol aux;
int i,cuantas;
cuantas=Variables_longitud(Tipo_cam(Variable_tip(var)));
genera(indentacion,"struct\n");
genera(indentacion,"{\n");
indentacion++;
for (i=0;i <cuantas;i++)
{
aux=Variables_elemento(Tipo_cam(Variable_tip(var)),i+1);
if (!Constructor(aux))
break;
genVariable(aux,"");
}
indentacion--;
genera(indentacion,"} %s;\n",Identificador_nombre(Variable_nom(var)));
}
Funciones
El siguiente nodo del árbol de sintaxis abstracta es la lista de funciones que se encuentran en el fuente en 4GL. Esta lista también incluye la definición de los listados y de los formularios de entrada, que se definen fuera de las funciones. El generador de código recorre esta lista, comprobando si se trata de una función, un listado o un formulario de entrada, y generando el código correspondiente a cada uno de ellos.:
void genFunciones()
{
Arbol aux;
int i,cuantas;
cuantas=Funciones_longitud(Fichero_fun(raiz));
for (i=1;i <=cuantas;i++)
{
aux=Funciones_elemento(Fichero_fun(raiz),i);
if (!Constructor(aux))
break;
if (Constructor(aux)==REPORT)
genReport(aux);
else if (Constructor(aux)==FORM)
genForm(aux);
else if (Constructor(Funcion_cod(aux)))
genFuncion(aux);
}
}
En el caso de que se tratara de una función, se generan las declaraciones de
las variables locales y de los parámetros. Seguidamente se genera el código
necesario para desapilar los parámetros de entrada y por último se
genera el código de las sentencias que componen la función:
void genFuncion(Arbol fn)
{
indentacion=0;
lst_locales=Acciones_var(Funcion_cod(fn));
printf("Generando función: %s\n",Identificador_nombre(Funcion_nom(fn)));
genera(indentacion,"\n/*********************************************\n");
genera(indentacion," * Función: %-30.30s*\n",
Identificador_nombre(Funcion_nom(fn)));
genera(indentacion,"/***********************************************/\n");
if (strcmp(Identificador_nombre(Funcion_nom(fn)),"main"))
genera(indentacion,"void _Fn_%s()\n",
Identificador_nombre(Funcion_nom(fn)));
else
genera(indentacion,"main(int argc, char *argv[])\n");
genera(indentacion,"{\n");
indentacion=1;
/* Primero genero las vars locales */
if (Constructor(Acciones_var(Funcion_cod(fn))))
genVariables(Acciones_var(Funcion_cod(fn)),"");
/* La funcion 'main' es un caso especial... */
if (!strcmp(Identificador_nombre(Funcion_nom(fn)),"main"))
{
genera(indentacion,"int _i,_j; /* Usadas para inicializar los colores
*/\n");
genera(indentacion,"arg_count=argc-1; _arg_val=argv; /* Variables globales
que guardan los
parametros */\n");
genera(indentacion,"/* Preparo las curses */\n");
genera(indentacion,"_4GLinit();\n");
if (db!=NULL)
{
genera(indentacion,"/* Preparo los valores de los flags de estado
*/\n");
genera(indentacion,"notfound=PGRES_EMPTY_QUERY;\n");
genera(indentacion,"found=PGRES_TUPLES_OK;\n");
genera(indentacion,"ok=PGRES_COMMAND_OK;\n");
genera(indentacion,"/* Conecto con la base de datos */\n");
genera(indentacion,"_conn=_4GLsetdb(\"%s\");\n\n",
Identificador_nombre(Database_nom(db)));
genera(indentacion,"if (_4GLfechaEuro==1)\n");
genera(indentacion,"{\n");
genera(indentacion+1,"_4GLexec(_conn,\"set DateStyle to
'European';\");\n");
genera(indentacion+1,"_4GLexec(_conn,\"set DateStyle to
'SQL';\");\n");
genera(indentacion,"}\n");
}
genera(indentacion,"pila_param=nueva_pila();\n");
genera(indentacion,"pila_exp=nueva_pila();\n\n");
}
/* Leo los parámetros recibidos */
if (Constructor(Funcion_par(fn)))
genParams(Funcion_par(fn));
/* Genero las acciones */
genSentencias(Acciones_cod(Funcion_cod(fn)));
if (!strcmp(Identificador_nombre(Funcion_nom(fn)),"main"))
{
genera(indentacion,"_4GLexit(0); /* Terminamos */\n");
}
indentacion=0;
genera(indentacion,"} /* Fin de la función %s */\n\n",Identificador_nombre(Funcion_nom(fn)));
}
La función main es un caso especial, ya que es la primera que se ejecuta al
iniciar el programa. En la generación del código para esta función se
incluyen sentencias para inicializar la interfaz de usuario (ncurses),
preparar el valor de algunas variables globales y conectar con la base de datos
si es necesario. Al final de la función se genera el código para cerrar
la conexión con la base de datos. No se va a estudiar aquí la generación
de código de las distintas sentencias que componen el
lenguaje. Se expone como ejemplo la generación del código de las sentencias de
asignación, que es representativo del uso de la pila y de las características
de la conversión automática de tipos. Evaluación de expresiones: La
pila Para la evaluación de expresiones se ha implementado una pila
interna. Esta pila es gestionada en tiempo de ejecución por el programa usando
las funciones que se incluyen en la librería de run- time. La pila se ha
implementado como una lista enlazada. Cada elemento de la lista es un registro
con la siguiente estructura:
enum tipos {TIPO_VOID, TIPO_INT, TIPO_FLOAT, TIPO_STR,
TIPO_DATE, TIPO_TIME, TIPO_DATETIME, TIPO_INTERVAL,
TIPO_RECORD};
struct pila
{
enum tipos tipo;
union
{
char *cadena;
long entero;
double real;
_4GLDATE fecha;
_4GLTIME time;
_4GLDATETIME datetime;
_4GLINTERVAL interval;
} valor;
struct pila *siguiente;
};
El campo tipo indica el tipo del dato que se almacena en este elemento de la pila. Esto es necesario puesto que la pila puede albergar datos de cualquier tipo, y es necesario conocer el tipo del dato para operar con él. El campo valor se define como una unión de todos los tipos básicos soportados por el lenguaje.
Las operaciones que se pueden realizar sobre la pila son las siguientes:
. Apilar valores
Se han implementado funciones para apilar cada uno de los tipos de datos básicos soportados por el lenguaje. Al apilar un dato se guarda también información sobre su tipo
. Desapilar valores
Hay una función para desapilar cada tipo de datos. La función comprueba el tipo del dato que hay en la cima de la pila y realiza las conversiones necesarias para convertirlo al tipo de datos deseado.
. Operar con los valores de la cima de la pila
Se han implementado las siguientes operaciones:
. Suma: Esta operación toma dos datos de la cima de la pila, los suma y apila el resultado.
El tipo de datos del resultado así como su contenido dependerá de los tipos de datos de los operandos. Así, si se suman dos cadenas el resultado es una nueva cadena, resultado de concatenar las dos iniciales, mientras que si se suman dos números el resultado es otro número, resultado de sumar los dos originales.
. Resta/Producto/Division: Se toman dos datos de la cima de la pila, se restan y se apila el resultado.
. Comparar los valores de la cima de la pila
Se han implementado también las operaciones de comparación. Todas estas operaciones toman dos valores de la pila y apilan un 1 o un 0, según se haya o no cumplido la comparación.
. Operaciones lógicas.
. AND: esta operación toma dos valores de la pila y apila un 0 si alguno de
los dos valores originales era 0, o un 1 en caso contrario.
. OR: esta operación toma dos valores de la pila y apila un 0 si los dos
valores originales eran 0, o un 1 en caso contrario.
. NOT: esta operación toma un valor de la pila y apila un 1 si el valor
original era un 0, o un 0 en caso contrario.
Veamos un ejemplo:
define cadena char(50)
let cadena="dentro de 10 minutos seran las "+(time()+600)
El árbol de sintaxis abstracta de la expresión será:
Y el código que se genera es el siguiente:
push_int(pila_exp, 600);
_Fn_time();
op_suma(pila_exp);
push_str(pila_exp, "Dentro de 10 minutos seran las ");
op_suma(pila_exp);
_4GLstrncpy(cadena,pop_str(pila_exp),50);
push_str(pila_exp, cadena);
wprintw(curwin,"%s",pop_str(pila_exp));
En primer lugar se apila el valor 600, de tipo entero
Seguidamente se llama a la función time(). Esta función apila un dato de tipo
TIME
conteniendo la hora actual.
Figura 3. Evaluación de expresiones
expresión
Término
Izquierdo
Operador
Término
Derecho
Término
Izquierdo
Término
Derecho
Operador
"dentro de 10 minutos serán las "
time() 600
+
+
La función op_suma() desapila los dos elementos que están en la cima de la
pila. Comprueba el tipo de dato del primero de ellos (TIME) y del segundo
(INTEGER) y realiza la operación correspondiente: sumarle al valor de
tipo TIME la cantidad de segundos indicada por el otro valor. El resultado
es otro valor de tipo TIME que es apilado. Ahora se apila la cadena "dentro
de 10 minutos seran las " y se vuelve a llamar a la función op_suma.
La funcion op_suma() desapila los dos elementos de la cima de la pila. El
primero de ellos es de tipo STRING y el segundo es de tipo TIME. La función
convierte el dato de tipo TIME a tipo STRING y concatena las dos cadenas.
El resultado es apilado.
Ahora queda en la pila un solo dato, de tipo STRING que contiene el resultado
de la operación. Para realizar la asignación, se desapila la cadena y es
copiada en la variable de destino. Como se ha visto en este ejemplo, la
pila se usa tanto para evaluar expresiones como para pasar parámetros a
funciones y recibir los valores devueltos. De esta manera se facilita el uso de
los valores devueltos por funciones en expresiones (como en el caso del
ejemplo), ya que al salir de las
funciones ya nos encontramos con los valores devueltos en la pila.
hy4GL: compilador de lenguaje 4GL para PostgreSQL