martes, 28 de febrero de 2023

Notacion Polaca

Definición - ¿Qué significa la notación polaca (PN)?

La notación polaca es una forma de notación para expresar ecuaciones aritméticas, lógicas y algebraicas. Su característica distintiva más básica es que los operadores se colocan a la izquierda de sus operandos. Si el operador tiene un número fijo definido de operandos, la sintaxis no requiere paréntesis o paréntesis para disminuir la ambigüedad.

La notación polaca también se conoce como notación prefijada, notación polaca prefijada, notación polaca normal, notación Varsovia y notación Lukasiewicz.

La notación polaca, también conocida como notación de prefijo o notación prefija, es una forma de notación para la lógica, la aritmética, el álgebra y la computación. Su característica distintiva es que coloca los operadores a la izquierda de sus operandos. Si la aridad de los operadores es fija, el resultado es una sintaxis que carece de paréntesis u otros signos de agrupación, y todavía puede ser analizada sin ambigüedad. El lógico polaco Jan Łukasiewicz inventó esta notación alrededor de 1920 para simplificar la lógica proposicional.

Al igual que la de postfijo, la notación polaca permite prescindir de los paréntesis en el caso de operadores de aridad fija conocida. Por ejemplo, la operación 5 * (12 + 4). puede escribirse en prefijo como: * 5 (+ 12 4); o sencillamente: * 5 + 12 4 (y como 5 12 4 + *en postfijo).

La notaciones de prefijo (o polaca, en homenaje a Jan Łukasiewicz), de infijo y de postfijo (o polaca inversa) son formas de escritura de expresiones algebraicas que se diferencian por la posición relativa que toman los operadores y los operandos. En la notación de prefijo, el operador se escribe delante de los operandos (+ 3 4), entre los operandos en la notación de infijo (3 + 4) y tras los operandos en la de posfijo (3 4 +).

La notación de prefijo fue propuesta en 1924 por el matemático, lógico y filósofo polaco Jan Łukasiewicz (1878-1956), de allí el nombre alternativo por la que se conoce.

Al igual que la de postfijo, la notación polaca permite prescindir de los paréntesis en el caso de operadores de aridad fija conocida. Por ejemplo, la operación 5 * (12 + 4).puede escribirse en prefijo como: * 5 (+ 12 4); o sencillamente: * 5 + 12 4 (y como 5 12 4 + *en postfijo).

Conectivas en notación polaca
Tradicionalmente, la notación polaca usa letras mayúsculas para expresar conectivas, tomando normalmente la primera letra del nombre de la conectiva en polaco. Aquí tenemos una lista con los símbolos más comunes:
Conectiva
Notación estándar
Notación polaca
Negación
¬
N
Implicación
C
Conjunción
K
Disyunción
A
Bicondicional
E
Posibilidad
M
Necesidad
L
Cuantificador universal
Π
Cuantificador existencial
Σ
Definición de fórmula bien formada en notación polaca
Tomando la lista anterior de conectivas en notación polaca más un conjunto de variables proposicionales como alfabeto de un lenguaje, tenemos la siguiente definición recursiva de fórmula bien formada en dicho lenguaje:
·         Sea α una variable proposicional. α es una fórmula bien formada.
·         Sean αβ fórmulas bien formadas. CαβKαβAαβEαβΠαΣα son fórmulas bien formadas.
Método para traducir una fórmula en notación estándar a notación polaca
(1) Buscamos la conectiva principal y escribimos la equivalente en notación polaca.
(2) Si la conectiva es unaria, tomamos su argumento como una subfórmula y aplicamos (1).
(3) Si la conectiva es binaria, (3a) tomamos el primer argumento y aplicamos (1), a continuación tomamos el segundo argumento y aplicamos (1).
(4) Si el símbolo es una variable proposicional, la escribimos a continuación.
El procedimiento acaba en un número finito de pasos dado que en sucesivas aplicaciones de (1) la complejidad de la fórmula disminuye. Si se trata de una fórmula bien formada, el procedimiento acabará por darnos otra fórmula bien formada en notación polaca. Baste esta descripción para mostrar el carácter formal del procedimiento.
En la práctica, resultaría tedioso aplicar este procedimiento paso por paso. Resulta más adecuado entender la mecánica por medio de ejemplos y aplicarla directamente; con cierto entrenamiento la traducción se hace de manera casi automática.
Como ejemplo de este procedimiento vamos a utilizar una instancia de la ley de De Morgan, que en notación estándar escribiríamos así: ¬(p  q)  (¬ ¬q). En este ejemplo la conectiva principal es la implicación, una conectiva binaria, cuyos argumentos tienen como conectiva principal la negación y la disyunción respectivamente. Tendremos que traducir las sucesivas subfórmulas hasta las variables variables proposicionales.
Notación estándar
Notación polaca
Operación
¬(p  q)  (¬p  ¬q)
C
(1), (3)
¬(p  q) → (¬p  ¬q)
C_____ _____
  (3a)
¬(p  q)
CN
    (1)
¬(p  q)
CN____
      (2)
  p  q
CNK_ _
        (1), (3)
  p  q
CNKp
          (3a), (4)
  p  q
CNKpq
          (3b), (4)
¬(p  q)  (¬p  ¬q)
CNKpq_____
  (3b)
            ¬p  ¬q
CNKpqA__ __
    (1), (3)
            ¬p  ¬q
CNKpqA__ __
      (3a)
            ¬p
CNKpqAN_
        (1), (2)
            ¬p
CNKpqANp
          (4)
            ¬p  ¬q
CNKpqANp__
      (3b)
                 ¬q
CNKpqANpN_
        (1), (2)
                 ¬q
CNKpqANpNq
          (4)

viernes, 24 de febrero de 2023

2 ejercico de recorrido de arbol prefija

 

7,3,6,2,5,6,3,1,7,7,3,4,9,5,3,6,8,2,6,8,3,1

EJERCICIO DE Reorrido de arbol prefija

 


6,7,9
7,5,1,3,4,6,7



Recirrido de arbol prefija

RECORRIDO DE ARBOLPREFIJA

En el orden preorden se recorre de la siguiente manera: raíz, subárbol izquierdo, subárbol derecho. En el orden inorden se recorre de la siguiente manera: subárbol izquierdo, raíz, subárbol derecho. En el orden postorden se recorre de la siguiente manera: subárbol izquierdo, subárbol derecho, raíz.

En ciencias de la computación, el recorrido de árboles se refiere al proceso de visitar de una manera sistemática, exactamente una vez, cada nodo en una estructura de datos de árbol (examinando y/o actualizando los datos en los nodos). Tales recorridos están clasificados por el orden en el cual son visitados los nodos. Los siguientes algoritmos son descritos para un árbol binario, pero también pueden ser generalizados a otros árboles.

Recorrido en profundidad-primero

Árbol binario

  • Preorden: (raíz, izquierdo, derecho). Para recorrer un árbol binario no vacío en preorden, se deben realizar las siguientes operaciones recursivamente en cada nodo, comenzando con el nodo de raíz:
  1. Visite la raíz
  2. Atraviese el sub-árbol izquierdo
  3. Atraviese el sub-árbol derecho
  • Inorden: (izquierdo, raíz, derecho). Para recorrer un árbol binario no vacío en inorden (simétrico), se deben realizar las siguientes operaciones recursivamente en cada nodo:
  1. Atraviese el sub-árbol izquierdo
  2. Visite la raíz
  3. Atraviese el sub-árbol derecho
  • Postorden: (izquierdo, derecho, raíz). Para recorrer un árbol binario no vacío en postorden, se deben realizar las siguientes operaciones recursivamente en cada nodo:
  1. Atraviese el sub-árbol izquierdo
  2. Atraviese el sub-árbol derecho
  3. Visite la raíz

En general, la diferencia entre preorden, inorden y postorden es cuándo se recorre la raíz. En los tres, se recorre primero el sub-árbol izquierdo y luego el derecho.

  • En preorden, la raíz se recorre antes que los recorridos de los subárboles izquierdo y derecho
  • En inorden, la raíz se recorre entre los recorridos de los árboles izquierdo y derecho, y
  • En postorden, la raíz se recorre después de los recorridos por el subárbol izquierdo y el derecho

Preorden (antes), inorden (en medio), postorden (después).

Árbol genérico

Para recorrer un árbol no vacío en orden de profundidad-primero, hay que realizar las siguientes operaciones recursivamente en cada nodo:

  1. Realice la operación pre-orden
  2. Para i=1 a n-1 haga
    1. Visite al hijo[i], si existe
    2. Realice la operación in-orden
  3. Visite al hijo[n], si existe
  4. Realice la operación post-orden

donde n es el número de nodos hijos. Dependiendo del problema actual, las operaciones de pre-orden, in-orden o post-orden pueden ser vacías (void), o usted puede querer visitar solamente un nodo de hijo específico, así que estas operaciones pueden ser consideradas opcionales. También, en la práctica, más de una de las operaciones de pre-orden, in-orden y post-orden pueden ser requeridas. Por ejemplo, al insertar en un árbol ternario, una operación de pre-orden es realizada comparando elementos. Una operación de post-orden puede luego ser necesitada para rebalancear el árbol.

Recorrido en anchura-primero

Los árboles también pueden ser recorridos en orden por nivel (de nivel en nivel), donde visitamos cada nodo en un nivel antes de ir a un nivel inferior. Esto también es llamado recorrido en anchura-primero o recorrido en anchura.

 

miércoles, 22 de febrero de 2023

1.7 Manejo de errores semanticos

¿Qué es un error semántico?

Los errores semánticos son errores que se producen en una capa muy concreta dentro del proceso de compilación. Si nos encontramos con un error que no sabemos solucionar, tendremos que investigar y saber qué es un error semántico en C puede ayudarnos a localizar el origen del problema. Según el Diccionario de la Real Academia Española, la semántica léxica es la rama de la semántica que estudia el significado de las palabras así como las diversas relaciones de sentido que se establecen entre ellas. Y precisamente en esta última parte de la definición es donde está la clave, en las relaciones de sentido que se establecen. Y es que un código puede tener una estructura correcta desde el punto de vista sintáctico pero carecer de sentido desde el punto de vista de significado. Podremos afirmar, por tanto, que semántica implica significado.  manejo de errores semanticos










Errores léxicos

Los errores léxicos se detectan cuando el analizador léxico intenta reconocer componentes léxicos en el código fuente.


Errores sintácticos

Un error de sintaxis se detecta cuando el analizador sintáctico espera un símbolo que no corresponde al que se acaba de leer. Los analizadores sintácticos LL y LR tienen la ventaja de que pueden detectar errores sintácticos lo más pronto posible, es decir, se genera un mensaje de error en cuanto el símbolo analizado no sigue la secuencia de los símbolos analizados hasta ese momento.


Error semántico

Los errores semánticos corresponden a la semántica del lenguaje de programación, la cual normalmente no está descrita por la gramática. Los errores semánticos más comunes son la omisión de declaraciones.



Errores Lógicos

 Los comete el programador
 Ejemplo: una llamada infinitamente recursiva




















7.2 Tratamiento de los errores léxicos

TRATAMIENTO DE LOS ERRORES LEXICOS
Un traductor debe adoptar alguna estrategia para detectar, informar y recuperarse para seguir analizando hasta el final.
Las respuestas ante el error pueden ser:
·         Inaceptables: Provocadas por fallos del traductor, entrada en lazos infinitos, producir resultados erróneos, y detectar sólo el primer error y detenerse.
·         Aceptables: Evitarla avalancha de errores (mala recuperación) y, aunque más complejo, informar y reparar el error de forma automática. La conducta de un Analizador de Léxico es el de un Autómata finito o “scanner”.
·         Detección del error: El analizador de Léxico detecta un error cuando no existe transición desde el estado que se encuentra con el símbolo de la entrada. El símbolo en la entrada no es el esperado.

Los errores léxicos se detectan cuando el analizador léxico intenta reconocer componentes léxicos y la cadena de caracteres de la entrada no encaja con ningún patrón. Son situaciones en las que usa un carácter invalido (@,$,",>,...), que no pertenece al vocabulario del lenguaje de programación, al escribir mal un identificador, palabra reservada u operador.

Errores léxicos típicos son:
·         Nombre ilegales de identificadores: un nombre contiene caracteres inválidos.
·         Números incorrectos: Un numero contiene caracteres inválidos o no está formado correctamente, por ejemplo 3,14 en vez de 3.14 o 0.3.14.
·         Errores de ortografía en palabras reservadas: caracteres omitidos, adicionales o cambiados de sitio, por ejemplo la palabra while en vez de hwile.
·         Fin de archivo: se detecta un fin de archivo a la mitad de un componente léxico.

Los errores léxicos se deben a descuidos del programador. En general, la recuperación de errores léxicos es sencilla y siempre se traduce en la generación de un error de sintaxis que será detectado más tarde por el analizador sintáctico cuando el analizador léxico devuelve un componente léxico que el analizador sintáctico no espera en esa posición.
Los métodos de recuperación de errores léxicos se basan bien en saltarse caracteres en la entrada hasta que un patrón se ha podido reconocer; o bien usar otros métodos más sofisticados que incluyen la inserción, borrado, sustitución de un carácter en la entrada o intercambio de dos caracteres consecutivos.
 Una buena estrategia para la recuperación de errores léxicos:
Universidad Nacional del Santa Curso: Teoría de Compiladores
·         Si en el momento de detectar el error ya hemos pasado por algún estado final ejecutamos la acción correspondiente al último estado final visitado con el lexema formado hasta que salimos de él; el resto de caracteres leídos se devuelven al flujo de entrada y se vuelve al estado inicial;

·         Si no hemos pasado por ningún estado final, advertimos que el carácter encontrado no se esperaba, lo eliminamos y proseguimos con el análisis.
TRATAMIENTO DE LOS ERRORES SINTACTICOS


7.3 Tratamiento de errores sintácticos

Muchos errores de naturaleza sintáctica Recuperación: Al producirse un error el compilador debe ser capaz de informar del error y seguir compilando. (Ideal)
El manejo de errores de sintaxis es el más complicado desde el punto de vista de la creación de compiladores. Nos interesa que cuando el compilador encuentre un error, se recupere y siga buscando errores. Por lo tanto el manejador de errores de un analizador sintáctico debe tener como objetivos:

·         Indicar los errores de forma clara y precisa. Aclarar el tipo de error y su localización.
·         Recuperarse del error, para poder seguir examinando la entrada.
·         No ralentizar significativamente la compilación.

Un buen compilador debe hacerse siempre teniendo también en mente los errores que se  pueden producir; con ello se consigue:

·         Simplificar la estructura del compilador.
·         Mejorar la respuesta ante los errores.

Tenemos varias estrategias para corregir errores, una vez detectados:


Ignorar el problema (Panicmode)

Consiste en ignorar el resto de la entrada hasta llegar a una condición de seguridad.
Una condición tal se produce cuando nos encontramos un token especial (por ejemplo un ‘;’ o un ‘END’). A partir de este punto se sigue analizando normalmente.

Recuperación a nivel de frase

Intenta recuperar el error una vez descubierto. En el caso anterior, por ejemplo, podría haber sido lo suficientemente inteligente como para insertar el token ‘;’. Hay que tener cuidado con este método, pues puede dar lugar a recuperaciones infinitas.




Reglas de producción adicionales para el control de errores

La gramática se puede aumentar con las reglas que reconocen los errores más comunes. En el caso anterior, se podría haber puesto algo como:
sent_erróne a Ú sent_sin_acabar sentencia_acabada ’;’
sentencia_acabada Ú sentencia ‘;’
sent_sin_acabar Ú sentencia
Lo cual nos da mayor control en ciertas circunstancias


Corrección Global

Dada una secuencia completa de tokens a ser reconocida, si hay algún error por el que no se puede reconocer, consiste en encontrar la secuencia completa más
parecida que sí se pueda reconocer. Es decir, el analizador sintáctico le pide toda la secuencia de tokens al léxico, y lo que hace es devolver lo más parecido a la cadena de entrada pero sin errores, así como el árbol que lo reconoce.


7.4 Tratamiento errores semánticos

Comprobacion de tipos

Aspectos generales

Un lenguaje con comprobación fuerte de tipos es capaz de garantizar que los programas se pueden ejecutar sin errores de tipo, por lo que los errores de tipo se detectarán siempre en tiempo de compilación.
Como mínimo, ante un error, un comprobador de tipos debe informar de la naturaleza y posición del error y recuperarse para continuar con la comprobación del resto del programa a analizar.

Veamos algunas de las operaciones a tener en cuenta en una comprobación de tipos:
·         Conversión de tipos: A veces es necesario transformar el tipo de una expresión para utilizar correctamente un operador o para pasar de forma adecuada un parámetro a una función.
·         Coerción: Es una conversión de tipos que realiza de forma implícita el propio compilador. Si es el programador el que realiza la conversión se tratará entonces de una conversión explícita.
·         Sobrecarga de operadores: La sobrecarga se resuelve determinando el tipo de cada una de las expresiones intervinientes en la sobrecarga.
·         Funciones polimórficas: Son aquellas que trabajan con argumentos cuyo tipo puede cambiaren distintas llamadas a la función.


Especificación de un comprobador de tipos básico

Básicamente se deberán realizar dos tareas:
a)    Asignación de tipos: en las declaraciones.
b)    Evaluación y comprobación de tipos: En las expresiones y en las funciones, así como en las sentencias.



Otras comprobaciones semánticas y recuperación de errores semánticos

Dentro de las comprobaciones estáticas (en el momento de la compilación), tenemos la detección e información de errores como:

·         Comprobaciones de tipos: operadores aplicados a operandos incompatibles, asignación de tipos incompatibles, llamadas a funciones con tipos no adecuados, etc.
·         Comprobaciones de flujo de control: las sentencias que hacen que el flujo de control abandone una construcción debe tener alg ´un lugar a donde transmitir el control. Por ejemplo: Unbreak debe estar dentro de una proposición while,  for o switch en C.
·         Comprobaciones de unicidad: situaciones en las que solo se puede definir un objeto una vez exactamente. Por ejemplo: Un identificador, las etiquetas case dentro de un switch.

Solo nos hemos centrado en las comprobaciones de tipo. Las otras son e  cierto modo rutinarias y se pueden realizar fácilmente insertando acciones intercaladas en el código para realizarlas, por eje. Cuando se introduce un identificador en la Tabla de Símbolos.


¿







Cómo manejar errores?Un compilador es un sistema que en la mayoría de los casos tiene quemanejar unaentrada incorrecta. Sobre todo en las primeras etapas de la creación de un programa, esprobable que el compilador se utilizará para efectuar las características que deberíaproporcionar un buen sistema de edición dirigido por la sintaxis, es decir, para determinarsi las variables han sido declaradas antes de usarla, o si faltan corchetes o algo así. Por lotanto, el manejo de errores es parte importante de un compilador y el escritor delcompilador siempre debe tener esto presente durante su diseño.Hay que señalar que los posibles errores ya deben estar considerados al diseñar unlenguaje de programación. Por ejemplo, considerar si cada proposición del lenguaje deprogramación comienza con una palabra clave diferente (excepto la proposición deasignación, por supuesto). Sin embargo, es indispensable lo siguiente:1.El compilador debe ser capaz dedetectar erroresen la entrada;2. El compilador deberecuperarse de los erroressin perder demasiada información;3.Y sobre todo, el compilador debeproducir un mensaje de errorque permita alprogramador encontrar y corregir fácilmente los elementos (sintácticamente)incorrectos de su programa.

Emulador

 link de enmulador https://app.box.com/s/xo6qhgmzgmcpf24tvaytx88u34bth83i link de Informacion https://app.box.com/s/r0sbpslk0dmydm1tl8dd37y4...