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:
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.