Setup

#load "ocaml-talf/src/talf.cma";;
#directory "ocaml-talf/src";;
open List;;
open Conj;;
open Auto;;
open Ergo;;
open Graf;;
 

Automata de pila

Un automata finito viene definido por la 7-tupla AP = (Q,E,T,q0,d,Z,F) donde:

Qconjunto de estadosestado conjunto
Ealfabeto de entradasimbolo conjunto
Talfabeto de la pilasimbolo conjunto
q0estado inicialestado
dfuncion de transicionarco_ap conjunto
Zsimbolo de inicio de pilasimbolo
Fconjunto estados finalesestado conjunto

Donde arco_ap esta formado por:

estado origenestado
estado destinoestado
simbolo terminal de entradasimbolo
simbolo de la cima de la pilasimbolo
cadena de simbolos de la pilasimbolo list

El simbolo especial de inicio de pila se denota zeta

Ejemplo:

let ap1 = Ap (Conjunto [Estado "0"; Estado "1"; Estado "2"; Estado "3"], (*conjunto de estados*)
 
    Conjunto [Terminal "a"; Terminal "b"], (*alfabeto de entrada*)
 
    Conjunto [No_terminal ""; No_terminal "A"], (*alfabeto de la pila*)
 
    Estado "0", (*estado inicial*)
 
    (*funcion de tansicion*)
    Conjunto [Arco_ap (Estado "0", Estado "1", Terminal "a", No_terminal "",
                       [No_terminal "A"; No_terminal ""]);
              Arco_ap (Estado "1", Estado "1", Terminal "a", No_terminal "A",
                       [No_terminal "A"; No_terminal "A"]);
              Arco_ap (Estado "1", Estado "2", Terminal "b", No_terminal "A",
                       []);
              Arco_ap (Estado "2", Estado "2", Terminal "b", No_terminal "A",
                       []);
              Arco_ap (Estado "2", Estado "3", Terminal "", No_terminal "",
                       [No_terminal ""])],
 
    No_terminal "", (*simbolo de inico de pila*)
 
    Conjunto [Estado "3"]);; (*estados finales*)
 

Gramatica independiente del contexto

Una gramatica independiente dle contexto esta definida por la 4-tupla GIC(N,T,P,S) donde:

Nsimbolos no terminalessimbolo conjunto
Tsimbolos terminalessimbolo conjunto
Preglas de produccionregla_gic conjunto
Ssimbolo inicial (axioma)simbolo

donde regla_gic esta formado por:

entradasimbolo
salidasimbolo list

Ejemplo:

let gic1 = Gic (Conjunto [No_terminal "S"; No_terminal "A"; No_terminal "B"], (*simbolos no terminales*)
 
     Conjunto [Terminal "a"; Terminal "b"; Terminal "c"], (*simbolos terminales*)
 
     Conjunto [ (*reglas de produccion*)
         Regla_gic (No_terminal "S", [Terminal "a"; No_terminal "A"]);
         Regla_gic (No_terminal "A", [Terminal "a"; Terminal "b"; Terminal "c"; No_terminal "A"]);
         Regla_gic (No_terminal "A", [Terminal "b"; No_terminal "B"]);
         Regla_gic (No_terminal "B", [Terminal "b"; Terminal "c"; No_terminal "B"]);
         Regla_gic (No_terminal "B", [])],
 
     No_terminal "S");; (*simbolo inicial*)
 

Ejercicio 1

Implemente una función ap_of_gic : Auto.gic Auto.ap que reciba como argumento una gramática independiente del contexto, y que devuelva el autómata de pila correspondiente a dicha gramática.

La función es relativamente simple, mapea los elementos de la gramática a el automata de pila. Tiene un par de funciones auxiliares que generan los arcos.
No estoy seguro de que la implementación sea completamente funcional, sospecho tiene algún error que hace que algunas funciones aplicadas sobre el autómata resultante no den el resultado esperado.

let ap_of_gic = function
    Gic (Conjunto n, Conjunto t, Conjunto p, No_terminal s) ->
     let rec aux1 acum = function
         [] -> acum
       | Regla_gic (No_terminal x, l) :: t ->
          aux1 ((Arco_ap (Estado "1", Estado "1", Terminal "", No_terminal x, l)) :: acum) t
       | _ -> raise (Failure "ap_of_gic: la gramatica no es regular")
 
     in
     let rec aux2 acum = function
         [] -> acum
       | Terminal x :: t ->
          aux2 ((Arco_ap (Estado "1", Estado "1", Terminal x, Terminal x, [])) :: acum) t;
       | _ -> raise (Failure "ap_of_gic: la gramatica no es regular")
     in
     let
       pila =
       Arco_ap (Estado "0", Estado "1", Terminal "", No_terminal "",
                [No_terminal s; No_terminal ""]) ::
       (aux1 [] p) @
       (aux2 [] t) @
       [Arco_ap (Estado "1", Estado "2", Terminal "", No_terminal "", [No_terminal ""])]
     in
     Ap (Conjunto [Estado "0"; Estado "1"; Estado "2"], (*conjunto de estados*)
         Conjunto t,                                    (*alfabeto de entrada*)
         Conjunto (No_terminal ""::n),                  (*alfabeto de la pila*)
         Estado "0",                                    (*estado inicial*)
         Conjunto pila,                                 (*funcion de tansicion*)
         No_terminal "",                                (*simbolo de inicio de pila*)
         Conjunto [Estado "2"])                         (*estados finales*)
  | _ -> raise (Failure "ap_of_gic: el axioma de la gramatica es un terminal");;
 

Ejemplo de automata y gramatica equivalente

let ap2 = Ap (Conjunto [Estado "0"; Estado "1"; Estado "2"], (*conjunto de estados*)
 
    Conjunto [Terminal "a"; Terminal "b"], (*alfabeto de entrada*)
 
    Conjunto [No_terminal ""; No_terminal "S"], (*alfabeto de la pila*)
 
    Estado "0", (*estado inicial*)
 
    (*funcion de transicion*)
    Conjunto [Arco_ap (Estado "0", Estado "1", Terminal "", No_terminal "",
                       [No_terminal "S"; No_terminal ""]);
              Arco_ap (Estado "1", Estado "1", Terminal "a", No_terminal "a",
                       []);
              Arco_ap (Estado "1", Estado "1", Terminal "b", No_terminal "b",
                       []);
              Arco_ap (Estado "1", Estado "1", Terminal "", No_terminal "S",
                       [No_terminal "a"; No_terminal "S"]);
              Arco_ap (Estado "1", Estado "1", Terminal "", No_terminal "S",
                       [No_terminal "b"; No_terminal "S"]);
              Arco_ap (Estado "1", Estado "1", Terminal "", No_terminal "S",
                       []);
              Arco_ap (Estado "1", Estado "2", Terminal "", No_terminal "",
                       [No_terminal ""])],
 
    No_terminal "", (*simbolo de inico de pila*)
 
    Conjunto [Estado "2"]);; (*estados finales*)
 
let gic2 = Gic (Conjunto [No_terminal "S"], (*simbolos no terminales*)
 
     Conjunto [Terminal "a"; Terminal "b"], (*simbolos terminales*)
 
     Conjunto [ (*reglas de produccion*)
         Regla_gic (No_terminal "S", [Terminal "a"; No_terminal "S"]);
         Regla_gic (No_terminal "S", [Terminal "b"; No_terminal "S"]);
         Regla_gic (No_terminal "S", [])],
 
     No_terminal "S");; (*simbolo inicial*)
 
ap_of_gic gic2;;
 

Ejercicio 2

Sobre un autómata de pila resultante y ante una cadena de entrada, implementar una función para que además proporcione el conjunto de reglas activadas al procesar dicha cadena.

Funciones auxiliares de escaner

Estas funciones fueron obtenidas de la librería ocaml-latf

exception No_encaja;;
 
let encaja (estado, cadena, pila_conf) (Arco_ap (origen, destino, entrada, cima, pila_arco)) =
  let
    nuevo_estado =
    if estado = origen then (*Arco tiene el origen adecuado*)
      destino
    else
      raise No_encaja
  and
    nueva_cadena =
    if entrada = Terminal "" then (*entrada es epsilon*)
      cadena
    else
      if (cadena <> []) && (entrada = hd cadena) then (*entrada coincide con cadena*)
        tl cadena
      else
        raise No_encaja
  and
    nueva_pila_conf =
    if cima = Terminal "" then (*cima del arco es espsilon*)
      pila_arco @ pila_conf
    else
      if (pila_conf <> []) && (cima = hd pila_conf) then (*cima del arco coincide con pila*)
        pila_arco @ (tl pila_conf)
      else
        raise No_encaja
  in
  (nuevo_estado, nueva_cadena, nueva_pila_conf) ;;
 
let es_conf_final finales = function
    (estado, [], _) -> pertenece estado finales (*estado es final y cadena vacia*)
  | _ -> false;;
 

escaner_ap

La funcion retornará todos los arcos que encajen durante el escaner. Esto significa que también retornará arcos que no llevan al resultado true pero que se recorren durante el escaner.

let escaner_ap_reglas cadena (Ap (_, _, _, inicial, Conjunto delta, zeta, finales)) =
  let rec aux = function
      ([], [], _, reglas_activadas) -> (false, rev reglas_activadas)
    | ([], l, _, reglas_activadas) -> aux (l, [], delta, reglas_activadas)
    | (_::cfs, l, [], reglas_activadas) -> aux (cfs, l, delta, reglas_activadas)
    | (cf::cfs, l, a::arcos, reglas_activadas) ->
       try
         let
           ncf = encaja cf a
         in
         if es_conf_final finales ncf then
           (true, rev (a :: reglas_activadas))
         else
           aux (cf::cfs, ncf::l, arcos, a::reglas_activadas)
       with
         No_encaja -> aux (cf::cfs, l, arcos, reglas_activadas)
  in
  aux ([(inicial, cadena, [zeta])], [], delta, []) ;;
 

Esta función además de los arcos retorna la configuración que que encaja con dicho arco, lo que es util para entender un poco mejor porqué, como expliqué en la anterior función, también se retornan arcos que no son parte del camino de procesamiento final.

let escaner_ap_test cadena (Ap (_, _, _, inicial, Conjunto delta, zeta, finales)) =
  let rec aux = function
      ([], [], _, reglas_activadas) -> (false, rev reglas_activadas)
    | ([], l, _, reglas_activadas) -> aux (l, [], delta, reglas_activadas)
    | (_::cfs, l, [], reglas_activadas) -> aux (cfs, l, delta, reglas_activadas)
    | (cf::cfs, l, a::arcos, reglas_activadas) ->
       try
         let
           ncf = encaja cf a
         in
         if es_conf_final finales ncf then
           (true, rev ((cf, a):: reglas_activadas))
         else
           aux (cf::cfs, ncf::l, arcos, (cf, a)::reglas_activadas)
       with
         No_encaja -> aux (cf::cfs, l, arcos, reglas_activadas)
  in
  aux ([(inicial, cadena, [zeta])], [], delta, []) ;;
 

Ejemplo de uso

escaner_ap_reglas (cadena_of_string "a") ap;;