EN ESTA UNIDAD SE DAN A CONOCER LOS CONCEPTOS QUE SON NECESARIOS PARA PODER ADENTRARNOS EN MATERIA...
TEMARIO:
INTRODUCCION
LENGUAJES DE PROGRAMACION
PROCESADORES DE LENGUAJE
FASES DE UN COMPILADOR
JERARQUIAS DE CHOMSKY
FORMAS DE BACKUS NAUR
INTRODUCCION
En el año de 1946 se desarrolló el primer ordenador digital. En un principio, estas máquinas ejecutaban instrucciones consistentes en códigos numéricos que señalan a los circuitos de la máquina los estados correspondientes a cada operación. Esta expresión mediante códigos numéricos se llamó Lenguaje Máquina, interpretado por un secuenciador cableado o por un microprograma. Pero los códigos numéricos de las máquinas son engorrosos. Pronto los primeros usuarios de estos ordenadores descubrieron la ventaja de escribir sus programas mediante claves más fáciles de recordar que esos códigos numéricos; al final, todas esas claves juntas se traducían manualmente a Lenguaje Máquina. Estas claves constituyen los llamados lenguajes ensambladores, que se generalizaron en cuanto se dio el paso decisivo de hacer que las propias máquinas realizaran el proceso mecánico de la traducción. A este trabajo se le llama ensamblar el programa.
Los trabajos de investigación se orientaron entonces hacia la creación de un lenguaje que expresara las distintas acciones a realizar de una manera lo más sencilla posible para el hombre. Es por eso que en el año de en 1950, John Backus dirigió una investigación en I.B.M. en un lenguaje algebraico. En 1954 se empezó a desarrollar un lenguaje que permitía escribir fórmulas matemáticas de manera traducible por un ordenador. Le llamaron FORTRAN (FORmulae TRANslator). Fue el primer lenguaje considerado de alto nivel. Se introdujo en 1957 para el uso de la computadora IBM modelo704. Permitía una programación más cómoda y breve que lo existente hasta ese momento, lo que suponía un considerable ahorro de trabajo. Surgió así por primera vez el concepto de un traductor, como un programa que traducía un lenguaje a otro lenguaje.
La tarea de realizar un compilador no fue fácil. El primer compilador de FORTRAN tardó 18 años-persona en realizarse y era muy sencillo. Esta corriente estuvo muy influida por los trabajos sobre gramáticas de contexto libre publicados por Chomsky dentro de su estudio de lenguajes naturales. Con estas ideas surgió un grupo europeo encabezado por el profesor F.L.Bauer . Este grupo definió un lenguaje de usos múltiples independiente de una realización concreta sobre una máquina. Pidieron colaboración a la asociación americana A.C.M. (Association for Computing Machinery) y se formó un comité en el que participó J. Backus que colaboraba en esta investigación. De esa unión surgió un informe definiendo un International Algebraic Language (I.A.L.), publicado en Zurich en 1958. Posteriormente este lenguaje se llamó ALGOL 58 (ALGOritmic Language). En 1969, el lenguaje fue revisado y llevó a una nueva versión que se llamó ALGOL 60.
En el ALGOL aparecen por primera vez muchos de los conceptos de los nuevos lenguajes
algorítmicos:
• Definición de la sintaxis en notación BNF (Backus-Naur Form).
• Formato libre.
• Declaración explícita de tipo para todos los identificadores.
• Estructuras iterativas más generales.
• Recursividad.
• Paso de parámetros por valor y por nombre.
• Estructura de bloques, lo que determina la visibilidad de los identificadores.
En 1958, Strong y otros proponían una solución al problema de que un compilador fuera utilizable por varias máquinas objeto. Para ello se dividía por primera vez el compilador en dos fases, designadas como el "front end" y el "back end". La primera fase (front end) es la encargada de analizar el programa fuente y la segunda fase (back end) es la encargada de generar código para la máquina objeto. El puente de unión entre las dos fases era un lenguaje intermedio que se designó con el nombre de UNCOL (UNiversal Computer Oriented Language
Ya en estos años se van poniendo las bases para la división de tareas en un compilador. Así, en 1959 Rabin y Scott proponen el empleo de autómatas deterministas y no deterministas para el reconocimiento lexicográfico de los lenguajes. Rápidamente se aprecia que la construcción de analizadores léxicos a partir de expresiones regulares es muy útil en la implementación de los compiladores.
En 1968, Johnson apunta diversas soluciones. En 1975, con la aparición de LEX surge el concepto de un generador automático de analizadores léxicos a partir de expresiones regulares, basado en el sistema operativo UNIX.A partir de los trabajos de Chomsky ya citados, se produce una sistematización de la sintaxis de los lenguajes de programación, y con ello un desarrollo de diversos métodos de análisis sintáctico.
Con la aparición de la notación BNF - desarrollada en primer lugar por Backus en 1960 cuando trabajaba en un borrador del ALGOL 60, modificada en 1963 por Naur y formalizada por Knuth en 1964 - se tiene una guía para el desarrollo del análisis sintáctico. Los diversos métodos de parsers ascendentes y descendentes se desarrollan durante la década de los 60. En 1959, Sheridan describe un método de parsing de FORTRAN que introducía paréntesis adicionales alrededor de los operandos para ser capaz de analizar las expresiones. Más adelante, Floyd introduce la técnica de la precedencia de operador y el uso de las funciones de precedencia. A mitad de la década de los 60, Knuth define las gramáticas LR y describe la construcción de una tabla canónica de parser LR. Por otra parte, el uso por primera vez de un parsing descendente recursivo tuvo lugar en el año 1961. En el año 1968 se estudian y definen las gramáticas LL así como los parsers predictivos..
En los primeros años de la década de los 70, se describen los métodos SLR y LALR de parser LR. Debido a su sencillez y a su capacidad de análisis para una gran variedad de lenguajes, la técnica de parsing LR va a ser la elegida para los generadores automáticos de parsers. A mediados de los 70, Johnson crea el generador de analizadores sintácticos YACC para funcionar bajo un entorno UNIX .
Junto al análisis sintáctico, también se fue desarrollando el análisis semántico.
En los primeros lenguajes (FORTRAN y ALGOL 60) los tipos posibles de los datos eran muy simples, y la comprobación de tipos era muy sencilla. No se permitía la coerción de tipos, pues ésta era una cuestión difícil y era más fácil no permitirlo. Con la aparición del ALGOL 68 se permitía que las expresiones de tipo fueran construidas sistemáticamente. Más tarde, de ahí surgió la equivalencia de tipos por nombre y estructural. El manejo de la memoria como una implementación tipo pila se usó por primera vez en 1958 en el primer proyecto de LISP. La inclusión en el ALGOL 60 de procedimientos recursivos potenció el uso de la pila como una forma cómoda de manejo de la memoria. Dijkstra introdujo posteriormente el uso del display para acceso a variables no locales en un lenguaje de bloques.
La técnica de la optimización apareció desde el desarrollo del primer compilador de FORTRAN. Backus comenta cómo durante el desarrollo del FORTRAN se tenía el miedo de que el programa resultante de la compilación fuera más lento que si se hubiera escrito a mano. Para evitar esto, se introdujeron algunas optimizaciones en el cálculo de los índices dentro de un bucle. Pronto se sistematizan y se recoge la división de optimizaciones independientes de la máquina y dependientes de la máquina. Entre las primeras están la propagación de valores , el arreglo de expresiones, la eliminación de redundancias, etc.
Entre las segundas se podría encontrar la localización de registros, el uso de instrucciones propias de la máquina y el reordenamiento de código. A partir de 1970 comienza el estudio sistemático de las técnicas del análisis de flujo de datos. Su repercusión ha sido enorme en las técnicas de optimización global de un programa. En la actualidad, el proceso de la compilación ya está muy asentado. Un compilador es una herramienta bien conocida, dividida en diversas fases.
De todas formas, y en contra de lo que quizá pueda pensarse, todavía se están llevando a cabo varias vías de investigación en este fascinante campo de la compilación. Por una parte, se están mejorando las diversas herramientas disponibles.
LENGUAJES DE PROGRAMACION
Un lenguaje de programación es una construcción mental del ser humano para expresar programas. Está constituido por un grupo de reglas gramaticales, un grupo de símbolos utilizables, un grupo de términos con sentido único y una regla principal que resume las demás. Para que ésta construcción mental sea operable en un computador debe existir otro programa que controle la validez o no de lo escrito. A éste se le llama traductor.
Los lenguajes pueden ser de alto o bajo nivel. En los de bajo nivel las instrucciones son simples y cercanas al funcionamiento de la máquina, como por ejemplo el código máquina y el ensamblador. En los lenguajes de alto nivel hay un alto grado de abstracción y el lenguaje es más próximo a los humanos, como por ejemplo Léxico, PASCAL, Cobol o Java. La programación de los sistemas se hace a través de los lenguajes de programación, que posibilitan la comunicación de órdenes al ordenador o sistema.
Lenguaje de programación
Los lenguajes de programación son herramientas y una notación formal para describir algoritmos o funciones que serán ejecutadas por un ordenador que nos permiten crear programas y software. Entre ellos tenemos Delphi, Visual Basic, Pascal, Java entre otros más.
Un lenguaje de programación es una técnica estándar de comunicación que permite expresar las instrucciones que han de ser ejecutadas en una computadora. Consiste en un conjunto de reglas sintácticas y semánticas que definen un lenguaje informático.
Clasificación de los lenguajes de programación
Existen diferentes clases o tipos de lenguajes de programación:
Según su grado de independencia de la máquina
1) Lenguaje máquina: es el lenguaje de programación que entiende directamente la computadora o máquina.
- Forma más baja de un lenguaje de programación.
- Lenguaje de bajo nivel.
- Secuencias de 0 y 1’s, cada instrucción se representa por un código numérico y las direcciones igual.
- Es la notación que entiende directamente la PC.
- Las instrucciones y la estructura están ligadas directamente a la arquitectura de la PC.
2) Lenguaje ensamblador: fue el primer lenguaje de programación que trato de sustituir el lenguaje máquina por otro mucho más parecido al de los seres humanos.
- Es la versión simbólica del lenguaje máquina (nemónicos).
- Cada código de operación se indica por un código simbólico (ADD MUL DIV MOD).
- Las asignaciones de memoria se dan con nombres simbólicos (PAGO; NOMBRE).
3) Lenguaje de medio nivel
4) - Tiene algunas características de los lenguajes de bajo nivel: posibilidad de acceso directo a posiciones de memoria e indicaciones para que ciertas variables se almacenen en los registros del procesador.
- Presentan la posibilidad del manejo de estructuras de control y estructuras de datos de los lenguajes de alto nivel.
- Ejemplos de estos lenguajes: C y FORTH.
5) Lenguaje de alto nivel: Este tipo de lenguajes de programación son independientes de la máquina, lo podemos usar en cualquier computador con muy pocas modificaciones o sin ellas, son muy similares al lenguaje humano, pero precisan de un programa interprete o compilador que traduzca este lenguaje de programación de alto nivel a uno de bajo nivel como el lenguaje de máquina que la computadora pueda entender.
- Presentan características superiores a los lenguajes de medio nivel, aunque no tienen algunas posibilidades de acceso directo al sistema.
- Facilitan la estructura de programas con estructuras de datos complejas, utilización de bloques y procedimientos o subrutinas.
- Los lenguajes orientados a objetos pertenecen a esta clasificación y permiten definir tipos de datos abstractos que agrupan datos y métodos.
6) Lenguaje orientados a problemas concretos
- Se utilizan para la resolución de problemas en un campo específico.
- Comúnmente se conocen como “aplicaciones”.
Según la forma de sus instrucciones
1) Lenguajes imperativos o procedimentales
- Usan la instrucción o sentencia de asignación como construcción básica en la estructura de los programas.
- También se conocen como “lenguajes orientados a instrucciones”.
- Uso intensivo de variables para referenciar posiciones de memoria.
- La estructura de los programas está basada en instrucciones.
- Ejemplos de estos lenguajes: Pascal, C, C++, Ada, Fortran, Cobol.
2) Lenguajes declarativos
Son lenguajes de muy alto nivel cuya notación es muy próxima al problema del algoritmo que resuelven.
Ejemplos de estos lenguajes: LISP, APL, PROLOG.
3) Lenguajes concurrentes
Permiten la ejecución simultánea (paralela) de dos o varias tareas.
Ejemplos de estos lenguajes: Ada, Concurrent C, Aarhus, Concurrent Prolog.
Ejemplos de estos lenguajes: LISP, APL, PROLOG.
4) Lenguajes orientados a objetos
Soportan directamente tipos abstractos de datos y clases. Lenguajes basados en objetos, añaden mecanismos para soportar la herencia.
Ventajas de los lenguajes de alto nivel sobre los lenguajes de bajo nivel
Diseño modular de programas.
Facilitan la estructura de los programas.
Mayor capacidad de creación de estructuras de datos.
Son más fáciles de aprender, requieren pocos conocimientos de hardware, y están cerrados a ciertas zonas de la máquina.
Soporte para la programación orientada a objetos.
Los programadores se liberan de ocupaciones rutinarias con referencia a instrucciones simbólicas o numéricas, asignaciones de memoria, etc.
Ofrecen gran variedad de estructuras de control.
Los programas se depuran más fácilmente que los escritos en lenguaje máquina o ensamblador.
Permiten trabajar en modo gráfico.
El programador no necesita conocer la forma en que se colocan los datos en memoria.
PROCESADORES DE LENGUAJE
Un procesador de lenguaje es el nombre genérico que reciben las aplicaciones cuya entrada es un lenguaje (entrada no trivial).
Procesadores de lenguaje
Nombre genérico que reciben todas las aplicaciones informáticas en las cuales uno de los datos fundamentales de entrada es un lenguaje. Proporciona herramientas para ayudar al programador a escribir programas informáticos y a usar diferentes lenguajes de programación de forma práctica.
Que incluyen:
• Traductores: Programa que procesa un texto fuente y genera un texto objeto. Está escrito en un lenguaje de implementación. Se representa por la “Notación T”
Donde:
L. F. = Lenguaje origen que transforma el traductor.
L. O. = Lenguaje a que se traduce el lenguaje fuente.
L. I. = Lenguaje en que está escrito el traductor.
• Ensamblador:Si el lenguaje fuente es el lenguaje ensamblador y el lenguaje objeto es el lenguaje máquina, entonces el traductor se llama Ensamblador. Los ensambladores son traductores sencillos.
• Compilador:Traductor que transforma textos fuente de lenguaje de alto nivel a lenguaje de bajo nivel.
Tiempo de Compilación. Tiempo que se necesita para traducir un lenguaje de alto nivel a lenguaje de bajo nivel.
Tiempo de ejecución. Tiempo que tarda en ejecutarse un programa objeto.
• Montadores de enlace o enlazador (linker):Se produce cuando el lenguaje fuente permite una fragmentación de los programas en trozos, denominados de distintas formas según el lenguaje de programación empleado (módulos, units, librerías, procedimientos, funciones, subrutinas, etc.). Dichas partes pueden compilarse por separado, produciéndose los códigos objeto de cada una de estas partes.
• Cargadores: El cargador se encarga de colocar el fichero ejecutable en memoria; asignando el espacio necesario al programa en memoria y pasando el control a la primera de las instrucciones a ejecutar, comenzando a continuación la fase de ejecución
• Intérpretes: Son programas que simplemente ejecutan las instrucciones que encuentran en el texto fuente. Se da en tiempo de ejecución. Basic, LOGO, Prolog, son lenguajes interpretados.
• Decompiladores: Los decompiladores realizan la tarea inversa a los compiladores, es decir, son un caso particular de los traductores en los cuales el programa fuente es un lenguaje de bajo nivel y el lenguaje objeto es un lenguaje de nivel superior.
• Desensambladores: Un caso particular de los decompiladores son los desensambladores que traducen de código máquina a ensamblador. Son más fáciles, por la correspondencia directa entre las instrucciones de ensamblador y código máquina.
• Depuradores: Los depuradores son herramientas que permiten encontrar y corregir los errores de los programas (bugs). Suelen ir ligados a los compiladores de forma que el programador pueda comprobar y visualizar la correcta ejecución de un programa.
• Analizadores de rendimiento (profilers):Los analizadores de rendimiento son herramientas que permiten examinar el comportamiento de los programas en tiempo de ejecución, permitiendo comprobar qué zonas de código trabajan eficientemente y cuáles deberían ser revisadas por su bajo rendimiento.
• Optimizadores de código: Pueden ser herramientas independientes o estar incluidas en los compiladores e invocarse por medio de opciones de compilación, siendo ésta última forma como se suelen encontrar en la mayor parte de los compiladores comerciales.
• Compresores: Los compresores de archivos son una herramienta habitual de uso en el campo de la informática. Un caso particular son los compresores de ficheros ejecutables, que reducen el tamaño de los ejecutables.
• Preprocesadores: Los preprocesadores son un caso particular de traductor en el cual se traduce un lenguaje de alto nivel a “otro”, cuando el primero no puede pasar a lenguaje máquina directamente. El preprocesador realiza las sustituciones, pero no hace ningún tipo de análisis del contexto donde las realiza, esta es la principal diferencia entre un preprocesador y otros tipos de procesador de lenguaje. En otras palabras, los preprocesadores procesan un texto fuente modificándolo en cierta forma previamente a la compilación.
• Formateadores: Pueden ser de muchos tipos y con diferentes fines, desde los dedicados a formatear textos, ecuaciones o programas. Los formateadores de programas resaltan su sintaxis o su estructura, para lo cual es necesario conocer la sintaxis del lenguaje a formatear.
• Editores: Los editores de lenguajes de programación con sintaxis resaltada por medio de colores o tipos de letra permiten llamar la atención del programador en el momento mismo que está escribiendo el programa, sin necesidad de compilar, dado que llevan incorporada la sintaxis del lenguaje.
FASES DE UN COMPILADOR
Compilador
Son programas o herramientas encargadas de compilar. Un compilador toma un texto (código fuente) escrito en un lenguaje de alto nivel y lo traduce a un lenguaje comprensible por las computadoras (código objeto).
Ejemplos de compiladores
- Borland C++ Compiler 5.5
- Microsoft QuickBasic 7.1
- Borland Turbo Assembler
- Borland Turbo C++ 3.0
- Borland Turbo C++
- Borland Turbo Debugger 5.5
- Borland Turbo Pascal 7
- Apteryx Lisp 1.0
- Object Ada 7.1 Special Edition
- Macromedia Director MX 9.0
- Pacific C MS-DOS Compiler
- CA Clipper 5.3
- XBASE++ Compilador Windows para CA-Clipper
Clasificación de los compiladores
1) Compilador cruzado: Genera un código ejecutable en un ordenador distinto de aquel en que se realiza la compilación.
2) Compilador de montaje y ejecución: Se fragmenta el programa fuente en módulos que se compilan por separado, y una vez compilados se unen mediante un enlazador para formar un módulo ejecutable.
3) Compilador en una pasada: Examina el código fuente una sola vez, generando el código objeto.
4) Compilador de pasadas múltiples: Requiere varias lecturas del programa fuente para producir y optimizar el código objeto.
5) Compilador de optimización: Lee el código fuente, lo analiza, optimiza y descubre errores potenciales sin ejecutar el programa.
6) Compilador incremental: Compila el programa fuente, en caso de detectar errores al volver a compilar el programa corregido, solo compila las modificaciones que se han hecho respecto al primero.
7) Ensamblador: El lenguaje fuente es el lenguaje ensamblador.
8) Autocompilador: Es el compilador que está escrito en el mismo lenguaje a compilar, básicamente nos sirve para hacer ampliaciones al lenguaje, mejorar el código generado, etc.
9) Metacompilador: “Compilador de compiladores”. Obtiene como entrada la definición de un lenguaje y como salida el compilador para dicho lenguaje.
Proceso de compilación
Un compilador opera en fases, cada una de las cuales transforma el programa fuente de una representación en otra, hasta llegar al programa objeto.
FASES DEL PROCESO DE COMPILACIÓN
1. Análisis lineal: También llamado análisis léxico o exploración. En esta etapa la cadena de caracteres que constituye el programa fuente se lee de izquierda a derecha (de arriba hacia abajo) y se agrupa en componentes léxicos que son secuencias de caracteres que tienen un significado colectivo.
NOTA: Los espacios en blanco y comentarios se eliminan durante el análisis léxico.
2. Análisis jerárquico: También llamado análisis sintáctico. En esta etapa los caracteres o componentes léxicos se agrupan jerárquicamente en colecciones anidadas con un significado colectivo.
La representación gráfica del programa fuente se hace mediante un árbol de análisis sintáctico (jerarquías).
3. Análisis semántico: En esta etapa se revisa el programa fuente para tratar de encontrar errores semánticos y se reúne la información de los tipos para la fase posterior de generación de código.
Utiliza la estructura jerárquica determinada en la fase de análisis sintáctico para identificar los operadores y operandos, expresiones y proposiciones.
4. Generación de código intermedio: Algunos compiladores generan una representación intermedia explícita del programa fuente. A esta representación intermedia se le considera “un programa” para una máquina abstracta. El código intermedio debe cumplir con dos propiedades importantes: ser fácil de producir y fácil de traducir al programa objeto.
5. Optimización de código: Trata de mejorar el código intermedio de modo que resulte un código máquina más fácil de ejecutar. Hay mucha variación en la cantidad de optimización de código que ejecutan los compiladores. Un compilador de optimización realiza mucha optimización, ocupando gran parte del tiempo de compilación en esta tarea
6. Generación de código: Fase final de un compilador. Por lo general, consiste en código máquina o ensamblador. Las posiciones de memoria se seleccionan para cada una de las variables usadas por el programa. Después, cada una de las instrucciones intermedias se traducen a una secuencia de instrucciones de máquina que ejecutan la misma tarea.
Otras funciones del compilador:
Administrador de la Tabla de Símbolos
Una función esencial del compilador es registrar los identificadores utilizados en el programa fuente y reunir la información sobre los atributos de cada identificador.
Información que proporcionan los atributos: memoria asignada, tipo, ámbito (locales, globales).
Para procedimientos o funciones: número y tipo de argumentos, método para pasar argumentos, tipo de dato que devuelve.
Manejador de Errores
Cada fase del compilador puede encontrar errores. Después de detectar un error, cada fase debe tratar de alguna forma ese error para poder continuar la compilación, permitiendo la detección de más errores en el programa fuente.
El análisis sintáctico y semántico manejan la mayoría de errores detectables por el compilador.
El análisis léxico detecta errores donde los caracteres restantes de la entrada no forman ningún componente léxico del lenguaje.
El análisis sintáctico detecta los errores donde la cadena de componentes léxicos violan las reglas de la estructura.
El análisis semántico el compilador intenta detectar construcciones que tengan estructura sintáctica correcta pero que no tengan significado para la operación implicada. Por ejemplo, cuando se intenta sumar dos identificadores, donde uno corresponde al nombre de un arreglo y otro al nombre de una función.
Agrupamiento de las fases de compilación
Las fases del proceso de compilación se agrupan en una etapa inicial y una etapa final.
Etapa inicial
Comprende aquellas fases que dependen principalmente del lenguaje fuente y son en gran parte independientes de la máquina objeto.
- Análisis léxico
- Análisis sintáctico
- Análisis semántico
- Generación de código intermedio
Etapa final
Comprende aquellas fases del compilador que dependen de la máquina objeto. No dependen del lenguaje fuente, solo del lenguaje intermedio.
- Optimización de código
- Generación de código
JERARQUIAS DE CHOMSKY
En los años 50 Noam Chomsky (1928) realizó grandes avances en la concepción de un modelo matemático de las gramáticas en conexión con los lenguajes naturales.
Los metalenguajes son herramientas para la descripción formal de los lenguajes, facilitando no sólo la comprensión del lenguaje, sino también el desarrollo del compilador correspondiente. Ejemplos: las expresiones regulares que describen los componentes léxicos del lenguaje; las notaciones BNF, EBNF y los diagramas sintácticos que describen la sintaxis del lenguaje.
En lingüística la jerarquía de Chomsky es una clasificación jerárquica de distintos tipos de gramáticas formales que generan lenguajes formales. Esta jerarquía fue descrita por Noam Chomsky en 1956.
FORMAS DE BACKUS NAUR
John Backus es un informático estadounidense, ganador del Premio Turing en 1977 por sus trabajos en sistemas de programación de alto nivel, en especial por su trabajo con FORTRAN.
John Backus participó en la creación del primer lenguaje de alto nivel (FORTRAN), y posteriormente participó a principios de los años 60 en el desarrollo del lenguaje ALGOL que utilizó por primera vez la forma BNF.
La notación BNF o gramática independiente de contexto permite describir la sintaxis de las construcciones de los lenguajes de programación, utilizando los siguientes metasímbolos:
< > Encierra conceptos definidos o por definir (se utiliza para los no terminales en una gramática).
: : = Para definir o indicar equivalencia
| Separa las distintas alternativas
“ “ Indica que el símbolo entre comillas es un carácter que forma parte de la sintaxis del lenguaje.
( ) Para agrupaciones
Por ejemplo, para definir un identificador (nombre que se utiliza para identificar una variable) en BNF:
en este caso, a,b,c,d,e,...,z y 0,1,2,3,...,9 son terminales y el resto no terminales.