Nociones Básicas de Sintaxis Análisis de Lenguajes de Programación Mauro Jaskelioff 15/08/2017 Sintaxis I La sintaxis es la forma de un lenguaje. I Sintaxis concreta: Las secuencias exactas de caracteres que son programas sintácticamente válidos. 1+2+3 I 6= (1 + 2) + 3 6= (1 + 2) +3 Sintaxis abstracta: La estructura esencial de los programas sintácticamente válidos. 1+2+3 = (1 + 2) + 3 = (1 + 2) +3 Lenguajes Formales I I I I I Un lenguaje es un conjunto de palabras. Una palabra (cadena) es una secuencia finita de sı́mbolos. Con denotamos la palabra vacı́a, o sea la secuencia de cero sı́mbolos. Un sı́mbolo es un elemento de un conjunto finito denominado alfabeto Σ. Ejemplo: alfabeto palabras lenguajes Σ = {a, b } ε, a, b, aa, ab, ba, bb, aaa, aab, aba, abb, baa, bab, . . . ∅, {ε}, {a }, {b }, {a, aa }, {ε, a, aa, aaa }, {an | n > 0}, {an bn | n > 0, n par } Palabras de un Alfabeto I Dado un alfabeto Σ, se define el conjunto Σ∗ como el conjunto de secuencias sobre Σ. Inductivamente: I I I ∈ Σ∗ Para sı́mbolo x ∈ Σ y palabra w ∈ Σ∗ , tenemos que xw ∈ Σ∗ . Por ejemplo, para Σ = {0, 1} Σ∗ = {, 0, 1, 00, 10, 01, 11, 000, 100, 010, 110, 001, 101, 011, 111, ...} I Nota: Hay infinitas palabras en Σ∗ , pero cada palabra es de longitud finita. Lenguajes I Dado un alfabeto Σ, un lenguaje L es L ⊆ Σ∗ o equivalentemente L ∈ P(Σ∗ ) I Ejemplos I I El conjunto de programas válidos en C es un lenguaje sobre el conjunto de caracteres ASCII. El conjunto de programas válidos en Haskell es un lenguaje sobre el conjunto de caracteres Unicode. Gramáticas Libres de Contexto Gramáticas Libres de Contexto I I Las Gramáticas libres de contexto (CFG) son una manera de describir lenguajes libres de contexto. Capturan nociones frecuentes en lenguajes de programación: I I I I estructura anidada, paréntesis balanceados, palabras clave emparejadas como begin y end. La mayorı́a de los lenguajes razonables pueden ser reconocidos en forma bastante simple: basta un autómata de pila determinı́stico. Gramáticas Libres de Contexto Una gramática libre de contexto es una tupla (N, T, P, S) I N es un conjunto finito de no terminales I T es un conjunto finito de terminales (el alfabeto del lenguaje que se describe) I N ∩ T = ∅ (N y T son disjuntos) I S es el sı́mbolo inicial, un elemento de N . I P es un conjunto de producciones de la forma A → α, donde A ∈ N y α ∈ (N ∪ T )∗ . Ejemplo de CFG I G = ({S, A}, {a, b}, P, S) con P compuesto por las siguientes producciones: S →ε S → aA A → bS I Las producciones con el mismo lado izq, se pueden agrupar (Backus-Naur Form o BNF): S → ε | aA I A veces las producciones se escriben A ::= α. La relación de derivación directa I Para obtener el lenguaje generado por una gramática G = (N, T, P, S) definimos la relación binaria ⇒ para cadenas de N ∪ T como la menor relación tal que αAγ ⇒ αβγ I I cuando A → β es una producción de G. Una producción puede ser aplicada sin importar el contexto (de ahı́, libre de contexto). Ejemplo: para la gramática S → ε | aA A → bS tenemos que S ⇒ε S ⇒ aA aA ⇒ abS SaAaa ⇒ SabSaa La relación de derivación I I La relación de derivación ⇒∗ es la clausura reflexiva transitiva de ⇒. O sea, es la menor relación sobre cadenas en N ∪ T tal que: I I I I α ⇒∗ β si α ⇒ β α ⇒∗ α α ⇒∗ β si α ⇒∗ γ ∧ γ ⇒∗ β Ejemplo: para la gramática S → ε | aA A → bS tenemos que S ⇒∗ ε S ⇒∗ aA aA ⇒∗ abS S ⇒∗ abS S ⇒∗ ababS S ⇒∗ abab Lenguaje generado por una gramática I El lenguaje generado por una gramática G = (N, T, P, S), se denota L(G), y se define como: L(G) = {w | w ∈ T ∗ ∧ S ⇒∗ w } I I I Un lenguaje L es libre de contexto (CFL) sii L = L(G) para algún CFG G. Una cadena α ∈ (N ∪ T )∗ es una forma sentencial sii S ⇒∗ α. Ejemplo: para la gramática G S → ε | aA A → bS L(G) = {(ab)i | i > 0} = {, ab, abab, ababab, . . . } Árbol de Parseo Un árbol es una derivación o árbol de parseo de un CFG G = (N, T, P, S) si: I cada nodo tiene una etiqueta en N ∪ T ∪ {}, I la etiqueta de la raı́z es S, I la etiquetas de los nodos interiores están en N , I si el nodo n tiene etiqueta A e hijos n1 , n2 , . . . , nk (de izq. a der.) con etiquetas X1 , X2 , . . . , Xk , entonces A → X1 X2 . . . Xk es una producción en P , I si un nodo n tiene etiqueta , entonces n es una hoja y es hijo único. Árbol de parseo: Ejemplo I Dada la gramática G S a S → ε | aA A → bS La cadena abab ∈ L(G) tiene el árbol de parseo I I I A b S a A b S La cadena de etiquetas en las hojas de izq. a der. es el resultado del árbol. El resultado es una forma sentencial de G. Propiedad: α es el resultado de un árbol sii S ⇒∗ α. Ejercicio G = ({E , Ep , V , I , D }, {+, −, ∗, /, (, ), x , y, z , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, P, E) donde P consiste de las producciones E Ep V I D → E + E | E − E | E ∗ E | E / E | Ep → V | I | (E ) →x |y |z → DI | D →0|1|2|3|4|5|6|7|8|9 Dibujar el árbol de parseo de las siguientes expresiones I I 1+x 42 ∗ x − z I I (13 + 7 ∗ x ∗ (y − 123)) (1 Gramáticas Ambiguas I I I I Una CFG G es ambigua si alguna palabra en L(G) tiene más de un árbol de parseo. Un CFL para el que toda CFG es ambigua es inherentemente ambiguo. La mayorı́a de los CFLs no son inherentemente ambiguos, por lo que una CFG G ambigua, usualmente puede ser transformada a una equivalente G0 que no es ambigua. En general, la ambigüedad de una CFG no es decidible. Eliminando Ambigüedad: el else colgado I La siguiente gramática posee el problema del “else colgado” (dangling else). Stmt → if Expr then Stmt | if Expr then Stmt else Stmt | other I el programa if expr 1 then if expr 2 then stmt 1 else stmt 2 I tiene 2 árboles de parseo posibles. Por lo tanto la gramática es ambigua. Eliminando Ambigüedad: el else colgado Árbol 1: Árbol 2: Desambiguando gramáticas I I I Resolver la ambigüedad es importante. ¡Los dos árboles tienen diferente semántica! Por ejemplo, suponer que expr 1 evalúa a True y expr 2 a False. ¿Qué sentencias se ejecutan en cada caso? Elegimos una interpretación “Asociamos cada else al then más cercano que no esté asociado a otro else.” I I I O sea, preferimos el árbol 1. ¿Cómo lo formalizamos? Transformando la gramática en una equivalente pero sin ambigüedades. Desambiguando gramáticas I Idea: Una sentencia entre un then y un else debe ser una sentencia “matcheada”. Stmt → MatchedStmt | UnmatchedStmt MatchedStmt → if Expr then MatchedStmt else MatchedStmt | other UnmatchedStmt → if Expr then Stmt | if Expr then MatchedStmt else UnmatchedStmt Ejercicio Considere la gramática para expresiones aritméticas vista anteriormente. ¿Es ambigua? Justificar. I I Las expresiones aritméticas se desambiguan mediante convenciones como precedencia de operadores y asociatividad. Las mismas ideas se pueden usar para desambiguar gramáticas. Sintaxis Concreta y Abstracta I Los árboles de parseo que vimos se refieren a la sintaxis concreta. I I I I Se refieren a cadenas sin estructura. Contienen detalles poco interesantes. Su estructura no es única si la gramática es ambigua. Por esto, es conveniente trabajar con árboles de sintaxis abstracta. I I La sintaxis abstracta es un árbol. No puede haber problemas de ambigüedad. Las gramáticas son mas simples ya que no es necesario tener en cuenta formato ni paréntesis. Ejemplo de sintaxis concreta I La sintaxis concreta de un lenguaje de expresiones aritméticas (simplificado) es, en BNF: hdigiti ::= ’0’ | ’1’ | ... | ’9’ hnati ::= hdigiti | hdigiti hnati hintexpi ::= hnati | hintexpi ’+’ hintexpi | hintexpi ’*’ hintexpi | ’(’ hintexpi ’)’ I I La sintaxis es ambigua 10 ∗ 2 + 3 = ? La desambiguamos especificando asociatividad y precedencia de operadores. Sintaxis Abstracta I I Lo que nos interesa son los naturales y expresiones como entidades abstractas. El árbol de sintaxis abstracta (AST) en BNF es: hintexpi ::= hnati | hintexpi + hintexpi | hintexpi ∗ hintexpi I I Los AST no contienen ambigüedades. ¿Pero qué es lo que estamos definiendo? Definiendo Términos I I Los AST me definen un conjunto de términos El conjunto de términos del lenguaje es el menor conjunto T tal que 1. n ∈ N entonces n ∈ T 2. t, u ∈ T entonces t + u ∈ T 3. t, u ∈ T entonces t ∗ u ∈ T I También podemos definir el conjunto mediante reglas de inferencia n∈N n∈T I t∈T u∈T t+u ∈T t∈T u∈T t∗u ∈T Los sı́mbolos + y ∗ son arbitrarios y pueden ser diferentes a la sintaxis concreta. Implementación en Haskell I En Haskell podemos usar un tipo de datos algebraico para representar el AST de un lenguaje. data IntExp = Num Int | Sum IntExp IntExp | Prod IntExp IntExp I I Los elementos de IntExp son árboles con etiquetas Num, Sum y Prod en sus nodos. En la implementación se ve bien claro que tratamos con árboles y que los nombres de los constructores son arbitrarios. Lenguaje de Expresiones Aritméticas Consideremos el árbol de sintaxis abstracta (AST) de un lenguaje: t ::= true | false | if t then t else t | 0 | succ t | pred t | iszero t Un programa es simplemente un término del lenguaje: I El programa if false then 0 else 1 evalúa a 1. I El programa iszero (pred (succ 0)) evalúa true. Detalles de notación I I I I I Las metavariables están en el metalenguaje. Se usan para representar entidades del lenguaje objeto. La metavariable t, letras cercanas como u, s y r , y variaciones como t1 o t 0 representan términos del lenguaje. Un término es una frase que computa. Una expresión es más general (por ejemplo podrı́amos tener una expresión de tipos.) Salvo que se aclare siempre nos referimos a la sintaxis abstracta. Los paréntesis se usan exclusivamente para expresar un árbol como una cadena de texto. Los Términos dados Inductivamente I Podemos definir el conjunto de términos inductivamente: Los términos son el menor conjunto T tal que 1. {true, false, 0} ⊆ T ; 2. si t1 ∈ T , entonces {succ t1 , pred t1 , iszero t1 } ⊆ T ; 3. si t1 ∈ T , t2 ∈ T , y t3 ∈ T , entonces if t1 then t2 else t3 ∈ T I I El requerimiento que T sea el conjunto más chico dice que en T sólo están los elementos requeridos por las tres cláusulas. Recordar que T es un conjunto de árboles. Los Términos dados por Reglas de Inferencia true ∈ T t1 ∈ T succ t1 ∈ T false ∈ T 0∈T t1 ∈ T t1 ∈ T pred t1 ∈ T iszero t1 ∈ T t1 ∈ T t2 ∈ T t3 ∈ T if t1 then t2 else t3 ∈ T I I En este estilo, usualmente se deja implı́cito que uno está interesado en el menor conjunto que satisface las reglas. A menudo decimos “reglas de inferencia”, pero cuando contienen metavariables, son en realidad esquemas de reglas. Los Términos en forma concreta I Para cada i ∈ N, definimos un conjunto Si . S0 = ∅ Si+1 = {true, false, 0} ∪ {succ t1 , pred t1 , iszero t1 | t1 ∈ Si } ∪ {if t1 then t2 else t3 | t1 , t2 , t3 ∈ Si } I Luego, definimos S= [ Si i∈N I Ejercicio: ¿Cuántos elementos tiene S3 ? Solución: |Si+1 | = |Si |3 + |Si | × 3 + 3, |S0 | = 0. |S3 | = 59439 Equivalencia de las definiciones I I La definición inductiva y la dada por reglas caracterizan los términos como un conjunto que satisface cierta propiedad de clausura. La definición concreta muestra como construir el conjunto como el lı́mite de una secuencia. Proposición (T = S ) El conjunto T fue definido como el menor conjunto que satisface ciertas condiciones. Para probar el teorema basta: 1. probar que S satisface las condiciones; 2. probar que todo conjunto que satisface las condiciones contiene a S . Resumen I I I I I I Los lenguajes de programación son lenguajes formales. En general, basta gramáticas libres de contexto para describir su sintaxis concreta. La sintaxis concreta se refiere a cadenas, por lo que puede ser ambigua. La sintaxis abstracta trata con árboles, por lo que no es ambigua. Para procesar un programa, el primer paso es obtener su árbol de sintaxis abstracta (AST). Un AST es un conjunto de términos definidos inductivamente. I Caracterizaciones. Pruebas por inducción estructural. Bibliografı́a I Types and Programming Languages. B.C. Pierce. I Foundations of Programming Languages. J.C. Mitchell.