Veamos hoy cómo se arma un parser con GrammGen, usando código. La idea de GrammGen es definir cómo construir un árbol con las expresiones que detectamos en un texto, según el lenguaje que querramos implementar. Y que esa definición sea por código, no por una gramática escrita en texto, sino simplemente por código.
Presenté en el anterior post a un ejemplo de consola, que emplea a GrammGen. Un parser se define usando reglas. Las reglas indican:
– Cómo procesar los caracteres del texto
– Cómo formar entonces elementos del texto, como números, nombres, operadores
– Cómo ir armando desde esos elementos, expresiones, comandos que definamos para nuestro lenguaje a implementar.
En el ejemplo de calculadora
https://github.com/ajlopez/GrammGen/tree/master/Samples/Calculator
las reglas están definidas en el parser:
https://github.com/ajlopez/GrammGen/blob/master/Samples/Calculator/Calculator/ExpressionParser.cs
Veamos las primeras:
private static Rule[] rules = new Rule[]{Rule.Or(' ', '\t', '\r', '\n').Skip(),Rule.Or('+', '-').Generate("Oper0"),Rule.Or('*', '/').Generate("Oper1"),// ...
La idea es conseguir una Expression. La primera regla define que cualquiera (Rule.Or) de los elementos espacio, tabulación, retorno de carro, nueva línea, se de ignodrar (.Skip()). Se puede poner más de una regla de este tipo, por ejemplo, para definir comentarios que deben ignorarse del texto de entrada.
La segunda regla define que tanto + (el signo más) como – (el signo menos) producen un terminal llamado Oper0. Se diferencian de los * (el signo por) y / (el signo de dividir) porque queremos tener precedencia entre los operadores.
Hay una regla para formar enteros:
Rule.Get("0-9").OneOrMore().Generate("Integer", MakeIntegerConstantExpression),
El Rule.Get permite especificar un rango de caracteres, y luego, con la fluent interface definida, se le puede adosar el .OneOrMore(), que especifica que esa regla se puede aplicar una o varias veces. Cuando no se pueda aplicar más, termina generando un node llamado “Integer”. Pero en GrammGen, un node, además de tener un nombre, puede tener un objeto asociado. Al .Generate se le puede pasar como segundo parámetro una función que cree ese objeto. Lo que hace MakeIntegerConstantExpression es:
– Recibe el objeto que se formó con la regla, en este caso, un string con todos los dígitos concatenados que se fueron encontrando.
– Lo transforma a entero
– Devuelve un objeto ConstantExpression que representa a ese entero en el árbol a evaluar.
El objecto ConstantExpression lo definimos nosotros, no GrammGen. Nosotros tenemos la libertad de definir los objetos que queremos adosar en cada nodo del árbol que GrammGen va formando.
El código de este método entonces es:
private static object MakeIntegerConstantExpression(object obj){int value = int.Parse((string)obj);return new ConstantExpression(value);}
En los próximos posts veremos cómo se forman los nodos de operaciones binarias, cómo se trata la precedencia de operadores, cómo se implementa la recursión a derecha, y la evaluación de la expresión.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com