COBOL
Estructura de COBOL
Un program COBOL está compuesto de 4 divisiones bien diferenciadas, que son:
1. Identification Division (Identificación): Contiene información del programa, el autor, nombre del programa, fecha de creación, comentarios, etc.
2. Environment Division (Ambiente): Describe las características que debe tener el ordenador para poder ejecutar el programa, los dispositivos de Entrada / Salida, el compilador, etc.
3. Data Division (Información): Incluye los nombres de las variables, las características de las variables, el contenido que van a tener, sus nombres, etc.
4. Procedure Division (Procedimiento): Describe las órdenes y operaciones que se van a realizar y el orden de las mismas.
La estructura general sería la siguiente...
IDENTIFICATION DIVISION
PROGRAM-ID
AUTHOR
INSTALLATION
DATE-WRITTEN
DATE-COMPILED
SECURITY
ENVIRONMENT DIVISION
CONFIGURATION SECTION
SOURCE-COMPUTER
OBJECT-COMPUTER
SPECIAL-NAMES
INPUT-OUTPUT SECTION
FILE CONTROL
I-O-CONTROL
DATA DIVISION
FILE SECTION
...
FICHERO
REGISTRO
...
WORKING-STORATE SECTION
...
VARIABLE
...
LINKAGE SECTION
...
PARAMETRO
...
PROCEDURE DIVISION
...
SECCION
PARRAFO
SENTENCIA
...
PROGRAM-ID
AUTHOR
INSTALLATION
DATE-WRITTEN
DATE-COMPILED
SECURITY
ENVIRONMENT DIVISION
CONFIGURATION SECTION
SOURCE-COMPUTER
OBJECT-COMPUTER
SPECIAL-NAMES
INPUT-OUTPUT SECTION
FILE CONTROL
I-O-CONTROL
DATA DIVISION
FILE SECTION
...
FICHERO
REGISTRO
...
WORKING-STORATE SECTION
...
VARIABLE
...
LINKAGE SECTION
...
PARAMETRO
...
PROCEDURE DIVISION
...
SECCION
PARRAFO
SENTENCIA
...
Tipos de Datos
División de Información se utiliza para definir las variables utilizadas en el programa. Para describir los datos en COBOL, uno debe comprender los siguientes términos:
- Nombre de Datos
- Número de Nivel
- Cláusula Imagen
- Cláusula de Valor
01 TOTAL-STUDENTS PIC9(5) VALUE '125'. | | | | | | | | | | | | Level Number Data Name Picture Clause Value Clause
Nombre de Datos
Nombres de datos debe estar definido en la División de Información antes de utilizarlas en la Division de Procedure. Deben tener un nombre definido por el usuario; palabras reservadas no se pueden utilizar. Nombres de datos da referencia a los lugares de memoria donde se almacenan los datos reales. Pueden ser primarias o tipo de grupo.El ejemplo siguiente muestra los datos válidos y no válidos los nombres:
Valid: WS-NAME TOTAL-STUDENTS A100 100B Invalid: MOVE (Reserved Words) COMPUTE (Reserved Words) 100 (No Alphabet) 100+B (+ is not allowed)
Número de Nivel
Número de nivel se utiliza para especificar el nivel de datos en un registro. Que se utilizan para diferenciar los elementos elementales y elementos de grupo. Elementos elementales pueden agruparse para crear elementos de grupo.
- Elementos elementales no puede dividirse. Número de nivel, los datos nombre, foto y cláusula cláusula de valor (opcional) se utiliza para describir un tema elemental.
- Grupo los elementos constan de uno o más elementos elementales. Número de nivel, nombre de los datos, y cláusula de valor (opcional) que se utilizan para describir un elemento de grupo. Grupo número de nivel es siempre 01.
El ejemplo siguiente muestra Grupo elemental y los elementos:
DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-NAME PIC X(25). ---> ELEMENTARY ITEM 01 WS-CLASS PIC 9(2) VALUE '10'. ---> ELEMENTARY ITEM 01 WS-ADDRESS. ---> GROUP ITEM 05 WS-HOUSE-NUMBER PIC 9(3). ---> ELEMENTARY ITEM 05 WS-STREET PIC X(15). ---> ELEMENTARY ITEM 05 WS-CITY PIC X(15). ---> ELEMENTARY ITEM 05 WS-COUNTRY PIC X(15) VALUE 'INDIA'. ---> ELEMENTARY ITEM
Cláusula de Imagen
Cláusula Imagen se utiliza para definir los siguientes elementos:
- Tipo de datos pueden ser numéricos, alfabéticos o alfanuméricos. Tipo Numérico consiste sólo de dígitos 0 a 9. Alfabético tipo consta de las letras de la A a la Z y espacios. Tipo Alfanumérico consta de dígitos, letras y caracteres especiales.
- Signo puede utilizarse con datos numéricos. Puede ser + o .
- Posición del punto decimal se puede usar con datos numéricos. Posición asumida es la posición del punto decimal y no se incluyen en los datos.
- Longitud define el número de bytes utilizados en el elemento de datos.
Los símbolos utilizados en una imagen cláusula:
Símbolo | Descripción |
---|---|
9 | Valor Numérico |
A | Orden alfabético |
X | Alfanumérico |
V | Decimal implícito |
S | Signo |
P | Decimal asumido |
El siguiente ejemplo muestra el uso de PIC cláusula:
IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-NUM1 PIC S9(3)V9(2). 01 WS-NUM2 PIC PPP999. 01 WS-NUM3 PIC S9(3)V9(2) VALUE -123.45. 01 WS-NAME PIC A(6) VALUE 'ABCDEF'. 01 WS-ID PIC X(5) VALUE 'A121$'. PROCEDURE DIVISION. DISPLAY "WS-NUM1 : "WS-NUM1. DISPLAY "WS-NUM2 : "WS-NUM2. DISPLAY "WS-NUM3 : "WS-NUM3. DISPLAY "WS-NAME : "WS-NAME. DISPLAY "WS-ID : "WS-ID. STOP RUN.
JCL para ejecutar el programa COBOL:
//SAMPLE JOB(TESTJCL,XXXXXX),CLASS=A,MSGCLASS=C //STEP1 EXEC PGM=HELLO
Cuando se compila y ejecuta el programa antes mencionado, se produce el resultado siguiente:
WS-NUM1 : +000.00 WS-NUM2 : .000000 WS-NUM3 : -123.45 WS-NAME : ABCDEF WS-ID : A121$
Cláusula de Valor
Cláusula de valor es una cláusula opcional que se usa para inicializar los elementos de datos. Los valores pueden ser literales numéricos, alfanuméricos, literal o figurativa constante. Puede utilizarse tanto con los elementos elementales y grupo.
El siguiente ejemplo muestra el uso de cláusula de valor:
IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-NUM1 PIC 99V9 VALUE IS 3.5. 01 WS-NAME PIC A(6) VALUE 'ABCD'. 01 WS-ID PIC 99 VALUE ZERO. PROCEDURE DIVISION. DISPLAY "WS-NUM1 : "WS-NUM1. DISPLAY "WS-NAME : "WS-NAME. DISPLAY "WS-ID : "WS-ID. STOP RUN.
JCL para ejecutar el programa COBOL:
//SAMPLE JOB(TESTJCL,XXXXXX),CLASS=A,MSGCLASS=C //STEP1 EXEC PGM=HELLO
Cuando se compila y ejecuta el programa antes mencionado, se produce el resultado siguiente:
WS-NUM1 : 03.5 WS-NAME : ABCD WS-ID : 00
Estructura Repetitiva
Párrafos o Rutinas. Siempre que se ejecuta un programa en COBOL, se hace de forma secuencial, empezando desde arriba hasta que encuentre la instrucción de finalizar el programa. Las rutinas es una forma de alterar ese orden, es decir, le damos el control a un párrafo y ejecuta todas las instrucciones que haya en éste.
Ejemplo:
IDENTIFICATION DIVISION. PROGRAM-ID. Parrafos. AUTHOR. Ismael. * Esto es un ejemplo usando rutinas o párrafos.
PROCEDURE DIVISION. Inicio. DISPLAY "Este es el principio del programa" PERFORM Rutina2 DISPLAY "Ahora voy a terminar el programa" STOP RUN. Rutina1. DISPLAY "Estoy en la rutina1" PERFORM Rutina3. Rutina2. DISPLAY "Ahora estoy en la rutina2" PERFORM Rutina1. Rutina3. DISPLAY "Ahora estoy en la rutina3".
Si observamos detenidamente, lo que hacemos es el párrafo Inicio, dentro de este llamamos a la Rutina 2, dentro de éste llamamos a la Rutina 1 y por último desde la rutina1 llamamos al párrafo rutina3. Una vez terminado estas rutinas, el programa vuelve a su orden normal, es decir, realiza las sentencias que le faltaban del párrafo Inicio, terminando así con la ejecución del programa. También hay que decir, que hacer párrafos estructura un poco mejor el programa y puede resultar un poco mas legible y comprensible.
PERFORM…THRU. Esto es otra forma de usar los párrafos como rutinas, es ejecutando más de un párrafo a la vez. Veamos un ejemplo:
IDENTIFICATION DIVISION. PROGRAM-ID. Programa. AUTHOR. Ismael. DATA DIVISION. WORKING-STORAGE SECTION. 01 NOMBRE PIC X(12). 01 APELLIDO PIC X(12). PROCEDURE DIVISION. INICIO. PERFORM PIDENOMBRE THRU PIDEAPELLIDO STOP RUN. PIDENOMBRE. DISPLAY "DAME TU NOMBRE:" ACCEPT NOMBRE. PIDEAPELLIDO. DISPLAY "DAME TUS APELLIDOS:" ACCEPT APELLIDO.
PERFORM…TIMES. Vamos a ampliar un poco el número de veces que queremos que repita un proceso. Esta sentencia sirve para repetir un párrafo un número de veces determinado, su sintáxis es la siguiente:
Vamos a hacer un ejemplo, para aclarar un poco como funciona:
IDENTIFICATION DIVISION. PROGRAM-ID. Programa4. AUTHOR. Ismael. DATA DIVISION. WORKING-STORAGE SECTION. 01 NOMBRE PIC X(12). 01 APELLIDO PIC X(12). PROCEDURE DIVISION. INICIO. PERFORM PIDENOMBRE THRU PIDEAPELLIDO 3 TIMES STOP RUN. PIDENOMBRE. DISPLAY "DAME TU NOMBRE:" ACCEPT NOMBRE. PIDEAPELLIDO. DISPLAY "DAME TUS APELLIDOS:" ACCEPT APELLIDO.
PERFORM…UNTIL. Este tipo de sentencia sirve para realizar un párrafo hasta que se cumpla la condición. Veamos un ejemplo:
IDENTIFICATION DIVISION. PROGRAM-ID. Programa4. AUTHOR. Ismael. DATA DIVISION. WORKING-STORAGE SECTION. 77 LI PIC 99 VALUE 0. PROCEDURE DIVISION. INICIO. DISPLAY "PROGRAMA DE SALUDO" PERFORM SALUDAR UNTIL LI =5 STOP RUN. SALUDAR. ADD 1 TO LI DISPLAY "SALUDO Nº.: ", LI.
También podemos hacer el bucle de otra forma:
IDENTIFICATION DIVISION. PROGRAM-ID. Programa4. AUTHOR. Ismael. DATA DIVISION. WORKING-STORAGE SECTION. 77 LI PIC 99 VALUE 0. PROCEDURE DIVISION. INICIO. DISPLAY "PROGRAMA DE SALUDO" PERFORM UNTIL LI =5 ADD 1 TO LI DISPLAY "SALUDO Nº.: ", LI END-PERFORM STOP RUN.
Si observamos un poco, la diferencias radican en que ahora tiene un “fin del bucle”.
PERFORM…VARYING. Este tipo de bucles se le da valor a una variable y se aumenta o disminuye dicho valor y el proceso lo hace tantas veces hasta que se cumpla la condición. Con este tipo de bucle nos ahorraríamos algunas instrucciones, ya que hace varias instrucciones en una sola. Veamos un ejemplo:
IDENTIFICATION DIVISION. PROGRAM-ID. Programa4. AUTHOR. Ismael. DATA DIVISION. WORKING-STORAGE SECTION. 77 LI PIC 99 VALUE 0. PROCEDURE DIVISION. INICIO. PERFORM VARYING LI FROM 1 BY 1 UNTIL LI >5 DISPLAY "Saludo Nº.: ", LI END-PERFORM STOP RUN.
¿Como trabajo el VARYING?. Lo primero que hace es iniciar la variable LI al valor que indica el FROM, pregunta por la condición y si no se cumple realiza la sentencias que hay dentro. A continuación aumenta el valor de LI según lo que tenga en el BY y vuelve a preguntar por la condición…y así sucesivamente hasta que se cumpla la condición. En este caso realizará las sentencias con los valores 1, 2, 3, 4 y 5.
Estructura Condicional
Por norma general, un programa escrito en COBOL se ejecuta forma secuencial, es decir, una sentencia tras otra de forma que llegue hasta la instrucción de final de programa. Existen instrucciones en las que pueden variar este orden de ejecución, como son las condiciones.
Utilizando IF: Cuando un programa se encuentra una instrucción IF, lo que hace es evaluar la condición. Si es verdadera realiza las instrucciones que se encuentre en en THEN. Si es falsa ejecutará las instrucciones que se encuentre en el ELSE. Una vez que termine la sentencia del IF se debe especificar su fin con END-IF.
Veamos como se debería hacer la estructura de un IF:
Bloque1 Bloque2 IF variable1 < Variable2 THEN Bloque3 Bloque4 ELSE Bloque5 END-IF
Operadores Relacionales
- > Mayor que.
- < Menor que.
- >= Mayor o igual que.
- <= Menor o igual que.
- <> Distinto que.
Existen dos tipos de condiciones, las simples y las compuestas.
Las condiciones simples son aquellas que preguntamos por solo una condición, como por ejemplo “Si Variable1 es menor que Variable2 Entonces…”.
Las condiciones compuestas son aquellas que usamos dos o más condiciones simples a la vez, como por ejemplo “Si Variable1 es menor que Variable2, que Variable3 sea igual a Variable4 Entonces..”
En las condiciones compuestas existen unos operadores que sirven para juntar varias condiciones simples; OR y AND.
Las condiciones siempre se leen de izquierda a derecha y sólo evalúan si la condición es verdadera o falsa. Vamos a ver unos ejemplos y voy explicando sus diferencias.
IF Variable1>10 AND Variable<26 THEN Setencia1… END-IF IF NOT Variable1<10 OR Variable2=50 AND Variable3>150 THEN Sentencia1… END-IF
AND: Usando esta cláusula evalúa las dos condiciones a la vez, es decir, en este ejemplo será verdadero, sólo y exclusivamente cuando se cumplan las dos condiciones a la vez. Si alguna de la variables no cumpliera la condición, el resultado sería falso y no haría la sentencias que hay en el THEN.
OR: A diferencia de la cláusula anterior, ésta evalúa las condiciones una a una y cuando sea verdadera una de ellas o las dos a la vez, realiza las instrucciones que encuentra en el THEN.
Hay que tener en cuenta que COBOL mantiene un orden prioridad al evaluar las condiciones.
- Paréntesis.
- NOT.
- AND.
- OR.
En el segundo ejemplo se interpretaría de la siguiente forma:
- Lo primero que evalúa es el “NOT (Variable1<10)”.
- Lo siguiente sería “(Variable2=50) AND (Variable3>150)”.
- Por último sería la claúsula OR “(NOT (Variable1<10)) OR ((Variable2=50) AND (Variable3>150))”.
Nombres de Condiciones. Los nombre de condiciones son variables que se definen como nivel 88, en los que se definen los valores posibles que puede contener la variable o el identificador. Su sintaxis es la siguiente:
Cuando usamos los nombres de condiciones, la cláusula VALUE no asigna valores a una variable, lo que hace es dar los posibles valores que puede contener esa condición para que sea verdadera.
Lista de Valores.
02 Departamento PIC X. 88 Desarrollo VALUE I,L,T.
Rango de Valores. También podemos asociar varios nombres de condiciones a un misma variable, como en este caso.
02 Edad PIC 9(3). 88 Niño VALUE 0 THRU 12. 88 Adolescente VALUE 13 THRU 19. 88 Adulto VALUE 20 THRU 999.
Utilizando nombres de condiciones. Veamos un ejemplo:
WORKING-STORAGE SECTION. 01 Caracter PIC X. 88 Vocales VALUE "a", "e", "i", "o", "u". 88 Consonantes VALUE "b", "c", "d", "f", "g", "h". 88 Numeros VALUE "0" THRU "9". …etc.
ACCEPT Caracter. IF Vocales THEN DISPLAY "El digito "Caracter "es una vocal" ELSE IF Consonantes THEN DISPLAY "El digito " Caracter " es una consonante" ELSE IF Numeros THEN DISPLAY "El digito " Caracter " es un numero" ELSE DISPLAY "El digito introducido es incorrecto" END-IF END-IF END-IF …
Utilizando EVALUATE. Esta cláusula es otra forma de evaluar condiciones, pero de forma estructurada, como tipo “Casos”. Veamos su sintaxis:
Podemos utilizar como ejemplo el ejercicio anterior de las vocales, pero veremos lo que cambia usando EVALUATE:
ACCEPT Caracter. EVALUATE TRUE WHEN Vocales DISPLAY "Es una vocal" WHEN Consonantes DISPLAY "Es una consonante" WHEN Numeros DISPLAY "Es un número" WHEN OTHER DISPLAY "Digito incorrecto" END-EVALUATE …
Si nos fijamos hemos usado la cláusula TRUE para evaluar que nombres de condiciones (nivel 88) son verdaderas, también podemos utilizar condiciones, rangos de números o incluso variables. Veamos otro ejemplo:
EVALUATE Caracter WHEN "0" Sentencias… WHEN "1" THRU “9” Sentencias… WHEN "a" Sentencias… WHEN "x" Sentencias… END-EVALUATE
Funciones
Declaración: La descripción de los ficheros se hace prácticamente igual que en los demás compiladores, salvo algunas apreciaciones:
- SELECT CLIENTE ASSIGN TO "CLIENTES.DAT"
ORGANIZATION INDEXED ACCESS DYNAMIC
LOCK MODE AUTOMATIC
RECORD CLI-CODIGO
ALTERNATE RECORD CLI-NOMBRE, CLI-CODIGO
FILE STATUS STA-CLIENTE.
En el caso de la declaración de las claves, tener en cuenta que soporta el sistema de claves no dedundantes, pero al contrario que otros compiladores, no soporta nombre lógicos para dichas claves, por lo que los START hay que hacerlos por toda la declaración de la clave.
Por ejemplo, en la declaración anterior, si hicieramos un START de dicho archivo para la clave alterna, lo haríamos de la siguiente forma:
- START CLIENTE KEY NOT LESS CLI-NOMBRE, CLI-CODIGO INVALID KEY .....
END-START
Yo personalmente tengo una aplicación en RM/Cobol para el control de presencia y rendimiento en mi empresa y en la actualidad sigue funcionando, pero todos los datos estadísticos y de consultas, los he realizado con PowerCobol y por supuesto ambos leen y escriben en los mismos archivos.
Programas sin pantallas
Si deseamos incorporar programas que no tienen ventanas, es decir rutinas para cálculos en los que pasamos una serie de valores, utilizaremos PowerGem. Es un editor de proyectos muy completo. Con el, abriremos nuestro programa fuente y lo compilaremos. El nos crea un .obj (programa objeto) y luego solo tendremos que ir a PowerCobol e incorporarlo en nuestro módulo.
Las variables comunes en el programa se definen en la LINKAGE SECTION, como siempre, pero en las ventanas de PC las podremos definir como GLOBAL EXTERNAL.
La llamada desde cualquier evento se realizará como siempre:
- CALL 'nombredeprograma' USING variables
PC dispone de muchas rutinas interesantes para uso en ficheros, información del sistema, números, fechas, etc... Todas están explicadas en los manuales y deben ser tenidas en cuenta, ya que muchas de ellas, son de muchísima utilidad.
Para llamarlas, se utiliza la misma forma que para llamar a una rutina normal, es decir utilizando el comando CALL, por ejemplo si queremos borrar fisicamente un fichero del disco utilizamos la siguiente:
- CALL 'CBL_DELETE_FILE' USING 'nombredeficheroaborrar' RETURNING variable
Variables
Las variables se definen como siempre en la WORKING. Pero al tener la posibilidad de definirla en la WORKING de cada evento, las variables pueden tener varios "estados":
- Una variable definida en la WORKING del form, debemos indicarle que será GLOBAL para que tengo efecto durante todos los eventos de dicho FORM. Es decir esa variable va manteniendo su valor siempre que estemos en la ventana.
- En un evento podemos definir una variable (incluso con el mismo nombre que otra que tengamos como global) pero su ámbito solo será para dicho evento.
- Para utilizar una variable en mas de una ventana la declararemos como EXTERNAL, además de GLOBAL, así su valor irá por todas las ventanas que así la tengan definida. Su uso es similar a la LINKAGE SECTION. Evidentemente esa misma variable debe de estar definida también como EXTERNAL en las demás ventanas que queramos que su valor la acompañe.
- Siguiendo éstas premisas, las variables de un fichero, es decir, su FD debe de ir también con la palabra GLOBAL o EXTERNAL, según queramos que sea su ámbito. Yo personalmente recomiendo definir la FD solo como GLOBAL.
- WORKING-STORAGE SECTION.
01 VARIABLES GLOBAL.
02 CONTADOR PIC 999.
02 TEXTO PIC X(40).
02 CAMPO PIC X(10).
02 NOMBRE PIC 9(8)V99.
FD CLIENTES GLOBAL LABEL RECORD STANDARD.
Invocación: Los programas de aplicación CICS® escritos en lenguaje COBOL se compilan utilizando el mandato cicstcl en archivos de código de tiempo de ejecución COBOL-IT (.so en Linux y .cit en Windows). Estos archivos de código son adecuados para la carga dinámica mediante el sistema de tiempo de ejecución de COBOL-IT.
Los archivos creados no se pueden ejecutar directamente mediante el mandato cobcrun, porque contienen ciertos símbolos no resueltos que se espera que los proporcione el servidor de aplicaciones CICS. Consulte el apartado Servidores de aplicaciones para obtener más información.
Para ejecutar la transacción, CICS busca el nombre y la ubicación de un programa CICS, y utiliza el recurso de carga dinámica suministrado por el sistema de tiempo de ejecución COBOL-IT para cargar y ejecutar el programa en el servidor de aplicaciones CICS.
Implementación: Figura 1 muestra un ejemplo del código generado para una clase de regla personalizada. En este ejemplo se presupone lo siguiente:
- La clase de regla es com.example.CobolRule (consulte Tabla 1).
- Los elementos siguientes se han seleccionado en la página Plantilla de regla COBOL:
- IdentificationDivision
- DataDivision
- ProcedureDivision
- El nombre del paquete está establecido en la porción de nombre de paquete de la clase de regla, com.example.
- El nombre de clase se establece en la parte de nombre de clase de la clase de regla CobolRule.
- Se genera un método visit() para cada uno de los tres elementos seleccionados en la página del asistente Plantilla de regla COBOL.
Figura 1. Clase para implementar una regla personalizada
// El nombre del paquete está establecido en la porción de nombre de paquete de analysisRule.ruleclass
package com.example;
import java.util.ArrayList;
import java.util.List;
import com.ibm.etools.cobol.application.model.cobol.*;
import com.ibm.rsar.analysis.codereview.cobol.custom.rules.AbstractCustomCobolAnalysisRule;
import com.ibm.rsar.analysis.codereview.cobol.custom.model.util.*;
// El nombre de clase se establece en la parte de nombre de clase de analysisRule.ruleclass
public class CobolRule extends
AbstractCustomCobolAnalysisRule {
@Override
public List<ASTNode> performRule(ASTNode baseNode) {
final List<ASTNode> tokens = new ArrayList<ASTNode>();
COBOLVisitorAdapter adapter = new COBOLVisitorAdapter();
adapter.accept(baseNode, new AbstractCOBOLVisitor() {
@Override
public void unimplementedVisitor(String s) {
}
@Override
public boolean visit(IdentificationDivision node) {
//PENDIENTE analizar nodo para violaciones de regla y añadir nodos de violación a símbolos
return true;
}
@Override
public boolean visit(DataDivision node) {
//PENDIENTE analizar nodo para violaciones de regla y añadir nodos de violación a símbolos
return true;
}
@Override
public boolean visit(ProcedureDivision node) {
//PENDIENTE analizar nodo para violaciones de regla y añadir nodos de violación a símbolos
return true;
}
});
return tokens;
}
}
Comments
Post a Comment