Anterior Post
Una de las primeras clases que implemente en AjLispRb es el ambiente (“environment”). Esta vez lo llamé contexto: un diccionaro donde guardar pares nombre/valor, los valores de los átomos con nombre. El código:
module AjLisp
class Context
def initialize(parent = nil)
@parent = parent
@values = Hash.new
end
def getValue(name)
if @values.has_key?(name)
return @values[name]
end
if @parent != nil
return @parent.getValue(name)
end
return nil
end
def setValue(name, value)
@values[name] = value
end
end
end
La clase fue codificada usando TDD (Test-Driven Development), los primeros tests en test/test_context.rb. Algunos tests:
def test_not_defined_is_nil
context = AjLisp::Context.new
assert_nil(context.getValue(:foo))
end
def test_set_and_get_value
context = AjLisp::Context.new
context.setValue(:foo, "bar")
assert_equal("bar", context.getValue(:foo))
end
def test_get_value_from_parent
parent = AjLisp::Context.new
parent.setValue(:foo, "bar")
context = AjLisp::Context.new(parent)
assert_equal("bar", context.getValue(:foo))
end
Inicialmente, yo usaba strings para los nombre, pero ahora estoy usando los símbolos de Ruby como :foo. Tengo un test que asegura la independencia de los valores del contexto padre y del hijo:
def test_override_value_from_parent
parent = AjLisp::Context.new
parent.setValue(:foo, "bar")
context = AjLisp::Context.new(parent)
context.setValue(:foo, "bar2")
assert_equal("bar2", context.getValue(:foo))
assert_equal("bar", parent.getValue(:foo))
end
Cada contexto puede tener un contexto padre. Hay un contexto “tope”, definido lib/ajlisp.rb:
module AjLisp
@context = Context.new
@context.setValue :quote, FPrimitiveQuote.instance
@context.setValue :first, PrimitiveFirst.instance
@context.setValue :rest, PrimitiveRest.instance
@context.setValue :cons, PrimitiveCons.instance
@context.setValue :list, PrimitiveList.instance
@context.setValue :lambda, FPrimitiveLambda.instance
@context.setValue :flambda, FPrimitiveFLambda.instance
@context.setValue :mlambda, FPrimitiveMLambda.instance
@context.setValue :let, FPrimitiveLet.instance
@context.setValue :define, FPrimitiveDefine.instance
@context.setValue :do, FPrimitiveDo.instance
@context.setValue :if, FPrimitiveIf.instance
@context.setValue :definef, FPrimitiveDefinef.instance
@context.setValue :definem, FPrimitiveDefinem.instance
@context.setValue :+, PrimitiveAdd.instance
@context.setValue :-, PrimitiveSubtract.instance
@context.setValue :*, PrimitiveMultiply.instance
@context.setValue :/, PrimitiveDivide.instance
def self.context
return @context
end
# ...
end
Los nuevos contexts son creado por varias primitivas. En el comienzo, existe el contexto tope definido arriba, como:
Si tenemos que definir una función que retorna el segundo elemento de una lista, podemos usar la primitiva define:
(define second (a) (first (rest a)))
ahora tenemos en el contexto tope una nueva entrada para second:
El contexto tope tiene ahí en esa nueva entrada el valor que representa un lambda (lambda (a) (first (rest a)) (en realidad, debería aclarar que se guarda el resultado de haber evaluado el lambda, que es un closure (clausura) pero ya llegaremos al tema en próximo post).
Cuando invocamos:
(second (quote (one two three)))
un nuevo contexto se crea, con su padre apuntando al contexto tope (de nuevo, es algo más complejo, veremos clausuras). Ese nuevo contexto tiene un par nombre/valor:
Este contexto es descartado LUEGO de esta evaluaciónThat de second (puede sobrevivir indirectamente si luego de la evaluación quedó referenciado por una clausura). Cuando definimos un valor simple:
(define one 1)
el contexto tope es modificado, para tener un nuevo par nombre/valor:
Próximos temas: primitivas y formas especiales, su invocación, clausuras, macros, e invocación de métodos nativos de Ruby.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez