* mira las velas*
* mira hacia arriba *
* desea ver mas preguntas de python en la lista de python *
* sopla *
estas cosas funcionan?
sino me compre una torta y unas velas en vano.
al menos tengo torta... *
* not
viernes, octubre 30, 2009
sábado, octubre 17, 2009
Mapeo de busquedas de couchdb-lucene en couchapp
hasta ahora las vistas de couchdb-lucene las metía en couchdb a mano usando la interfaz de administración, pero me molestaba tener un txt por ahí con la vista y no tenerlo en el svn (menos me gustaba que couchapp no lo manejara).
después de preguntar en el irc de couchapp me dijeron que couchapp mapea directorios y archivos a estructuras json, así que lo que antes era
se convierte en una jerarquía de directorios en la raíz de mi couchapp como la siguiente
_design/search-docs pasa a ser _design/fresita (porque ese es el nombre de mi aplicación). Podemos encontrar las búsquedas en el path
_fti/$app-name/$search
por ejemplo:
_fti/fresita/authors?q=Mariano
todo mas prolijo y manejable..
después de preguntar en el irc de couchapp me dijeron que couchapp mapea directorios y archivos a estructuras json, así que lo que antes era
{
"_id": "_design/search-docs",
"fulltext": {
"documents": {
"index": "function(doc) { /* algo */ }"
},
"repos": {
"index": "function(doc) { /* algo */ }"
}
"authors": {
"index": "function(doc) { /* algo */ }"
}
}
}
se convierte en una jerarquía de directorios en la raíz de mi couchapp como la siguiente
fulltext/
|-- authors
| `-- index.js
|-- documents
| `-- index.js
`-- repos
`-- index.js
_design/search-docs pasa a ser _design/fresita (porque ese es el nombre de mi aplicación). Podemos encontrar las búsquedas en el path
_fti/$app-name/$search
por ejemplo:
_fti/fresita/authors?q=Mariano
todo mas prolijo y manejable..
martes, octubre 13, 2009
calculadora en erlang parte (multiplicación división y modulo)
como la otra vez voy a marcar las diferencias
calc_lexer.xrl:
Definitions.
D = [0-9]
AOP = (\+|-)
MOP = (\*|/|%)
WS = ([\000-\s]|#.*)
Rules.
{AOP} : {token,{add_operator,TokenLine,list_to_atom(TokenChars)}}.
{MOP} : {token,{mul_operator,TokenLine,list_to_atom(TokenChars)}}.
{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
{D}+\.{D}+ : {token,{float,TokenLine,list_to_float(TokenChars)}}.
{WS}+ : skip_token.
Erlang code.
----------------------------------------
nada del otro mundo, hacemos lo mismo que con los signos de adicion pero cambiando la expresion regular y el identificador.
ahora el parser:
calc_parser.yrl
Nonterminals
predicate.
Terminals
add_operator mul_operator integer float.
Rootsymbol predicate.
Left 300 add_operator.
Left 400 mul_operator.
predicate -> predicate add_operator predicate : {unwrap('$2'), '$1', '$3'}.
predicate -> predicate mul_operator predicate : {unwrap('$2'), '$1', '$3'}.
predicate -> integer : unwrap('$1').
predicate -> float : unwrap('$1').
Erlang code.
unwrap({_,_,V}) -> V.
----------------------------------
de nuevo, agregamos el identificador del nuevo simbolo no terminal, especificamos la asociatividad de la nueva operacion (tiene precedencia sobre la adicion). Y agregamos la regla para que lo transforme en una estructura de datos.
por ultimo la calculadora en si
calc.erl
-module(calc).
-export([solve/1]).
solve(String) ->
{ok, Tokens, _Endline} = calc_lexer:string(String),
{ok, Tree} = calc_parser:parse(Tokens),
matches(Tree).
matches(A) when is_number(A) -> A;
matches({'+', A, B}) -> matches(A) + matches(B);
matches({'-', A, B}) -> matches(A) - matches(B);
matches({'*', A, B}) -> matches(A) * matches(B);
matches({'/', A, B}) -> matches(A) / matches(B);
matches({'%', A, B}) -> matches(A) rem matches(B);
matches(_) -> error.
----------------------------------------------
agregamos el matching de las nuevas operaciones.
para probar un poco el codigo de build_calc:test()
y por ultimo lo probamos
en la proxima agregamos expresiones anidadas (parentesis)
calc_lexer.xrl:
Definitions.
D = [0-9]
AOP = (\+|-)
MOP = (\*|/|%)
WS = ([\000-\s]|#.*)
Rules.
{AOP} : {token,{add_operator,TokenLine,list_to_atom(TokenChars)}}.
{MOP} : {token,{mul_operator,TokenLine,list_to_atom(TokenChars)}}.
{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
{D}+\.{D}+ : {token,{float,TokenLine,list_to_float(TokenChars)}}.
{WS}+ : skip_token.
Erlang code.
----------------------------------------
nada del otro mundo, hacemos lo mismo que con los signos de adicion pero cambiando la expresion regular y el identificador.
ahora el parser:
calc_parser.yrl
Nonterminals
predicate.
Terminals
add_operator mul_operator integer float.
Rootsymbol predicate.
Left 300 add_operator.
Left 400 mul_operator.
predicate -> predicate add_operator predicate : {unwrap('$2'), '$1', '$3'}.
predicate -> predicate mul_operator predicate : {unwrap('$2'), '$1', '$3'}.
predicate -> integer : unwrap('$1').
predicate -> float : unwrap('$1').
Erlang code.
unwrap({_,_,V}) -> V.
----------------------------------
de nuevo, agregamos el identificador del nuevo simbolo no terminal, especificamos la asociatividad de la nueva operacion (tiene precedencia sobre la adicion). Y agregamos la regla para que lo transforme en una estructura de datos.
por ultimo la calculadora en si
calc.erl
-module(calc).
-export([solve/1]).
solve(String) ->
{ok, Tokens, _Endline} = calc_lexer:string(String),
{ok, Tree} = calc_parser:parse(Tokens),
matches(Tree).
matches(A) when is_number(A) -> A;
matches({'+', A, B}) -> matches(A) + matches(B);
matches({'-', A, B}) -> matches(A) - matches(B);
matches({'*', A, B}) -> matches(A) * matches(B);
matches({'/', A, B}) -> matches(A) / matches(B);
matches({'%', A, B}) -> matches(A) rem matches(B);
matches(_) -> error.
----------------------------------------------
agregamos el matching de las nuevas operaciones.
para probar un poco el codigo de build_calc:test()
test() ->
0 = calc:solve("1 + 2 - 3"),
6 = calc:solve("1 + 2 + 3"),
-4 = calc:solve("1 - 2 - 3"),
0.0 = calc:solve("1.0 + 2 - 3"),
6.0 = calc:solve("1 + 2.0 + 3"),
-4.0 = calc:solve("1 - 2.0 - 3"),
3.1 = calc:solve("1.0 + 2.1"),
7 = calc:solve("1 + 2 * 3"),
10 = calc:solve("2 * 3 + 4"),
7.1 = calc:solve("1.1 + 2 * 3"),
10.2 = calc:solve("2 * 3.1 + 4"),
1 = calc:solve("11 % 2"),
2 = calc:solve("1 + 11 % 2"),
11 = calc:solve("11 % 2 + 10"),
ok.
y por ultimo lo probamos
$ erl
Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [kernel-poll:false]
Eshell V5.6.5 (abort with ^G)
1> c(build_calc).
{ok,build_calc}
2> build_calc:build().
Old inliner: threshold=0 functions=[{yeccpars2_7_,1},
{yeccpars2_6_,1},
{yeccpars2_3_,1},
{yeccpars2_2_,1},
{yeccpars2_7_,1},
{yeccpars2_6_,1},
{yeccpars2_3_,1},
{yeccpars2_2_,1}]
ok
3> rl(calc).
ok
4> rl(calc_parser).
ok
5> rl(calc_lexer).
ok
6> build_calc:test().
ok
en la proxima agregamos expresiones anidadas (parentesis)
haciendo andar couchdb-lucene 0.4 en couchdb 0.10.0
como la mayoría de los posts son recordatorios para cuando lo tenga que hacer de nuevo y me haya olvidado y por si a alguien le sirve :D
principalmente seguí estas dos guías
http://github.com/rnewson/couchdb-lucene
http://wiki.fluidproject.org/pages/viewpage.action?pageId=6823331
pero siempre la generalidad termina dejando algunas cosas flotando que hacen que uno pelee (hasta que mira "watch tail /usr/local/var/log/couchdb/couch.log")
supongo que tienen instalado y andando couchdb 0.10.0 (va a ser el caso si usan ubuntu karmic :)
lo primero que hacemos es bajar la versión estable de couchdb-lucene (actualmente la 0.4)
luego (como dice el README que uno no lee) desempacamos el jar.gz con unpack200, si lo desempacamos con file-roller java se queja de que es un jar corrupto.
creamos un directorio donde lucene pueda trabajar tranquilo
copiamos el jar
cambiamos el dueño y los permisos al directorio
configuramos couchdb para que use lucene como indexador de full text search
(mi archivo local.ini tiene lo siguiente)
(no, ese no es mi password :)
reiniciamos couchdb (lo matamos y lo levantamos de nuevo, esto depende de como lo levantaste, si no sabes, hace "ps aux | grep couchdb" y mata todo lo que parezca sospechoso :D)
lo levantamos de nuevo (yo lo levanto así)
vamos a la interfaz de administracion:
elegimos nuestra base de datos, elegimos crear nuevo documento, clickeamos la pestaña source arriba a la derecha y pegamos algo parecido a lo siguiente:
el _id cambia por el nombre que mas te guste, las vistas que están adentro de fulltext también cambian según por que quieras indexar, yo estoy indexando por titulo y contenido de unos documentos.
ahora agregamos algunos documentos que puedan ser indexados, para sacarnos la duda de que lucene esta andando mirar la salida de /usr/local/var/log/couchdb/couch.log no debería haber mensajes de error y "ls /usr/local/var/lib/lucene" nos tendia que mostrar varios archivos de nombres extravagantes.
para probar nuestra búsqueda luego de agregar algunos documentos hacemos una query (yo agregue un documento que decía ficticio por todos lados :D)
lo cual me devolvió
principalmente seguí estas dos guías
http://github.com/rnewson/couchdb-lucene
http://wiki.fluidproject.org/pages/viewpage.action?pageId=6823331
pero siempre la generalidad termina dejando algunas cosas flotando que hacen que uno pelee (hasta que mira "watch tail /usr/local/var/log/couchdb/couch.log")
supongo que tienen instalado y andando couchdb 0.10.0 (va a ser el caso si usan ubuntu karmic :)
lo primero que hacemos es bajar la versión estable de couchdb-lucene (actualmente la 0.4)
wget http://cloud.github.com/downloads/rnewson/couchdb-lucene/couchdb-lucene-0.4-jar-with-dependencies.jar.gz
luego (como dice el README que uno no lee) desempacamos el jar.gz con unpack200, si lo desempacamos con file-roller java se queja de que es un jar corrupto.
unpack200 couchdb-lucene-0.4-jar-with-dependencies.jar.gz couchdb-lucene-0.4-jar-with-dependencies.jar
creamos un directorio donde lucene pueda trabajar tranquilo
sudo mkdir /usr/local/var/lib/lucene
copiamos el jar
sudo cp couchdb-lucene-0.4-jar-with-dependencies.jar /usr/local/var/lib/lucene
cambiamos el dueño y los permisos al directorio
chown -R couchdb:couchdb /usr/local/var/lib/lucene
chmod -R 0770 /usr/local/var/lib/lucene
configuramos couchdb para que use lucene como indexador de full text search
sudo vim /usr/local/etc/couchdb/local.ini
(mi archivo local.ini tiene lo siguiente)
; CouchDB Configuration Settings
; Custom settings should be made in this file. They will override settings
; in default.ini, but unlike changes made to default.ini, this file won't be
; overwritten on server upgrade.
[couchdb]
;max_document_size = 4294967296 ; bytes
[httpd]
;port = 5984
;bind_address = 127.0.0.1
[log]
;level = debug
[couch_httpd_auth]
;secret = replace this with a real secret
[external]
fti=/usr/bin/java -Dcouchdb.lucene.dir=/usr/local/var/lib/lucene -jar /usr/local/var/lib/lucene/couchdb-lucene-0.4-jar-with-dependencies.jar -search
[update_notification]
indexer=/usr/bin/java -Dcouchdb.lucene.dir=/usr/local/var/lib/lucene -jar /usr/local/var/lib/lucene/couchdb-lucene-0.4-jar-with-dependencies.jar -index
[couchdb]
os_process_timeout=60000 ; increase the timeout to 60 seconds.
[httpd_db_handlers]
_fti = {couch_httpd_external, handle_external_req, <<"fti">>}
; To create an admin account uncomment the '[admins]' section below and add a
; line in the format 'username = password'. When you next start CouchDB, it
; will change the password to a hash (so that your passwords don't linger
; around in plain-text files). You can add more admin accounts with more
; 'username = password' lines. Don't forget to restart CouchDB after
; changing this.
[admins]
marianoguerra = mysecretpassword
;admin = mysecretpassword
(no, ese no es mi password :)
reiniciamos couchdb (lo matamos y lo levantamos de nuevo, esto depende de como lo levantaste, si no sabes, hace "ps aux | grep couchdb" y mata todo lo que parezca sospechoso :D)
lo levantamos de nuevo (yo lo levanto así)
sudo -i -u couchdb couchdb -b
vamos a la interfaz de administracion:
firefox http://localhost:5984/_utils/
elegimos nuestra base de datos, elegimos crear nuevo documento, clickeamos la pestaña source arriba a la derecha y pegamos algo parecido a lo siguiente:
{
"_id": "_design/search-docs",
"fulltext": {
"by_title": {
"index": "function(doc) { var ret=new Document(); ret.add(doc.title); return ret }"
},
"by_content": {
"index": "function(doc) { var ret=new Document(); ret.add(doc.body); return ret }"
}
}
}
el _id cambia por el nombre que mas te guste, las vistas que están adentro de fulltext también cambian según por que quieras indexar, yo estoy indexando por titulo y contenido de unos documentos.
ahora agregamos algunos documentos que puedan ser indexados, para sacarnos la duda de que lucene esta andando mirar la salida de /usr/local/var/log/couchdb/couch.log no debería haber mensajes de error y "ls /usr/local/var/lib/lucene" nos tendia que mostrar varios archivos de nombres extravagantes.
para probar nuestra búsqueda luego de agregar algunos documentos hacemos una query (yo agregue un documento que decía ficticio por todos lados :D)
curl http://localhost:5984/fresita/_fti/search-docs/by_title?q=ficticio
lo cual me devolvió
{"q":"default:ficticio","etag":"1244bf34b28","view_sig":"7368cf7d6f68ec4c60f40c52303de534","skip":0,"limit":25,"total_rows":1,"search_duration":14,"fetch_duration":0,"rows":[{"id":"9ed8514725e1dca56c57471286f5f389","score":2.556901454925537}]}
lunes, octubre 12, 2009
sábado, octubre 10, 2009
calculadora en erlang parte 2 (agregando floats)
este cambio es muy simple asi que va a ser un post muy corto, voy a resaltar los cambios
en calc_lexer.xrl
Definitions.
D = [0-9]
AOP = (\+|-)
WS = ([\000-\s]|#.*)
Rules.
{AOP} : {token,{add_operator,TokenLine,list_to_atom(TokenChars)}}.
{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
{D}+\.{D}+ : {token,{float,TokenLine,list_to_float(TokenChars)}}.
{WS}+ : skip_token.
Erlang code.
en calc_parser.yrl
Nonterminals
predicate.
Terminals
add_operator integer float.
Rootsymbol predicate.
Left 300 add_operator.
predicate -> predicate add_operator predicate : {unwrap('$2'), '$1', '$3'}.
predicate -> integer : unwrap('$1').
predicate -> float : unwrap('$1').
Erlang code.
unwrap({_,_,V}) -> V.
en calc.erl
-module(calc).
-export([solve/1]).
solve(String) ->
{ok, Tokens, _Endline} = calc_lexer:string(String),
{ok, Tree} = calc_parser:parse(Tokens),
matches(Tree).
matches(A) when is_number(A) -> A;
matches({'+', A, B}) -> matches(A) + matches(B);
matches({'-', A, B}) -> matches(A) - matches(B);
matches(_) -> error.
para facilitar la construccion, les dejo el codigo que tengo en build_calc.erl
en calc_lexer.xrl
Definitions.
D = [0-9]
AOP = (\+|-)
WS = ([\000-\s]|#.*)
Rules.
{AOP} : {token,{add_operator,TokenLine,list_to_atom(TokenChars)}}.
{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
{D}+\.{D}+ : {token,{float,TokenLine,list_to_float(TokenChars)}}.
{WS}+ : skip_token.
Erlang code.
en calc_parser.yrl
Nonterminals
predicate.
Terminals
add_operator integer float.
Rootsymbol predicate.
Left 300 add_operator.
predicate -> predicate add_operator predicate : {unwrap('$2'), '$1', '$3'}.
predicate -> integer : unwrap('$1').
predicate -> float : unwrap('$1').
Erlang code.
unwrap({_,_,V}) -> V.
en calc.erl
-module(calc).
-export([solve/1]).
solve(String) ->
{ok, Tokens, _Endline} = calc_lexer:string(String),
{ok, Tree} = calc_parser:parse(Tokens),
matches(Tree).
matches(A) when is_number(A) -> A;
matches({'+', A, B}) -> matches(A) + matches(B);
matches({'-', A, B}) -> matches(A) - matches(B);
matches(_) -> error.
para facilitar la construccion, les dejo el codigo que tengo en build_calc.erl
-module(build_calc).
-export([build/0, test/0]).
build() ->
leex:file(calc_lexer),
yecc:file(calc_parser),
compile:file(calc_lexer),
compile:file(calc_parser),
compile:file(calc),
ok.
viernes, octubre 09, 2009
escribiendo una calculadora en erlang parte uno
buscando una excusa para seguir aprendiendo erlang encontré una interesante, escribir algo así como un lenguaje de juguete paso a paso y ver para donde dispara (o donde lo abandono)
en una serie de posts (espero seguir escribiéndolos) voy a ir desde cero hasta un interprete interactivo que permita hacer cosas como estas (y mas)
< a = 10 + (2 * 3) # calculo totalmente innecesario
> 16
< b = a * 2
> 32
< a
> 16
< b
> 32
< a == b or b > a
> true
este post es la patada de inicio asi que vamos a ver la herramientas.
Como lenguaje de programación obviamente vamos a usar erlang (que es lo que quiero aprender :D).
para el análisis léxico vamos a usar leex y para el análisis sintáctico y generación del árbol vamos a usar yecc.
para tener erlang hagan algo similar a:
sudo aptitude install erlang
(adaptenlo a su distro)
yecc viene incluido en erlang así que andamos con suerte, a donde no andamos con suerte es con leex, que lo tendremos que bajar de aquí:
http://forum.trapexit.org/download.php?id=184&sid=9eba63c4011e517629e8d5a73e4e2ff5
pueden buscar si hay una versión mas nueva acá:
http://forum.trapexit.org/viewtopic.php?p=43924#43924
lo bajan, descomprimen y ejecutan make en el directorio, luego para poder usarlo en erlang lo que hice (quizás no muy bueno pero anda :P) es crear un directorio llamado /usr/lib/erlang/lib/leex-0.3 y poner adentro leex.beam y el directorio include.
ahora con todo instalado vamos a largarnos con nuestro primer intento (remito a la documentación de cada uno para darse una mejor idea de que hacen y como se usan).
vamos a definir un lenguaje que nos permita sumar y restar números enteros
creemos un directorio llamado match-1-add-sub-ints
y adentro creamos un archivo llamado calc_lexer.xrl con el siguiente contenido
este archivo lo que hace es separar un string en partes (o tokens) que identifican distintos componentes de nuestro lenguaje. En el caso de un lenguaje para sumar números enteros lo que queremos dividir son los números, los signos + y - y descartar los espacios (y también todo lo que venga después de # ya que son comentarios)
en la sección de definiciones definimos las distintas expresiones regulares.
definimos una expresión regular para números enteros
D = [0-9]
una para los signos + y -
AOP = (\+|-)
y una para los blancos y los comentarios
WS = ([\000-\s]|#.*)
luego en la sección Rules. definimos que debe generar (la parte de la derecha) cuando se encuentre con las expresiones de la izquierda.
si se encuentra con un signo + o - que genere una tupla que dice que es un operador de suma, que ponga la posición en donde encontró el token y que convierta lo que matchea la expresión a un atomo.
{AOP} : {token,{add_operator,TokenLine,list_to_atom(TokenChars)}}.
si se encuentra con uno o mas dígitos que genere una tupla que dice que es un entero, la posición del token y el numero entero que encontró.
{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
si encuentra blancos que los salte.
{WS}+ : skip_token.
ahora ya podemos dividir el texto de entrada en pedazos mas manejables gracias a leex, ahora vamos a ver como salio esto. Iniciamos un interprete de erlang y le decimos a leex que genere un modulo de erlang para manejar los tokens que definimos, luego compilamos el modulo generado y lo probamos con un string valido
como vemos nos devolvió una estructura de datos con las partes que definimos.
ahora vamos a definir un modulo para poder hacer algo con ese árbol que definimos.
creamos un archivo que se llame calc_parser.yrl con el siguiente contenido:
este documento define los nodos no terminales, los nodos terminales el símbolo raíz, la asociatividad de los operandos y luego define la sintaxis (para mas datos sobre las primeras secciones leer la documentación en linea de yecc o algun libro sobre lenguajes, otra opción es ponerse a romper esto y aprender :D).
la parte mas interesante es la que define que un predicado puede estar compuesto por la suma de dos predicados:
predicate -> predicate add_operator predicate : {unwrap('$2'), '$1', '$3'}.
o puede ser solo un entero
predicate -> integer : unwrap('$1').
lo que esta después de los dos puntos es el código erlang ejecutado cuando esa expresión es encontrada. Lo que hacemos es generar otra estructura de datos que vamos a manipular en erlang.
luego tenemos una función de utilidad en erlang para sacar un valor de interés de una tupla.
ahora generamos el modulo erlang que va a parsear la salida de leex y convertirla en otra estructura:
para los curiosos, si se fijan el resultado que nos dio es muy parecido a una expresión de lisp :)
{'-',{'+',1,2},3}
bueno, ahora tenemos algo que se parece mucho a lisp, podríamos escribir lisp! pero no :D vamos a interpretarlo con un programa de erlang.
creen un archivo calc.erl que contenga lo siguiente:
lo que hace este modulo es definir una función que recibe un string por parámetro con el calculo que queremos realizar, lo pasar por el analizador lexicografico, le da la salida de este como entrada al parser y con el árbol sintáctico resultante llama a la función matches que gracias al pattern matching se encarga de ejecutar recursivamente la instrucción correcta y devolver el resultado final (o un error en caso de que algo no matchee).
ahora vamos a compilar esto y probarlo un poco:
bueno, ya tenemos la primera versión de nuestra calculadora, en la próxima vamos a agregar soporte para números de punto flotante con algunos leves cambios.
para los fiacosos como yo, el codigo de este ejemplo (y de los subsiguientes) esta en github aca:
http://github.com/marianoguerra/match
en una serie de posts (espero seguir escribiéndolos) voy a ir desde cero hasta un interprete interactivo que permita hacer cosas como estas (y mas)
< a = 10 + (2 * 3) # calculo totalmente innecesario
> 16
< b = a * 2
> 32
< a
> 16
< b
> 32
< a == b or b > a
> true
este post es la patada de inicio asi que vamos a ver la herramientas.
Como lenguaje de programación obviamente vamos a usar erlang (que es lo que quiero aprender :D).
para el análisis léxico vamos a usar leex y para el análisis sintáctico y generación del árbol vamos a usar yecc.
para tener erlang hagan algo similar a:
sudo aptitude install erlang
(adaptenlo a su distro)
yecc viene incluido en erlang así que andamos con suerte, a donde no andamos con suerte es con leex, que lo tendremos que bajar de aquí:
http://forum.trapexit.org/download.php?id=184&sid=9eba63c4011e517629e8d5a73e4e2ff5
pueden buscar si hay una versión mas nueva acá:
http://forum.trapexit.org/viewtopic.php?p=43924#43924
lo bajan, descomprimen y ejecutan make en el directorio, luego para poder usarlo en erlang lo que hice (quizás no muy bueno pero anda :P) es crear un directorio llamado /usr/lib/erlang/lib/leex-0.3 y poner adentro leex.beam y el directorio include.
ahora con todo instalado vamos a largarnos con nuestro primer intento (remito a la documentación de cada uno para darse una mejor idea de que hacen y como se usan).
vamos a definir un lenguaje que nos permita sumar y restar números enteros
creemos un directorio llamado match-1-add-sub-ints
y adentro creamos un archivo llamado calc_lexer.xrl con el siguiente contenido
Definitions.
D = [0-9]
AOP = (\+|-)
WS = ([\000-\s]|#.*)
Rules.
{AOP} : {token,{add_operator,TokenLine,list_to_atom(TokenChars)}}.
{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
{WS}+ : skip_token.
Erlang code.
este archivo lo que hace es separar un string en partes (o tokens) que identifican distintos componentes de nuestro lenguaje. En el caso de un lenguaje para sumar números enteros lo que queremos dividir son los números, los signos + y - y descartar los espacios (y también todo lo que venga después de # ya que son comentarios)
en la sección de definiciones definimos las distintas expresiones regulares.
definimos una expresión regular para números enteros
D = [0-9]
una para los signos + y -
AOP = (\+|-)
y una para los blancos y los comentarios
WS = ([\000-\s]|#.*)
luego en la sección Rules. definimos que debe generar (la parte de la derecha) cuando se encuentre con las expresiones de la izquierda.
si se encuentra con un signo + o - que genere una tupla que dice que es un operador de suma, que ponga la posición en donde encontró el token y que convierta lo que matchea la expresión a un atomo.
{AOP} : {token,{add_operator,TokenLine,list_to_atom(TokenChars)}}.
si se encuentra con uno o mas dígitos que genere una tupla que dice que es un entero, la posición del token y el numero entero que encontró.
{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
si encuentra blancos que los salte.
{WS}+ : skip_token.
ahora ya podemos dividir el texto de entrada en pedazos mas manejables gracias a leex, ahora vamos a ver como salio esto. Iniciamos un interprete de erlang y le decimos a leex que genere un modulo de erlang para manejar los tokens que definimos, luego compilamos el modulo generado y lo probamos con un string valido
$ erl
Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [kernel-poll:false]
Eshell V5.6.5 (abort with ^G)
1> leex:file(calc_lexer).
{ok,"calc_lexer.erl"}
2> c(calc_lexer).
{ok,calc_lexer}
3> {ok, Tokens, _Endline} = calc_lexer:string("1 + 2 - 3 # esto es un comentario").
{ok,[{integer,1,1},
{add_operator,1,'+'},
{integer,1,2},
{add_operator,1,'-'},
{integer,1,3}],
1}
como vemos nos devolvió una estructura de datos con las partes que definimos.
ahora vamos a definir un modulo para poder hacer algo con ese árbol que definimos.
creamos un archivo que se llame calc_parser.yrl con el siguiente contenido:
Nonterminals
predicate.
Terminals
add_operator integer.
Rootsymbol predicate.
Left 300 add_operator.
predicate -> predicate add_operator predicate : {unwrap('$2'), '$1', '$3'}.
predicate -> integer : unwrap('$1').
Erlang code.
unwrap({_,_,V}) -> V.
este documento define los nodos no terminales, los nodos terminales el símbolo raíz, la asociatividad de los operandos y luego define la sintaxis (para mas datos sobre las primeras secciones leer la documentación en linea de yecc o algun libro sobre lenguajes, otra opción es ponerse a romper esto y aprender :D).
la parte mas interesante es la que define que un predicado puede estar compuesto por la suma de dos predicados:
predicate -> predicate add_operator predicate : {unwrap('$2'), '$1', '$3'}.
o puede ser solo un entero
predicate -> integer : unwrap('$1').
lo que esta después de los dos puntos es el código erlang ejecutado cuando esa expresión es encontrada. Lo que hacemos es generar otra estructura de datos que vamos a manipular en erlang.
luego tenemos una función de utilidad en erlang para sacar un valor de interés de una tupla.
ahora generamos el modulo erlang que va a parsear la salida de leex y convertirla en otra estructura:
$ erl
Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [kernel-poll:false]
Eshell V5.6.5 (abort with ^G)
1> yecc:file(calc_parser).
{ok,"calc_parser.erl"}
2> c(calc_parser).
{ok,calc_parser}
3> {ok, Tokens, _Endline} = calc_lexer:string("1 + 2 - 3").
{ok,[{integer,1,1},
{add_operator,1,'+'},
{integer,1,2},
{add_operator,1,'-'},
{integer,1,3}],
1}
4> {ok, Tree} = calc_parser:parse(Tokens)
4> .
{ok,{'-',{'+',1,2},3}}
para los curiosos, si se fijan el resultado que nos dio es muy parecido a una expresión de lisp :)
{'-',{'+',1,2},3}
[1]> (- (+ 1 2) 3)
0
bueno, ahora tenemos algo que se parece mucho a lisp, podríamos escribir lisp! pero no :D vamos a interpretarlo con un programa de erlang.
creen un archivo calc.erl que contenga lo siguiente:
-module(calc).
-export([solve/1]).
solve(String) ->
{ok, Tokens, _Endline} = calc_lexer:string(String),
{ok, Tree} = calc_parser:parse(Tokens),
matches(Tree).
matches(A) when is_integer(A) -> A;
matches({'+', A, B}) -> matches(A) + matches(B);
matches({'-', A, B}) -> matches(A) - matches(B);
matches(_) -> error.
lo que hace este modulo es definir una función que recibe un string por parámetro con el calculo que queremos realizar, lo pasar por el analizador lexicografico, le da la salida de este como entrada al parser y con el árbol sintáctico resultante llama a la función matches que gracias al pattern matching se encarga de ejecutar recursivamente la instrucción correcta y devolver el resultado final (o un error en caso de que algo no matchee).
ahora vamos a compilar esto y probarlo un poco:
$ erl
Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [kernel-poll:false]
Eshell V5.6.5 (abort with ^G)
1> c(calc).
{ok,calc}
2> calc:solve("1 + 2 - 3 # deberia dar 0").
0
3> calc:solve("1 + 2 - 3 - 4").
-4
bueno, ya tenemos la primera versión de nuestra calculadora, en la próxima vamos a agregar soporte para números de punto flotante con algunos leves cambios.
para los fiacosos como yo, el codigo de este ejemplo (y de los subsiguientes) esta en github aca:
http://github.com/marianoguerra/match
Suscribirse a:
Entradas (Atom)
Seguidores
Archivo del Blog
-
►
2011
(74)
- ► septiembre (4)
-
►
2010
(111)
- ► septiembre (8)
-
►
2008
(60)
- ► septiembre (8)
-
►
2007
(64)
- ► septiembre (1)
-
►
2006
(81)
- ► septiembre (1)