Si quiero escribir un compilador y hacer rodar a mano mi propio lexer / parser (sin lex / yacc o antlr), ¿cuál es el enfoque más fácil?

Se implementaría un analizador léxico simple como un “envoltorio” alrededor de una secuencia de entrada (que representa el texto fuente del programa) que le permite “retroceder” los caracteres en la secuencia. (java.io.PushbackReader es un ejemplo de esto). Tal lexer sería responsable de omitir el espacio en blanco en la entrada, reconocer los comentarios y omitirlos, y reconocer varios tokens. Las palabras clave pueden reconocerse junto con los identificadores, y luego se utilizaría un paso de búsqueda por separado para detectar una palabra clave y devolver el token de palabra clave apropiado, o insertar un identificador en la tabla de símbolos y devolver un token “identificador”. Los tokens de caracteres numéricos y especiales se pueden devolver directamente, con cierta anticipación necesaria para distinguir entre tokens como “>”, “> =” y “>>”.

En cuanto al análisis, una de las formas más comunes de escribir analizadores sin un generador de analizadores es escribir un analizador de descenso recursivo , que a menudo es adecuado para analizar construcciones de lenguaje de programación. Considere este fragmento de una gramática del lenguaje de programación (en algo similar a Backus-Naur Form, o BNF):

declaración :: = expresión
El | Sentencia IF ‘(‘ expresión ‘)’ [instrucción ELSE]
El | MIENTRAS declaración ‘(‘ expresión ‘)’
El | ‘{‘ declaración-lista ‘}’
;

Esto podría implementarse mediante un pseudocódigo que se parece a esto:

Sentencia parse_statement ()
{
Token t = peek_next_token ();
si (t == IF)
return parse_if_statement ();
más si (t == MIENTRAS)
return parse_while_statement ();
si no (t == ‘{‘)
return parse_block_statement ();
más
devolver nuevo ExpressionStatement (parse_expression ());
}

Sentencia parse_if_statement ()
{
match_token (IF);
match_token (‘(‘);
Expresión expr = parse_expression ();
match_token (‘)’);
Sentencia thenstmt = parse_statement ();
if (peek_next_token () == ELSE)
{
match_token (ELSE);
Sentencia elsestmt = parse_statement ();
devolver nuevo IfStatement (expr, thenstmt, elsestmt);
}
más
devolver nuevo IfStatement (expr, thenstmt, null);
}

Sentencia parse_while_statement ()
{
match_token (MIENTRAS);
match_token (‘(‘);
Expresión expr = parse_expression ();
match_token (‘)’);
Sentencia stmt = parse_statement ();
return new WhileStatement (expr, stmt);
}

Sentencia parse_block_statement ()
{
match_token (‘{‘);
List list = new ArrayList ();
while (peek_next_token ()! = ‘}’)
{
list.add (parse_statement ());
}
match_token (‘}’);
devolver nueva BlockStatement (lista);
}

En el código anterior, dos operaciones básicas interactúan con el lexer:

  • peek_next_token () mira el siguiente token en la entrada, sin consumirlo, y lo devuelve.
  • match_token () consume el siguiente token de la entrada y arroja un error si el token no es igual al que se pasó a la función.

Tampoco he definido parse_expression () por brevedad, y porque dejé “expresión” indefinida en el fragmento de gramática original.

Tenga en cuenta que este analizador utiliza la pila de llamadas para ayudar a rastrear lo que se analiza y crea un árbol de sintaxis abstracta (AST) correspondiente al texto del programa analizado.

El tema de la construcción de lexers y analizadores es mucho más complicado de lo que podría esperar en una sola respuesta. Para más información, vea los capítulos 3 y 4 de Compiladores: Principios, Técnicas y Herramientas , por Alfred V. Aho, Ravi Sethi. y Jeffrey D. Ullman (Addison-Wesley, 1986), el famoso “Libro del Dragón”.

Punta de sombrero: Aryeh Friedman para la A2A.

Los libros de texto del compilador le brindan una forma bastante sencilla de escribir un lexer y un analizador sintáctico. Además de eso, hay buenos tutoriales en Internet que pueden darle la introducción sobre cómo escribir un lenguaje de programación simple. Dos de ellos en los que puedo pensar ahora son:

1. Escriba un esquema en 48 horas: http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours .

2. Cómo escribir un intérprete Lisp en Python por Peter Norvig. Está disponible aquí – (Cómo escribir un intérprete (Lisp) (en Python)).

El segundo es bastante simple y le da la idea básica para escribir un analizador. Las mismas técnicas se pueden aplicar en su idioma de origen, es decir, Java.

Buena suerte con tu esfuerzo!

PD: La razón por la que se elige Lisp (y / o sus dialectos) para fines didácticos es que tiene un número muy pequeño de construcciones y se puede entender con un esfuerzo mínimo (al menos en comparación con otros idiomas).

Gracias por el A2A.

Amy Bowersox prácticamente lo clavó. Si no quieres usar uno de los generadores de analizador / analizador, entonces el descenso recursivo es tu mejor amigo. Así es como he creado compiladores para Algol, Algol-68, Pascal, BCPL, FORTRAN antes de comenzar a usar el kit de herramientas lex / yacc (y todavía funciona mejor para Pascal que lex / yacc, por lo que vale)

Aquí hay un enlace que puede serle útil. No soy fanático de Java, pero este proyecto es específico de Java y puede ser útil:
Un analizador de descenso recursivo simple