Saltar a contenido

Representación mediante Gramática BNF

Formalmente

Utilizamos gramáticas de tipo 3 (regulares) de la forma \(aA\), es decir, el símbolo terminal seguido del no terminal. Por esta razón, estas gramáticas son sensibles a la izquierda. Si dos reglas inician igual por la izquierda, EOPL no será capaz de distinguirlas, generando un conflicto de desplazamiento (shift conflict).

Jerarquía de Chomsky y Gramáticas Tipo 3

Las gramáticas tipo 3 (regulares) son las más restrictivas en la jerarquía de Chomsky, caracterizadas por tener producciones de la forma: - \(A \rightarrow aB\) (recursión a la derecha) - \(A \rightarrow a\) (terminal)

Estas son menos expresivas que las gramáticas libres de contexto (tipo 2), pero más fáciles de procesar computacionalmente.

Ejemplo: Lista de Números

<lista-numeros> ::= '()
                  ::= <int> <lista-numeros>

Ejemplos válidos:

'()
'(1 2 3)

Esta gramática define listas que contienen únicamente números enteros. El caso base es la lista vacía, y la regla inductiva añade un número al inicio de una lista válida.

Ejemplo: Árbol Binario

<b-tree> ::= <int>
           ::= <symbol> <b-tree> <b-tree>

Ejemplos válidos:

1
'(f 2 3)
'(f (k 2 3) (l (s 2 4) 3))

En el caso de (f 2 3):

graph TD
    f --> 2
    f --> 3

En el caso de '(f (k 2 3) (l (s 2 4) 3)):

graph TD
f --> k
f --> l
k --> A[2]
k --> C[3]
l --> s
l --> D[3]
s --> B[2]
s --> E[4]

Un árbol binario puede ser un número entero (hoja) o un símbolo con dos subárboles (nodo interno). Esta estructura es fundamental en ciencia de la computación para representar datos jerárquicos.

Ejemplo: Cálculo Lambda

<lambda-exp> ::= <identifier>
               ::= ("lambda" (<identifier>) <lambda-exp>)
               ::= (<lambda-exp> <lambda-exp>)

Ejemplos válidos:

'x
'(lambda (x) x)
'(lambda (y) (lambda (s) k))
'(x (lambda (p) (x y)))

El cálculo lambda es un sistema formal que permite expresar funciones anónimas y aplicación de funciones. Las tres reglas definen: 1. Una variable identificador 2. Una abstracción lambda (definición de función anónima) 3. Una aplicación de funciones

Código para Validar Datos

#lang eopl

#|
Gramática para lista de números
<lista-numeros> ::= '()
                  ::= <int> <lista-numeros>

Definición inductiva:
- Caso base: '() pertenece a lista-numeros
- Regla inductiva: si n es número y l es lista-numeros, entonces (n . l) es lista-numeros
|#

; in-Ln?: lista -> boolean
; Valida que una lista contenga únicamente números
; Procesa recursivamente: verifica que el primer elemento sea número
; y que el resto sea una lista válida
(define in-Ln?
  (lambda (l)
    (cond
      [(null? l) #T]                           ; Caso base: lista vacía es válida
      [else
       (and (number? (car l))                  ; Verifica que el primer elemento sea número
            (in-Ln? (cdr l)))])))              ; Recursión: valida el resto de la lista

(newline)
(display "Lista de números")
(newline)
(display (in-Ln? '(1 2 3 4 5)))                ; #T - todos son números
(newline)
(display (in-Ln? '(2 3 4 5 6 e 1)))            ; #F - contiene símbolo 'e'
(newline)

#|
Gramática para árbol binario
<b-tree> ::= <int>
           ::= <symbol> <b-tree> <b-tree>

Definición inductiva:
- Caso base: un número entero es un árbol binario (hoja)
- Regla inductiva: si s es símbolo, t1 y t2 son árboles binarios,
  entonces (s t1 t2) es un árbol binario (nodo interno)
|#

; in-tree?: árbol -> boolean
; Valida que la estructura cumpla con la definición de árbol binario
; Un árbol es válido si:
; 1. Es un número (hoja), o
; 2. Es una lista donde primer elemento es símbolo y los siguientes son árboles válidos
(define in-tree?
  (lambda (t)
    (cond
      [(number? t) #T]                         ; Caso base: un número es un árbol válido
      [else
       (and
        (symbol? (car t))                      ; Verifica que el nodo sea un símbolo
        (in-tree? (cadr t))                    ; Verifica que el subárbol izquierdo sea válido
        (in-tree? (caddr t)))])))              ; Verifica que el subárbol derecho sea válido

(newline)
(display "Árbol binario")
(newline)
(display (in-tree? 5))                         ; #T - número es árbol válido
(newline)
(display (in-tree? '(k 1 2)))                  ; #T - símbolo con dos números
(newline)
(display (in-tree? '(s (t 1 2) (k (n 2 3) (s 4 2))))) ; #T - árbol complejo válido
(newline)
(display (in-tree? '(s (t 1 2) (k (n 2 3) (1 4 2))))) ; #F - primer elemento (1) no es símbolo

#|
Gramática para cálculo lambda
<lambda-exp> ::= <identifier>
               ::= ("lambda" (<identifier>) <lambda-exp>)
               ::= (<lambda-exp> <lambda-exp>)

Definición inductiva:
- Caso base: un identificador (símbolo) es una expresión lambda válida
- Regla 1: (lambda (x) e) es válida si x es identificador y e es expresión lambda
- Regla 2: (e1 e2) es válida si e1 y e2 son expresiones lambda (aplicación)
|#

; in-lambda-exp?: expresión -> boolean
; Valida que una expresión sea una expresión lambda válida
; Utiliza tres patrones de análisis:
; 1. Símbolos simples (variables)
; 2. Abstracciones lambda: (lambda (var) body)
; 3. Aplicaciones de funciones: (func arg)
(define in-lambda-exp?
  (lambda (exp)
    (cond
      [(symbol? exp) #T]                       ; Caso base: símbolo es expresión lambda válida
      [(and
        (equal? (car exp) 'lambda)             ; Verifica que inicie con 'lambda
        (symbol? (caadr exp))                  ; Verifica que el parámetro sea un símbolo
        )
       (in-lambda-exp? (caddr exp))]           ; Recursión: valida el cuerpo de la lambda
      [else
       (and
        (in-lambda-exp? (car exp))             ; Verifica que el operador sea expresión lambda
        (in-lambda-exp? (cadr exp)))])))       ; Verifica que el argumento sea expresión lambda

(display "Expresión lambda")
(newline)
(display (in-lambda-exp? 'x))                  ; #T - variable simple
(newline)
(display (in-lambda-exp? '(lambda (x) y)))     ; #T - abstracción lambda
(newline)
(display (in-lambda-exp? '(lambda (x) (lambda (y) x)))) ; #T - lambda anidada
(newline)
(display (in-lambda-exp? '(x y)))              ; #T - aplicación de función
(newline)
(display (in-lambda-exp? '((lambda (x) y) (x y)))) ; #T - aplicación de lambda
(newline)
(display (in-lambda-exp? '((lambda (s) z) (x y)))) ; #T - composición válida

Relación entre Gramáticas BNF y Representación Inductiva

Las gramáticas BNF proporcionan una notación formal para especificar lenguajes, mientras que la representación inductiva define los datos que pertenecen a ese lenguaje. Cada regla en la gramática BNF corresponde a un caso en la función de pertenencia:

  • Reglas BNFCasos en cond
  • No terminalesLlamadas recursivas
  • TerminalesPredicados de tipo (number?, symbol?, etc.)

Tabla de Resumen

Concepto Definición Ejemplo Función de Validación
Gramática BNF Notación formal para especificar la sintaxis de un lenguaje <list> ::= '() \|\| <int> <list> Define estructura; validación con in-Ln?
Tipo 3 (Regular) Gramática menos expresiva de la jerarquía Chomsky; recursión a derecha A ::= aB Procesa de izquierda a derecha
Caso Base Regla BNF que produce un terminal sin recursión <b-tree> ::= <int> Primera rama del cond
Regla Recursiva Regla BNF que incluye el no terminal en su producción <lista> ::= <int> <lista> Llamada recursiva en cond
Shift Conflict Ambigüedad cuando dos reglas inician igual por la izquierda Reglas no diferenciables en EOPL Causa error en el parser
Árbol Binario Estructura jerárquica donde cada nodo tiene exactamente dos subárboles (f (k 1 2) 3) Validación con in-tree?
Cálculo Lambda Sistema formal para expresar funciones anónimas y aplicación (lambda (x) (+ x 1)) Validación con in-lambda-exp?
Abstracción Lambda Definición de función anónima; forma (lambda (param) body) (lambda (x) x) Analiza estructura y parámetros
Aplicación de Función Invocación de función; forma (func arg) (f x) Valida ambos operandos recursivamente
Recursión Estructural Seguir la estructura del lenguaje en la validación Procesar lista elemento a elemento Garantiza terminación y cobertura completa

Comentarios Adicionales

  • Las gramáticas tipo 3 son suficientes para lenguajes regulares pero insuficientes para lenguajes libres de contexto como el cálculo lambda (que requiere gramáticas tipo 2)
  • Los conflictos de desplazamiento (shift conflicts) en EOPL indican ambigüedad gramatical que debe resolverse reescribiendo las reglas
  • La validación recursiva de estructuras complejas (como árboles y expresiones lambda) refleja directamente la estructura de la gramática BNF
  • Las funciones de pertenencia actúan como parsers que verifican si un dato cumple la sintaxis definida por la gramática
  • La correspondencia entre BNF y código recursivo es sistemática: cada alternativa (::=) corresponde a un caso en cond, facilitando la traducción automática
  • El cálculo lambda es fundamental en programación funcional y teoría de lenguajes de programación, siendo la base teórica de lenguajes como Lisp y Scheme
  • Para lenguajes más complejos, se utilizan herramientas como generadores de parsers (yacc, bison) que automatizan la creación de validadores a partir de gramáticas BNF