Tabla de Contenidos

ANTLR

ANTLR (ANother Tool for Language Recognition, otra herramienta para reconocimiento de lenguajes) es la herramienta más utilizada actualmente para construir parsers, interpretes, compiladores y traductores de lenguajes a partir de gramáticas.

Estructura de un fichero de ANTLR

Un fichero para ANTLR tiene la extensión .g, debe definir en su primera línea el tipo de gramática que va a generar (lexer grammar, parser grammar o grammar) y el nombre de esta (que ha de ser el mismo que el nombre del fichero .g). Luego seguirán las opciones y demás secciones las cuales son opcionales, y al final la definición de la gramática…

grammar test;
r : 'call' ID ';' {System.out.println("invoke "+$ID.text);} ;
ID: 'a'..'z'+ ;
WS: (' '|'\n'|'\r')+   {$channel=HIDDEN;} ; // ignore whitespace

Este código se genera en Java (por defecto), cada vez que encuentre un call algo; será lanzado el System.out.println.

Opciones

Las opciones nos permiten definir la generación del resultado. Por ejemplo podemos definir el código generado, ANTLR permite generar resultados en gran variedad de lenguajes (C, Java, C#, Python…), para definir el lenguaje utilizaremos la “clave” language:

grammar FQL;
options {
     language = Java;
}

Sección @header

En esta sección agregaremos la cabecera para el archivo de código resultado, esta tendrá la definición del paquete, los imports, los using…

@header {
     package bbejeck.antlr.fql;
}

Sección @members

Código que se añadirá al parser generado (aquí podremos poner nuestras funciones dentro de la clase).

Sección @rulecatch

Cada regla de un parser es convertida a una llamada a un método dentro de un bloque try - catch. Podemos definir el bloque catch a partir de esta sección:

@rulecatch{
    catch (RecognitionException e){
            throw e;
      }
}

O en Python:

@rulecatch{
except RecognitionException, e: 
  print 'ERROR!!!!', 
  raw_input()
}

Sección @init

Para inicializar valores dentro de la clase del parser:

@init {
    self.line = {}
    self.line['parameters'] = {}
}

Luego podremos usarlas así:

logline returns [val]
    : globalmessage {val = self.line}
    | playerinfo {val = self.line}
    ;

Las reglas también pueden tener un init:

file returns [List<List<String>> data]
@init {data = new ArrayList<List<String>>();}
  :  (row {data.add($row.list);})+ EOF
  ;

Notas

Apuntes

Para ejecutar antlr3 haremos:

$ java org.antlr.Tool -o ./out/ GameLog.g 

Signos de gramática

Signos Significado
. Todo
* Puede existir, no existir o existir varias veces
+ Ha de existir al menos una vez
? Opcional
~ Negación
(x|y|z) Regla x, y o z
EOF Final de línea

Pequeños apuntes

Es importante definir antes lo que queremos que coincida:

BEGIN : 'begin' ;
ID : 'a'..'z' + ;
OTHER: . ; // match any other single character

Cuando tengamos varias reglas con las que case lo introducido podremos hacer alias de los tokens (si únicamente fuese uno podríamos hacer $PLAYER.text:

infouser : COMS p1=PLAYER COMS VERB COMS p2=PLAYER COMS WITH COMS GUN COMS (PARENT1 parameter PARENT2)* { print 'oh! > ', $p1.text, $p2.text }

Cuando queremos añadir un atributo:

player returns [ value ]: STRVALUE MAYOR NUMBER MENOR MAYOR STEAMDEF steam=NUMBER MENOR MAYOR (IDENTIFIER)* MENOR {$value = $steam.text};
infouser : COMS p1=player COMS VERB COMS p2=player COMS WITH COMS GUN COMS (PARENT1 parameter PARENT2)* { print 'oh! > ', $p1.value, $p2.value }

Lidiar con un retorno de lista (CSharp):

list returns [ List<string> ValueList ]
    @init { $ValueList = new List<string>(); }
    : '[]'
    | '[' value {$ValueList.Add(value);} (COMMA value {$ValueList.Add(value);})* ']' ;

Lidiar con atributos opcionales:

species	returns [int count] : atom DIGITS? { $count = int($DIGITS.text) if $DIGITS else 0 } ;

Podemos retornar varios valores:

foo returns [a, b, c]
  :  A B C            {a=$A.text; b=$B.text; b=$C.text}
  ;

Podemos inicializar valores dentro de reglas con @init:

calculate_mw returns [float mw]
@init { $mw = 0.0 }
: (species { $mw += $species.species_weight})* EOF ;

Para aprovechar la salida de un parser podremos hacer:

*** Fichero gramática (regla principal '') ****
[...]
calculate_mw returns [float mw]
@init { $mw = 0.0 }
: (species { $mw += $species.species_weight})* EOF ;
[...]
*** Fichero de código *************
[...]
def calculate_mw(formula):
    char_stream = antlr3.ANTLRStringStream(formula)
    lexer = MWGrammarLexer(char_stream)
    tokens = antlr3.CommonTokenStream(lexer)
    parser = MWGrammarParser(tokens)
    return parser.calculate_mw()
[...]

Para hacer coincidir otros elementos usaremos .:

logline : globalmessage { print 'info' }
        | rconcommand { print 'rcon' }
        | defvar { print 'var' }
        | .* { print 'lodemas' } ;

Un ejemplo para capturar excepciones:

except RecognitionException, e: 
  print "PARSING ERROR: "+str(e)+" parsing :"+str(self.input)
  pprint.pprint(e)
  pprint.pprint(self.input)

Atributos de tokens:

Documentos

Ejemplos míos

General

Python