rbplusplus 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +78 -0
- data/TODO +25 -0
- data/lib/inflections.rb +53 -0
- data/lib/inflector.rb +285 -0
- data/lib/jamis.rb +589 -0
- data/lib/rbplusplus.rb +31 -0
- data/lib/rbplusplus/builders/base.rb +116 -0
- data/lib/rbplusplus/builders/class.rb +62 -0
- data/lib/rbplusplus/builders/extension.rb +48 -0
- data/lib/rbplusplus/builders/module.rb +67 -0
- data/lib/rbplusplus/extension.rb +183 -0
- data/lib/rbplusplus/module.rb +56 -0
- data/lib/rbplusplus/rbplusplus.rb +3 -0
- data/lib/rbplusplus/writers/base.rb +25 -0
- data/lib/rbplusplus/writers/extension.rb +37 -0
- data/lib/rbplusplus/writers/multiple_files_writer.rb +84 -0
- data/lib/rbplusplus/writers/single_file_writer.rb +37 -0
- data/test/classes_test.rb +67 -0
- data/test/compiling_test.rb +85 -0
- data/test/extension_test.rb +38 -0
- data/test/file_writers_test.rb +68 -0
- data/test/functions_test.rb +26 -0
- data/test/headers/Adder.h +26 -0
- data/test/headers/Subtracter.hpp +20 -0
- data/test/headers/empty.h +3 -0
- data/test/headers/functions.h +19 -0
- data/test/headers/include/helper.h +8 -0
- data/test/headers/nested_classes.h +20 -0
- data/test/headers/with_includes.h +14 -0
- data/test/modules_test.rb +67 -0
- data/test/test_helper.rb +19 -0
- metadata +91 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
# Class representation of a ruby Module to be exposed in the extension.
|
3
|
+
# A Module acts much in the same way as Extension in that it can contain
|
4
|
+
# classes, functions, enumerations, etc.
|
5
|
+
class RbModule
|
6
|
+
|
7
|
+
# Modules can be nested
|
8
|
+
attr_accessor :modules
|
9
|
+
|
10
|
+
# Modules have a name
|
11
|
+
attr_accessor :name
|
12
|
+
|
13
|
+
# Access to the underlying RbGCCXML parser
|
14
|
+
attr_reader :node
|
15
|
+
|
16
|
+
# Registers a new module definition for this extension.
|
17
|
+
# Use Extension#module or RbModule#module instead
|
18
|
+
# of creating an instance of this class directly
|
19
|
+
#
|
20
|
+
# The block parameter is optional, you can also
|
21
|
+
# grab the reference of the Module and work with
|
22
|
+
# it as you want:
|
23
|
+
#
|
24
|
+
# module "Name" do |m|
|
25
|
+
# ...
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# or
|
29
|
+
#
|
30
|
+
# m = module "Name"
|
31
|
+
# ...
|
32
|
+
#
|
33
|
+
# Unlike Extension#new, no special processing is done
|
34
|
+
# in the block version, it's just there for convenience.
|
35
|
+
def initialize(name, parser, &block)
|
36
|
+
@name = name
|
37
|
+
@parser = parser
|
38
|
+
@modules = []
|
39
|
+
|
40
|
+
block.call(self) if block
|
41
|
+
end
|
42
|
+
|
43
|
+
# Specify a C++ namespace from which the contained code will
|
44
|
+
# be wrapped and exposed to Ruby under this Module.
|
45
|
+
def namespace(name)
|
46
|
+
@node = @parser.namespaces(name)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Register another module to be defined inside of
|
50
|
+
# this module. Acts the same as Extension#module.
|
51
|
+
def module(name, &block)
|
52
|
+
@modules << RbModule.new(name, @parser, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Writers
|
3
|
+
|
4
|
+
# Base class for all source code writers.
|
5
|
+
class Base
|
6
|
+
|
7
|
+
attr_reader :builder, :working_dir
|
8
|
+
|
9
|
+
# Writers all take a builder from which to write out
|
10
|
+
# the source code
|
11
|
+
def initialize(builder, working_dir)
|
12
|
+
@builder = builder
|
13
|
+
@working_dir = working_dir
|
14
|
+
end
|
15
|
+
|
16
|
+
# Write out the code. Must be implemented in a
|
17
|
+
# subclass
|
18
|
+
def write
|
19
|
+
raise "Writers must implement #write"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Writers
|
3
|
+
|
4
|
+
# Writes out the code for building the extension.
|
5
|
+
# This writer takes care of building the extconf.rb
|
6
|
+
# file with the appropriate options.
|
7
|
+
class ExtensionWriter < Base
|
8
|
+
|
9
|
+
# List of -I directives
|
10
|
+
attr_accessor :includes
|
11
|
+
|
12
|
+
# List of -L directives
|
13
|
+
attr_accessor :library_paths
|
14
|
+
|
15
|
+
# List of -l directives
|
16
|
+
attr_accessor :libraries
|
17
|
+
|
18
|
+
def write
|
19
|
+
extconf = File.join(working_dir, "extconf.rb")
|
20
|
+
|
21
|
+
@includes ||= []
|
22
|
+
|
23
|
+
inc_str = @includes.flatten.uniq.map {|i| "-I#{i}"}.join(" ")
|
24
|
+
lib_path_str = @library_paths.flatten.uniq.map {|i| "-L#{i}"}.join(" ")
|
25
|
+
lib_str = @libraries.flatten.uniq.map {|i| "-l#{i}"}.join(" ")
|
26
|
+
|
27
|
+
File.open(extconf, "w+") do |file|
|
28
|
+
file.puts "require \"rubygems\""
|
29
|
+
file.puts "require \"mkmf-rice\""
|
30
|
+
file.puts %Q($CPPFLAGS = $CPPFLAGS + " -I#{working_dir} #{inc_str} #{lib_path_str} #{lib_str}")
|
31
|
+
file.puts "create_makefile(\"#{builder.name}\")"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Writers
|
3
|
+
# Writer that takes a builder and writes out the code in
|
4
|
+
# multiple files.
|
5
|
+
class MultipleFilesWriter < Base
|
6
|
+
|
7
|
+
def write
|
8
|
+
_write_node(builder)
|
9
|
+
end
|
10
|
+
|
11
|
+
# How this works:
|
12
|
+
#
|
13
|
+
# We'll recurse through the builder heirarchy, starting at the bottom.
|
14
|
+
# This lets us to properly link up each file so that all classes / modules /
|
15
|
+
# functions get properly exposed.
|
16
|
+
def _write_node(node)
|
17
|
+
node.builders.each do |b|
|
18
|
+
_write_node(b)
|
19
|
+
end
|
20
|
+
|
21
|
+
filename = if node.parent
|
22
|
+
node.qualified_name.gsub(/::/, "_")
|
23
|
+
else
|
24
|
+
node.name
|
25
|
+
end
|
26
|
+
|
27
|
+
cpp_file = File.join(working_dir, "#{filename}.rb.cpp")
|
28
|
+
|
29
|
+
if node.parent
|
30
|
+
hpp_file = File.join(working_dir, "#{filename}.rb.hpp")
|
31
|
+
hpp_include = "#include \"#{hpp_file}\""
|
32
|
+
register_func = "register_#{filename}"
|
33
|
+
|
34
|
+
include_guard = "__RICE_GENERATED_#{filename}_HPP__"
|
35
|
+
|
36
|
+
register_func_arg = ""
|
37
|
+
register_func_prototype = ""
|
38
|
+
|
39
|
+
if !node.parent.is_a?(Builders::ExtensionBuilder)
|
40
|
+
register_func_arg = node.parent.rice_variable
|
41
|
+
register_func_prototype = "#{node.parent.rice_variable_type} #{register_func_arg}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Changes we need to make to the parent for everything to work across multiple
|
45
|
+
# files
|
46
|
+
#
|
47
|
+
# * Add an include to the hpp file
|
48
|
+
# * Add a call to the register method
|
49
|
+
node.parent.includes << hpp_include
|
50
|
+
|
51
|
+
node.parent.body << "#{register_func}(#{register_func_arg});"
|
52
|
+
|
53
|
+
# Modifications to this current node's code:
|
54
|
+
#
|
55
|
+
# * Add a register prototype to the header file
|
56
|
+
# * Set include in node to the header file
|
57
|
+
# * Wrap the body in a register method
|
58
|
+
|
59
|
+
File.open(hpp_file, "w+") do |hpp|
|
60
|
+
hpp.puts "#ifndef #{include_guard}"
|
61
|
+
hpp.puts "#define #{include_guard}"
|
62
|
+
hpp.puts ""
|
63
|
+
hpp.puts "void #{register_func}(#{register_func_prototype});"
|
64
|
+
hpp.puts "#endif"
|
65
|
+
end
|
66
|
+
|
67
|
+
node.includes << hpp_include
|
68
|
+
|
69
|
+
node.body = [
|
70
|
+
"void #{register_func}(#{register_func_prototype}) {",
|
71
|
+
node.body,
|
72
|
+
"}"
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
File.open(cpp_file, "w+") do |cpp|
|
77
|
+
cpp.puts node.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Writers
|
3
|
+
# Writer that takes a builder and writes out the code in
|
4
|
+
# one single file
|
5
|
+
class SingleFileWriter < Base
|
6
|
+
|
7
|
+
def write
|
8
|
+
process_code(builder)
|
9
|
+
|
10
|
+
filename = builder.name
|
11
|
+
cpp_file = File.join(working_dir, "#{filename}.rb.cpp")
|
12
|
+
|
13
|
+
File.open(cpp_file, "w+") do |cpp|
|
14
|
+
cpp.puts builder.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# What we do here is to go through the builder heirarchy
|
21
|
+
# and push all the code from children up to the parent,
|
22
|
+
# ending up with all the code in the top-level builder
|
23
|
+
def process_code(builder)
|
24
|
+
builder.builders.each do |b|
|
25
|
+
process_code(b)
|
26
|
+
end
|
27
|
+
|
28
|
+
return unless builder.parent
|
29
|
+
|
30
|
+
builder.parent.includes << builder.includes
|
31
|
+
builder.parent.body << builder.body
|
32
|
+
builder.parent.declarations << builder.declarations
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
context "Extension with wrapped classes" do
|
4
|
+
|
5
|
+
def setup
|
6
|
+
if !defined?(@@adder_built)
|
7
|
+
super
|
8
|
+
@@adder_built = true
|
9
|
+
Extension.new "adder" do |e|
|
10
|
+
e.sources full_dir("headers/Adder.h")
|
11
|
+
e.namespace "classes"
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'adder'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "should make classes available as Ruby runtime constants" do
|
19
|
+
assert defined?(Adder), "Adder isn't defined"
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "should make wrapped classes constructable" do
|
23
|
+
a = Adder.new
|
24
|
+
a.should.not.be.nil
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "should make functions of the class available" do
|
28
|
+
# Wrapped method names default to underscore'd
|
29
|
+
adder = Adder.new
|
30
|
+
adder.add_integers(1,2).should == 3
|
31
|
+
adder.add_floats(1.0, 2.0).should.be.close(3.0, 0.001)
|
32
|
+
adder.add_strings("Hello", "World").should == "HelloWorld"
|
33
|
+
adder.get_class_name.should == "Adder"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Explicit self
|
37
|
+
specify "should properly wrap static methods as class methods" do
|
38
|
+
Adder.do_adding(1, 2, 3, 4, 5).should == 15
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
context "Wrapping Classes within classes" do
|
44
|
+
|
45
|
+
def setup
|
46
|
+
if !defined?(@@nested_built)
|
47
|
+
super
|
48
|
+
@@nested_built = true
|
49
|
+
Extension.new "nested" do |e|
|
50
|
+
e.sources full_dir("headers/nested_classes.h")
|
51
|
+
e.namespace "classes"
|
52
|
+
end
|
53
|
+
|
54
|
+
require 'nested'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
specify "should properly make nested classes available" do
|
59
|
+
assert defined?(TestClass)
|
60
|
+
assert defined?(TestClass::InnerClass)
|
61
|
+
assert defined?(TestClass::InnerClass::Inner2)
|
62
|
+
|
63
|
+
TestClass.new.should.not.be.nil
|
64
|
+
TestClass::InnerClass.new.should.not.be.nil
|
65
|
+
TestClass::InnerClass::Inner2.new.should.not.be.nil
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
context "Compiler settings" do
|
4
|
+
|
5
|
+
specify "should be able to specify include paths" do
|
6
|
+
Extension.new "compiler" do |e|
|
7
|
+
e.sources full_dir("headers/with_includes.h"),
|
8
|
+
:include_paths => full_dir("headers/include")
|
9
|
+
e.namespace "code"
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'compiler'
|
13
|
+
|
14
|
+
assert defined?(func)
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "should be able to specify library paths" do
|
18
|
+
# Single path
|
19
|
+
e = Extension.new "libs_test"
|
20
|
+
e.working_dir = full_dir("generated")
|
21
|
+
e.sources full_dir("headers/empty.h"),
|
22
|
+
:library_paths => "/usr/lib/testing/123"
|
23
|
+
e.build
|
24
|
+
e.write
|
25
|
+
|
26
|
+
ext_file = full_dir("generated/extconf.rb")
|
27
|
+
|
28
|
+
contents = File.read(ext_file)
|
29
|
+
|
30
|
+
contents.should.match(%r(-L/usr/lib/testing/123))
|
31
|
+
|
32
|
+
# Clean up
|
33
|
+
`rm -rf #{full_dir('generated')}/*`
|
34
|
+
|
35
|
+
# Array of paths
|
36
|
+
e = Extension.new "libs_test"
|
37
|
+
e.working_dir = full_dir("generated")
|
38
|
+
e.sources full_dir("headers/empty.h"),
|
39
|
+
:library_paths => ["/usr/lib/testing/456", "/var/lib/stuff"]
|
40
|
+
e.build
|
41
|
+
e.write
|
42
|
+
|
43
|
+
ext_file = full_dir("generated/extconf.rb")
|
44
|
+
|
45
|
+
contents = File.read(ext_file)
|
46
|
+
|
47
|
+
contents.should.match(%r(-L/usr/lib/testing/456))
|
48
|
+
contents.should.match(%r(-L/var/lib/stuff))
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "should be able to link to external libraries" do
|
52
|
+
# Single library
|
53
|
+
e = Extension.new "libs_test"
|
54
|
+
e.working_dir = full_dir("generated")
|
55
|
+
e.sources full_dir("headers/empty.h"),
|
56
|
+
:libraries => "lib123"
|
57
|
+
e.build
|
58
|
+
e.write
|
59
|
+
|
60
|
+
ext_file = full_dir("generated/extconf.rb")
|
61
|
+
|
62
|
+
contents = File.read(ext_file)
|
63
|
+
|
64
|
+
contents.should.match(%r(-llib123))
|
65
|
+
|
66
|
+
# Clean up
|
67
|
+
`rm -rf #{full_dir('generated')}/*`
|
68
|
+
|
69
|
+
# Array of libraries
|
70
|
+
e = Extension.new "libs_test"
|
71
|
+
e.working_dir = full_dir("generated")
|
72
|
+
e.sources full_dir("headers/empty.h"),
|
73
|
+
:libraries => ["ponzor", "wonko", "prankit"]
|
74
|
+
e.build
|
75
|
+
e.write
|
76
|
+
|
77
|
+
ext_file = full_dir("generated/extconf.rb")
|
78
|
+
|
79
|
+
contents = File.read(ext_file)
|
80
|
+
|
81
|
+
contents.should.match(%r(-lponzor))
|
82
|
+
contents.should.match(%r(-lwonko))
|
83
|
+
contents.should.match(%r(-lprankit))
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
context "Ruby Extension creation" do
|
4
|
+
|
5
|
+
specify "should create a valid Ruby extension" do
|
6
|
+
Extension.new "ext_test" do |e|
|
7
|
+
e.sources full_dir("headers/empty.h")
|
8
|
+
end
|
9
|
+
|
10
|
+
should.not.raise LoadError do
|
11
|
+
require("ext_test")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
specify "should create a valid Ruby extension without a block" do
|
16
|
+
e = Extension.new "extension"
|
17
|
+
e.sources full_dir("headers/empty.h")
|
18
|
+
e.working_dir = File.join(File.expand_path(File.dirname(__FILE__)), "generated")
|
19
|
+
e.build
|
20
|
+
e.write
|
21
|
+
e.compile
|
22
|
+
|
23
|
+
should.not.raise LoadError do
|
24
|
+
require("ext_test")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "should properly build working dir as deep as needed" do
|
29
|
+
should.not.raise Errno::ENOENT do
|
30
|
+
Extension.new "extension" do |e|
|
31
|
+
e.sources full_dir("headers/empty.h")
|
32
|
+
e.working_dir = File.join(File.expand_path(File.dirname(__FILE__)),
|
33
|
+
"generated", "path1", "path2")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
context "Multiple file writer (default)" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@working_dir = File.expand_path(File.dirname(__FILE__) + "/generated")
|
7
|
+
|
8
|
+
e = Extension.new "adder"
|
9
|
+
e.working_dir = @working_dir
|
10
|
+
e.sources full_dir("headers/Adder.h")
|
11
|
+
|
12
|
+
e.module "Mod" do |m|
|
13
|
+
m.namespace "classes"
|
14
|
+
end
|
15
|
+
|
16
|
+
e.build
|
17
|
+
e.write
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "should properly split up code into multiple files" do
|
21
|
+
files = Dir["#{@working_dir}/*"]
|
22
|
+
files.size.should == 6
|
23
|
+
|
24
|
+
%w(
|
25
|
+
extconf.rb
|
26
|
+
Mod.rb.cpp
|
27
|
+
Mod.rb.hpp
|
28
|
+
classes_Adder.rb.cpp
|
29
|
+
classes_Adder.rb.hpp
|
30
|
+
adder.rb.cpp
|
31
|
+
).each do |wants|
|
32
|
+
assert_not_nil files.find {|got| File.basename(got) == wants }, "Didn't find #{wants}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
context "Single file writer" do
|
39
|
+
|
40
|
+
setup do
|
41
|
+
@working_dir = File.expand_path(File.dirname(__FILE__) + "/generated")
|
42
|
+
|
43
|
+
e = Extension.new "adder"
|
44
|
+
e.working_dir = @working_dir
|
45
|
+
e.sources full_dir("headers/Adder.h")
|
46
|
+
e.writer_mode :single
|
47
|
+
|
48
|
+
e.module "Mod" do |m|
|
49
|
+
m.namespace "classes"
|
50
|
+
end
|
51
|
+
|
52
|
+
e.build
|
53
|
+
e.write
|
54
|
+
end
|
55
|
+
|
56
|
+
specify "should properly write out all code in a single file" do
|
57
|
+
files = Dir["#{@working_dir}/*"]
|
58
|
+
files.size.should == 2
|
59
|
+
|
60
|
+
%w(
|
61
|
+
extconf.rb
|
62
|
+
adder.rb.cpp
|
63
|
+
).each do |wants|
|
64
|
+
assert_not_nil files.find {|got| File.basename(got) == wants }, "Didn't find #{wants}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|