Calculador (Mini) en Java
MiniCalculus… :P
Pues con la novedad que hoy tuve un poco de tiempo y viendo el post de @JaimeItlzc http://www.javamexico.org/foros/java_standard_edition/desarrollando_un_a...
Me decidí a jugar un poquito y crear mi propia calculadora.
¿Cómo se usa? Para declara una nueva variable usamos ‘rd’ seguido de un espacio en blanco y una palabra como ‘a’ y finalizando con ‘;’ o el carácter que más te guste : ). Toma el valor de 0 por omisión:
rd a;
Para asignar un valor a ‘a’ puede ser otra variable o un número:
a + 23;
rd b; b 3; a + b;
Para imprimir el valor de una variable usamos 'wr' y las mismas reglas.
Se le asigna el valor de retorno a la variable de la izquierda, incluso puedes asignar un número como variable algo como.
+ "rd a;"
+ "rd b;"
+ "rd respuesta_del_universo;"
+ "a 40;"
+ "b 2;"
+ "respuesta_del_universo +a;"
+ "respuesta_del_universo +b;"
+ "wr respuesta_del_universo;"
+ "42 +respuesta_del_universo;"
+ "wr 42"
[READ, a, 0]
[READ, b, 0]
[READ, respuesta_del_universo, 0]
[ASIGN, a, 40]
[ASIGN, b, 2]
[ASIGN+, respuesta_del_universo, 40]
[ASIGN+, respuesta_del_universo, 42]
[WRITE, respuesta_del_universo, 42]
[READ, 4, 0]
[ASIGN-NULL, 42, 0]
[WRITE, 42, 0]
Soporta las operaciones {+, -, *, /}
Dejo el código y luego espero comentarlo bien y arreglar un poco el desorden.
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author Rodrigo Salado Anaya <@Rodrigo_Salado>
*/
public class FindPattern {
private final Pattern[] patternList = {
Pattern.compile("rd\\s+(\\w+)"), // 1 read
Pattern.compile("wr\\s+(\\w+)"), // 2 write
Pattern.compile("(\\w+)\\s+(\\d+)"), // 3 asign
Pattern.compile("(\\w+)\\s*\\+\\s*(\\d+)"), // 4 asign +
Pattern.compile("(\\w+)\\s*\\-\\s*(\\d+)"), // 5 asign -
Pattern.compile("(\\w+)\\s*\\*\\s*(\\d+)"), // 6 asign *
Pattern.compile("(\\w+)\\s*\\/\\s*(\\d+)"), // 7 asign /
Pattern.compile("(\\w+)\\s*\\+\\s*(\\w+)"), // 8 asign + val
Pattern.compile("(\\w+)\\s*\\-\\s*(\\w+)"), // 9 asign - val
Pattern.compile("(\\w+)\\s*\\*\\s*(\\w+)"), // 10 asign * val
Pattern.compile("(\\w+)\\s*\\/\\s*(\\w+)"), // 11 asign / val
};
/**
* Revisa que tengan sentido las sentencias y genera la informacion necesaria.
* @param sentence sentencia que va a ser evaluada
* @return un Objeto de tipo Data.
*/
public Data getDataInformation(String sentence) {
Iterator patternMapInterator = getMatchers();
int key = 0;
Pattern pattern = null;
while (patternMapInterator.hasNext()) {
Map.Entry map = (Map.Entry) patternMapInterator.next();
key = (Integer) map.getKey();
pattern = (Pattern) map.getValue();
Matcher match = pattern.matcher(sentence.trim());
if (match.matches()) {
return new Data(key, match);
}
}
System.err.println("ERROR: Sentence[" + sentence + "] is incorrect");
return null;
}
/**
* Crea un mapa con un key el cual sera util al generar las operaciones.
* @return interador con los patrones a usar
*/
private Iterator getMatchers() {
Map<Integer, Pattern> syntaxType = new HashMap<Integer, Pattern>();
int index = 1;
for (Pattern pattern : patternList) {
syntaxType.put(index, pattern);
index = index + 1;
}
return syntaxType.entrySet().iterator();
}
}
import java.util.Map;
/**
*
* @author Rodrigo Salado Anaya <@Rodrigo_Salado>
*/
public class Calculator {
private Map<String, Integer> stack = new HashMap<String, Integer>();
private String id;
private String idString;
private int val;
public void doCalculus(Data data) {//:P
switch (data.getKey()) {
case 1:
id = data.getGroups().group(1);
val = 0;
stack.put(id, val);
printAction("READ", id, val);
break;
case 2:
id = data.getGroups().group(1);
if (!stack.containsKey(id)) {
printAction("WRITE-NULL", id, 0);
break;
}
val = stack.get(id);
printAction("WRITE", id, val);
break;
case 3:
id = data.getGroups().group(1);
val = Integer.valueOf(data.getGroups().group(2));
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
break;
}
stack.put(id, val);
printAction("ASIGN", id, val);
break;
case 4:
id = data.getGroups().group(1);
val = Integer.valueOf(data.getGroups().group(2));
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
break;
}
val = stack.get(id) + val;
stack.put(id, val);
printAction("ASIGN+", id, val);
break;
case 5:
id = data.getGroups().group(1);
val = Integer.valueOf(data.getGroups().group(2));
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
break;
}
val = stack.get(id) - val;
stack.put(id, val);
printAction("ASIGN-", id, val);
break;
case 6:
id = data.getGroups().group(1);
val = Integer.valueOf(data.getGroups().group(2));
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
break;
}
val = stack.get(id) * val;
stack.put(id, val);
printAction("ASIGN*", id, val);
break;
case 7:
id = data.getGroups().group(1);
val = Integer.valueOf(data.getGroups().group(2));
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
break;
}
val = stack.get(id) / val;
stack.put(id, val);
printAction("ASIGN/", id, val);
break;
case 8:
id = data.getGroups().group(1);
idString = data.getGroups().group(2);
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
if (!stack.containsKey(idString)) {
stack.put(idString, 0);
printAction("ASIGN-NULL", idString, 0);
break;
}
break;
}
val = stack.get(id) + stack.get(idString);
stack.put(id, val);
printAction("ASIGN+", id, val);
break;
case 9:
id = data.getGroups().group(1);
idString = data.getGroups().group(2);
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
if (!stack.containsKey(idString)) {
stack.put(idString, 0);
printAction("ASIGN-NULL", idString, 0);
break;
}
break;
}
val = stack.get(id) - stack.get(idString);
stack.put(id, val);
printAction("ASIGN-", id, val);
break;
case 10:
id = data.getGroups().group(1);
idString = data.getGroups().group(2);
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
if (!stack.containsKey(idString)) {
stack.put(idString, 0);
printAction("ASIGN-NULL", idString, 0);
break;
}
break;
}
val = stack.get(id) * stack.get(idString);
stack.put(id, val);
printAction("ASIGN*", id, val);
break;
case 11:
id = data.getGroups().group(1);
idString = data.getGroups().group(2);
if (!stack.containsKey(id)) {
stack.put(id, 0);
printAction("ASIGN-NULL", id, 0);
if (!stack.containsKey(idString)) {
stack.put(idString, 0);
printAction("ASIGN-NULL", idString, 0);
break;
}
break;
}
val = stack.get(id) / stack.get(idString);
stack.put(id, val);
printAction("ASIGN/", id, val);
break;
}
}
private void printAction(String action, String id, int val) {
System.out.println("[" + action + ", " + id + ", " + val + "]");
}
}
import java.util.Arrays;
import java.util.List;
/**
*
* @author Rodrigo Salado Anaya <@Rodrigo_Salado>
*/
final class Program {
/**
* Genera una lista de sentencias a compilar.
* @param programTxt Texto con el contenido del programa.
* @param lineSeparator Caracteres que delimitan el fin de cada sentencia.
* @return Lista de sentencias.
*/
public List getSentencesList(String programTxt, String lineSeparator) {
List tokenList = new ArrayList();
tokenList.addAll(Arrays.asList(programTxt.split(lineSeparator)));
return tokenList;
}
}
/**
*
* @author Rodrigo Salado Anaya <@Rodrigo_Salado>
*/
public class Editor {
private Program programa = new Program();
public void runProgram(String programTxt, String lineSeparator) {
Iterator tokens = programa.getSentencesList(programTxt, lineSeparator).iterator();
Calculator calculator = new Calculator();
while (tokens.hasNext()) {
String sentence = tokens.next().toString();
FindPattern findPattern = new FindPattern();
Data dataInformation = findPattern.getDataInformation(sentence);
if (dataInformation == null) {
System.exit(-1);
}
calculator.doCalculus(dataInformation);
}
}
}
/**
* Es el tipo basico para guardar la informacion
* de las sentencias y la operacion que deben de hacer
* @author Rodrigo Salado Anaya <@Rodrigo_Salado>
*/
public class Data {
private int key;
private Matcher groups;
public Data(int key, Matcher groups) {
this.key = key;
this.groups = groups;
}
public Matcher getGroups() {
return groups;
}
public int getKey() {
return key;
}
}
[READ, a, 0]
[ASIGN+, a, 3]
[ASIGN+, a, 6]
[WRITE, a, 6]
[ASIGN-NULL, 3, 0]
[WRITE, 3, 0]
[ASIGN+, 3, 34]
[WRITE, 3, 34]
@OscarRyz un parecido con Ryz es mera coincidencia jejejejeje hasta en las mejores familias pasa el plagio : P
- rodrigo salado anaya's blog
- Inicie sesión o regístrese para enviar comentarios
Comentarios
: ( Tiempo
Espero tener tiempo para hacerlo más bonito y re-factorizar como loco y ... y .. casi casi llegar a esto http://www.makinggoodsoftware.com/2011/03/27/the-obsession-with-beautifu...
En realidad solo fue un taller de practica, NO LO USES COMO TAREA jajaja o tu profe te va a regañar por lo feo del código...
Los nombre y todo lo demás esta muy improvisado y no deja claro que hace cada clase y los métodos ( lo hice en muy poco tiempo ... se nota no?).
Ahhh vaya ya entendí.. .. a
Ahhh vaya ya entendí.. .. a ver... si le movemos aquí...
y luego por acáaaaa..
tenemos. que...
a ver ahista para que sea interactivo "REPL" ( read , evaluete, print loop )
Ahí va el cambio:
/**
* Genera una lista de sentencias a compilar.
* @param programTxt Texto con el contenido del programa.
* @param lineSeparator Caracteres que delimitan el fin de cada sentencia.
* @return Lista de sentencias.
*/
//public List getSentencesList(String programTxt, String lineSeparator) {
// List tokenList = new ArrayList();
// tokenList.addAll(Arrays.asList(programTxt.split(lineSeparator)));
// return tokenList;
//}
public List getSentencesList( String ignored, final String lineSeparator ) {
return new AbstractList(){
public String get( int i ) { return ""; }
public int size() { return 0; }
public Iterator iterator() {
final Scanner scanner = new Scanner( System.in ); {
scanner.useDelimiter( lineSeparator );
}
return new Iterator(){
public boolean hasNext() {
return scanner.hasNext();
}
public Object next() {
return scanner.next();
}
public void remove(){}
};
}
};
}
}
Así ya lee de la entrada estándar y se comporta de forma interactiva.
:) :)
Je je está divertido.
Ja ja ja que onda con tu
Ja ja ja que onda con tu stack:
jejej estaba buscando donde estaba el push y el pop y mira nomás... :) :)
Me pregunto si...
Nap, no pude, me falta mucho
Nap, no pude, me falta mucho para traducirlo a Ryz :P
Chau!
Stack?
Jajaja ja si que chafis no? La idea en un inicio era hacer una pila.... Pero pues luego se me van las cabras.
:O súper Osacar y modificaste muy poquito :), gracias por darle un ratito y si fue divertido hacer esta calculadolla.
:)
Si me imagino que así
Si me imagino que así empezaste y luego... Ahh.. que diablos
Está muy bien, estoy leyendo la parte de "tables de símbolos" justo para algo como lo que estás haciendo y se oye requete fácil.
Luego que empiece a hacer algo más "correcto" posteo.
¿Que onda con tu otro proyecto?
Saludos.
Si súper por ...
Si súper por lo de la tabla de símbolos y hacerca de mi abbidb ammmm pues espero meterle mas galleta y dejar la flojera :S pero aun sumamente inmaduro :( .
Si estaría padre saber que tienes planeado para las tablas de símbolos.
Esto va en otro hilo pero será luego. Como le hacen los desarrolladores de lenguajes como scala para pasar su código a bytecode que entienda la JVM? Busque en Google pero no encuentro mucho ... Solo es por curiosidad.
Pues si tienes un AST que es
Pues si tienes un AST que es un representación en memoria de tu programa, puedes reescribir eso en cualquier cosa, lo puedes escribir en otro lenguaje ( ejemplo CofeeScript escribe JavaScript ) O en bytecode ( como Java ) o directamente en ensamblador como C.
Por ejemplo ( y por decir algo porque no lo conozco ) para declarar un entero llamado i escribirias en byte code:
00 02
Poooooor decir algo, para saber exactamente que instrucción corresponde checa esto:
http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.do...
Esto es lo que hacen las herramientas como javassist, y etc.
Cuando sepa más escribo sobre esto.
:)
Para hacerlo más formal...
Para hacerlo algo más formal que usar expresiones regulares que identifiquen las cadenas, puedes usar algo como ANTLR o similares para obtener el AST. De hecho el típico ejemplo con el que explican los analizadores lexicos/sintácticos suele ser una mini-calculadora :)
http://www.antlr.org/wiki/display/ANTLR3/Five+minute+introduction+to+ANT...
S!
Exacto. En ANTRL ( y supongo
+1 por el link
Exacto.
En ANTRL ( y supongo que en todos los contexto de lenguajes ) se les llama "reglas de producción" definidas en una gramática. Difieren de la expresiones regulares en que... ¬¬ la verdad no sé exactamente ( como explicarlo pues ), quizá en que pueden no ser regulares.
Por ejemplo un AST para la expresion:
Sería algo tipo:
/ \
3 +
/ \
4 5
Porque el operador "*" tiene precedencia ( alguién tiene idea de ¿POR QUE le dieron más precedencia a estos simbolos? )
Tabién se puede representar la operación pensandola como llamadas a funciones:
O si se mueve el primer parentesis a la izq y se reemplaza el nombre de la funcion por el token correspondiente se tendría algo similar a la sintaxis de LISP.
En el libro Language Implementation Patterns ( aquí está el prefacio si alguien le interesa ) escrito por el autor de ANTLR, explica como construir el AST a partir de las reglas de producción usando ANTLR o a manita. Esta muy interesante.
Gracias a ambos...
Gracias a ambos por la información, intente un ejemplo con ANTLR pero no me salio (: ( ) aun así me doy una idea (cultura general) de como funciona todo esto.
Bueno no queda más que intentar de nuevo.
Ciao : )
Por cierto IntellJ IDEA tiene
Por cierto IntellJ IDEA tiene un plugin que se llama "ANTRL works" que es una herramienta para visualizar tus gramática y todo eso:
Ejemplo:
Pero claro, para poder sacarle provecho a todo eso hay que tener la teoría primero y al menos saber como funciona un Parser hecho a mano.
Si te sirve de algo...
Definir una gramática de un lenguaje es parecido a definir un DTD, por si alguna vez has trabajado con eso (de hecho un DTD es la definicion de un mini-lenguaje en XML). Es basicamente descomponer las sentencias en trozos que a su vez se descomponen en más trozos... recursivamente hasta llegar a los elementos finales.
La "complicación" para entenderlo está normalmente en la parte de recursividad que tiene, ya que definir una operacion de suma, por ejemplo, no es simplemente definir a + b, ya que hay poder reconocer a + (b *c), por ejemplo, y a su vez (d-e) + (b+c), y a su vez ((d-e) + (b+c)) + (f % g) etc. etc. Es una especie de expresiones regulares + recusividad.
Y como dice OscarRyz, esa es "solo" la parte de leer el código fuente y obtener el AST (Abstract Syntax Tree, de ahí lo del arbol). Luego del AST puedes generar código (bytecode, por ejemplo) o puedes ejecutarlo directamente (caso de un intérprete). Tambien se le pueden hacer optimizaciones, dado el caso.
En mis tiempos me tocó hacer un pseudo-pascal en castellano, para hacerlo diferente del Pascal normal :), pero me tocó hacerlo en ADA, ¡toma esa!, con unas herramientas parecidas.
Otra opción más "cool" hoy en día para algunas cosas es hacerse un DSL (lenguaje específico de dominio...o de dominio específico según a quien le preguntes :D). Pero para eso van mejor los lenguajes dinámicos como por ejemplo Groovy, en lugar de Java.
S!