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 endLa 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)) endInicialmente, 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)) endCada 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 # ... endLos 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