I’m learning and practicing Ruby writing my AjLisp interpreter, as usual (early this year, I ported it to Javascript). TDD is my friend: write a test, run in red, code, run in green, refactor, and so on. My code, work in progress, is at:
https://github.com/ajlopez/AjLispRb
Initially, I wrote a single file, following the great and simple example:
Notice that Ruby has a ‘test/unit’ package, already in place, ready to use, after installation. After some research, I spitted the file in production code, and test code. I want to build a gem (Ruby package), so I studied the tutorial:
I have pending readings:
http://speakerdeck.com/u/pat/p/cut-polish-a-guide-to-crafting-gems
http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html
My code is not a gem, yet. But it have some gem structure:

The lib contains a single file:
require 'ajlisp/list.rb' require 'ajlisp/named_atom.rb' require 'ajlisp/context.rb' require 'ajlisp/string_source.rb' require 'ajlisp/token.rb' require 'ajlisp/lexer.rb' require 'ajlisp/parser.rb' require 'ajlisp/primitive.rb' require 'ajlisp/primitive_first.rb' require 'ajlisp/primitive_rest.rb' require 'ajlisp/primitive_cons.rb' require 'ajlisp/primitive_list.rb' require 'ajlisp/primitive_closure.rb' require 'ajlisp/fprimitive.rb' require 'ajlisp/fprimitive_quote.rb' require 'ajlisp/fprimitive_lambda.rb' require 'ajlisp/fprimitive_flambda.rb' require 'ajlisp/fprimitive_let.rb' require 'ajlisp/fprimitive_closure.rb' require 'ajlisp/fprimitive_define.rb' require 'ajlisp/primitive_add.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 "let", FPrimitiveLet.instance @context.setValue "define", FPrimitiveDefine.instance @context.setValue "+", PrimitiveAdd.instance def self.context return @context end def self.evaluate(context, item) if item.is_a? List or item.is_a? NamedAtom return item.evaluate(context) end return item end end
I wrote some primitives (normal forms, and special forms: the latter don’t evaluate their parameters before apply, like quote or define). Notice that the additional files are in a subfolder ajlisp inside lib, why? Because when this code is installed as a gem, all lib file code could be include with require(‘thenameofrubyfile’). If I put other files than ajlisp.rb, i.e. list.rb or atom.rb, those file names could be required, and maybe a name collision with other gems could occur. Then, the recommended practice (see other gem code in your Ruby installation folder) is to put additional files below lib folder.
In the test folder there is a top test.rb file that includes other test files:
require 'ajlisp' require 'test/unit' require "test_list.rb" require "test_named_atom.rb" require "test_context.rb" require "test_string_source.rb" require "test_token.rb" require "test_lexer.rb" require "test_parser.rb" require "test_primitive_first.rb" require "test_primitive_rest.rb" require "test_primitive_cons.rb" require "test_primitive_list.rb" require "test_primitive_closure.rb" require "test_primitive_add.rb" require "test_fprimitive_quote.rb" require "test_fprimitive_lambda.rb" require "test_fprimitive_let.rb" require "test_fprimitive_closure.rb" require "test_fprimitive_flambda.rb" require "test_fprimitive_define.rb" require "test_evaluate"
so you can run the tests from command line:
ruby –Ilib;test test\test.rb
If you have Windows, the runtest.cmd file contains this line.
Some test code:
require 'ajlisp' require 'test/unit' class TestList < Test::Unit::TestCase #... def test_create_with_first list = AjLisp::List.new("foo") assert_equal("foo", list.first) assert_nil(list.rest) end def test_create_with_first_and_rest rest = AjLisp::List.new("bar") list = AjLisp::List.new("foo", rest) assert_equal("foo", list.first) assert_not_nil(list.rest) assert_equal("bar", list.rest.first) assert_nil(list.rest.rest) end def test_create_from_array list = AjLisp::List.make [1, "a", "foo"] assert_not_nil list assert_equal 1, list.first assert_equal "a", list.rest.first assert_equal "foo", list.rest.rest.first assert_nil list.rest.rest.rest end #.. end
Every list is an object of this class, list.rb:
module AjLisp class List attr_reader :first attr_reader :rest def initialize(first=nil, rest=nil) @first = first @rest = rest end def evaluate(context) form = AjLisp::evaluate(context, @first) form.evaluate(context, self) end def self.make(array) if array and array.length > 0 first = array.shift if first.is_a? Array first = make(first) elsif first.is_a? Symbol first = NamedAtom.new first.to_s end return List.new first, make(array) end return nil end end end
The accessors first and rest are read-only. Thanks to the untyped nature of Ruby (like in Javascript) the implementation of this interpreter is straightforward, or without much code ceremony, at least.
In my new tests, I included the code into the AjLisp module, so I don’t need to write AjLisp:: prefix before referencing a class:
require 'ajlisp' require 'test/unit' module AjLisp class TestLexer < Test::Unit::TestCase def test_get_atom_token source = StringSource.new "atom" lexer = Lexer.new source token = lexer.nextToken assert_not_nil token assert_equal "atom", token.value assert_equal TokenType::ATOM, token.type assert_nil lexer.nextToken end def test_get_atom_token_with_spaces source = StringSource.new " atom " lexer = Lexer.new source token = lexer.nextToken assert_not_nil token assert_equal "atom", token.value assert_equal TokenType::ATOM, token.type assert_nil lexer.nextToken end #... end
Next topics: some implementation details, primitives vs fprimitives, context (nested environments with name/values), lambda and closures, lexer and parser.
Next steps: complete primitives (let, letrec, definef, do, if…), macro (mlambda, definem, macro expansion…)
Keep tuned!
Angel “Java” Lopez
[...] Previous Post [...]
Pingback by AjLisp in Ruby (2) Context with Name and Values « Angel “Java” Lopez on Blog — December 15, 2011 @ 5:41 pm