Analizando - Parsing

El análisis sintáctico , el análisis sintáctico o el análisis sintáctico es el proceso de analizar una cadena de símbolos , ya sea en lenguaje natural , lenguajes informáticos o estructuras de datos , conforme a las reglas de una gramática formal . El término análisis proviene del latín pars ( orationis ), que significa parte (del habla) .

El término tiene significados ligeramente diferentes en diferentes ramas de la lingüística y la informática . El análisis tradicional de oraciones se realiza a menudo como un método para comprender el significado exacto de una oración o palabra, a veces con la ayuda de dispositivos como diagramas de oraciones . Por lo general, enfatiza la importancia de las divisiones gramaticales como sujeto y predicado .

Dentro de la lingüística computacional, el término se usa para referirse al análisis formal por una computadora de una oración u otra cadena de palabras en sus constituyentes, lo que resulta en un árbol de análisis sintáctico que muestra su relación sintáctica entre sí, que también puede contener información semántica y de otro tipo ( valores p ). Algunos algoritmos de análisis pueden generar un bosque de análisis o una lista de árboles de análisis para una entrada sintácticamente ambigua .

El término también se utiliza en psicolingüística para describir la comprensión del lenguaje. En este contexto, el análisis sintáctico se refiere a la forma en que los seres humanos analizan una oración o frase (en lenguaje o texto hablado) "en términos de constituyentes gramaticales, identificando las partes del habla, relaciones sintácticas, etc." Este término es especialmente común cuando se discute qué señales lingüísticas ayudan a los hablantes a interpretar oraciones de senderos de jardín .

Dentro de la informática, el término se utiliza en el análisis de lenguajes informáticos , refiriéndose al análisis sintáctico del código de entrada en sus partes componentes para facilitar la escritura de compiladores e intérpretes . El término también puede usarse para describir una escisión o separación.

Lenguajes humanos

Métodos tradicionales

El ejercicio gramatical tradicional de análisis sintáctico, a veces conocido como análisis de cláusulas , implica dividir un texto en sus partes componentes del discurso con una explicación de la forma, función y relación sintáctica de cada parte. Esto se determina en gran parte a partir del estudio de las conjugaciones y declinaciones del idioma , que pueden ser bastante intrincadas para idiomas con muchas inflexiones . Analizar una frase como 'hombre muerde a perro' implica señalar que el sustantivo singular 'hombre' es el sujeto de la oración, el verbo 'muerde' es la tercera persona del singular del tiempo presente del verbo 'morder', y el sustantivo singular 'perro' es el objeto de la oración. A veces se utilizan técnicas como los diagramas de oraciones para indicar la relación entre los elementos de la oración.

El análisis era fundamental para la enseñanza de la gramática en todo el mundo de habla inglesa y, en general, se lo consideraba básico para el uso y la comprensión del lenguaje escrito. Sin embargo, la enseñanza general de tales técnicas ya no es actual.

Métodos computacionales

En algunos sistemas de procesamiento de lenguaje natural y traducción automática , los textos escritos en lenguajes humanos son analizados por programas de computadora. Los programas no pueden analizar fácilmente las oraciones humanas, ya que existe una ambigüedad sustancial en la estructura del lenguaje humano, cuyo uso es para transmitir significado (o semántica ) entre una gama potencialmente ilimitada de posibilidades, pero solo algunas de las cuales son pertinentes para el caso particular. Por lo tanto, una expresión "El hombre muerde a un perro" frente a "El perro muerde al hombre" es definida en un detalle, pero en otro idioma podría aparecer como "El hombre muerde a un perro" con una dependencia del contexto más amplio para distinguir entre esas dos posibilidades, si es que esa diferencia fue de preocupación. Es difícil preparar reglas formales para describir el comportamiento informal, aunque está claro que se están siguiendo algunas reglas.

Para analizar los datos del lenguaje natural, los investigadores primero deben ponerse de acuerdo sobre la gramática que se utilizará. La elección de la sintaxis se ve afectada por preocupaciones tanto lingüísticas como computacionales; por ejemplo, algunos sistemas de análisis sintáctico utilizan gramática funcional léxica , pero en general, se sabe que el análisis sintáctico de gramáticas de este tipo es NP-completo . La gramática de la estructura de frases dirigida por la cabeza es otro formalismo lingüístico que ha sido popular en la comunidad de análisis sintáctico, pero otros esfuerzos de investigación se han centrado en formalismos menos complejos, como el utilizado en Penn Treebank . El análisis poco profundo tiene como objetivo encontrar solo los límites de los componentes principales, como las frases nominales. Otra estrategia popular para evitar controversias lingüísticas es el análisis gramatical de dependencia .

La mayoría de los analizadores sintácticos modernos son, al menos en parte, estadísticos ; es decir, se basan en un corpus de datos de entrenamiento que ya ha sido anotado (analizado a mano). Este enfoque permite que el sistema recopile información sobre la frecuencia con la que ocurren diversas construcciones en contextos específicos. (Consulte aprendizaje automático ). Los enfoques que se han utilizado incluyen PCFG ( gramáticas probabilísticas libres de contexto) sencillas , entropía máxima y redes neuronales . La mayoría de los sistemas más exitosos utilizan estadísticas léxicas (es decir, consideran las identidades de las palabras involucradas, así como su parte del discurso ). Sin embargo, estos sistemas son vulnerables al sobreajuste y requieren algún tipo de suavizado para ser efectivos.

Los algoritmos de análisis para el lenguaje natural no pueden depender de que la gramática tenga propiedades "agradables" como ocurre con las gramáticas diseñadas manualmente para los lenguajes de programación. Como se mencionó anteriormente, algunos formalismos gramaticales son muy difíciles de analizar computacionalmente; en general, incluso si la estructura deseada no está libre de contexto, se utiliza algún tipo de aproximación libre de contexto a la gramática para realizar una primera pasada. Los algoritmos que usan gramáticas libres de contexto a menudo se basan en alguna variante del algoritmo CYK , generalmente con algo de heurística para eliminar análisis poco probables y ahorrar tiempo. (Ver análisis sintáctico del gráfico ). Sin embargo, algunos sistemas intercambian velocidad por precisión utilizando, por ejemplo, versiones de tiempo lineal del algoritmo shift-reduce . Un desarrollo algo reciente ha sido el cambio de clasificación del análisis sintáctico en el que el analizador propone una gran cantidad de análisis y un sistema más complejo selecciona la mejor opción. Los analizadores semánticos convierten los textos en representaciones de sus significados.

Psicolingüística

En psicolingüística , el análisis sintáctico implica no solo la asignación de palabras a categorías (formación de percepciones ontológicas), sino la evaluación del significado de una oración de acuerdo con las reglas de sintaxis extraídas por inferencias hechas de cada palabra en la oración (conocido como connotación ) . Esto ocurre normalmente cuando se escuchan o se leen las palabras. En consecuencia, los modelos psicolingüísticos de análisis sintáctico son necesariamente incrementales , lo que significa que construyen una interpretación a medida que se procesa la oración, que normalmente se expresa en términos de una estructura sintáctica parcial. La creación de estructuras inicialmente incorrectas ocurre cuando se interpretan oraciones de senderos de jardín .

Análisis del discurso

El análisis del discurso examina formas de analizar el uso del lenguaje y los eventos semióticos. El lenguaje persuasivo puede llamarse retórica .

Lenguajes informáticos

Analizador

Un analizador es un componente de software que toma datos de entrada (frecuentemente texto) y construye una estructura de datos , a menudo algún tipo de árbol de análisis , árbol de sintaxis abstracta u otra estructura jerárquica, dando una representación estructural de la entrada mientras verifica la sintaxis correcta. El análisis sintáctico puede ir precedido o seguido de otros pasos, o estos pueden combinarse en un solo paso. El analizador suele estar precedido por un analizador léxico independiente , que crea tokens a partir de la secuencia de caracteres de entrada; alternativamente, estos se pueden combinar en el análisis sintáctico sin escáner . Los analizadores pueden programarse a mano o pueden generarse automática o semiautomáticamente mediante un generador de analizadores sintácticos . El análisis es complementario a la creación de plantillas , que produce una salida formateada . Estos se pueden aplicar a diferentes dominios, pero a menudo aparecen juntos, como el par scanf / printf , o las etapas de entrada (análisis frontal) y salida (generación de código back-end) de un compilador.

La entrada a un analizador a menudo es texto en algún lenguaje de computadora , pero también puede ser texto en un lenguaje natural o datos textuales menos estructurados, en cuyo caso generalmente solo se extraen ciertas partes del texto, en lugar de construir un árbol de análisis sintáctico. Los analizadores van desde funciones muy simples como scanf , hasta programas complejos como la interfaz de un compilador de C ++ o el analizador de HTML de un navegador web . Una clase importante de análisis simple se realiza mediante expresiones regulares , en las que un grupo de expresiones regulares define un lenguaje regular y un motor de expresiones regulares que genera automáticamente un analizador para ese idioma, lo que permite la coincidencia de patrones y la extracción de texto. En otros contextos, las expresiones regulares se utilizan antes del análisis, como el paso de lexing, cuya salida luego es utilizada por el analizador.

El uso de analizadores sintácticos varía según la entrada. En el caso de los lenguajes de datos, un analizador se encuentra a menudo como la función de lectura de archivos de un programa, como la lectura de texto HTML o XML ; estos ejemplos son lenguajes de marcado . En el caso de los lenguajes de programación , un analizador es un componente de un compilador o intérprete , que analiza el código fuente de un lenguaje de programación de computadoras para crear alguna forma de representación interna; el analizador es un paso clave en la interfaz del compilador . Los lenguajes de programación tienden a especificarse en términos de una gramática libre de contexto determinista porque se pueden escribir analizadores rápidos y eficientes para ellos. En el caso de los compiladores, el análisis sintáctico en sí se puede realizar en una o varias pasadas; consulte el compilador de una pasada y el compilador de varias pasadas .

Las desventajas implícitas de un compilador de un solo paso se pueden superar en gran medida agregando arreglos , donde se prevé la reubicación del código durante el paso hacia adelante, y los arreglos se aplican al revés cuando se ha reconocido que el segmento del programa actual ha sido terminado. Un ejemplo en el que un mecanismo de reparación de este tipo sería útil sería una instrucción GOTO hacia adelante, donde se desconoce el objetivo del GOTO hasta que se complete el segmento del programa. En este caso, la aplicación de la corrección se retrasaría hasta que se reconociera el objetivo del GOTO. Por el contrario, un GOTO hacia atrás no requiere una reparación, ya que la ubicación ya se conocerá.

Las gramáticas libres de contexto están limitadas en la medida en que pueden expresar todos los requisitos de un idioma. De manera informal, la razón es que la memoria de tal lenguaje es limitada. La gramática no puede recordar la presencia de una construcción sobre una entrada arbitrariamente larga; esto es necesario para un idioma en el que, por ejemplo, se debe declarar un nombre antes de que se pueda hacer referencia a él. Sin embargo, las gramáticas más poderosas que pueden expresar esta restricción no se pueden analizar de manera eficiente. Por lo tanto, es una estrategia común crear un analizador relajado para una gramática libre de contexto que acepta un superconjunto de las construcciones de lenguaje deseadas (es decir, acepta algunas construcciones no válidas); más tarde, los constructos no deseados se pueden filtrar en el paso del análisis semántico (análisis contextual).

Por ejemplo, en Python, el siguiente es un código sintácticamente válido:

x = 1
print(x)

Sin embargo, el siguiente código es sintácticamente válido en términos de la gramática libre de contexto, lo que produce un árbol de sintaxis con la misma estructura que el anterior, pero es sintácticamente inválido en términos de la gramática sensible al contexto , que requiere que las variables se inicialicen antes usar:

x = 1
print(y)

En lugar de analizarse en la etapa de análisis sintáctico, esto se detecta verificando los valores en el árbol de sintaxis, por lo tanto, como parte del análisis semántico : en la práctica, la sintaxis sensible al contexto se analiza más fácilmente como semántica.

Resumen del proceso

Flujo de datos en un analizador típico

El siguiente ejemplo demuestra el caso común de analizar un lenguaje de computadora con dos niveles de gramática: léxico y sintáctico.

La primera etapa es la generación de tokens, o análisis léxico , mediante el cual el flujo de caracteres de entrada se divide en símbolos significativos definidos por una gramática de expresiones regulares . Por ejemplo, un programa de cálculo se vería en una entrada como " 12 * (3 + 4)^2" y dividirlo en las fichas 12, *, (, 3, +, 4, ), ^, 2, cada uno de los cuales es un símbolo significativo en el contexto de una expresión aritmética. El analizador léxico contendría normas para decirle que los personajes *, +, ^, (y )marcan el inicio de un nuevo token, fichas tan sin sentido como " 12*" o " (3no se generarán".

La siguiente etapa es el análisis sintáctico o el análisis sintáctico, que consiste en comprobar que los tokens forman una expresión permitida. Esto generalmente se hace con referencia a una gramática libre de contexto que define de manera recursiva los componentes que pueden formar una expresión y el orden en el que deben aparecer. Sin embargo, no todas las reglas que definen los lenguajes de programación pueden expresarse únicamente con gramáticas libres de contexto, por ejemplo, la validez de tipo y la declaración adecuada de identificadores. Estas reglas se pueden expresar formalmente con gramáticas de atributos .

La fase final es el análisis sintáctico semántico , que consiste en resolver las implicaciones de la expresión recién validada y tomar la acción adecuada. En el caso de una calculadora o intérprete, la acción es evaluar la expresión o programa; un compilador, por otro lado, generaría algún tipo de código. También se pueden utilizar gramáticas de atributos para definir estas acciones.

Tipos de analizadores

La tarea del analizador es esencialmente determinar si la entrada se puede derivar del símbolo de inicio de la gramática y cómo. Esto se puede hacer esencialmente de dos formas:

  • Análisis de arriba hacia abajo: el análisis de arriba hacia abajo puede verse como un intento de encontrar las derivaciones más a la izquierda de un flujo de entrada mediante la búsqueda de árboles de análisis mediante una expansión de arriba hacia abajo de las reglas gramaticales formales dadas . Las fichas se consumen de izquierda a derecha. La elección inclusiva se utiliza para adaptarse a la ambigüedad al expandir todos los lados derechos alternativos de las reglas gramaticales. Esto se conoce como el enfoque de la sopa primordial. Muy similar a la diagramación de oraciones, la sopa primordial descompone los elementos constitutivos de las oraciones.
  • Análisis de abajo hacia arriba : un analizador puede comenzar con la entrada e intentar reescribirla en el símbolo de inicio. Intuitivamente, el analizador intenta localizar los elementos más básicos, luego los elementos que los contienen, etc. Los analizadores LR son ejemplos de analizadores ascendentes. Otro término utilizado para este tipo de analizador es análisis Shift-Reduce .

Los analizadores de LL y el analizador de descendencia recursiva son ejemplos de analizadores de arriba hacia abajo que no pueden adaptarse a las reglas de producción recursivas izquierdas . Aunque se ha creído que las implementaciones simples de análisis de arriba hacia abajo no pueden acomodar la recursividad directa e indirecta por la izquierda y pueden requerir una complejidad exponencial de tiempo y espacio mientras se analizan gramáticas ambiguas libres de contexto , Frost ha creado algoritmos más sofisticados para el análisis de arriba hacia abajo. , Hafiz y Callaghan que acomodan la ambigüedad y la recursividad izquierda en el tiempo polinomial y que generan representaciones de tamaño polinomial del número potencialmente exponencial de árboles de análisis sintáctico. Su algoritmo es capaz de producir derivaciones tanto a la izquierda como a la derecha de una entrada con respecto a una gramática libre de contexto dada .

Una distinción importante con respecto a los analizadores es si un analizador genera una derivación más a la izquierda o una derivación más a la derecha (consulte la gramática libre de contexto ). Los analizadores sintácticos LL generarán una derivación más a la izquierda y los analizadores LR generarán una derivación más a la derecha (aunque normalmente a la inversa).

Algunos Se han diseñado algoritmos de análisis sintáctico paralenguajes de programación visual. Los analizadores de lenguajes visuales a veces se basan engramáticas gráficas.

Se han utilizado algoritmos de análisis adaptativo para construir interfaces de usuario de lenguaje natural "autoexpandibles" .

Software de desarrollo analizador

Algunas de las herramientas de desarrollo de analizadores más conocidas incluyen las siguientes:

Mirar hacia el futuro

Programa C que no se puede analizar con menos de 2 tokens de anticipación. Arriba: extracto de gramática C. Abajo: un analizador ha digerido los tokens " " y está a punto de elegir una regla para derivar Stmt . Mirando solo el primer token de anticipación " ", no puede decidir cuál de las dos alternativas debe elegir Stmt ; el último requiere echar un vistazo a la segunda ficha.int v;main(){v

Lookahead establece el máximo de tokens entrantes que un analizador puede usar para decidir qué regla debe usar. La búsqueda anticipada es especialmente relevante para los analizadores sintácticos LL , LR y LALR , donde a menudo se indica explícitamente colocando la búsqueda anticipada en el nombre del algoritmo entre paréntesis, como LALR (1).

La mayoría de los lenguajes de programación , el objetivo principal de los analizadores, se definen cuidadosamente de tal manera que un analizador con una búsqueda anticipada limitada, normalmente uno, puede analizarlos, porque los analizadores con una búsqueda anticipada limitada suelen ser más eficientes. Un cambio importante en esta tendencia se produjo en 1990 cuando Terence Parr creó ANTLR para su doctorado. tesis, un generador de analizador sintáctico para analizadores sintácticos LL ( k ) eficientes , donde k es cualquier valor fijo.

Los analizadores LR suelen tener solo unas pocas acciones después de ver cada token. Son shift (agregue esta ficha a la pila para una reducción posterior), reduzca (saque fichas de la pila y forme una construcción sintáctica), final, error (no se aplica ninguna regla conocida) o conflicto (no sabe si cambiar o reducir) .

Lookahead tiene dos ventajas.

  • Ayuda al analizador a tomar la acción correcta en caso de conflictos. Por ejemplo, analizar la instrucción if en el caso de una cláusula else.
  • Elimina muchos estados duplicados y alivia la carga de una pila adicional. El analizador sintáctico no anticipado de lenguaje AC tendrá alrededor de 10,000 estados. Un analizador de anticipación tendrá alrededor de 300 estados.

Ejemplo: analizar la expresión 1 + 2 * 3

El conjunto de reglas de análisis de expresiones (llamado gramática) es el siguiente,
Regla 1: E → E + E La expresión es la suma de dos expresiones.
Regla 2: E → E * E La expresión es el producto de dos expresiones.
Regla 3: E → número La expresión es un número simple
Regla 4: + tiene menos precedencia que *

La mayoría de los lenguajes de programación (excepto algunos como APL y Smalltalk) y las fórmulas algebraicas dan mayor prioridad a la multiplicación que a la suma, en cuyo caso la interpretación correcta del ejemplo anterior es 1 + (2 * 3) . Tenga en cuenta que Rule4 anterior es una regla semántica. Es posible reescribir la gramática para incorporarla a la sintaxis. Sin embargo, no todas estas reglas pueden traducirse en sintaxis.

Acciones simples de analizador no anticipado

Entrada inicial = [1, +, 2, *, 3]

  1. Cambie "1" a la pila desde la entrada (anticipándose a la regla 3). Entrada = [+, 2, *, 3] Pila = [1]
  2. Reduce "1" a la expresión "E" según la regla 3. Pila = [E]
  3. Cambie "+" a la pila desde la entrada (anticipándose a la regla 1). Entrada = [2, *, 3] Pila = [E, +]
  4. Cambie "2" a la pila desde la entrada (anticipándose a la regla 3). Entrada = [*, 3] Pila = [E, +, 2]
  5. Reduzca el elemento de pila "2" a la Expresión "E" según la regla 3. Pila = [E, +, E]
  6. Reduzca los elementos de la pila [E, +, E] y la nueva entrada "E" a "E" según la regla1. Pila = [E]
  7. Cambie "*" a la pila desde la entrada (anticipándose a la regla2). Entrada = [3] Pila = [E, *]
  8. Cambie "3" a la pila desde la entrada (anticipándose a la regla 3). Entrada = [] (vacía) Pila = [E, *, 3]
  9. Reduzca el elemento de pila "3" a la expresión "E" según la regla3. Pila = [E, *, E]
  10. Reduzca los elementos de la pila [E, *, E] y la nueva entrada "E" a "E" según la regla2. Pila = [E]

El árbol de análisis y el código resultante no es correcto según la semántica del lenguaje.

Para analizar correctamente sin mirar hacia adelante, hay tres soluciones:

  • El usuario debe encerrar las expresiones entre paréntesis. A menudo, esta no es una solución viable.
  • El analizador debe tener más lógica para retroceder y reintentar cada vez que se infringe o no se completa una regla. Se sigue un método similar en analizadores de LL.
  • Alternativamente, el analizador sintáctico o la gramática deben tener una lógica adicional para retrasar la reducción y reducir solo cuando esté absolutamente seguro de qué regla reducir primero. Este método se utiliza en analizadores sintácticos LR. Esto analiza correctamente la expresión pero con muchos más estados y una mayor profundidad de pila.
Acciones del analizador anticipado
  1. Cambie 1 a la pila en la entrada 1 antes de la regla 3. No se reduce de inmediato.
  2. Reduzca el elemento 1 de la pila a una expresión simple en la entrada + según la regla 3. La anticipación es +, por lo que estamos en camino a E +, por lo que podemos reducir la pila a E.
  3. Cambie + a la pila en la entrada + antes de la regla1.
  4. Cambie 2 a la pila en la entrada 2 antes de la regla 3.
  5. Reduzca el elemento 2 de la pila a Expresión en la entrada * según la regla 3. El lookahead * solo espera E antes.
  6. Ahora la pila tiene E + E y aún así la entrada es *. Ahora tiene dos opciones, cambiar según la regla2 o reducir según la regla1. Dado que * tiene mayor precedencia que + según la regla4, cambiamos * a la pila en anticipación a la regla2.
  7. Cambie 3 a la pila en la entrada 3 antes de la regla 3.
  8. Reduzca el elemento 3 de la pila a Expresión después de ver el final de la entrada según la regla 3.
  9. Reducir los elementos de la pila E * E a E según la regla 2.
  10. Reduzca los elementos de la pila E + E a E según la regla 1.

El árbol de análisis generado es correcto y simplemente más eficiente que los analizadores no anticipados. Esta es la estrategia que se sigue en los analizadores sintácticos LALR .

Ver también

Referencias

21. Códigos HTML de análisis libre [1]

Otras lecturas

enlaces externos