Created
April 9, 2010 20:00
-
-
Save matthewd/361526 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 78de83ee04902e003647369c55dd85ff1b58238f Mon Sep 17 00:00:00 2001 | |
From: Matthew Draper <matthew@trebex.net> | |
Date: Sat, 10 Apr 2010 05:29:35 +0930 | |
Subject: [PATCH] Revised 'add a builtin class' howto | |
--- | |
doc/howto/add_a_builtin_class.txt | 213 ++++++++++++++++++++++--------------- | |
1 files changed, 128 insertions(+), 85 deletions(-) | |
diff --git a/doc/howto/add_a_builtin_class.txt b/doc/howto/add_a_builtin_class.txt | |
index 329d68c..f1ddecf 100644 | |
--- a/doc/howto/add_a_builtin_class.txt | |
+++ b/doc/howto/add_a_builtin_class.txt | |
@@ -1,17 +1,16 @@ | |
# Howto - Add a Builtin Class to the VM | |
-TODO: Imported from Lighthouse wiki. Needs review and update. | |
+TODO: Imported from Lighthouse wiki. Updated; needs review. | |
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`. | |
+The following lines of code were originally taken from commit 9488e4ac00. They | |
+have since been modified to reflect changes to this procedure. | |
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. | |
+named like `builtin/someclass.[hc]pp`. In this case, we added | |
+`builtin/dir.hpp` and `builtin/dir.cpp`. | |
All the header files are guarded with preprocessor directives to prevent | |
multiple inclusion. The format of the constant is shown below. | |
@@ -19,150 +18,194 @@ 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 C++ code for the VM uses the namespace `rubinius`. The `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). | |
+that follows. In this case, there is one field, `path_`. The `path_` field | |
+will contain a `String` instance that carries the path name for the open | |
+directory, which is then made accessible via attr_accessor. | |
+ | |
+This class also contains an internal DIR* (directory stream) for the open | |
+directory. This pointer will not be accessible from normal Ruby code. | |
namespace rubinius { | |
class Dir : public Object { | |
public: | |
- const static size_t fields = 1; | |
- const static object_type type = DirType; | |
+ const static object_type type = DirType; | |
- MemoryPointer* data; // slot | |
+ private: | |
+ DIR *os_; | |
+ String *path_; // 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) | |
- }; | |
+ attr_accessor(path, String); | |
+ | |
+Following the field declarations, there are prototypes for the `Dir` methods. | |
+Several of these are annotated as primitives. The only method whose name is | |
+significant is the `init` static method, which is responsible for defining the | |
+class and object type. | |
+ | |
+ static void init(STATE); | |
+ | |
+ static Dir* create(STATE); | |
+ | |
+ static void finalize(STATE, Dir* dir); | |
+ | |
+ // Ruby.primitive :dir_allocate | |
+ static Dir* allocate(STATE, Object* self); | |
+ | |
+ // 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. | |
+The rest of the implementation for `Dir` is in `builtin/dir.cpp`. | |
- #include "builtin.hpp" | |
- #include "builtin_dir.hpp" | |
+Note that `init` defines the `Dir` class within the VM, and registers its | |
+object_type. `create` and `allocate` (the latter a primitive) deal with | |
+creation of object instances, along with registering the finalizer for each | |
+object as it's created. | |
+ | |
+ #include "builtin/dir.hpp" | |
#include "ffi.hpp" | |
+ // [..] | |
#include <sys/types.h> | |
#include <dirent.h> | |
namespace rubinius { | |
+ void Dir::init(STATE) { | |
+ GO(dir).set(state->new_class("Dir", G(object))); | |
+ G(dir)->set_object_type(state, DirType); | |
+ } | |
+ | |
Dir* Dir::create(STATE) { | |
- Dir* d = (Dir*)state->om->new_object(G(dir), Dir::fields); | |
- SET(d, data, Qnil); | |
+ Dir* d = state->new_object<Dir>(G(dir)); | |
+ d->os_ = 0; | |
+ | |
+ state->om->needs_finalization(d, (FinalizerFunction)&Dir::finalize); | |
return d; | |
} | |
+ Dir* Dir::allocate(STATE, Object* self) { | |
+ Dir* dir = create(state); | |
+ | |
+ if(Class* cls = try_as<Class>(self)) { | |
+ dir->klass(state, cls); | |
+ } | |
+ | |
+ return dir; | |
+ } | |
+ | |
+ void Dir::finalize(STATE, Dir* dir) { | |
+ if(dir->os_) { | |
+ closedir(dir->os_); | |
+ dir->os_ = 0; | |
+ } | |
+ } | |
+ | |
+ | |
void Dir::guard(STATE) { | |
- // TODO: raise IOError, "closed directory" | |
- if(data->nil_p()) { | |
- throw std::runtime_error("dir->data is nil"); | |
+ if(!os_) { | |
+ Exception::io_error(state, "closed directory"); | |
} | |
} | |
- OBJECT Dir::open(STATE, String* path) { | |
- DIR* d = opendir(path->byte_address(state)); | |
+ Object* Dir::open(STATE, String* path) { | |
+ if(os_) closedir(os_); | |
- if(!d) state->raise_from_errno("Unable to open directory"); | |
- SET(this, data, MemoryPointer::create(state, d)); | |
+ os_ = opendir(path->c_str()); | |
- return Qnil; | |
+ if(!os_) { | |
+ Exception::errno_error(state, "Unable to open directory", errno, path->c_str()); | |
+ return 0; | |
+ } | |
+ | |
+ this->path(state, path); | |
+ | |
+ return Qtrue; | |
} | |
- OBJECT Dir::close(STATE) { | |
+ Object* Dir::close(STATE) { | |
guard(state); | |
- DIR* d = (DIR*)data->pointer; | |
- if(d) { | |
- SET(this, data, Qnil); | |
- closedir(d); | |
- return Qtrue; | |
+ if(os_) { | |
+ closedir(os_); | |
+ os_ = 0; | |
} | |
- return Qfalse; | |
+ return Qnil; | |
} | |
- OBJECT Dir::closed_p(STATE) { | |
- return data->nil_p() ? Qtrue : Qfalse; | |
+ Object* Dir::closed_p(STATE) { | |
+ return os_ ? Qfalse : Qtrue; | |
} | |
- OBJECT Dir::read(STATE) { | |
+ Object* Dir::read(STATE) { | |
guard(state); | |
- DIR* d = (DIR*)data->pointer; | |
- struct dirent *ent = readdir(d); | |
+ struct dirent *ent = readdir(os_); | |
if(!ent) return Qnil; | |
return String::create(state, ent->d_name); | |
} | |
- OBJECT Dir::control(STATE, FIXNUM kind, INTEGER pos) { | |
+ Object* Dir::control(STATE, Fixnum* kind, Integer* pos) { | |
guard(state); | |
+ switch(kind->to_native()) { | |
+ case 0: | |
+ seekdir(os_, pos->to_native()); | |
+ return Qtrue; | |
+ case 1: | |
+ rewinddir(os_); | |
+ return Qtrue; | |
+ case 2: | |
+ return Integer::from(state, telldir(os_)); | |
+ } | |
+ return Qnil; | |
} | |
} | |
-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). | |
+In `globals.hpp` we define a global GC root to hold the class, so it doesn't | |
+get GCed. | |
147 TypedRoot<Class*> dir; | |
... | |
157 dir(&roots) | |
-In `objects.cpp` we bootstrap the `Dir` class. | |
+In `rakelib/vm.rake` we add our new header file. | |
- 181 GO(dir).set(new_class(object, Dir::fields)); | |
- 182 G(dir)->instance_type = Object::i2n(DirType); | |
- ... | |
- 191 G(dir)->setup(this, "Dir"); | |
+ 101 vm/builtin/dir.hpp | |
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 "vm/test/test.hpp" | |
+ #include "builtin/dir.hpp" | |
#include <cstdio> | |
#include <sys/stat.h> | |
-- | |
1.7.0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment