Saltar al contenido

ANTLR ¿Cuál es la forma más sencilla de realizar python como la gramática dependiente de la sangría?

Recabamos por el mundo online para tener para ti la respuesta para tu inquietud, en caso de alguna difcultad déjanos la pregunta y te contestamos con mucho gusto.

Solución:

No sé cuál es la forma más fácil de manejarlo, pero la siguiente es una forma relativamente sencilla. Siempre que haga coincidir un salto de línea en su lexer, haga coincidir opcionalmente uno o más espacios. Si hay espacios después del salto de línea, compare la longitud de estos espacios con el tamaño de sangría actual. Si es mayor que el tamaño de sangría actual, emita un Indent token, si es menor que el tamaño de sangría actual, emite un Dedent token y si es el mismo, no hagas nada.

También querrá emitir una serie de Dedent tokens al final del archivo para permitir que cada Indent tener una coincidencia Dedent simbólico.

Para que esto funcione correctamente, debe agregue un salto de línea inicial y final a su archivo fuente de entrada!

ANTRL3

Una demostración rápida:

grammar PyEsque;

options 
  output=AST;


tokens 
  BLOCK;


@lexer::members 

  private int previousIndents = -1;
  private int indentLevel = 0;
  java.util.Queue tokens = new java.util.LinkedList();

  @Override
  public void emit(Token t) 
    state.token = t;
    tokens.offer(t);
  

  @Override
  public Token nextToken() 
    super.nextToken();
    return tokens.isEmpty() ? Token.EOF_TOKEN : tokens.poll();
  

  private void jump(int ttype) 
    indentLevel += (ttype == Dedent ? -1 : 1);
    emit(new CommonToken(ttype, "level=" + indentLevel));
  


parse
 : block EOF -> block
 ;

block
 : Indent block_atoms Dedent -> ^(BLOCK block_atoms)
 ;

block_atoms
 :  (Id | block)+
 ;

NewLine
 : NL SP?
   
     int n = $SP.text == null ? 0 : $SP.text.length();
     if(n > previousIndents) 
       jump(Indent);
       previousIndents = n;
     
     else if(n < previousIndents) 
       jump(Dedent);
       previousIndents = n;
     
     else if(input.LA(1) == EOF) 
       while(indentLevel > 0) 
         jump(Dedent);
       
     
     else 
       skip();
     
   
 ;

Id
 : ('a'..'z' | 'A'..'Z')+
 ;

SpaceChars
 : SP skip();
 ;

fragment NL     : 'r'? 'n' | 'r';
fragment SP     : (' ' | 't')+;
fragment Indent : ;
fragment Dedent : ;

Puede probar el analizador con la clase:

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import org.antlr.stringtemplate.*;

public class Main 
  public static void main(String[] args) throws Exception 
    PyEsqueLexer lexer = new PyEsqueLexer(new ANTLRFileStream("in.txt"));
    PyEsqueParser parser = new PyEsqueParser(new CommonTokenStream(lexer));
    CommonTree tree = (CommonTree)parser.parse().getTree();
    DOTTreeGenerator gen = new DOTTreeGenerator();
    StringTemplate st = gen.toDOT(tree);
    System.out.println(st);
  
    

Si ahora coloca lo siguiente en un archivo llamado in.txt:

AAA AAAAA
  BBB BB B
  BB BBBBB BB
    CCCCCC C CC
  BB BBBBBB
    C CCC
      DDD DD D
      DDD D DDD

(¡Tenga en cuenta los saltos de línea inicial y final!)

luego verá la salida que corresponde al siguiente AST:

ingrese la descripción de la imagen aquí

Tenga en cuenta que mi demostración no produciría suficientes abolladuras en sucesión, como eliminar las abolladuras de ccc para aaa (Se necesitan 2 tokens dedent):

aaa
  bbb
    ccc
aaa

Necesitarías ajustar el código dentro else if(n < previousIndents) ... para posiblemente emitir más de 1 token dedent basado en la diferencia entre n y previousIndents. En la parte superior de mi cabeza, eso podría verse así:

 else if(n < previousIndents) 
   // Note: assuming indent-size is 2. Jumping from previousIndents=6 
   // to n=2 will result in emitting 2 `Dedent` tokens
   int numDedents = (previousIndents - n) / 2; 
   while(numDedents-- > 0) 
     jump(Dedent);
   
   previousIndents = n;
 

ANTLR4

Para ANTLR4, haga algo como esto:

grammar Python3;

tokens  INDENT, DEDENT 

@lexer::members 
  // A queue where extra tokens are pushed on (see the NEWLINE lexer rule).
  private java.util.LinkedList tokens = new java.util.LinkedList<>();
  // The stack that keeps track of the indentation level.
  private java.util.Stack indents = new java.util.Stack<>();
  // The amount of opened braces, brackets and parenthesis.
  private int opened = 0;
  // The most recently produced token.
  private Token lastToken = null;
  @Override
  public void emit(Token t) 
    super.setToken(t);
    tokens.offer(t);
  

  @Override
  public Token nextToken() 
    // Check if the end-of-file is ahead and there are still some DEDENTS expected.
    if (_input.LA(1) == EOF && !this.indents.isEmpty()) 
      // Remove any trailing EOF tokens from our buffer.
      for (int i = tokens.size() - 1; i >= 0; i--) 
        if (tokens.get(i).getType() == EOF) 
          tokens.remove(i);
        
      

      // First emit an extra line break that serves as the end of the statement.
      this.emit(commonToken(Python3Parser.NEWLINE, "n"));

      // Now emit as much DEDENT tokens as needed.
      while (!indents.isEmpty()) 
        this.emit(createDedent());
        indents.pop();
      

      // Put the EOF back on the token stream.
      this.emit(commonToken(Python3Parser.EOF, ""));
    

    Token next = super.nextToken();

    if (next.getChannel() == Token.DEFAULT_CHANNEL) 
      // Keep track of the last token on the default channel.
      this.lastToken = next;
    

    return tokens.isEmpty() ? next : tokens.poll();
  

  private Token createDedent() 
    CommonToken dedent = commonToken(Python3Parser.DEDENT, "");
    dedent.setLine(this.lastToken.getLine());
    return dedent;
  

  private CommonToken commonToken(int type, String text) 
    int stop = this.getCharIndex() - 1;
    int start = text.isEmpty() ? stop : stop - text.length() + 1;
    return new CommonToken(this._tokenFactorySourcePair, type, DEFAULT_TOKEN_CHANNEL, start, stop);
  

  // Calculates the indentation of the provided spaces, taking the
  // following rules into account:
  //
  // "Tabs are replaced (from left to right) by one to eight spaces
  //  such that the total number of characters up to and including
  //  the replacement is a multiple of eight [...]"
  //
  //  -- https://docs.python.org/3.1/reference/lexical_analysis.html#indentation
  static int getIndentationCount(String spaces) 
    int count = 0;
    for (char ch : spaces.toCharArray()) 
      switch (ch) 
        case 't':
          count += 8 - (count % 8);
          break;
        default:
          // A normal space char.
          count++;
      
    

    return count;
  

  boolean atStartOfInput() 
    return super.getCharPositionInLine() == 0 && super.getLine() == 1;
  


single_input
 : NEWLINE
 | simple_stmt
 | compound_stmt NEWLINE
 ;

// more parser rules

NEWLINE
 : ( atStartOfInput()?   SPACES
   | ( 'r'? 'n' | 'r' ) SPACES?
   )
    next == 'n' 
 ;

// more lexer rules

Tomado de: https://github.com/antlr/grammars-v4/blob/master/python3/Python3.g4

Hay una biblioteca de código abierto. antlr-denter para ANTLR v4 que ayuda a analizar las sangrías y las abolladuras por usted. Consulte su archivo README para saber cómo usarlo.

Dado que es una biblioteca, en lugar de fragmentos de código para copiar y pegar en su gramática, su manejo de sangría se puede actualizar por separado del resto de su gramática.

Hay una forma relativamente simple de hacer este ANTLR, que escribí como un experimento: DentLexer.g4. Esta solución es diferente de las otras mencionadas en esta página que fueron escritas por Kiers y Shavit. Se integra con el tiempo de ejecución únicamente a través de una anulación del Lexer. nextToken() método. Hace su trabajo examinando tokens: (1) un NEWLINE el token activa el inicio de una fase de "seguimiento de sangría"; (2) espacios en blanco y comentarios, ambos configurados en el canal HIDDEN, se cuentan e ignoran, respectivamente, durante esa fase; y, (3) cualquierHIDDEN token finaliza la fase. Por lo tanto, controlar la lógica de sangría es una simple cuestión de configurar el canal de un token.

Ambas soluciones mencionadas en esta página requieren un NEWLINE token para tomar también todos los espacios en blanco subsiguientes, pero al hacerlo no puede manejar comentarios de varias líneas que interrumpen ese espacio en blanco. Dent, en cambio, mantiene NEWLINE y los tokens de espacios en blanco se separan y pueden manejar comentarios de varias líneas.

Su gramática se configuraría como a continuación. Tenga en cuenta que las reglas de lexer NEWLINE y WS tienen acciones que controlan la pendingDent Estado y realizar un seguimiento del nivel de sangría con el indentCount variable.

grammar MyGrammar;

tokens  INDENT, DEDENT 

@lexer::members 
    // override of nextToken(), see Dent.g4 grammar on github
    // https://github.com/wevrem/wry/blob/master/grammars/Dent.g4


script : ( NEWLINE | statement )* EOF ;

statement
    :   simpleStatement
    |   blockStatements
    ;

simpleStatement : LEGIT+ NEWLINE ;

blockStatements : LEGIT+ NEWLINE INDENT statement+ DEDENT ;

NEWLINE : ( 'r'? 'n' | 'r' ) 
    if (pendingDent)  setChannel(HIDDEN); 
    pendingDent = true;
    indentCount = 0;
    initialIndentToken = null;
 ;

WS : [ t]+ 
    setChannel(HIDDEN);
    if (pendingDent)  indentCount += getText().length(); 
 ;

BlockComment : '/*' ( BlockComment | . )*? '*/' -> channel(HIDDEN) ;   // allow nesting comments
LineComment : '//' ~[rn]* -> channel(HIDDEN) ;

LEGIT : ~[ trn]+ ~[rn]*;   // Replace with your language-specific rules...

Reseñas y calificaciones del artículo

Si conservas alguna suspicacia o capacidad de arreglar nuestro división te sugerimos ejecutar un exégesis y con mucho placer lo leeremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *