2009-06-30

Más rapidito

Como les estuve contando, sigo escribiendo mi wiki. Ya parsea un subconjunto interesante del lenguaje definido por trac.
Siguiendo la tradición, les cuento como está avanzando el tokenizer. Al tokenizer lo simplifiqué para que devuelva la expresión regular que matcheo junto con el match (en vez del "tipo"). Esto hizo que la interfase para definir las reglas para tokenizar sea más simple. Si no hay ninguna regla que matchee sigue devolviendo ["string", :text].
Sin más, acá el código:

module Rapidito
class Tokenizer

def initialize( *delimiters )
@delimiter_list = delimiters + [/\z/]
@match_cache = nil
end

def source
valid_cache? ? @match_cache[0].to_s + @source : @source
end

def source=(s)
@match_cache = nil
@source = s
end

def has_next?
!@source.empty? || valid_cache?
end

def valid_cache?
(!@match_cache.nil?) && (@match_cache[0].to_s.length > 0)
end

def next_match
@delimiter_list.map {|regex| [regex.match(@source),regex]}.reject {|p| p[0].nil?}.inject do
|better,new|
better_pos = better[0].pre_match.length
new_pos = new[0].pre_match.length

if better_pos < new_pos
better
elsif new_pos < better_pos
new
elsif better[0].to_s.length > new[0].to_s.length
better
else
new
end
end
end

def next_token
if @match_cache #cached delimiter
rv = @match_cache
@match_cache = nil
return rv
end

match = next_match
p = match[0].pre_match.length
@source = @source[p + match[0].to_s.length, @source.length]

if p == 0 #delimiter
match
else #text
@match_cache = match
[match[0].pre_match, :text]
end
end

def all_tokens
tokens = []
while has_next?
tokens << next_token
end
tokens
end
end
end

Y si miran los tests de unidad, van a ver que también quedaron más lindos:
require 'test/unit'
require 'rapidito/tokenizer'

include Rapidito

class TokenizerTest < Test::Unit::TestCase

def test_no_token
tok = Tokenizer.new
tok.source = "aaaa"
assert_equal true, tok.has_next?
assert_equal ["aaaa", :text], tok.next_token
assert_equal false, tok.has_next?
end

def assert_all_tokens( expected, tokenizer )
assert_equal expected,
tokenizer.all_tokens.map { |token, kind| [token.to_s, kind] }
end

def test_two_delimiters
tok = Tokenizer.new(
/\|/, /;;/
)

tok.source = "aa|bbb;;;;cccc"
assert_all_tokens \
[ ["aa", :text], ["|", /\|/], ["bbb", :text],
[";;", /;;/], [";;", /;;/], ["cccc", :text] ],
tok

tok.source = "aa;;bbb||cccc"
assert_all_tokens \
[ ["aa", :text], [";;", /;;/], ["bbb", :text],
["|", /\|/], ["|", /\|/], ["cccc", :text] ],
tok
end

def test_choose_longest_match
tok = Tokenizer.new(
/aa/, /aaa/
)
tok.source = "aaaa"
assert_all_tokens [ ["aaa", /aaa/], ["a", :text ] ], tok
end

def test_reset_precache
tok = Tokenizer.new(
/\|/, /,/
)
tok.source = "original start|original end"
tok.next_token
tok.source = "new start,new end"
assert_equal ["new start", :text], tok.next_token
end

def test_almost_finished
tok = Tokenizer.new( /!/ )
tok.source = "bang!"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal false, tok.has_next?
end

def test_carriage_return_ending
tok = Tokenizer.new( /!/ )
tok.source = "bang!\n"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal true, tok.has_next?
assert_equal "\n", tok.next_token[0].to_s
assert_equal false, tok.has_next?
end

def test_transparent_caching
tok = Tokenizer.new( /!/ )
tok.source = "bang!pum"
tok.next_token

assert_equal "!pum", tok.source
end

def test_match_klass
tok = Tokenizer.new( /!/ )
tok.source = "!bang!pum"

assert_equal \
[MatchData, String, MatchData, String],
tok.all_tokens.map { |tok, kind| tok.class }
end
end

Happy hacking,
Aureliano.

2009-06-19

Bouncer versión 2

Hice una nueva versión del bouncer que permite bouncear a varios targets desde un mismo host:

#!/usr/bin/env ruby

# == Synopsis
#
# bouncer.rb: Redirects TCP connections to distant machines. Handles simultaneously many connections.
#
# == Usage
#
# ruby redirect.rb [OPTION]
#
# -h, --help:
# Show help
#
# --ip ip, -i ip:
# Accept connections from ip (default 127.0.0.1)
#
# --port port, -p port:
# Listen on port (default 12345)
#
# --target ip:port, -t ip:port
# Connect to ip:port (default 127.0.0.1:23456)
#

require 'getoptlong'
require 'rdoc/usage'
require 'socket'

class Bouncer
def self.for_host( source_ip, start_port, targets )
rules = {}
targets.each_with_index do
|target, index|
rules[ start_port + index ] = target
end
self.new( source_ip, rules )
end
def initialize( source_ip, port_target_map )
@port_target_map = port_target_map
@source_ip = source_ip

@servers = @port_target_map.keys.map do
|port|
ss = TCPServer.new( "", port )
ss.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
ss
end

@descriptors = @servers.clone
@next_step = {}
end

def handle_new_connection( ss )
incoming = ss.accept
if incoming.peeraddr[3] == @source_ip
begin
outgoing = TCPSocket.new( *@port_target_map[incoming.addr[1]] )
@next_step[ incoming ] = outgoing
@next_step[ outgoing ] = incoming

@descriptors += [ incoming, outgoing ]
rescue
silent_close( incoming )
end
else
silent_close( incoming )
end
end

def silent_close( sock )
begin
sock.close
rescue
#do nothing intentionally
end
end

def propagate(sock)
next_sock = @next_step[sock]
next_sock.write(sock.read_nonblock(1000 * 1000))
end

def finish_connection(sock)
next_sock = @next_step[sock]
[ sock, next_sock ].each do
|s|
silent_close(s)
@descriptors.delete(s)
@next_step.delete(s)
end
end

def run
loop do
connections = select( @descriptors )
connections[0].each do
|sock|
if @servers.include? sock then
handle_new_connection( sock )
else
begin
sock.eof? ? finish_connection(sock) : propagate(sock)
rescue
finish_connection(sock)
end
end
end
end
end
end

if $0 == __FILE__ then

opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--ip', '-i', GetoptLong::REQUIRED_ARGUMENT ],
[ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
[ '--target', '-t', GetoptLong::REQUIRED_ARGUMENT ]
)

ip = '127.0.0.1'
port = '12345'
target = '127.0.0.1:23456'

opts.each do
|opt, arg|
case opt
when '--help'
RDoc::usage
exit
when '--ip'
ip = arg
when '--port'
port = arg
when '--target'
target = arg
end
end

port = port.to_i
target = target.split(":")

trap("SIGINT") do
exit
end

Bouncer.new(ip, port => [target[0], target[1].to_i]).run
end

2009-06-18

Mousewheel para arriba en vmware

Encontré como hacer para que ande bien la ruedita del mouse adentro de un Guest de VMWare con Kubuntu 9.04. Hay que instalar el paquete xserver-xorg-input-vmmouse.
Happy hacking,
Aureliano.

2009-06-17

Cambiando de resolución en X

El comando xrandr sirve, entre otras cosas para cambiar la resolución de X desde un shell. Primero listo las resoluciones disponibles:


$ xrandr
Screen 0: minimum 320 x 200, current 1280 x 1024, maximum 1280 x 1280
VGA connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm
1280x1024 60.0*+ 75.0 60.0*
1152x864 75.0
1024x768 75.0 60.0
800x600 75.0 60.3
640x480 75.0 59.9
720x400 70.1

y puedo elegir la que quiera con el parámetro -s. Por ejemplo:

$ xrandr -s 800x600

Happy hacking,
Aureliano

2009-06-13

Pequeñas delicias de las expresiones regulares

Como les conté acá y acá, estoy escribiendo un tokenizador para un wiki que estoy programando. Y hoy me encontré con una cosa muy extraña de las expresiones regulares.
En ruby la función match sirve para buscar el primer match de una regex dentro de un string. Por ejemplo (usando el irb):


irb(main):001:0> m = /a/.match "babab"
=> #<MatchData "a">
irb(main):002:0> m.pre_match
=> "b"
irb(main):003:0> m[0]
=> "a"

En particular, el pre_match es lo que está antes del match en el string. También según había entendido (mal) /\Z/ matchea con el final del string. Por ejemplo:

irb(main):004:0> m = /\Z/.match "hola"
=> #<MatchData "">
irb(main):005:0> m.pre_match
=> "hola"

Pero, /\Z/ tiene un comportamiento muy extraño, aunque documentado, cuando el último caracter antes del final es un \n. Lo que pasa es que el pre_match queda ¡sin el \n del final!. Lo muestro en el irb:

irb(main):006:0> m = /\Z/.match "\n"
=> #<MatchData "">
irb(main):007:0> m.pre_match
=> ""

Para que no se manduque el \n, hay que usar /\z/ (¡en minúscula!):

irb(main):008:0> m = /\z/.match "\n"
=> #<MatchData "">
irb(main):009:0> m.pre_match
=> "\n"

Por lo tanto tuve que tocar el tokenizer, ahora la función de initialize quedó así (miren el cambio de la "Z" a "z"):
    def initialize( delimiters )
@delimiter_list = [[/\z/, :finish]] +
delimiters.to_a.map { |k,arr| arr.map { |re| [re, k] } }.inject([]) { |ac,ps| ac + ps }
@match_cache = nil
end

Y el test que captura el problema que genera usar \Z en vez de \z quedó así:
  def test_carriage_return_ending
tok = Tokenizer.new( :a_kind => [/!/] )
tok.source = "bang!\n"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal true, tok.has_next?
assert_equal "\n", tok.next_token[0].to_s
assert_equal false, tok.has_next?
end

Happy hacking,
Aureliano.

2009-06-12

Tokenizer de rapidito, segunda versión

Como les conté hace un par de posts, estoy haciendo un wiki y blogueando sobre el tokenizer
Hoy les cuento de la segunda versión del tokenizer. A esta versión (que no es compatible con la anterior) le agregué la posibilidad de asociar un tipo (kind) a cada regexp que define un token. Por lo tanto, el método next_match ahora devuelve un par [match, kind]. Los matches de texto común (no delimitadores) tienen kind :text. Los matches de los delimitadores vienen con la MatchData entera, para poder usarla si interesan partes del match a la hora de procesar el token. También dejé de usar =~ para matchear regexps, ya que el
Me quedaron un par de cosas con dudas después de implementar esto.

  1. ¿Se puede hacer en ruby que busque un match de una regexp en un string a partir de una posición? Hacer una regexp del tipo /.{34,}(regex de verdad)/ no vale.
  2. ¿Puedo forzar a la unión de expresiones regulares a buscar el match más largo (en vez del primero que matchee)?
A pesar de las dudas, igual tengo una versión que creo que es significativamente mejor que la versión anterior.
Así que como la otra vez dejo el código:
module Rapidito
class Tokenizer

attr_reader :source

def initialize( delimiters )
@delimiter_list = [[/\Z/, :finish]] +
delimiters.to_a.map { |k,arr| arr.map { |re| [re, k] } }.inject([]) { |ac,ps| ac + ps }
@match_cache = nil
end

def source=(s)
@match_cache = nil
@source = s
end

def has_next?
!@source.empty? || valid_cache?
end

def valid_cache?
(!@match_cache.nil?) && (@match_cache[0].to_s.length > 0)
end

def next_match
@delimiter_list.map {|p| [p[0].match(@source), p[1]]}.reject {|p| p[0].nil?}.inject do
|better,new|
better_pos = better[0].pre_match.length
new_pos = new[0].pre_match.length

if better_pos < new_pos
better
elsif new_pos < better_pos
new
elsif better[0].to_s.length > new[0].to_s.length
better
else
new
end
end
end

def next_token
if @match_cache #cached delimiter
rv = @match_cache
@match_cache = nil
return rv
end

match = next_match
p = match[0].pre_match.length
@source = @source[p + match[0].to_s.length, @source.length]

if p == 0 #delimiter
[match[0].to_s, match[1]]
else #text
@match_cache = match
[match[0].pre_match, :text]
end
end

def all_tokens
tokens = []
while has_next?
tokens << next_token
end
tokens
end
end
end

Y los tests de unidad:
require 'test/unit'
require 'rapidito/tokenizer'

include Rapidito

class TokenizerTest < Test::Unit::TestCase

def test_no_token
tok = Tokenizer.new( {} )
tok.source = "aaaa"
assert_equal true, tok.has_next?
assert_equal ["aaaa", :text], tok.next_token
assert_equal false, tok.has_next?
end

def assert_all_tokens( expected, tokenizer )
assert_equal expected,
tokenizer.all_tokens.map { |token, kind| [token.to_s, kind] }
end

def test_two_delimiters
tok = Tokenizer.new(
:a_kind => [/\|/, /;;/]
)

tok.source = "aa|bbb;;;;cccc"
assert_all_tokens \
[ ["aa", :text], ["|", :a_kind], ["bbb", :text],
[";;", :a_kind], [";;", :a_kind], ["cccc", :text] ],
tok

tok.source = "aa;;bbb||cccc"
assert_all_tokens \
[ ["aa", :text], [";;", :a_kind], ["bbb", :text],
["|", :a_kind], ["|", :a_kind], ["cccc", :text] ],
tok
end

def test_choose_longest_match
tok = Tokenizer.new(
:a_kind => [/aa/, /aaa/]
)
tok.source = "aaaa"
assert_equal [ ["aaa", :a_kind], ["a", :text ] ], tok.all_tokens
end

def test_reset_precache
tok = Tokenizer.new(
:a_kind => [/\|/, /,/]
)
tok.source = "original start|original end"
tok.next_token
tok.source = "new start,new end"
assert_equal ["new start", :text], tok.next_token
end

def test_almost_finished
tok = Tokenizer.new( :a_kind => [/!/] )
tok.source = "bang!"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal false, tok.has_next?
end
end

Happy hacking,
Aureliano.

2009-06-11

Syntax highlighting de Ruby en mi blog

Tomando como base lo que dice en este post, hice un script en ruby que genera html con clases que se pueden poner coloritos con css:

#!/usr/bin/env ruby

require 'rubygems'
require 'syntax/convertors/html'

class Syntax::Convertors::HTML
def convert( text, klass="" )
html = "<pre class=\"#{klass}\">"
regions = []
@tokenizer.tokenize( text ) do |tok|
value = html_escape(tok)
case tok.instruction
when :region_close then
regions.pop
html << "</span>"
when :region_open then
regions.push tok.group
html << "<span class=\"#{tok.group}\">#{value}"
else
if tok.group == ( regions.last || :normal )
html << value
else
html << "<span class=\"#{tok.group}\">#{value}</span>"
end
end
end
html << "</span>" while regions.pop
html << "</pre>"
html
end
end

convertor = Syntax::Convertors::HTML.for_syntax "ruby"
puts convertor.convert( $stdin.read, "ruby" )



En el script monkeypatchié un toque para que agregue la clase "ruby" al tag pre que engloba todo el código y aparte estoy usando estos estilos:

<style>
pre.ruby {
background-color: #ffffcc;
color: #000000;
padding: 10px;
font-size: 1.1em;
overflow: auto;
margin: 4px 0px;
width: 95%;
border: thin dashed;
}
.ruby .normal {}
.ruby .comment { color: #005; font-style: italic; }
.ruby .keyword { color: #A00; font-weight: bold; }
.ruby .method { color: #077; }
.ruby .class { color: #074; }
.ruby .module { color: #050; }
.ruby .punct { color: #447; font-weight: bold; }
.ruby .symbol { color: #099; }
.ruby .string { color: #944; }
.ruby .char { color: #F07; }
.ruby .ident { color: #004; }
.ruby .constant { color: #07F; }
.ruby .regex { color: #B66; }
.ruby .number { color: #D55; }
.ruby .attribute { color: #377; }
.ruby .global { color: #3B7; }
.ruby .expr { color: #227; }
</style>


Una cosa más, si estás viendo este post en otro lugar que no sea aurelianito.blogspot.com no vas a ver el resaltado de sintaxis (ya que no va a tener los estilos).

Hasta la próxima,
Aureliano

2009-06-06

Tokenizer de rapidito

Después de mirar un rato el estado de las bibliotecas para hacer wikis en Ruby, y descubrir que ninguna me servía, Decidí que tenía que poner cartas en el asunto y hacer la mía. Mi idea es implementar el markup de Trac, haciéndolo extensible, y agregarle un par de cositas que por ahora son un secreto :-p.

Lo importante del asunto es que puse manos en el asunto. Al final, después de un intento fallido de hackear mi camino al andar, decidí que lo mejor es armar un tokenizer y un parser que use esos tokens para generar el árbol del que extraeré el HTML.

Así que me puse a programar. Como no encontré ningún tokenizer en ruby, programé uno. El tokenizer se contruye con un montón de expresiones regulares que definen cada delimitador. Después se le setea una fuente de caracteres (un string) y separa el string en los delimitadores de arriba (que se devuelven como símbolos) y cadenas que no matchean con ninguno de los delimitadores (que devuelve como strings).

Bueno, basta de cháchara, acá tá el código:


module Rapidito
class Tokenizer

def initialize( *delimiters )
@regexp = Regexp.union( *delimiters + [/$/] )
end

attr_accessor :source

def has_next?
! @source.empty?
end

def next_token
p = (@source =~ @regexp)
if p == 0 #delimiter
token = nil
@source.sub!( @regexp ) { |match| token=match.to_sym; "" }
token
else #text
token = @source[0,p]
@source = @source[p,@source.length]
token
end
end

def all_tokens
tokens = []
while has_next?
tokens << next_token
end
tokens
end

end
end



Y, acá abajo la única documentación que hice hasta ahora, o sea los tests de unidad:

require 'test/unit'
require 'rapidito/tokenizer'

include Rapidito

class TokenizerTest < Test::Unit::TestCase

def test_no_token
tok = Tokenizer.new
tok.source = "aaaa"
assert_equal true, tok.has_next?
assert_equal "aaaa", tok.next_token
assert_equal false, tok.has_next?
end

def test_two_delimiters
tok = Tokenizer.new( /\|/, /;;/ )
tok.source = "aa|bbb;;;;cccc"
assert_equal [ "aa", :"|", "bbb", :";;", :";;", "cccc" ], tok.all_tokens

tok.source = "aa;;bbb||cccc"
assert_equal [ "aa", :";;", "bbb", :"|", :"|", "cccc" ], tok.all_tokens
end

def test_choose_first_match
tok = Tokenizer.new( /aa/, /aaa/ )
tok.source = "aaa"
assert_equal [ :aa, "a" ], tok.all_tokens
end

end


Happy hacking,
Aureliano.

PD: ¿Prefieren que ponga el código con syntax highlighting?

2009-06-01

Quine en ruby

En el ruby quiz pusieron como problema de la semana hacer un quine en ruby. Un quine es un programa que genera como salida el código fuente del mismo (ver wikipedia).

Así que puse manos a la obra. Agarre el último quine en C de la página de wikipedia:


main() { char *s="main() { char *s=%c%s%c; printf(s,34,s,34); }"; printf(s,34,s,34); }


Y lo traduje a ruby:

s="s=%c%s%c;printf(s,34,s,34)";printf(s,34,s,34)


En ruby no hace falta tener main, porque todo lo que agarra el intérprete lo ejecuta y tampoco hay que declarar variables, así que quedó de 48 caracteres.

Y después hice uno que hace que tenga un ENTER al final, quedando más lindo cuando lo corrés de la consola:

s="s=%c%s%c;printf(s,34,s,34,13,10)%c%c";printf(s,34,s,34,13,10)


Fueron 10 minutos divertidos.

Happy hacking,
Aureliano.

2009-05-31

99 botellas de cerveza

Yo no soy un fan de Perl, pero a veces aparecen cosas increíbles. Por ejemplo, este es un programa válido en perl:


''=~( '(?{' .('`' |'%') .('[' ^'-')
.('`' |'!') .('`' |',') .'"'. '\\$'
.'==' .('[' ^'+') .('`' |'/') .('['
^'+') .'||' .(';' &'=') .(';' &'=')
.';-' .'-'. '\\$' .'=;' .('[' ^'(')
.('[' ^'.') .('`' |'"') .('!' ^'+')
.'_\\{' .'(\\$' .';=('. '\\$=|' ."\|".( '`'^'.'
).(('`')| '/').').' .'\\"'.+( '{'^'['). ('`'|'"') .('`'|'/'
).('['^'/') .('['^'/'). ('`'|',').( '`'|('%')). '\\".\\"'.( '['^('(')).
'\\"'.('['^ '#').'!!--' .'\\$=.\\"' .('{'^'['). ('`'|'/').( '`'|"\&").(
'{'^"\[").( '`'|"\"").( '`'|"\%").( '`'|"\%").( '['^(')')). '\\").\\"'.
('{'^'[').( '`'|"\/").( '`'|"\.").( '{'^"\[").( '['^"\/").( '`'|"\(").(
'`'|"\%").( '{'^"\[").( '['^"\,").( '`'|"\!").( '`'|"\,").( '`'|(',')).
'\\"\\}'.+( '['^"\+").( '['^"\)").( '`'|"\)").( '`'|"\.").( '['^('/')).
'+_,\\",'.( '{'^('[')). ('\\$;!').( '!'^"\+").( '{'^"\/").( '`'|"\!").(
'`'|"\+").( '`'|"\%").( '{'^"\[").( '`'|"\/").( '`'|"\.").( '`'|"\%").(
'{'^"\[").( '`'|"\$").( '`'|"\/").( '['^"\,").( '`'|('.')). ','.(('{')^
'[').("\["^ '+').("\`"| '!').("\["^ '(').("\["^ '(').("\{"^ '[').("\`"|
')').("\["^ '/').("\{"^ '[').("\`"| '!').("\["^ ')').("\`"| '/').("\["^
'.').("\`"| '.').("\`"| '$')."\,".( '!'^('+')). '\\",_,\\"' .'!'.("\!"^
'+').("\!"^ '+').'\\"'. ('['^',').( '`'|"\(").( '`'|"\)").( '`'|"\,").(
'`'|('%')). '++\\$="})' );$:=('.')^ '~';$~='@'| '(';$^=')'^ '[';$/='`';


Y genera la letra de la canción 99 bottles of beer.

Para ver la explicación sobre como hicieron esto, mirá acá.

Happy hacking,
Aureliano.

PD: ¿Se podrá hacer algo así en ruby?

2009-05-16

Referencia de administradores de consorcio

En el consorcio donde vivo estamos cambiando de administración. Para ello se recibieron propuestas escritas de un montón de administradoras. Después de ser analizadas, elegimos 4 para entrevistar. De esas 4, si sale todo bien, deberíamos elegir a la brevedad cuál de ellas será nuestra próxima administración.
Por lo tanto, quería pedirles que me cuenten si tienen referencias (tanto buenas como malas) de estas administraciones. Las mismas son:

Muchas gracias,
Aureliano.

2009-05-14

Pinky y Cerebro


Gracias Pedro Varangot por la idea.

Y para los que no conocen la versión original:

2009-05-06

2009-05-05

Bouncer básico en ruby

Estuve programando un bouncer, usando como base el load balancer que hice el año pasado. Un bouncer es un programa que poner un server TCP en un puerto y cada vez que recibe una conexión abre una conexión a otro lado y las "ata", haciendo que el server al que se conecta le conteste al cliente que se conecta a él.
En mi caso lo programé para tener una salida controlada del entorno de máquinas virtuales que estoy armando para tener más ordenadas las cosas en el trabajo.
Por si les interesa, acá abajo pongo el código completo.


#!/usr/bin/env ruby

# == Synopsis
#
# redirect.rb: Redirects TCP connections to distant machines. Handles simultaneously many connections.
#
# == Usage
#
# ruby redirect.rb [OPTION]
#
# -h, --help:
# Show help
#
# --ip ip, -i ip:
# Accept connections from ip (default 127.0.0.1)
#
# --port port, -p port:
# Listen on port (default 12345)
#
# --target ip:port, -t ip:port
# Connect to ip:port (default 127.0.0.1:23456)
#

require 'getoptlong'
require 'rdoc/usage'
require 'socket'

class Redirect
def initialize( source_ip, listening_port, target_ip, target_port )
@source_ip = source_ip
@target_ip = target_ip
@target_port = target_port

@server_socket = TCPServer.new( "", listening_port )
@server_socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )

@descriptors = [ @server_socket ]
@next_step = {}
end

def handle_new_connection
incoming = @server_socket.accept
if incoming.peeraddr[3] == @source_ip
begin
outgoing = TCPSocket.new( @target_ip, @target_port )

@next_step[ incoming ] = outgoing
@next_step[ outgoing ] = incoming

@descriptors += [ incoming, outgoing ]
rescue
silent_close( incoming )
end
else
silent_close( incoming )
end
end

def silent_close( sock )
begin
sock.close
rescue
#do nothing intentionally
end
end

def propagate(sock)
next_sock = @next_step[sock]
next_sock.write(sock.read_nonblock(1000 * 1000))
end

def finish_connection(sock)
next_sock = @next_step[sock]
[ sock, next_sock ].each do
|s|
silent_close(s)
@descriptors.delete(s)
@next_step.delete(s)
end
end

def run
loop do
connections = select( @descriptors )
connections[0].each do
|sock|
if sock == @server_socket then
handle_new_connection
else
begin
sock.eof? ? finish_connection(sock) : propagate(sock)
rescue
finish_connection(sock)
end
end
end
end
end
end

if $0 == __FILE__ then

opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--ip', '-i', GetoptLong::REQUIRED_ARGUMENT ],
[ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
[ '--target', '-t', GetoptLong::REQUIRED_ARGUMENT ]
)

ip = '127.0.0.1'
port = '12345'
target = '127.0.0.1:23456'

opts.each do
|opt, arg|
case opt
when '--help'
RDoc::usage
exit
when '--ip'
ip = arg
when '--port'
port = arg
when '--target'
target = arg
end
end

port = port.to_i
target = target.split(":")

trap("SIGINT") do
exit
end

Redirect.new(ip, port, target[0], target[1].to_i).run
end

2009-04-21

Charla de erlang

El viernes pasado di una charla sobre erlang en el trabajo. En la misma, muestro como escribir programas secuenciales en erlang y después un poco de programación distribuida. Para presentar usé esta presentación (ojo, tenés que abrirla con OpenOffice). Abajo pongo lo que planifiqué dar (que es muy parecido a lo que di).

Programación secuencial

"Variables" inmutables

1> X = 1.
1
2> X = 2.
** exception error: no match of right hand side value 2
3> X = 1.
1

Atomos

4> aaaa.
aaaa
5> bbbb.
bbbb
6> Y = bbbb.
bbbb
7> Y.
bbbb
8> Y = aaaa.
** exception error: no match of right hand side value aaaa

Tuplas y listas (y pattern matching)

9> L = [1, 2, 3]
.
[1,2,3]
10> L2 = [-1, 0 | L]
.
[-1,0,1,2,3]
11> [H1, H2, H3 | _ ] = L2
.
[-1,0,1,2,3]
12> H1.
-1
13> H2.
0
14> H3.
1

15> Yo = {person, {fname, "Aureliano"}, {lname, "Calvo"}}.
{person,{fname,"Aureliano"},{lname,"Calvo"}}
16> Yo.
{person,{fname,"Aureliano"},{lname,"Calvo"}}
17> {person, {fname, Name},_} = Yo.
{person,{fname,"Aureliano"},{lname,"Calvo"}}
18> Name.
"Aureliano"

Expresiones case

34> X = 1. EL = []. L = [1,2,3].
1
35> []
36> [1,2,3]

38> case X of [] -> empty_list; [H|T] -> {head, H}; _ -> not_a_list end.
not_a_list
39> case EL of [] -> empty_list; [H|T] -> {head, H}; _ -> not_a_list end.
empty_list
40> case L of [] -> empty_list; [H|T] -> {head, H}; _ -> not_a_list end.
{head,1}

Funciones

En fruit.erl,

price( pear ) -> 4.0;
price( banana ) -> 3.0;
price( apple ) -> 6.0;
price( orange ) -> 2.0;

En consola,

13> fruit:price( pear ).
4.0
14> fruit:price( banana ).
3.0

Usando pattern matching un toque más en serio: En fruit.erl,

price( [] ) -> 0.0;
price( [{Kilos, Fruit}|Rest] ) -> Kilos * price( Fruit ) + price( Rest ).

En la consola,

15> fruit:price( [] ).
0.0
18> fruit:price( [{2.5, banana}, {1.5, apple}] ).
16.5

Pero para que use mejor el espacio (y no se acabe el stack), lo escribo tail recursive (acá explico que el stack sino se va a la mierda):

price2( L ) -> price2( L, 0.0 ).

price2( [], Acum ) -> Acum;
price2( [{Kilos, Fruit}|Rest], Acum ) -> price2( Rest, Kilos * price( Fruit ) + Acum ).

Programación distribuida

spawn, send y receive

spawn lanza un proceso nuevo de erlang, send (!) manda un mensaje a un proceso, y receive lo recibe.

Hago el fruit_shop.erl.

42> c( fruit_shop).
{ok,fruit_shop}
43> FS = fruit_shop:spawn_shop( [ {banana, 15.0}, {pear, 10.0}, {apple, 10.0}], [ {banana, 3.0}, {pear, 4.0}, {apple, 6.0} ] ).

Y lo uso:

(a@tango)3> fruit_shop:stock(FS).
[{banana,15.0},{pear,10.0},{apple,10.0}]
(a@tango)8> fruit_shop:buy(FS, [{2.0, apple}]).
{purchased,12.0}
(a@tango)10> fruit_shop:stock(FS).
[{apple,8.0},[{banana,15.0},{pear,10.0}]]

Por último, lanzar procesos en otros nodos es casi lo mismo: (spawn con un parámetro más, todo el resto igual).

Pongo 2 consolas de erlang:

>erl -sname a -setcookie oreo
>erl -sname b -setcookie oreo

En la primera lanzo la frutería sobre el segundo nodo:

(a@tango)7> FS = fruit_shop:spawn_shop( 'b@tango', [ {banana, 15.0}, {pear, 10.0
}, {apple, 10.0}], [ {banana, 3.0}, {pear, 4.0}, {apple, 6.0} ] ).

Hago lo mismo que antes:

(a@tango)3> fruit_shop:stock(FS).
[{banana,15.0},{pear,10.0},{apple,10.0}]
(a@tango)8> fruit_shop:buy(FS, [{2.0, apple}]).
{purchased,12.0}
(a@tango)10> fruit_shop:stock(FS).
[{apple,8.0},[{banana,15.0},{pear,10.0}]]

Mato la segunda consola y ahora las operaciones dan timeout:

(a@tango)11> fruit_shop:stock(FS).
timeout
(a@tango)12> fruit_shop:buy(FS, [{2.0, apple}]).
timeout

2009-04-18

2009-04-10

como sacar numeros del 0 al 9 con los digitos 4 4 4 4 (parte 3)

Sigo jugando con los 4s y ahora quería contarles una curiosidad. Si saco la potenciación de las operaciones posibles, se pueden armar los mismos números que antes:

0 == (4 / (4 * (4 * 4)))
1 == (4 / (4 / (4 / 4)))
2 == (4 / ((4 + 4) / 4))
3 == ((4 + (4 + 4)) / 4)
4 == (4 - (4 / (4 * 4)))
5 == ((4 + (4 * 4)) / 4)
6 == (4 + ((4 + 4) / 4))
7 == (4 + (4 - (4 / 4)))
8 == (4 - (4 - (4 + 4)))
9 == (4 + (4 + (4 / 4)))
10 ==
11 ==
12 == (4 * (4 - (4 / 4)))
13 ==
14 ==
15 == ((4 * 4) - (4 / 4))
16 == (4 * (4 / (4 / 4)))
17 == ((4 / 4) + (4 * 4))
18 ==
19 ==
20 == (4 * (4 + (4 / 4)))
21 ==
22 ==
23 ==
24 == (4 + (4 + (4 * 4)))
25 ==
26 ==
27 ==
28 == ((4 * (4 + 4)) - 4)
29 ==
30 ==
31 ==
32 == ((4 * 4) + (4 * 4))
33 ==
34 ==
35 ==
36 == (4 + (4 * (4 + 4)))
37 ==
38 ==
39 ==
40 ==
41 ==
42 ==
43 ==
44 ==
45 ==
46 ==
47 ==
48 == (4 * (4 + (4 + 4)))
49 ==
50 ==


¿Loco no? Para probarlo, saquen ** de la lista de posibles operadores y corran el script de la entrada anterior del blog.

Happy hacking,
Aureliano.

como sacar numeros del 0 al 9 con los digitos 4 4 4 4 (parte 2)

En la entrada anterior del blog puse el desafío de calcular los números del 1 al 50 haciendo operaciones aritméticas sobre 4 4s, y como generarlos para los números que van del 0 al 9. Pero me quedé con la duda de si es posible hacerlo para todos los números menores a 50, así que escribí un programita en ruby que calcula todas las posibles evaluaciones usando los operadores +, -, *, / y **. Lo único, hay que tener en cuenta que la división es entera, por lo que hay algunos resultados poco intuitivos.

Al final, el primer número imposible es el 10 (por eso no encontraba como armarlo :D). Acá les muestro la salida del programa:

0 == (4 / (4 ** (4 ** 4)))
1 == (4 ** (4 / (4 ** 4)))
2 == (4 / ((4 + 4) / 4))
3 == (4 - (4 ** (4 - 4)))
4 == (4 ** (4 ** (4 - 4)))
5 == (4 + (4 ** (4 - 4)))
6 == (4 + ((4 + 4) / 4))
7 == (4 + (4 - (4 / 4)))
8 == (4 - (4 - (4 + 4)))
9 == (4 + (4 + (4 / 4)))
10 ==
11 ==
12 == (4 * (4 - (4 / 4)))
13 ==
14 ==
15 == ((4 * 4) - (4 / 4))
16 == (4 * (4 ** (4 / 4)))
17 == ((4 / 4) + (4 * 4))
18 ==
19 ==
20 == (4 * (4 + (4 / 4)))
21 ==
22 ==
23 ==
24 == (4 + (4 + (4 * 4)))
25 ==
26 ==
27 ==
28 == ((4 * (4 + 4)) - 4)
29 ==
30 ==
31 ==
32 == ((4 ** 4) / (4 + 4))
33 ==
34 ==
35 ==
36 == (4 + (4 * (4 + 4)))
37 ==
38 ==
39 ==
40 ==
41 ==
42 ==
43 ==
44 ==
45 ==
46 ==
47 ==
48 == (4 * (4 + (4 + 4)))
49 ==
50 ==

Y para los geeks codeadores como yo, este fue el script rubyistico que usé para calcular las cosas:
def template_expressions( leafs = 4 )
  return "4" if leafs == 1
  previous_level = template_expressions( leafs - 1 )
  previous_level.map do
    |expr|
    results = []
    (0...expr.length).each do
      |i|
      if expr[i..i] == "4"
        new_result = expr.clone
        new_result[i] = "(4 o 4)"
        results << new_result
      end
    end
    results
  end.flatten
end

OPERANDS = %W{+ - * / **}

def sample_evaluations( leafs = 4 )
  evaluations = {}
  exprs = template_expressions(leafs)
  exprs.each do
    |expr|
    sample_evaluations0( leafs, expr, evaluations )
  end
  evaluations
end

def sample_evaluations0( leafs, expr, evaluations )
  if leafs == 0
    begin
      evaluations[eval( expr )] = expr
    rescue ZeroDivisionError
    end
    return
  end
  
  OPERANDS.each do
    |op|
    new_expr = expr.sub( /o/, op)
    sample_evaluations0( leafs - 1, new_expr, evaluations )
  end
end

evaluations = sample_evaluations
(0..50).each { |i| puts "#{i} == #{evaluations[i]}" }


Happy hacking,
Aureliano.

como sacar numeros del 0 al 9 con los digitos 4 4 4 4

Hoy estaba mirando la página del Google Analytics del blog y encontré una busqueda muy rara que entró por acá: "como sacar numeros del 1 al 50 con los digitos 4 4 4 4". Estuve pensando un poco que quería decir e inventé que tenía que hacer operaciones aritméticas comunes sobre los 4 4s y obtener los números del 1 al 50.
Me salieron los números del 1 al 9 (y le agregué el cero de yapa). Los anoté en notación "ruby" para que puedan hacer cut&paste en el irb.
0: 4+4-4-4
1: (4+4)/(4+4)
2: (4/4) + (4/4)
3: (4+4+4)/4
4: (4**(4-4))*4
5: (4**(4-4))+4
6: ((4+4) / 4) + 4
7: (4+4) - (4/4)
8: 4+4+4-4
9: (4+4) + (4/4)

Ahora me queda la duda de si se pueden, o no, armar todos los números del 1 al 50 de esta manera. Quizás debería escribir un programita que prueba todas las opciones.

Happy hacking,
Aureliano.

2009-03-14

Gitcasts

Hace un tiempo que estoy jugando con la idea de empezar a usar git como control de versiones. Pero como estoy acostumbrado a años de subversion, aunque entiendo que tiene ventajas, me cuesta mucho hacer el pase. Las cosas que me detienen son que tiene menos soporte en Windows, la integración con eclipse es peor y usamos subversion en el trabajo.
Las cosas que me tientan son que está bueno poder tener versiones sin conectarse a un server, que es una cosa nueva para aprender, que parece estar bien hecho y que hay un montón de gente usándolo y le sirve. La idea de usar un control de versiones distribuido me gusta mucho.
Así que me puse a usarlo en un proyectito que estoy haciendo para mi, y empecé a buscar como usarlo en internet. Y me encontré con unos screencasts que están re-buenos y quiero compartir con ustedes. Les recomiendo especialmente que vean el screencast de los slides de la rails-conf.

Me voy a dormir,
Aureliano.

2009-03-03

¡Soy papá!

Update: pueden ver las fotos de Nico en su blog.

El sábado 28 de febrero (o sea, anteayer) a las 12:39 hs nació Nicolás, mi primogénito. Estoy muy contento y emocionado y quería compartir con ustedes la aventura del nacimiento.

Contracciones


El viernes, cuando llegué del laburo, mi mujer me dijo que estaba con contracciones. Ya había tenido contracciones antes pero no muy seguido. Alrededor de las 9 y media empezamos a medir cuándo empezaban. Y se hacían cada vez más frecuentes. A partir de las 11 (o 12) se estabilizaron cada 10 minutos (aprox). Las contracciones se hacían cada vez más fuertes y a la una y media yo me adelanté y saqué el auto de la cochera, lo puse en la puerta del edificio y subí el bolso con las cosas que teníamos preparado para el parto. También me tomé un café re-cargado para estar despierto cuando hiciera falta llevarla al hospital (en mi cabeza, en una media hora).
Pero me apuré al re-pedo. Las contracciones se estabilizaron en los 10' de frecuencia. Y con contracciones cada 10 minutos, la partera nos dijo que esperemos y que la llamemos cuando lleguen a ser cada 5'. Las contracciones siguieron cada 10' un par de horas más y yo encontré que las risas podían inducir partos. Así que primero estuve leyendole a Agus un montón de chistes malos que encontré x la web; y como no se reía, vimos ZooLander. Mientras tanto seguimos anotando las contracciones. Como había tomado un montón de café, no me podía dormir (aunque Agus me lo pidió). Alrededor de las 5AM (¿o 6?), se fue el efecto del café. Las contracciones seguían cada 10 minutos, pero un poco más dolorosas.
Palmé y me dormí. Me despertó Agus a las 9. Me dijo que las contracciones al fin eran cada 5 minutos. Así que habló con la partera y quedamos encontrarnos en la Suizo.

La Suizo


Llegamos antes de las 10 a la Suizo. Fuimos al tercer piso. Le dimos el carnet de OSDE de mi mujer a la recepcionista y nos encontramos con la partera. En ese momento, ella nos dijo que no había habitaciones y que nos ¡teníamos que ir!. También la revisó y vio que el cuello del útero estaba casi completamente borrado, y que tenía un poquitito de dilatación.
Bajamos y pedí en el Valet Parking que me devuelvan el auto. Evidentemente Nico no nacería ahí.
Después de esperar un rato largo (las calles de la zona están medio enquilombadas porque hicieron Pueyrredón doble mano) trajeron el autito y partimos a la clínica Otamendi, donde la partera hizo una reserva.

Llegando a la Otamendi


Dejé a Agustina e Irma en la puerta de la clínica, y fui a estacionar. El estacionamiento de esta clínica salía ¡$55 x día! y había cola. Así que me fui a buscar otro. Encontré uno a 2 cuadras a $35 x día, que abre las 24hs y estacioné ahí. Fui a la clínica, a admisiones. Yo tenía el carnet de OSDE de Agus así que terminé el trámite y subí a la habitación que le habían asignado.

Preparativos para el parto


Llegué a la habitación 509, que es la habitación que asignaron para que estuviera Agus hasta que liberen una de las de internación. Al toque la partera me dijo que fuera con ella a cambiarme para presenciar el parto.
Acá empezó el primer inconveniente. Llegué a una especie de vestuario en el sexto piso del Otamendi donde se suponía que me tenían que dar la ropa que hace falta para entrar en un quirófano y darme un locker para guardar la mía. En el vestuario lo que encontré es un montón de ambos de cirujía tirados, lockers cerrados, y médicos rapiñando los 3 que quedaban. La partera vio lo mismo en su vestuario y volvimos.
A Agus ya la habían preparado y estaba lista para ser mamá. Después de un ratito (que me pareció una eternidad), me trajeron la ropa para entrar a la sala de partos, me cambié, dejé las cosas en la habitación 509 (la enfermera me dijo que dejaban todo con llave) y fui con la partera al parto.

El parto


Entré a la parte de cirujía, y ya estaba todo encaminado. La partera fue derecho a la sala de partos y a mi me dijeron que espere un rato. Mientras tanto me puse unas cosas para cubrir las zapatillas y otra para el pelo. Esperé un rato, y pedí si podía entrar. La enfermera que estaba en la puerta de la sala de parto me dijo que sí.
Entré y estaba el obstetra de Agus metiéndole una barreta en la cachucha. Por lo que había leído, hacen eso para romper la bolsa así que no me asusté. Rompió la bolsa, empezaron las contracciones fuertes, Agus imploró x la peridural y, después de chequear que el bebé esté bien, decidieron dársela. Ahí me echaron de la sala.
Esperé un rato más y volví a preguntar si podía entrar. De nuevo dijeron que sí. Cuando volví apoyaron la piernas de Agus levantadas a 90 grados. El parto, inminente. Aún con peridural a Agus le dolían muuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuucho las contracciones, así que aprovechando que ya tenía puesto un cateter en la espalda le dieron una segunda dosis. Después de eso hizo los pujos y salió Nicolás. A los 5 segundo de salir empezó a llorar. Cortaron el cordón umbilical y lo apoyaron un segundo sobre Agus.

Chequeos varios


Tal como me dijeron en el curso de preparto, ahí mismo dejé a mi mujer para que haga el alumbramiento (o sea, que expulse la placenta) y me fui siguiendo a mi flamante hijo. En una sala aledaña lo pesaron, midieron, le pusieron las pulseritas identificatorias y lo limpiaron un poco. Cuando terminaron, me lo dieron para que lo alce. Y se lo llevé a Agus, que había terminado de expulsar la placenta. Pusieron el bebé encima de Agus un toque más, terminamos algo de papeleo (incluyendo que me dieron el certificado de nacimiento) y vinieron de Nursery a llevarse al bebé. Yo me fui con el bebé.

El dilema


Todavía seguía en la 509. La hab. 509 es muy pequeña y era incómod estar ahí. Así que cuando la "nurse" me dijo si me llevaba al bebé directamente o dejaba que lo lleven un ratito a la nursery para acicalarlo, dudé un toque y después decidí que lo mejor era que se lo llevaran. Yo me fui a cambiar a la habitación (todavía tenía la ropa del quirófano).

La tarde


Llegué a la habitación, me cambié y al toque apareció Agustina, que me cagó a pedos porque dejé que se lleven a Nicolás a la nursery. Le expliqué que dejé que fuera porque me pareció lo mejor, porque la habitación es chiquita, etc y, por suerte, al toque lo trajeron así que la discusión se desvaneció. Esperamos un ratito y me puse a preguntar cuando nos pasaban a una habitación decente, sin conseguir demasiadas respuestas.
A las 2 de la tarde (más o menos), bajé a Admisiones para que me informen cuándo iba a tener el pase de habitación. No solo no había nadie, tampoco había nadie en la recepción y había gente haciendo cola hacía 45', para que los atiendan. Subí al quinto piso a acompañarla a Agus (Nico estaba frito) y al ratito volví a bajar.
Cuando bajé de nuevo, atendían en recepción así que me quejé de que antes no había nadie y que tuve problemas con la ropa para entrar a la sala de parto. Dejé una nota por escrito en la hoja 188 del libro de quejas. Si pasan por el sanatorio pueden pedirlo para leerla. También hablé con la supervisora de hotelería un rato y me quejé de que la habitación era muy chica, de que no tiene baño, de que no puedo recibir gente (y es un nacimiento), de que no había gente en Admisiones ni en recepción, que no andaba el interno de la hab. 509, etc. O sea, le quemé la cabeza. La mina me prometió que iba a hacer todo lo que pudiera y que iba a tener la habitación ese mismo día, así que volví al quinto piso. Después de un rato, llamó la mina diciendome que ya tenían habitación para nosotros. La 321. La 321 es una habitación de más categoría de lo que cubre mi plan de OSDE, tiene recepción, es grande y me la bonifican. Parece que quejarme sirvió.
Llegaron mi suegra y mi cuñada a la hab. chiquita, porque Agus quería que vinieran. Entró mi suegra a la hab y me quedé afuera hablando con mi cuñada. Después nos avisó la enfermera que nos asignaron la habitación y al ratito vienen a buscar a Nicolás para llevarlo. Me voy con los bolsos y él a la nueva habitación.
La nueva habitación (tal como me dijeron) estaba re-buena. Grande, luminosa, cómoda. Dejo durmiendo a Nico y me pongo a ver tele. Llegan Agus y su parentela están un rato más y se van. La verdad que estabamos muertos. A las 8 le traen la cena a Agus, yo compro un sanguche (que estaba bárbaro) en el restaurant del hotel y me lo traen a la habitación. Cenamos, nos bañamos, y a las 9 palmé.

Segundo día


Día de visitas. A la mañana vienen mis hermanos, mi tía y mis viejos. Se quedaron un rato (después aprenderíamos que demasiado). Al mediodía viene la puericultora y descubrimos que Nico no está tomando la teta bien. Después de debuggear el problema un rato, llega a la conclusión de que Nico no toma porque los pezones de Agus son muy chiquitos y no sabe succionar todavía. Para workarroundear el problema, compramos unas pezoneras, y Nico se prendió. Y la puericultora nos dijo que tengamos a ralla a las visitas para captar las señales "de teta" de Nico.
A la tarde vinieron mi abuela con mi vieja y su primo Gregory, que me cuidaba de chico, y que es como un tío para mi, y que ahora vive en Israel. También vinieron mi cuñado y su hija y una tía de Agustina. A todos les dije que estén poco tiempo porque nos dijo la puericultora.
A la noche la llamamos a mi suegra para coordinar la salida de la clínica. Ella nos ayudó a irnos de la clínica.

Chau clínica


Nos levantamos a la mañana. Llevaron a Nico a hacer unos exámenes (donde le pincharon el talón, pobrecito), fui a hacer el trámite administrativo del alta (o sea, pagar), inicié el trámite del DNI de Nico y nos quedamos esperando a la neonatóloga. Alrededor del 1/2 día llegó mi suegra. A las 12:30hs (o sea, media hora después de la hora del alta) fui a averiguar xq no venía la neonatóloga. Después de como 1 hora de quilombo, exigí que le dieran el almuerzo a Agus; compré 2 sanguches para mi suegra y yo y llegó la neonatóloga, que adujo que no sabía que tenía que darnos el alta. Revisó a Nicolás, lo llevó a la nursery para hacerle el control oftalmológico, terminamos de comer, e hicimos el alta.

La vuelta


Bajamos y fuimos a la puerta de la clínica. Yo agarré el bolso y me fui a buscar el auto. Arranqué el auto y lo llevé hasta la puerta de la clínica. Mientras venía, atrás a la derecha el auto hacía un ruido raro. Cuando andaba más rápido el ruido era más rápido.
Llegué a la clínica, subí con dificultad el asiento de Nico al auto y lo encastré en la base y me di cuenta que la rueda trasera derecha estaba completamente desinflada. Saqué la sillita y Agus y la mamá se fueron en un taxi. Yo abrí el baúl, saqué el tapizado, saqué la rueda de auxilio, saqué el crique y la llave. Aflojé la rueda desinflada, subí el auto con el crique, cambié la rueda y fui solo para casa.
Cuando llegué a casa a las 4 de la tarde, me la encontré a mi suegra cuidando a mi nene y a mi mujer durmiendo, palmada. Y yo al borde de un ataque de nervios. Mi suegra se fue porque tenía que ir a trabajar y me quedé cuidando a Nico que, por suerte, estaba dormido.