# Howto - Add a Builtin Class to the VM

TODO: Imported from Lighthouse wiki. Needs review and update.

The following documents adding the C++ class `Dir` to the VM. This class
provides primitives and other functionality used by the Ruby `Dir` class.

The following lines of code are taken from this commit: 9488e4ac00. The line
numbers (where shown) should be accurate for `git show 9488e4ac00`.

All the C++ classes that correspond to Ruby classes are currently in files
named like `builtin_someclass.[hc]pp`. In this case, we added
`builtin_dir.hpp` and `builtin_dir.cpp`. This may be restructured into a
subdirectory like `builtin/dir.cpp` so check the current source.

All the header files are guarded with preprocessor directives to prevent
multiple inclusion. The format of the constant is shown below.

    #ifndef RBX_BUILTIN_DIR_HPP
    #define RBX_BUILTIN_DIR_HPP

The C++ code for the VM uses the namespace `rubinius`. The variable `fields`
identifies the number of fields in this object following the header, and
`object_type` is a unique identifier for this Ruby class.

The fields for this class are identified by the <code>// slot</code> comment
that follows. In this case, there is one field, `data`. The `data` field will
contain a `MemoryPointer` instance that carries the <code>DIR*</code>
(directory stream) for the open directory. `data` will be accessible from
normal Ruby code as an instance of `MemoryPointer` (or `nil` if the directory
is not open).

    namespace rubinius {
      class Dir : public Object {
        public:
        const static size_t fields = 1;
        const static object_type type = DirType;

        MemoryPointer* data; // slot

Following the field declarations, there are prototypes for the `Dir` methods.
Several of these are annotated as primitives.

        static Dir* create(STATE);
        // Ruby.primitive :dir_open
        OBJECT open(STATE, String *path);
        // Ruby.primitive :dir_close
        OBJECT close(STATE);
        // Ruby.primitive :dir_closed_p
        OBJECT closed_p(STATE);
        // Ruby.primitive :dir_read
        OBJECT read(STATE);
        // Ruby.primitive :dir_control
        OBJECT control(STATE, FIXNUM kind, INTEGER pos);
        void guard(STATE);

        class Info : public TypeInfo {
        public:
          BASIC_TYPEINFO(TypeInfo)
        };
      };
    };

    #endif

The rest of the (not yet complete) implementation for `Dir` is in
`builtin_dir.cpp`. Note that in the <code>Dir::create</code> sets the `data`
field to `Qnil` (nil in Ruby code). The <code>Dir::open</code> method sets
`data` to a new `MemoryPointer` instance. <code>Dir::read</code>,
<code>Dir::control</code>, and <code>Dir::close</code> reference the `DIR*`
stored in the `data` field.

    #include "builtin.hpp"
    #include "builtin_dir.hpp"
    #include "ffi.hpp"

    #include <sys/types.h>
    #include <dirent.h>

    namespace rubinius {
      Dir* Dir::create(STATE) {
        Dir* d = (Dir*)state->om->new_object(G(dir), Dir::fields);
        SET(d, data, Qnil);

        return d;
      }

      void Dir::guard(STATE) {
        // TODO: raise IOError, "closed directory"
        if(data->nil_p()) {
          throw std::runtime_error("dir->data is nil");
        }
      }

      OBJECT Dir::open(STATE, String* path) {
        DIR* d = opendir(path->byte_address(state));

        if(!d) state->raise_from_errno("Unable to open directory");
        SET(this, data, MemoryPointer::create(state, d));

        return Qnil;
      }

      OBJECT Dir::close(STATE) {
        guard(state);

        DIR* d = (DIR*)data->pointer;
        if(d) {
          SET(this, data, Qnil);
          closedir(d);
          return Qtrue;
        }

        return Qfalse;
      }

      OBJECT Dir::closed_p(STATE) {
        return data->nil_p() ? Qtrue : Qfalse;
      }

      OBJECT Dir::read(STATE) {
        guard(state);

        DIR* d = (DIR*)data->pointer;
        struct dirent *ent = readdir(d);

        if(!ent) return Qnil;

        return String::create(state, ent->d_name);
      }

      OBJECT Dir::control(STATE, FIXNUM kind, INTEGER pos) {
        guard(state);

      }
    }

In `object_types.hpp` we add our `DirType` to the types enumeration.

    169    DirType

In `builtin/object.cpp` we add an entry to convert `DirType` into a text
representation "Dir".

    124    case DirType:
    125      type = "Dir";
    126      break;

In `globals.hpp` we appease the GC gods (TODO: explain this).

    147    TypedRoot<Class*> dir;
    ...
    157      dir(&roots)

In `objects.cpp` we bootstrap the `Dir` class.

    181    GO(dir).set(new_class(object, Dir::fields));
    182    G(dir)->instance_type = Object::i2n(DirType);
    ...
    191    G(dir)->setup(this, "Dir");

One of the great things about the new C++ VM is that there are tests. We added
tests for `Dir` in <code>test/test_dir.hpp</code>.

    #include "builtin_dir.hpp"
    #include <cstdio>
    #include <sys/stat.h>

    #include <cxxtest/TestSuite.h>

    using namespace rubinius;

    class TestDir : public CxxTest::TestSuite {
      public:

      VM *state;
      Dir *d;
      void setUp() {
        state = new VM(1024);
        d = Dir::create(state);
      }

      void tearDown() {
        if(!d->closed_p(state)->true_p()) d->close(state);
        delete state;
      }

      void test_create() {
        TS_ASSERT_EQUALS(d->obj_type, DirType);
        TS_ASSERT(d->data->nil_p());
      }

      void test_open() {
        String* path = String::create(state, ".");
        TS_ASSERT_EQUALS(d->open(state, path), Qnil);
        TS_ASSERT(!d->data->nil_p());
      }

      void test_open_raises_exception() {
        String* path = String::create(state, "nonexistent");
        // TODO: change to TS_ASSERT_RAISES(d->open(state, path), {Ruby IOError});
      }

      void test_close_raises_exception_if_closed() {
        // TODO: TS_ASSERT_RAISES(d->close(state), {Ruby IOError});
      }

      void test_close() {
        String* path = String::create(state, ".");
        d->open(state, path);
        TS_ASSERT_EQUALS(d->close(state), Qtrue);
        TS_ASSERT(d->data->nil_p());
      }

      void test_closed_p() {
        TS_ASSERT_EQUALS(d->closed_p(state), Qtrue);
        String* path = String::create(state, ".");
        d->open(state, path);
        TS_ASSERT_EQUALS(d->closed_p(state), Qfalse);
      }

      char* make_directory() {
        char *dir = tmpnam(NULL);
        mkdir(dir, S_IRWXU);
        return dir;
      }

      void remove_directory(const char *dir) {
        rmdir(dir);
      }

      void test_read() {
        char *dir = make_directory();
        String* path = String::create(state, dir);
        d->open(state, path);
        String* name = (String*)d->read(state);
        TS_ASSERT_EQUALS(name->byte_address(state)[0], '.');
        remove_directory(dir);
      }

      void test_read_returns_nil_when_no_more_entries() {
        char *dir = make_directory();
        String* path = String::create(state, dir);
        d->open(state, path);
        d->read(state);
        d->read(state);
        TS_ASSERT(d->read(state)->nil_p());
        remove_directory(dir);
      }
    };
