Sintaxis de Los Lenguajes de Programación

69
Capítulo 2. Sintaxis de los Lenguajes de Programación Raúl José Palma Mendoza

description

sintaxis de los lenaguejs de programcion con ejemplos

Transcript of Sintaxis de Los Lenguajes de Programación

  • Captulo 2. Sintaxis de los Lenguajes de Programacin

    Ral Jos Palma Mendoza

  • Captulo 2. Sintaxis de los Lenguajes de Programacin

    A diferencia de los lenguajes naturales como el Espaol o el Ingls, los lenguajes de programacin deben ser precisos, su sintaxis y semntica deben ser definidos sin ambigedad.

    Para lograr esto los diseadores de lenguajes usan notaciones formales sintcticas y semnticas.

  • Captulo 2. Sintaxis de los Lenguajes de Programacin

    Por ejemplo, podramos definir la sintaxis de los nmeros naturales con la siguiente notacin:digit '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

    non_zero_digit '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

    natural_number non_zero_digit digit *

    Los dgitos no son ms que smbolos y les damos significado cuando decimos que representan los nmeros naturales de cero a nueve, o si decimos que representan colores o la nota de una evaluacin.

  • Captulo 2. Sintaxis de los Lenguajes de Programacin

    En este captulo nos concetraremos en la sintaxis de los lenguajes, especificamente en: Cmo especificar las reglas estructurales de un

    lenguaje de programacin. Cmo el compilador identifica la estructura de un

    programa dado. Para la primera tarea se usan expresiones

    regulares y gramticas libres de contexto, para la segunda estn los escners y los parsers.

  • 2.1 Especificando La Sintaxis: Expresiones Regulares y Gramticas Libres de Contexto

    Un conjunto de cadenas que pueden se definido usando las siguientes tres reglas es llamado un conjunto regular o un lenguaje regular: Concatenacin Alternacin Repeticin

    Los conjuntos regulares son generados por expresiones regulares y son reconocidos por los escners.

  • 2.1 Especificando La Sintaxis: Expresiones Regulares y Gramticas Libres de Contexto

    Un conjunto de cadenas que pueden se definido usando las anteriores tres reglas ms la recursin es llamado un Lenguaje Libre de Contexto (CFL).

    Los Lenguajes Libres de Contexto son generados por Gramticas Libres de Contexto y reconocidos por los parsers.

  • 2.1.1 Tokens y Expresiones Regulares

    Los tokens vienen en varios tipos como: palabras clave, identificadores, constantes de varios tipos, etc. Algunos tipos estn formados por una sola cadena (ej.: operador de incremento) y otros como el identificador queestn formados por un grupo de cadenas que tienen una forma comn (ej.: miVariable).

    El trmino token se usa para referirse tanto de forma genrica (ej.: identificador) como a un caso especfico (ej.: miVariable).

  • 2.1.1 Tokens y Expresiones Regulares

    Ejemplo: El lenguaje C tiene ms de 100 tipos de tokens, entre ellos: 37 palabras clave (double, if, return, etc.), identificadores (mi_variable, sizeof, printf, etc.), enteros (765, 0xfd23), nmeros de coma-flotante (6.223e4), constantes de caracteres ('x', '\'', '\0170'), literales de cadena (snerk, hola soy yo), 54 puntuadores (+, ], ->, *=, :, ||, etc.).

  • 2.1.1 Tokens y Expresiones Regulares

    Para especificar los tokens usamos la notacin de las expresiones regulares. Una expresin regular es cualquiera de las siguientes: Un caracter. La cadena vaca, . Dos expresiones regulares una despus de la otra

    (concatenacin). Dos expresiones regulares separadas por una barra

    vertical (alternacin). Una expresin regular seguida de una estrella de

    Kleene (repeticin).

  • 2.1.1 Tokens y Expresiones Regulares

    Ejemplo: Sintaxis de las constantes numricas aceptadas por una calculadora simple:

    number integer | real

    integer digit digit *

    real integer exponent | decimal (exponent |' ')decimal digit * ('.' digit | digit '.') digit *

    exponent ('e' | 'E') ('+' | '-' |' ') integerdigit '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

  • 2.1.1 Tokens y Expresiones Regulares

    Los smbolos a la izquierda de representan los nombres de las expresiones regulares, el primero que es number lo escojimos como nombre de token, el resto sirve como ayuda para construir expresiones ms grandes.

    Los parentesis se usan para evitar ambigedad con respecto al lugar donde termina un subexpresin e inicia otra.

    Observe que la recursin no se utiliza, ni siquiera de forma indirecta.

  • 2.1.1 Tokens y Expresiones Regulares

    Asuntos de Formato Algunos lenguajes hacen diferencia entre

    maysculas y minsculas (ej.: Modula 2/3, C y sus descendientes) otros no (ej: Ada, Pascal, Common Lisp).

    Algunos lenguajes slo permiten letras y dgitos en sus identificadores (ej: Modula 3 y Standard Pascal), otros permiten guiones bajos (Pascal) y otros una variedad de smbolos (Common Lisp).

  • 2.1.1 Tokens y Expresiones Regulares

    Asuntos de Formato Los lenguajes modernos estn incluyendo

    soporte para conjuntos de caracteres multibyte, basados generalemente los estndares Unicode e ISO/IEC 10646.

    Muchos permiten estos tipos de caracteres en cadenas y comentarios y cada vez tambin se estn permitiendo tambin en los identificadores.

  • 2.1.1 Tokens y Expresiones Regulares

    Asuntos de Formato Muchos de los lenguajes modernos son tambin de

    formato libre es decir que los programas son una secuencia simple de tokens, lo que importa es el orden de los mismos y no su posicin en una pgina impresa. El espacio en blanco entre tokens es ignorado y slo se usa para separar un token del siguiente.

    Algunos lenguajes son excepcin a esta regla, algunos usan los saltos de lnea para separar expresiones (ej.: Visual Basic). Para otros la indentacin es importante por ejemplo para determinar el cuerpo de un ciclo (ej: Haskell y Phyton).

  • 2.1.2 Gramticas Libres de Contexto

    Las expresiones regulares son buenas para definir tokens, pero no pueden definir construcciones anidadas que son centrales en los lenguajes de programacin, por ejemplo para definir la estructura de una expresin aritmtica:expr 'id' | 'number' | '-' expr | '(' expr ')' | expr op expr

    op '+' | '-' | '*' | '/'

    Nota: Se usan los parentesis como tokens.

  • 2.1.2 Gramticas Libres de Contexto

    Cada una de las reglas de una gramtica libre de contexto se conoce como produccin.

    Los smbolos de la izquierda de una produccin se conoce como variables o smbolos no terminales.

    Puede haber cualquier nmero de producciones para el mismo smbolo no terminal.

    Los smbolos que conforman las cadenas derivadas de la gramtica se conocen como smbolos terminales (entre comillas simples ''').

  • 2.1.2 Gramticas Libres de Contexto

    Los smbolos terminales no pueden aparecer en el lado izquierdo de una produccin y son los tokens del lenguaje que defina la gramtica libre de contexto.

    Uno de los no terminales es llamado el smbolo de inicio y nombra a la construccin definida por toda la gramtica.

    La notacin usada para expresar las gramticas libres de contexto es conocida como BNF (Backus-Naur Form).

  • 2.1.2 Gramticas Libres de Contexto

    Originalmente la forma BNF no inclua la estrella de Kleene ni los parntesis para evitar ambigedades, al incluirlos en la notacin BNF, le llamaremos EBNF (Extended BNF).

    Por ejemplo podramos definir una lista de identificadores usando EBNF como:

    id_list ' id' (',' 'id')*

    que sera equivalente a (en BNF):

    id_list 'id'

    id_list id_list ',' 'id'

  • 2.1.3 Derivaciones y rboles de Parseo

    Ejemplo: Derive la cadena pendiente * x + interecepto

    expr expr op expr expr op id expr + id expr op expr + id expr op id + id expr * id + id id * id + id (pediente) * (x) + (intercepto)

  • 2.1.3 Derivaciones y rboles de Parseo

    Una derivacin es una serie de reemplazos que nos permiten concluir que una cadena de terminales surge de un smbolo de inicio siguiendo las producciones de una gramtica libre de contexto.

    Cada cadena de smbolos en el camino es llamada forma sentencial. La ltima forma sentencial que consiste slo de terminales es llamada el producto de la derivacin.

    En la derivacin anterior se reemplaz el no terminal ms a la derecha, y por eso decimos que fue una derivacin ms a la derecha. Tambin se pudo haber hecho una derivacin ms a la izquierda o seguido un efoque mezclado.

  • 2.1.3 Derivaciones y rboles de Parseo

  • 2.1.3 Derivaciones y rboles de Parseo

    Usamos un rbol de parseo para representar grficamente una derivacin, la raz del rbol es el smbolo de inicio, sus hojas son el producto y los nodos intermedios representan el uso de una produccin.

    Existen infinitas gramticas libres de contexto para un determinado lenguaje. Aunque algunas son ms tiles que otras, al disear un lenguaje de programacin se busca una que refleje la estructura interna de los programas de forma que sea til para el resto del compilador.

  • 2.1.3 Derivaciones y rboles de Parseo

    Una grmatica que permite que se genere ms de un rbol de parseo para una cadena de terminales se dice que es ambiga y esto genera problemas a la hora de construir un parser (aunque muchos parser las permiten).

    Ejercicio: Demostrar mediante un rbol de parseo que la gramtica que hemos definido para las expresiones aritmticas es ambiga.

  • 2.1.3 Derivaciones y rboles de Parseo

    Ejemplo: Agregando producciones que capturen la asocitividad y la precedencia de los operadores podemos modificar la gramtica anterior y convertirla en una gramtica sin ambigedades.

    expr term | expr add_op term

    term factor | term mult_op factor

    factor 'id' | 'number' | '-' factor | ( expr )

    add_op '+' | '-'

    mult_op '*' | '/'

  • 2.1.3 Derivaciones y rboles de Parseo

    Ejemplo: Un rbol de parseo para la expresin 3+4*5

  • 2.1.3 Derivaciones y rboles de Parseo

    Ejercicio: Cree un rbol de parseo para la expresin 10 - 4 - 3.

  • 2.2 Escaneo

    Dadas las siguientes definiciones de tokens, para un lenguaje simple de una calculadora:assign ':='

    plus '+'

    minus '-'

    times '*'

    div '/'

    lparen '('

    rparen ')'

  • 2.2 Escaneo

    id letter (letter | digit)* except for read and write

    number digit digit * | digit * ('.' digit | digit '.') digit *

    comment '/*' (non-* | '*' non-/)* '*'* '/' | '//' (non-newline)* newline

    Observemos que en la definicin de id se han escrito dos tokens que son excepciones: read y write.

    Adems por brevedad se us los tokens non-*, non-/ y non-newline para referirse a todos los carcteres que no son el asterisco, la pleca y una nueva linea respectivamente.

  • 2.2 Escaneo

    Qu mtodo usamos para reconocer los tokens de nuestro lenguaje?

    A la primera respuesta a esta pregunta le llamamos opcin ad hoc, por ser un software especfico para los tokens de nuestro lenguaje.

    A continuacin mostraremos el pseudocdigo de un escner de este tipo, que despus de encontrar un token retorna al parser y cuando es invocado nuevamente busca un nuevo token usando los siguientes caracteres disponibles.

  • skip any initial white space (spaces, tabs, and newlines)

    if cur_char {'(', ')', '+', '-', '*'} return the corresponding single-character token

    if cur_char = ':' read the next character if it is '=' then return assign else announce an error

    if cur_char = '/' peek at the next character if it is '*' or '/' read additional chars until "*/" or newline is seen, respectively jump back to top of code else return div

    (continua en la siguiente diapositiva ...)

  • if cur_char = . read the next character if it is a digit read any additional digits return number else announce an error

    if cur_char is a digit read any additional digits and at most one decimal point return numberelse announce an error

  • 2.2 Escaneo

    Ejemplo: La capacidad de anidar comentarios puede ser buena para el programador (para comentar temporalmente grandes porciones de cdigo), pero la mayora de los escners no reconocen estructuras anidadas. Por esto en C++ y en C99, no se permite anidar comentarios del mismo estilo, pero si se permite anidarlos con estilos diferentes (anidar // dentro de /* */ y viceversa).

    Aunque se recomienda mejor usar la compilacin condicional (#if).

  • 2.2 Escaneo

    La regla del token ms largo, implica que es cada invocacin del escner se tratar de leer el token de mayor longitud posible, por ejemplo en un lenguaje como C se leer un token com 3.1416 y nunca como 3, luego . y despus 1416.

  • 2.2 Escaneo

    Generalmente los compiladores de produccin usan escners ad hoc pues el cdigo es ms compacto y eficiente.

    Aunque durante el desarrollo es preferible construir un escner de una forma ms estructurada como una representacin explcita de un autmata finito.

    Los autmatas finitos puede ser creados automticamente de un conjunto de expresiones regulares (facilitando los cambios).

  • Ejemplo de un autmata finito para los tokens del lenguaje de calculadora definidos (continua en la siguiente diapositiva...)

  • Los estados finales donde se reconoce el token tiene un doble crculo.

  • 2.2.1 Generando un Autmata Finito

    Aunque podemos generar un autmata a mano, es ms comn hacerlo automticamente a partir de un conjunto de expresiones regulares usando una herramienta de software. Ahora veremos el proceso manual.

    El autmata de la figura anterior es determinista (AFD), no hay ambigedad en lo que tiene que hacer, porque en un estado dado con un carcter de entrada dado nunca hay ms de una posible transicin (un arco o arista con la misma etiqueta).

  • 2.2.1 Generando un Autmata Finito

    El proceso de generacin sigue tres pasos, el primero consiste en convertir las expresiones regulares en un autmata no determinista (AFND) que se diferencia de un AFD en que: Puede haber ms de una transicin desde un

    estado a otro etiquetada con el mismo carcter. Pueden existir transiciones epsilon, etiquetadas

    con .

  • 2.2.1 Generando un Autmata Finito

    Decimos que el AFND acepta un token si existe un camino desde el estado inicial hasta el final en el cual sus transiciones no epsilon, llevan como etiquetas los caracteres del token en orden.

    Para evitar la necesidad de hacer una bsqueda entre todos los caminos por uno que funcione, el siguiente paso consiste en convertir el AFND en un AFD, y el tercer paso consiste en optimizar el AFD para que tenga el menor nmero de estados posible.

  • 2.2.1 Generando un Autmata Finito

    Una expresin regular que consiste en un simple carcter es equivalente al AFD de ilustrado en la parte (a) de la siguiente figura. De la misma forma una expresin regular compuesta por la cadena vaca se muestra como un AFND de dos estados unidos por una transicin epsilon.

    En las partes (b), (c) y (d) se muestran los AFND que se generan a partir de la concatenacin, la alternacin y la cerradura de Kleene, respectivamente.

  • 2.2.1 Generando un Autmata Finito

    Ejemplo: Consideremos la creacin de un AFD para el token decimal, que habiamos definido como:

    decimal digit * ('.' digit | digit '.') digit *

    En las ilustraciones a continuacin, usamos d para referirnos a la expresin regular digit, por brevedad.

  • 2.2.1 Generando un Autmata Finito

  • 2.2.1 Generando un Autmata Finito

  • 2.2.1 Generando un Autmata Finito

    Para transformar el AFND en un AFD usaremos una contruccin llamada conjunto de conjuntos, la idea clave es que un estado del AFD represente un grupo de estados a los que el AFND que pudo haber llegado a partir del mismo carcter en la entrada.

    Al inicio antes de consumir cualquier entrada el AFND podra estar en el estado 1 o podra hacer transiciones epsilon a los estados 2, 4, 5 o 8. Por esta razn creamos un estado A en el AFD que represente este conjunto.

  • 2.2.1 Generando un Autmata Finito

    En una entrada de d nuestro AFND podra moverse del estado 2 al 3 o del 8 al 9 y del estado 3 podra hacer transiciones epsilon a los estados 2, 4, 5 u 8, por esta razn sea crea el estado B como lo muestra la ilustracin a continuacin.

    Desde el estado A, con un ., nuestro AFND podra moverse del estado 5 al 6, no hay ms transiciones posibles con este carcter desde ninguno de los estados de A ni tampoco transiciones epsilon desde el estado 6, por lo que creamos el estado C en el AFD slo con el estado 6.

  • 2.2.1 Generando un Autmata Finito

    Regresando al estado B, con un d podramos movernos del estado 2 al 3 o del estado 8 al 9. Pero al llegar al 3 podramos hacer transiciones epsilon a los estados 2,4,5,u 8 y como todos estos estados estn en B creamos un ciclo desde B hacia B en el AFD.

    Siempre en B, con un . podramos movernos del estado 5 al 6 o del 9 al 10 y luego desde 10 al 11,12 y 14 a travs de transiciones epsilon. Por eso creamos el estado D segn la ilustracin.

  • 2.2.1 Generando un Autmata Finito

    El estado D se marca como final porque contiene el estado 14 que es final el AFND.

    Continuando de este modo se crean tambin los estados finales E, F y G mostrados en la ilustracin que contienen todos al estado 14 del AFND y por tanto son marcados como finales.

    NOTA: La imagen original en el libro contiene un error, el estado G del AFD no debe contener al estado 11 del AFND. El error ya est corregido en la imagen a continuacin.

  • 2.2.1 Generando un Autmata Finito

    En el ejemplo anterior el AFD termina siendo ms pequeo que el AFND, esto es porque el lenguaje regular usado es muy simple. En teora, el nmero de estados en el AFD podra se exponencial al nmero de estados en el AFND, pero este extremo no es comn en la prctica.

  • 2.2.1 Generando un Autmata Finito

    El tercer paso en el proceso es la optimizacin del AFD, para lograr usaremos la siguiente construccin inductiva:

    NOTA: A pesar que el libro dice claramente que el mtodo se aplica para cualqueir AFD, se encontr casos por ejemplo: en ('e'|'E')('+'|'-')d* en que al hacer la clasificacin se obtiene un autmata que no corresponde con la expresin regular, en otros casos funcion hacer ms de dos particiones como en: da | da da '.' da | da da '.' da | da da '.' da | da da

  • NOTA: Al momento de hacer optimizacin con las clases de equivalencia se encontr conveniente recorrer en orden alafabtico cada uno de los estados de la clase revisando sus transiciones haber si todas estn includas en la clase de equivalencia.

  • 2.2.1 Generando un Autmata Finito

    Inicialmente clasificamos los estado del AFD en dos clases de equivalencia una con los estados finales y otra con los no finales.

    Luego, repetidamente buscamos una clase de equivalencia X y smbolo c tales que al recibir c como entrada los estados de X hacen transiciones a estados en k > 1 clases de equivalencia distintas.

  • 2.2.1 Generando un Autmata Finito

    Posteriormente, particionamos X en k clases de equivalencia de modo que todos los estados en una de las nuevas clases tengan transiciones con el smbolo c a un miembro de la misma clase de equivalencia inicial X.

  • 2.2.1 Generando un Autmata Finito

    En nuestro ejemplo, haciendo la primera clasificacin podramos los estados A, B y C en una clase y los estados D, E, F y G en otra, como se ve en la ilustracin (a) siguiente.

    Luego vemos que hay ambigedad en las transiciones tanto para ambos smbolos: . y d, siguiendo lo enunciado anteriormente separamos la clase ABC en dos clases AB y C, de modo que todos los estados de AB tienen una transicin en d a estados que formaban parte de la clase ABC, (especficamente de A a B y de B a B), esto se ve en la ilustracin (b).

  • 2.2.1 Generando un Autmata Finito

    Observamos que an queda ambigedad desde AB con el ., por lo que ahora nos vemos obligados a separar la clase AB en A y B, de modo que A tiene una transicin a C en ., siendo C un estado de la clase inicial ABC. De esta forma obtenemos el resultado final mostrado en la ilustracin (c).

    En este punto ya no hay ms ambigedades por lo que ya tenemos nuestro AFD mnimo con cuatro estados.

  • 2.2.1 Generando un Autmata Finito

  • 2.2.1 Generando un Autmata Finito

    En el ejemplo anterior construimos un autmata que es capaz de reconocer un slo token: decimal. Para construir un escner para un lenguaje con n diferentes tipos de tokens, iniciamos con un AFND como el que se ve en la ilustracin a continuacin.

    Dados los AFND llamados Mi, 1

  • 2.2.1 Generando un Autmata Finito

    En contraste con la alternacin no creamos un estado final, mantenemos los que ya estn, cada uno etiquetado para el token del cul es final.

    Luego convertimos el AFND resultante en un AFD de la misma forma que ya lo hicimos, tomando en cuenta que si los estados finales de diferentes tokens se mezclan en el AFD, es por que tenemos definiciones de tokens ambiguas.

  • 2.2.1 Generando un Autmata Finito

  • 2.2.1 Generando un Autmata Finito

    Finalmente al hacer la optimizacin iniciamos con n + 1 clases de equivalencia, una clase para todos los estados no finales y n clases para los n diferentes estados finales que existen.

  • 2.2.2 Cdigo del Escner

    Podemos implementar un escner a partir del AFD usando dos mtodos principales:

    El primero consiste en usar enunciados switchs anidados que imiten la estructura del AFD. Este es generalmente el mtodo usado cuando se programa a mano.

    A continuacin mostramos la estructura de escner programado de esta forma.

  • state := 1 -- start stateloop read cur_char case state of 1 : case cur_char of ' ', '\t', '\n' : ... 'a' ... 'z' : ... '0' ... '9' : ... '>' : ... ... 2 : case cur_char of ... ... n: case cur_char of ...

  • 2.2.2 Cdigo del Escner

    En el cdigo anterior el enunciado switch exterior cubre los estados del autmata y los enunciados interiores cubren las transiciones entre los estados. La mayora de stos simplemente establecen un nuevo estado y algunos retornan del escner con el token actual (si el ltimo carcter ledo no forma parte del token se coloca de nuevo en el flujo de entrada antes de retornar).

  • 2.2.2 Cdigo del Escner

    El segundo mtodo para implementar un escner consiste en crear una tabla y un driver para la tabla. ste mtodo es preferido por la herramientas que generan escners automticamente, pues es ms fcil de generar para un programa que generar cdigo. Ej.: Unix lex/flex.

    Por cuestiones de tiempo y espacio no ahondaremos en detalles sobre este mtodo.

  • 2.2.2 Cdigo del Escner

    Existen dos aspectos del cdigo que usualmente se desvan de la formalidad de un autmata finito: el primero es el manejo de la palabras clave y el segundo es la necesidad de observar hacia adelante cuando un token puede ser extendido por dos o ms caracteres, no slo por uno (prefijo no trivial).

    Con respecto al primer aspecto, es posible crear un autmata finito que diferencia entre palabras clave e identificadores, pero esto implicar agregar muchos estados.

  • 2.2.2 Cdigo del Escner

    La mayora de los escners tratan a las palabras clave como excepciones a la regla de los identificadores, antes de retornar un identificador al parser, el escner revisa en una tabla si en realidad no se trata de una palabra clave.

    Para explicar el segundo aspecto enunciamos un ejemplo a continuacin.

  • 2.2.2 Cdigo del Escner

    Ejemplo: En el lenguaje C se da el problema del prefijo no trivial, con el manejo de los caracteres punto ., pues stos forman en s mismos un token o pueden formar parte de otro como un nmero real . Suponiendo que el escner ha recientemente ledo un 3 y luego encuentra un . necesita observar los caracteres siguientes para distinguir entre 3.14 (un slo token), 3.foo (tres tokens: 3, . y foo, sintcticamente invlidos pero tokens) o por ejemplo 3...foo (cinco tokens sintcticamente invlidos, nuevamente).

    Slide 1Slide 2Slide 3Slide 4Slide 5Slide 6Slide 7Slide 8Slide 9Slide 10Slide 11Slide 12Slide 13Slide 14Slide 15Slide 16Slide 17Slide 18Slide 19Slide 20Slide 21Slide 22Slide 23Slide 24Slide 25Slide 26Slide 27Slide 28Slide 29Slide 30Slide 31Slide 32Slide 33Slide 34Slide 35Slide 36Slide 37Slide 38Slide 39Slide 40Slide 41Slide 42Slide 43Slide 44Slide 45Slide 46Slide 47Slide 48Slide 49Slide 50Slide 51Slide 52Slide 53Slide 54Slide 55Slide 56Slide 57Slide 58Slide 59Slide 60Slide 61Slide 62Slide 63Slide 64Slide 65Slide 66Slide 67Slide 68Slide 69