Capítulo 4 Uso de análisis de Java CUP.docx

32
Capítulo 4 Uso de análisis de Java CUP CUP es un generador de analizadores sintácticos. Se necesita un programa CUP - esencialmente una LALR (1) apta para su procesamiento gramática, y genera un programa Java que analizar la entrada que satisfaga esa gramática. CUP supone que un analizador léxico se proporciona por separado. Normalmente, el analizador léxico es generado usando JFlex. Código de soporte adicional puede ser proporcionado en Java. Un ejemplo (INTERP1) A continuación JFlex, CUP, y los archivos fuente de Java en conjunto generan un simple "intérprete". Esta intérprete lee las líneas de entrada, los analiza, y "interpreta" de ellos. Una línea de entrada es una sentencia de asignación, que especifica el valor de una variable o una expresión que se imprima. En el primer caso, el intérprete evalúa la expresión, y almacena el valor en la variable. En el segundo caso el intérprete evalúa la expresión, y se imprime el valor. El analizador léxico package grammar; import java.io.*; import java_cup.runtime.*; %% %public %type Symbol %char %{ public Symbol token( int tokenType ) { System.err.println( "Obtain token " + sym.terminal_name( tokenType ) + " \"" + yytext() + "\"" ); return new Symbol( tokenType, yychar,

description

uso de java cup

Transcript of Capítulo 4 Uso de análisis de Java CUP.docx

Capítulo 4 Uso de análisis de Java CUP

CUP es un generador de analizadores sintácticos. Se necesita un programa CUP - esencialmente una LALR (1) apta para su procesamiento gramática, y genera un programa Java que analizar la entrada que satisfaga esa gramática.

CUP supone que un analizador léxico se proporciona por separado. Normalmente, el analizador léxico es generado usando JFlex. Código de soporte adicional puede ser proporcionado en Java.

Un ejemplo (INTERP1)

A continuación JFlex, CUP, y los archivos fuente de Java en conjunto generan un simple "intérprete". Esta intérprete lee las líneas de entrada, los analiza, y "interpreta" de ellos. Una línea de entrada es una sentencia de asignación, que especifica el valor de una variable o una expresión que se imprima.

En el primer caso, el intérprete evalúa la expresión, y almacena el valor en la variable.

En el segundo caso el intérprete evalúa la expresión, y se imprime el valor.

El analizador léxico

package grammar; import java.io.*;import java_cup.runtime.*;

%%

%public%type Symbol%char

%{ public Symbol token( int tokenType ) { System.err.println( "Obtain token " + sym.terminal_name( tokenType ) + " \"" + yytext() + "\"" ); return new Symbol( tokenType, yychar, yychar + yytext().length(), yytext() ); }

%}

number = [0-9]+ident = [A-Za-z][A-Za-z0-9]*space = [\ \t]newline = \r|\n|\r\n

%%

"=" { return token( sym.ASSIGN ); }"+" { return token( sym.PLUS ); }

"-" { return token( sym.MINUS ); }"*" { return token( sym.TIMES ); }"/" { return token( sym.DIVIDE ); }"(" { return token( sym.LEFT ); }")" { return token( sym.RIGHT ); } {newline} { return token( sym.NEWLINE ); }{space} { }

{number} { return token( sym.NUMBER ); }{ident} { return token( sym.IDENT ); }

. { return token( sym.error ); }<<EOF>> { return token( sym.EOF ); }

El analizador léxico coincide con símbolos especiales "=", "+", "-", "/", "(", ")", constantes enteros, y los identificadores. En este caso, las nuevas líneas son sintácticamente importante, pero los espacios y las pestañas no son, así que devolver un token para nuevas líneas, pero no para los espacios en blanco y tabuladores. Si hay un error de léxico, vuelvo un token de error (que genera un error de sintaxis para el analizador).

El analizador

package grammar;

import java.util.*;import java.io.*;import java_cup.runtime.*;

action code {: Hashtable table = new Hashtable(); :};

parser code {: private Yylex lexer; private File file;

public parser( File file ) { this(); this.file = file; try { lexer = new Yylex( new FileReader( file ) ); } catch ( IOException exception ) { throw new Error( "Unable to open file \"" + file + "\"" ); } }... :};

scan with {:

return lexer.yylex(); :};

terminal LEFT, RIGHT, NEWLINE, PLUS, MINUS, TIMES, DIVIDE, ASSIGN;terminal String NUMBER;terminal String IDENT;

nonterminal StmtList, Stmt;nonterminal Integer Expr, Term, Factor;

start with StmtList;

StmtList::= | StmtList Stmt ;Stmt::= IDENT:ident ASSIGN Expr:expr NEWLINE {: table.put( ident, expr ); :} | Expr:expr NEWLINE {: System.out.println( expr.intValue() ); :} | error NEWLINE | NEWLINE ;

Expr::= Expr:expr PLUS Term:term {: RESULT = new Integer( expr.intValue() + term.intValue() ); :} | Expr:expr MINUS Term:term {: RESULT = new Integer( expr.intValue() - term.intValue() ); :} | MINUS Term:term {: RESULT = new Integer( - term.intValue() ); :} | Term:term {: RESULT = term; :} ;

Term::= Term:term TIMES Factor:factor

{: RESULT = new Integer( term.intValue() * factor.intValue() ); :} | Term:term DIVIDE Factor:factor {: RESULT = new Integer( term.intValue() / factor.intValue() ); :} | Factor:factor {: RESULT = factor; :} ;

Factor::= LEFT Expr:expr RIGHT {: RESULT = expr; :} |

NUMBER:value {: RESULT = new Integer( value ); :} | IDENT:ident {: Integer value = ( Integer ) table.get( ident ); if ( value == null ) { parser.report_error( "Undeclared Identifier " + ident, new Symbol( sym.IDENT, identleft, identright, ident ) ); value = new Integer( 0 ); } RESULT = value; :} ; The lines action code {: Hashtable table = new Hashtable(); :};

agregar código a una clase de la acción privada utilizada para contener el código de las acciones. En este caso no tengo añade una variable de tabla hash que representa una "tabla de símbolos" para realizar el mapeo de identificadores de los valores. (¿No es maravilloso no tener que programar estas cosas!)

las líneas

parser code {: private Yylex lexer;

private File file;

public parser( File file ) { this(); this.file = file; try { lexer = new Yylex( new FileReader( file ) ); } catch ( IOException exception ) { throw new Error( "Unable to open file \"" + file + "\"" ); } }... :};

añadir código a la clase parser. En este caso he añadido un constructor, y el campo de instancia variables que representan el analizador léxico y el archivo de entrada.

las líneas

scan with {: return lexer.yylex(); :};

especifica que para obtener un token, el analizador debe invocar lexer.yylex (). Así, no hay obligación de utilizar JFlex para generar el analizador léxico, aunque por lo general es una buena idea.

las líneas

terminal LEFT, RIGHT, NEWLINE, PLUS, MINUS, TIMES, DIVIDE, ASSIGN;

terminal String NUMBER;terminal String IDENT;

especificar los nombres de los símbolos terminales en la gramática. Los símbolos terminales izquierdo,

DERECHA, nueva línea, más, menos, TIEMPOS, dividir ASSIGN no tienen ningún valor asociado.

NÚMERO IDENT y tiene una cadena como un valor.

las líneas

nonterminal StmtList, Stmt;nonterminal Integer Expr, Term, Factor;

especificar los nombres de los símbolos no terminales en la gramática. Cuando de acuerdo con un constructo correspondiente a StmtList o Stmt, no devuelve ningún valor. Cuando de acuerdo con un constructo correspondiente a Expr, término o Factor, nos devuelven un valor de tipo Integer. Los valores deben ser

Los objetos no, de un tipo primitivo.

La línea de

start with StmtList;

especifica que el símbolo inicial es la StmtList no terminal.

CUP genera una clase llamada sym, que contiene las definiciones de la terminal y no terminal

símbolos como constantes enteras.

/** CUP generated class containing symbol constants. */public class sym { /* terminals */ static final int EOF = 0; static final int LEFT = 2; static final int DIVIDE = 9; static final int NEWLINE = 4; static final int NUMBER = 11; static final int error = 1; static final int MINUS = 7; static final int TIMES = 8; static final int ASSIGN = 10; static final int IDENT = 12; static final int PLUS = 6; static final int RIGHT = 3;

/* nonterminals */ static final int $START = 0; static final int StmtList = 1; static final int Factor = 5; static final int Expr = 3; static final int Stmt = 2; static final int Term = 4; }

No sé por qué les imprime en un orden extraño, pero supongo que es porque pone el terminal y símbolos no terminales en una tabla hash, y el orden de enumeración depende de sus códigos hash. El orden es diferente para diferentes implementaciones de Java.

Presumiblemente, la diferencia es que la función de hash para cadenas son diferentes para las distintas implementaciones de Java.

Las líneas restantes especificar la gramática de la entrada que se ajustará, además de acciones a llevarse a cabo, cuando se produce una reducción. Si eliminamos el código adicional que ver con las acciones, tenemos la gramática básica:

StmtList::=

| StmtList Stmt

;

Stmt::= IDENT ASSIGN Expr NEWLINE | Expr NEWLINE | error NEWLINE | NEWLINE ;

Expr::= Expr PLUS Term | Expr MINUS Term | MINUS Term | Term ;

Term::= Term TIMES Factor | Term DIVIDE Factor | Factor ;

Factor::= LEFT Expr RIGHT | NUMBER | IDENT ;

Un programa es una lista de instrucciones terminadas por líneas nuevas. Una declaración puede ser un nulo declaración (para las personas que gustan de líneas en blanco), una declaración de asignación de la forma IDENT "="

Expr o simplemente un Expr (expresión). Una expresión puede implicar términos combinados con la adición, resta y operadores de negación. Un término puede implicar factores, combinados con la multiplicación y los operadores de división. Un factor puede ser una expresión parenthesised, o un número o identificador.

Tenga en cuenta que yo he dado la negación misma precedencia que la suma y la resta, como ocurre en las matemáticas. La mayoría de los lenguajes de programación modernos dan negación y otros operadores de prefijo a mayor prioridad que los operadores infijos.

La regla Stmt :: error = NEWLINE se utiliza para indicar al analizador qué hacer si la entrada no de acuerdo con la gramática. Básicamente dice que si tenemos un error de sintaxis, cortar la pila

hacia atrás hasta el punto en el que empezamos a analizar el Stmt, empujar un símbolo especial de error en la pila, entonces descartar fichas hasta que tengamos una nueva línea.

Ahora, así como analizar nuestra entrada, se desea calcular el valor de la expresión, y ya sea asignarlo a una variable (en el caso de una sentencia de asignación), o imprimir el valor (en el caso de una expresión por sí mismo). Por lo tanto tenemos que añadir acciones a nuestra definición de gramática.

Cada constructo que se está analizando se le da un valor asociado. Símbolos terminales reciben su valor del analizador léxico (el campo de valor de la ficha). Para no terminales, el valor es especificado en la acción asociada con la regla. Una acción se escribe después del lado derecho de la regla, y es esencialmente el código de Java, incluida en {: ... :}.

Tenemos que tener una forma de referirse al valor asociado con un símbolo en la mano derecha lado de la regla. Para lograr esto, se etiqueta el símbolo añadiendo un ":" y un identificador, y utilizar este identificador en la acción para referirse al valor. También tenemos que tener una forma de especificando el valor asociado con el símbolo en el lado izquierdo de la regla. para lograr esto, asignamos a la variable resultado.

Por ejemplo, considere el siguiente código:

Expr::= Expr:expr PLUS Term:term {: RESULT = new Integer( expr.intValue() + term.intValue() ); :} | Expr:expr MINUS Term:term {: RESULT = new Integer( expr.intValue() - term.intValue() ); :} | MINUS Term:term {: RESULT = new Integer( - term.intValue() ); :} | Term:term {: RESULT = term; :} ;

El valor de una construcción de la forma expr "+" término es el valor de expr, más el valor del término, con todos los valores almacenados en los objetos de tipo Integer.

A veces no desea especificar un valor, pero desea ejecutar código efectos secundarios.

Stmt::= IDENT:ident ASSIGN Expr:expr {:

table.put( ident, expr ); :} | Expr:expr {: System.out.println( expr.intValue() ); :} ;

Así, si hacemos coincidir ident "=" expr, evaluamos Expr, y almacenar el valor en la tabla hash, con el identificador como clave. Si tenemos sólo una expr, luego imprimir su valor fuera.

El programa principal

Al igual que antes, es necesario código para iniciar las cosas. Invocamos a un constructor para la clase analizador para crear un analizador. A continuación, realizar el análisis utilizando este analizador. He dado main () un directorio nombre como un parámetro. El directorio contiene un archivo de entrada. Archivos de error y de salida son generado en este directorio.

import grammar.*;import java.io.*;import java_cup.runtime.*;

public class Main {

public static void main( String[] argv ) { String dirName = null;

try { for ( int i = 0; i < argv.length; i++ ) { if ( argv[ i ].equals( "-dir" ) ) { i++; if ( i >= argv.length ) throw new Error( "Missing directory name" ); dirName = argv[ i ]; } else { throw new Error( "Usage: java Main -dir directory" ); } }

if ( dirName == null ) throw new Error( "Directory not specified" );

System.setErr( new PrintStream( new FileOutputStream( new File( dirName, "program.err" ) ) ) ); System.setOut( new PrintStream( new FileOutputStream( new File( dirName, "program.out" ) ) ) );

parser p = new parser( new File( dirName, "program.in" ) ); p.parse(); // p.debug_parse(); // For debugging } catch ( Exception e ) {

System.err.println( "Exception at " ); e.printStackTrace(); } } }

El método parse () invoca al analizador de tiempo de ejecución de CUP. Este es un método que realiza una

LALR (1) de análisis, invocando el código dentro de "exploración con {... :} "Cada vez que necesita para obtener una

token. El método parse () devuelve un objeto del tipo de símbolos, formado por el valor devuelto por el símbolo de inicio, junto con su tipo y posiciones izquierda y derecha.

También hay un método llamado debug_parse () que no sólo realiza un análisis sintáctico, sino que también genera mensajes de depuración en el archivo de error estándar. Proporciona una buena manera de determinar lo que es pasando, si hay algo malo con nuestra gramática.

Por ejemplo, si tuviéramos la entrada

a = 3b = 4a * a + b * b

Entonces debug_parse () generará una salida como

# Initializing parserObtain token IDENT "a"# Current token is IDENT # Reduce by rule StmtList ::= # Shift nonterminal StmtList to push state #1# Shift token IDENT to push state #3Obtain token ASSIGN "="# Current token is ASSIGN

# Shift token ASSIGN to push state #27Obtain token NUMBER "3"# Current token is NUMBER# Shift token NUMBER to push state #2Obtain token NEWLINE ""# Current token is NEWLINE# Reduce by rule Factor ::= NUMBER # Shift nonterminal Factor to push state #6# Reduce by rule Term ::= Factor # Shift nonterminal Term to push state #7# Reduce by rule Expr ::= Term # Shift nonterminal Expr to push state #28# Shift token NEWLINE to push state #29Obtain token IDENT "b"# Current token is IDENT# Reduce by rule Stmt ::= IDENT ASSIGN Expr NEWLINE

# Shift nonterminal Stmt to push state #10# Reduce by rule StmtList ::= StmtList Stmt # Shift nonterminal StmtList to push state #1# Shift token IDENT to push state #3Obtain token ASSIGN "="# Current token is ASSIGN # Shift token ASSIGN to push state #27Obtain token NUMBER "4"# Current token is NUMBER# Shift token NUMBER to push state #2Obtain token NEWLINE ""# Current token is NEWLINE# Reduce by rule Factor ::= NUMBER # Shift nonterminal Factor to push state #6# Reduce by rule Term ::= Factor # Shift nonterminal Term to push state #7# Reduce by rule Expr ::= Term # Shift nonterminal Expr to push state #28# Shift token NEWLINE to push state #29Obtain token IDENT "a"# Current token is IDENT# Reduce by rule Stmt ::= IDENT ASSIGN Expr NEWLINE # Shift nonterminal Stmt to push state #10# Reduce by rule StmtList ::= StmtList Stmt # Shift nonterminal StmtList to push state #1# Shift token IDENT to push state #3Obtain token TIMES "*" # Current token is TIMES # Reduce by rule Factor ::= IDENT # Shift nonterminal Factor to push state #6# Reduce by rule Term ::= Factor # Shift nonterminal Term to push state #7# Shift token TIMES to push state #16Obtain token IDENT "a"# Current token is IDENT# Shift token IDENT to push state #14Obtain token PLUS "+"# Current token is PLUS# Reduce by rule Factor ::= IDENT # Shift nonterminal Factor to push state #19# Reduce by rule Term ::= Term TIMES Factor # Shift nonterminal Term to push state #7# Reduce by rule Expr ::= Term

# Shift nonterminal Expr to push state #5# Shift token PLUS to push state #22Obtain token IDENT "b"# Current token is IDENT# Shift token IDENT to push state #14Obtain token TIMES "*"# Current token is TIMES# Reduce by rule Factor ::= IDENT # Shift nonterminal Factor to push state #6# Reduce by rule Term ::= Factor # Shift nonterminal Term to push state #24# Shift token TIMES to push state #16

Obtain token IDENT "b"# Current token is IDENT# Shift token IDENT to push state #14Obtain token NEWLINE ""# Current token is NEWLINE# Reduce by rule Factor ::= IDENT # Shift nonterminal Factor to push state #19# Reduce by rule Term ::= Term TIMES Factor # Shift nonterminal Term to push state #24# Reduce by rule Expr ::= Expr PLUS Term # Shift nonterminal Expr to push state #5# Shift token NEWLINE to push state #26Obtain token EOF ""# Current token is EOF# Reduce by rule Stmt ::= Expr NEWLINE # Shift nonterminal Stmt to push state #10# Reduce by rule StmtList ::= StmtList Stmt # Shift nonterminal StmtList to push state #1# Shift token EOF to push state #11Obtain token EOF ""# Current token is EOF# Reduce by rule $START ::= StmtList EOF # Shift nonterminal $START to push state #-1

CUP Sintaxis Especificaciones

Este material se recoge en el manual de CUP.

Una especificación consiste en:

• Paquete y especificaciones de importación,

• Componentes de código de usuario,

• El símbolo (terminal y no terminal) listas, precedencia declaraciones y la gramática.

Cada una de estas partes deben aparecer en el orden indicado.

Paquete e Importación Especificaciones

Una especificación comienza con el paquete opcional y declaraciones de importación. Estos tienen la misma sintaxis y jugar el mismo papel, como el paquete de declaraciones de importación y se encuentra en una normal de Java programa. Una declaración del paquete es de la forma:

package name;

donde nombre es un identificador de paquete Java, posiblemente en varias partes separadas por ".". En general, los

CUP emplea convenciones de Java léxicas. Así, por ejemplo, los dos estilos de comentarios de Java son apoyado, y los identificadores se construyen a partir de una carta, o un guión bajo (_), que puede ser seguido por cero o más letras, dígitos y guiones bajos.

Después de una declaración del paquete opcional, puede haber cero o más declaraciones de importación. Como en un

Java programa éstos tienen la forma:

import package_name.class_name;import package_name.*;

La declaración del paquete indica que el paquete de las clases sym y parser que se generan se in Las declaraciones de importación que figuran en el pliego de condiciones también se mostrará en la fuente de archivo de la clase analizador, lo que permite que varios nombres de paquete a ser utilizado suministrado por el usuario en código de acción.

Componentes de Código de Usuario

Tras el paquete opcional y declaraciones de importación son una serie de declaraciones facultativas que permite que el código de usuario que se incluye como parte del analizador generado. Como una parte del archivo del analizador, para no-public class se produce para contener todas las acciones de usuario incrustadas. La primera acción sección de código de declaración permite que el código a ser incluidos en esta clase. Métodos y campos de uso por el código incrustado en la gramática normalmente se coloca en esta sección (un típico ejemplo podría ser símbolo métodos de manipulación de la tabla). Esta declaración tiene la forma:

action code {: ... :};

donde {... :} Es una cadena de código cuyo contenido será colocado directamente dentro de la acción de clase declaración.

Después de la declaración de código de acción es una declaración analizador de código opcional. Esta declaración permite que los métodos y campos que se colocan directamente dentro de la clase analizador generado. Esta declaración es muy similar a la declaración de código de acción y toma la forma:

parser code {: ... :};

Una vez más, el código de la cadena de código se coloca directamente en la definición de analizador clase generada.

A continuación en la especificación es la declaración facultativa init que tiene la forma:

init with {: ... :};

Esta declaración proporciona un código que será ejecutado por el intérprete antes de que se pide la primera

token. Normalmente, esto se usa para inicializar el analizador léxico, así como diversos cuadros y otras estructuras de datos que puedan ser necesarios para las acciones. En este caso, el código dado en la cadena de código forma el cuerpo de un método de vacío dentro de la clase analizador.

El final (opcional) sección del código de usuario de la especificación indica que el analizador debe preguntar para el siguiente token del analizador léxico. Esto tiene la forma:

scan with {: ... :};

Al igual que con la cláusula init, el contenido de la cadena de código forma el cuerpo de un método en el generado analizador. Sin embargo, en este caso, el método devuelve un objeto de tipo

java_cup.runtime.Symbol. En consecuencia, el código que se encuentra en la búsqueda de la cláusula debería volver tal valor.

Listas de símbolos

Tras código suministrado por el usuario es la primera parte de la especificación requerida: la lista de símbolos.

Estas declaraciones son responsables de nombrar y el suministro de un tipo para cada terminal y símbolo no terminal que aparece en la gramática. Como se indicó anteriormente, cada terminal y símbolo no terminal se representa en tiempo de ejecución por un objeto símbolo. En el caso de terminales, estos son devueltos por el analizador léxico y se coloca en la pila de análisis sintáctico. El analizador léxico debe poner el valor del terminal en el campo de valor. No terminales reemplazar una serie de

Símbolo objetos en la pila de análisis cada vez que el lado derecho de una regla se reduce. En para indicar al analizador qué tipos de objeto se debe utilizar para la cual símbolo, terminal y declaraciones no terminales se utilizan. Éstos toman la forma:

terminal classname name1, name2, ...;nonterminal classname name1, name2, ...;terminal name1, name2, ...;nonterminal name1, name2, ...;

El nombre de clase especificado representa el tipo del valor de dicho terminal o no terminal.

Al acceder a estos valores a través de las etiquetas, el usuario utiliza el tipo declarado. El nombre de clase puede ser de cualquier tipo. Si no hay ningún nombre de clase se da, entonces el terminal o no terminal tiene ningún valor.

Una etiqueta se refiere a un símbolo tendrá un valor nulo.

Precedencia y capacidad de asociación declaraciones

En la tercera sección, que es opcional, especifica las precedencias y asociatividad de los terminales.

Más sobre esto más adelante.

La Gramática

La sección final de la declaración CUP ofrece la gramática. Esta sección comienza opcionalmente con una declaración de la forma:

start with nonterminal;

Esto indica que no terminal es el principio u objetivo no terminal para el análisis. Si un comienzo no terminal no se declara explícitamente, a continuación, el no terminal en el lado izquierdo de la primera norma será utilizado. Al final de un análisis sintáctico éxito, CUP devuelve un objeto de tipo

java_cup.runtime.Symbol. Este campo de Symbol valor instancia contiene la reducción final resultar.

La propia gramática sigue la declaración de inicio opcional. Cada regla de la gramática tiene una izquierda lado no terminal seguido por el símbolo ":: =", que es seguido por una serie de cero o más acciones, terminal o símbolos no terminales, seguido por un facultativo precedencia especificación, y termina con un punto y coma (;).

Cada símbolo en el lado derecho opcionalmente se puede marcar con un nombre. Los nombres de etiquetas aparecen después del nombre del símbolo separados por dos puntos (:). Los nombres de etiqueta debe ser único en el regla, y se puede utilizar en el código de acción para referirse al valor del símbolo. Junto con la etiqueta, dos variables se crean más, que son la etiqueta más a la izquierda y más a la derecha de la etiqueta.

Estos son valores int que contienen las ubicaciones derecho e izquierdo de lo que el terminal o cubiertas no terminales en el archivo de entrada. Estos valores deben ser debidamente inicializada en los terminales por el analizador léxico. Los valores de la izquierda y la derecha y luego propagarse a no terminales a los que reglas reducir.

Si hay varias reglas para el mismo no terminal que puede ser declarado juntos. En este caso las reglas de comenzar con el no terminal y ":: =". Esto es seguido por múltiples lados derechos cada una separada por una barra (|). El conjunto completo de reglas es entonces terminada por un punto y coma.

Las acciones aparecen en el lado derecho como cadenas de código (por ejemplo, el código Java dentro de {: ...:} delimitadores). Estos son ejecutados por el analizador en el punto cuando la parte de la regla a la izquierda de la acción ha sido reconocido. (Tenga en cuenta que el analizador léxico se han devuelto el una muestra más allá del punto de la acción ya que el intérprete necesita este token de preanálisis adicional para reconocimiento.)

Especificaciones de precedencia seguir todos los símbolos y acciones de la parte derecha de la regla cuya prioridad se está asignando. Precedencia especificación permite que una regla se le asigna un precedencia no se basa en el último terminal en él. Más sobre esto más adelante.

Opciones de línea de comandos al ejecutar CUP

CUP está escrito en Java. Para invocarlo, hay que utilizar el intérprete de Java para invocar la estática java_cup.Main método (), pasándole una matriz de cadenas que contiene opciones. Suponiendo un UNIX máquina, la forma más sencilla de hacer esto es por lo general para invocarlo directamente desde la línea de comandos con un comando como:

java -jar "$LIB330/java_cup.jar" options < inputfile

Una vez en ejecución, CUP espera encontrar un archivo de especificación como estándar de entrada y produce dos

Archivos de código fuente de Java como salida.

Además del archivo de especificación, el comportamiento de CUP también puede cambiarse haciendo pasar varias opciones a la misma. Opciones legales incluyen:

-Package nombre

Especificar que las clases de análisis gramatical y sym han de ser colocados en el paquete nombrado. Por defecto, no especificación del paquete se coloca en el código generado (por lo tanto las clases por defecto a la especial

"Sin nombre" de paquetes).

-Parser nombre

Salida del analizador y el código en un archivo de acción (y de clase) con el nombre dado en lugar del predeterminado de "analizador".

Símbolos de nombre

Genere el código de símbolo constante en una clase con el nombre que se da en lugar del predeterminado de

"Sym".

-Interface

Emite el código de símbolo constante como una interfaz en lugar de como una clase.

-Nonterms

Coloque constantes para no terminales en la clase símbolo constante. El analizador no necesita estas constantes de símbolo, así que no son normalmente de salida. Sin embargo, puede ser muy útil para se refieren a estas constantes cuando se depura un analizador generado.

-Esperamos número

Durante la construcción del sistema analizador puede detectar que una situación ambigua se produciría en tiempo de ejecución. Esto se llama un conflicto. En general, el analizador puede ser incapaz de decidir si

SHIFT (leer otro símbolo) o reducir (sustituir la parte reconocida de la derecha de una regla con su a mano izquierda). Esto se conoce como desplazamiento / reducción de conflictos. De manera similar, el analizador no puede ser capaz de decidir entre reducciones con dos reglas diferentes. Esto se llama una reducción / reducción de conflictos.

Normalmente, si uno o más de estos conflictos se producen, la generación de analizador se aborta. Sin embargo, en ciertos casos considerados cuidadosamente, puede ser útil para resolver estos conflictos. En este caso CUP utiliza la convención YACC y resuelve desplazamiento / reducción conflictos por el cambio y reducción / reducción conflictos con la "máxima prioridad" regla (la declarada por primera vez en la especificación). En orden para permitir frenado automático de los conflictos-esperar opción se debe dar exactamente lo que indica cuántos conflictos se esperaba. Los conflictos resueltos por precedencias y asociatividades son no se informó.

-Nowarn

Esta opción hace que todos los mensajes de advertencia (por oposición a los mensajes de error) producidos por la sistema a ser suprimida.

Curso

Esta opción hace que el sistema para imprimir los mensajes cortos que indican su progreso a través de diversos partes del proceso de generación de analizador.

-Dump_grammar

-Dump_states

-Dump_tables

-Dump

Estas opciones hacen que el sistema para producir un volcado legible por humanos de la gramática, la construido analizar los estados (a menudo necesarios para resolver los conflictos de análisis sintáctico), y las tablas de análisis sintáctico (raramente necesaria), respectivamente. El

vertimiento opción se puede utilizar para producir todos estos vertederos.

 -Dumpto archivo

Esto es una mejora local, para permitir que la información de diagnóstico para ir a un archivo, en lugar de stderr. Esto es útil en los ordenadores, que no permiten la redirección de la salida de error. El nombre del archivo es relativa para el directorio de origen.

-Debug

Esta opción produce voluminosa información de depuración interna sobre el sistema mientras se ejecuta.

Se trata normalmente de interés sólo para los mantenedores del sistema mismo.

-Source SourceDir

Esto es una mejora local, para permitir que el directorio para el archivo de fuente que se especifica en la opciones. Si el-dest opción no se especifica, el directorio de origen también se utiliza como la directorio de destino para los archivos generados.

-Dest DESTDIR

Esto es una mejora local, para permitir que el directorio para los archivos generados que se especificarán en las opciones.

Entrada fileName

Esto es una mejora local, para permitir que el archivo de origen taza que se especifica en las opciones, en lugar que al redirigir la entrada estándar. Esto es necesario en sistemas que no sean UNIX, que no lo hacen admite la redirección de archivos. El nombre del archivo es relativa al directorio de origen.

Mi comando más habitual para ejecutar CUP en el interior de la secuencia de comandos de shell y es createcompiler.bash algo como

java -jar "$CCUPJAR" -nonterms \-expect 1 -progress -source "$SOURCEDIR" \-dump -dumpto "parser.states" \-input "parser.cup" &> cup.error

La estructura de la fuente de Java generado por CUP

Supongamos que tomamos nuestro INTERP1 ejemplo. ¿Qué significa el código generado por CUP parece?

El paquete java_cup.runtime debe ser utilizado por cualquier analizador sintáctico generado por CUP. Copa del propio (aparte de la primera

versión) fue escrito usando CUP, por lo que incluso CUP utiliza este paquete.

La clase lr_parser en el paquete java_cup.runtime es una clase abstracta. Se proporciona la algoritmo general para implementar cualquier LALR (1) parser, en el método parse (). el algoritmo corresponde aproximadamente a

Start with a stack of the form state 0 --->grows where state 0 corresponds to having seen no input;currToken = GetToken();forever { /* Index the action table by the top of stack state and the current token to get the action to perform */ CurrAction = Action[ TopStack(), currToken ]; switch ( CurrAction.ActionSort ) { case SHIFT: /* Push the indicated state onto the stack */ currToken.setState( CurrAction.ShiftState ); Push( currToken ); /* Get the next token */ currToken = GetToken(); break; case REDUCE: /* Perform code associated with CurrAction.ReduceRule (e.g., build node of tree corresponding to the grammar rule); */ nonTerminal = CurrAction.ReduceRule.do_action(); /* Pop the states corresponding to the righthand side of the rule off the stack */ Pop CurrentAction.ReduceRule.RHS.length() states off the stack; /* Index the goto table by the state uncovered on the top of stack and the nonterminal corresponding to the lefthand side of the rule and push the state found in the goto table onto the stack; */

nonTerminal.setState( GoTo[ TopStack(), CurrentAction.ReduceRule.LHS ] ); Push( nonTerminal ); break; case ACCEPT: /* Complete parsing */ return; case ERROR: /* Generate an error message */

error(); break; } }

La clase lr_parser supone que la regla, acción y tablas reducir (goto) estará a cargo de la subclase analizador generado mediante la ejecución de CUP en la gramática que escribimos. Del mismo modo, se asume que un do_action método () se proporcionarán a corresponder con el código Java en el acciones para la gramática. Hay un número de otros métodos abstractos en esta clase, tales como la exploración () método, para obtener el siguiente token.

La clase virtual_parse_stack en el paquete java_cup.runtime proporciona soporte adicional para analizar por delante en caso de un error de sintaxis.

La clase de símbolos en el paquete java_cup.runtime representa el tipo de un símbolo terminal devuelto por el analizador léxico y el símbolo terminal o no terminal inserta en la pila por el intérprete. Contiene campos para indicar el tipo símbolo, el estado del símbolo, el valor del símbolo, y la posición de los extremos izquierdo y derecho del símbolo del archivo de entrada.

La clase parser creado mediante la ejecución CUP extiende la clase lr_parser en el java_cup.runtime paquete. Esta clase contiene las tablas correspondientes (calculado por CUP).

CUP copia el código de

parser code {: private Yylex lexer; private File file;

public parser( File file ) { this(); this.file = file; try { lexer = new Yylex( new FileReader( file ) );

} catch ( IOException exception ) { throw new Error( "Unable to open file \"" + file + "\"" ); } }... :};

en el cuerpo de la clase analizador.

CUP copia el código de

scan with {: return lexer.yylex(); :};

en el método de exploración () en la clase parser.

public java_cup.runtime.Symbol scan() throws java.lang.Exception { return lexer.yylex(); }

CUP CUP crea una clase de acciones $ $ parser para encapsular el usuario que se proporciona código de acción, y crea una instancia de esta clase como un campo de la clase parser.

CUP copia el código de

action code {: Hashtable table = new Hashtable(); :};

en el cuerpo de la clase de acción.

CUP también construye un método CUP do_action $ parser $ () en la clase de la acción, con un interruptor declaración que contiene el usuario que se proporciona código de acción. Las referencias a las etiquetas dentro de la normas se sustituyen por referencias al campo de valor del símbolo correspondiente en la pila.

Un nuevo símbolo es devuelta, con el tipo símbolo, el valor del símbolo, y la posición de los extremos izquierdo y derecho del símbolo en el archivo de entrada llenado pulg

public final java_cup.runtime.Symbol CUP$parser$do_action( int CUP$parser$act_num, java_cup.runtime.lr_parser CUP$parser$parser, java.util.Stack CUP$parser$stack, int CUP$parser$top) throws java.lang.Exception { /* Symbol object for return from actions */ java_cup.runtime.Symbol CUP$parser$result;

/* select the action based on the action number */ switch (CUP$parser$act_num) { /*. . . . . . . . . . . . . . . . . . . .*/ case 16: // Factor ::= IDENT { Integer RESULT = null; int identleft =((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top0)).left;

int identright =((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top0)).right;

String ident = (String)((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-0)).value; Integer value = ( Integer ) table.get( ident ); if ( value == null ) { parser.report_error( "Undeclared Identifier " + ident, new Symbol( sym.IDENT, identleft, identright, ident ) ); value = new Integer( 0 ); } RESULT = value; CUP$parser$result = new java_cup.runtime.Symbol(5/*Factor*/,((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top0)).left,

((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-0)).right, RESULT); } return CUP$parser$result;... /*. . . . . . . . . . . . . . . . . . . .*/ case 14: // Factor ::= LEFT Expr RIGHT { Integer RESULT = null; int exprleft =((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top1)).left;

int exprright =((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top1)).right;

Integer expr = (Integer)((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-1)).value; RESULT = expr;

CUP$parser$result = new java_cup.runtime.Symbol(5/*Factor*/,((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top2)).left,

((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top0)).right,RESULT);

} return CUP$parser$result;...

CUP también genera el sym.java archivo. Este archivo declara constantes simbólicas correspondientes a cada tipo de símbolo. Mi versión modificada de CUP también genera texto correspondiente a la nombres de los símbolos y el texto de las reglas gramaticales. Esta información se utiliza cuando realizar una debug_parse (), para producir mejores mensajes de diagnóstico.

public class sym { /* terminals */ public static final int MINUS = 6; public static final int IDENT = 11; public static final int DIVIDE = 8; public static final int LEFT = 2; public static final int NUMBER = 10;...

/* nonterminals */ static final int Factor = 5; static final int Term = 4; static final int Stmt = 2; static final int Expr = 3; static final int $START = 0; static final int StmtList = 1; public static String terminal_name( int id ) { switch ( id ) { case 6: return "MINUS"; case 11: return "IDENT"; case 8: return "DIVIDE"; case 2: return "LEFT"; case 10: return "NUMBER"; case 0: return "EOF";... } } public static String non_terminal_name( int id ) { switch ( id ) { case 5: return "Factor"; case 4: return "Term"; case 2: return "Stmt"; case 3: return "Expr";

case 0: return "$START"; case 1: return "StmtList"; default: return "unknown non_terminal" + id; } } public static String rule_name( int id ) { switch ( id ) { case 16: return "Factor ::= IDENT "; case 15: return "Factor ::= NUMBER ";

case 14: return "Factor ::= LEFT Expr RIGHT "; case 13: return "Term ::= Factor "; case 12: return "Term ::= Term DIVIDE Factor "; case 11: return "Term ::= Term TIMES Factor "; case 10: return "Expr ::= Term ";... } }}

Por último, CUP puede generar un resumen de los estados, y la acción y tablas goto.

===== Terminals =====[0] EOF [1] error [2] LEFT [3] RIGHT [4] NEWLINE [5] PLUS [6] MINUS [7] TIMES [8] DIVIDE [9] ASSIGN [10] NUMBER [11] IDENT

===== Nonterminals =====[0] $START [1] StmtList [2] Stmt [3] Expr [4] Term [5] Factor

===== Rules =====[0] $START ::= StmtList EOF [1] StmtList ::= [2] StmtList ::= StmtList Stmt [3] Stmt ::= IDENT ASSIGN Expr NEWLINE [4] Stmt ::= Expr NEWLINE [5] Stmt ::= error NEWLINE [6] Stmt ::= NEWLINE [7] Expr ::= Expr PLUS Term [8] Expr ::= Expr MINUS Term [9] Expr ::= MINUS Term [10] Expr ::= Term [11] Term ::= Term TIMES Factor [12] Term ::= Term DIVIDE Factor [13] Term ::= Factor [14] Factor ::= LEFT Expr RIGHT [15] Factor ::= NUMBER [16] Factor ::= IDENT

===== LALR(1) States =====START lalr_state [0]: { [StmtList ::= (*) StmtList Stmt , {EOF error LEFT NEWLINE MINUS NUMBERIDENT }] [StmtList ::= (*) , {EOF error LEFT NEWLINE MINUS NUMBER IDENT }] [$START ::= (*) StmtList EOF , {EOF }]

}transition on StmtList to state [1]

-------------------lalr_state [1]: { [Factor ::= (*) LEFT Expr RIGHT , {NEWLINE PLUS MINUS TIMES DIVIDE }] [Term ::= (*) Term TIMES Factor , {NEWLINE PLUS MINUS TIMES DIVIDE }] [Expr ::= (*) Expr MINUS Term , {NEWLINE PLUS MINUS }] [StmtList ::= StmtList (*) Stmt , {EOF error LEFT NEWLINE MINUS NUMBERIDENT }] [Stmt ::= (*) error NEWLINE , {EOF error LEFT NEWLINE MINUS NUMBER IDENT }] [Factor ::= (*) IDENT , {NEWLINE PLUS MINUS TIMES DIVIDE }] [Term ::= (*) Factor , {NEWLINE PLUS MINUS TIMES DIVIDE }] [Expr ::= (*) Term , {NEWLINE PLUS MINUS }] [Expr ::= (*) Expr PLUS Term , {NEWLINE PLUS MINUS }]

[Stmt ::= (*) Expr NEWLINE , {EOF error LEFT NEWLINE MINUS NUMBER IDENT }] [Factor ::= (*) NUMBER , {NEWLINE PLUS MINUS TIMES DIVIDE }] [Term ::= (*) Term DIVIDE Factor , {NEWLINE PLUS MINUS TIMES DIVIDE }] [Expr ::= (*) MINUS Term , {NEWLINE PLUS MINUS }] [Stmt ::= (*) NEWLINE , {EOF error LEFT NEWLINE MINUS NUMBER IDENT }] [$START ::= StmtList (*) EOF , {EOF }] [Stmt ::= (*) IDENT ASSIGN Expr NEWLINE , {EOF error LEFT NEWLINE MINUSNUMBER IDENT }]}transition on NEWLINE to state [12]transition on Factor to state [11]transition on error to state [10]transition on Term to state [9]transition on Stmt to state [8]transition on EOF to state [7]transition on NUMBER to state [6]transition on LEFT to state [5]transition on Expr to state [4]transition on IDENT to state [3]transition on MINUS to state [2]...-------- ACTION_TABLE --------From state #0 EOF:REDUCE(rule 1) error:REDUCE(rule 1) LEFT:REDUCE(rule 1) NEWLINE:REDUCE(rule 1) MINUS:REDUCE(rule 1) NUMBER:REDUCE(rule 1) IDENT:REDUCE(rule 1) From state #1 EOF:SHIFT(state 7) error:SHIFT(state 10) LEFT:SHIFT(state 5) NEWLINE:SHIFT(state 12) MINUS:SHIFT(state 2) NUMBER:SHIFT(state 6) IDENT:SHIFT(state 3) From state #2 LEFT:SHIFT(state 5) NUMBER:SHIFT(state 6) IDENT:SHIFT(state 17) From state #3 NEWLINE:REDUCE(rule 16) PLUS:REDUCE(rule 16) MINUS:REDUCE(rule 16) TIMES:REDUCE(rule 16) DIVIDE:REDUCE(rule 16) ASSIGN:SHIFT(state 26) ...-------- REDUCE_TABLE --------From state #0:

StmtList:GOTO(1)From state #1: Stmt:GOTO(8) Expr:GOTO(4) Term:GOTO(9) Factor:GOTO(11)From state #2: Term:GOTO(29) Factor:GOTO(11)From state #3:...

Lo que ellos llaman producciones, llamo reglas. Lo que ellos llaman la mesa de reducir, que yo llamo la tabla goto.

Los estados son muy útiles cuando se depura una gramática que tiene conflictos en su interior.