rbplusplus 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
data/lib/rbplusplus.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/rbplusplus")
|
3
|
+
|
4
|
+
gem 'rbgccxml'
|
5
|
+
require 'rbgccxml'
|
6
|
+
|
7
|
+
require 'inflector'
|
8
|
+
require 'rbplusplus/rbplusplus'
|
9
|
+
|
10
|
+
module RbPlusPlus
|
11
|
+
|
12
|
+
RBPP_COMMENT = "// This file generated by rb++"
|
13
|
+
|
14
|
+
autoload :Extension, "rbplusplus/extension"
|
15
|
+
autoload :RbModule, "rbplusplus/module"
|
16
|
+
|
17
|
+
module Builders
|
18
|
+
autoload :Base, "rbplusplus/builders/base"
|
19
|
+
autoload :ClassBuilder, "rbplusplus/builders/class"
|
20
|
+
autoload :ExtensionBuilder, "rbplusplus/builders/extension"
|
21
|
+
autoload :ModuleBuilder, "rbplusplus/builders/module"
|
22
|
+
end
|
23
|
+
|
24
|
+
module Writers
|
25
|
+
autoload :Base, "rbplusplus/writers/base"
|
26
|
+
autoload :ExtensionWriter, "rbplusplus/writers/extension"
|
27
|
+
autoload :MultipleFilesWriter, "rbplusplus/writers/multiple_files_writer"
|
28
|
+
autoload :SingleFileWriter, "rbplusplus/writers/single_file_writer"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Builders
|
3
|
+
|
4
|
+
# Top class for all source generation classes. A builder has three seperate
|
5
|
+
# code "parts" to fill up for the source writer:
|
6
|
+
#
|
7
|
+
# includes
|
8
|
+
# declarations
|
9
|
+
# body
|
10
|
+
#
|
11
|
+
# includes:
|
12
|
+
# The list of #include's needed for this builder's code to compile
|
13
|
+
#
|
14
|
+
# declarations:
|
15
|
+
# Any extra required functions or class declarations that will be defined
|
16
|
+
# outside of the main body of the code
|
17
|
+
#
|
18
|
+
# body:
|
19
|
+
# The body is the code that will go in the main control function
|
20
|
+
# of the file. For extensions, it's Init_extension_name() { [body] }.
|
21
|
+
# For classes it's usually a register_ClassName() { [body] }, and so on.
|
22
|
+
#
|
23
|
+
# All builders can access their parent and add pieces of code to any of these
|
24
|
+
# three parts
|
25
|
+
#
|
26
|
+
class Base
|
27
|
+
|
28
|
+
attr_reader :name, :node
|
29
|
+
|
30
|
+
# Any given builder has a list of sub-builders of any type
|
31
|
+
attr_accessor :builders
|
32
|
+
|
33
|
+
# Builders need to constcuct the following code parts
|
34
|
+
#
|
35
|
+
# The list of includes this builder needs
|
36
|
+
attr_accessor :includes
|
37
|
+
# The list of declarations to add
|
38
|
+
attr_accessor :declarations
|
39
|
+
# The body code
|
40
|
+
attr_accessor :body
|
41
|
+
|
42
|
+
# Link to the parent builder who created said builder
|
43
|
+
attr_accessor :parent
|
44
|
+
|
45
|
+
# The name of the C++ variable related to this builder.
|
46
|
+
attr_accessor :rice_variable
|
47
|
+
attr_accessor :rice_variable_type
|
48
|
+
|
49
|
+
# Create a new builder.
|
50
|
+
def initialize(name, parser)
|
51
|
+
@name = name
|
52
|
+
@node = parser
|
53
|
+
@builders = []
|
54
|
+
@includes = []
|
55
|
+
@declarations = []
|
56
|
+
@body = []
|
57
|
+
end
|
58
|
+
|
59
|
+
# All builders must implement this method
|
60
|
+
def build
|
61
|
+
raise "Builder needs to implement #build"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Builders should use to_s to make finishing touches on the generated
|
65
|
+
# code before it gets written out to a file.
|
66
|
+
def to_s
|
67
|
+
[self.includes.flatten.uniq, "", self.declarations, "", self.body].flatten.join("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get the full qualified name of the related gccxml node
|
71
|
+
def qualified_name
|
72
|
+
@node.qualified_name
|
73
|
+
end
|
74
|
+
|
75
|
+
# Register all classes
|
76
|
+
def build_classes
|
77
|
+
@node.classes.each do |klass|
|
78
|
+
builder = ClassBuilder.new(self, klass)
|
79
|
+
builder.build
|
80
|
+
builders << builder
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Compatibility with Rice 1.0.1's explicit self requirement, build a quick
|
85
|
+
# wrapper that includes a self and discards it, forwarding the call as needed.
|
86
|
+
#
|
87
|
+
# Returns: the name of the wrapper function
|
88
|
+
def build_function_wrapper(function)
|
89
|
+
wrapper_func = "wrap_#{function.qualified_name.gsub(/::/, "_")}"
|
90
|
+
|
91
|
+
proto_string = ["Rice::Object self"]
|
92
|
+
call_string = []
|
93
|
+
|
94
|
+
function.arguments.map{|arg| [arg.cpp_type.name, arg.name]}.each do |parts|
|
95
|
+
type = parts[0]
|
96
|
+
name = parts[1]
|
97
|
+
proto_string << "#{type} #{name}"
|
98
|
+
call_string << "#{name}"
|
99
|
+
end
|
100
|
+
|
101
|
+
proto_string = proto_string.join(",")
|
102
|
+
call_string = call_string.join(",")
|
103
|
+
return_type = function.return_type.name
|
104
|
+
returns = "" if return_type == "void"
|
105
|
+
returns ||= "return"
|
106
|
+
|
107
|
+
declarations << "#{return_type} #{wrapper_func}(#{proto_string}) {"
|
108
|
+
declarations << "\t#{returns} #{function.qualified_name}(#{call_string});"
|
109
|
+
declarations << "}"
|
110
|
+
|
111
|
+
wrapper_func
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Builders
|
3
|
+
|
4
|
+
# This class handles generating source for Class nodes
|
5
|
+
class ClassBuilder < Base
|
6
|
+
|
7
|
+
# Different initializer to keep things clean
|
8
|
+
def initialize(parent, node)
|
9
|
+
super(node.name, node)
|
10
|
+
self.parent = parent
|
11
|
+
end
|
12
|
+
|
13
|
+
def build
|
14
|
+
class_name = node.name
|
15
|
+
full_name = node.qualified_name
|
16
|
+
self.rice_variable = "rb_c#{class_name}"
|
17
|
+
self.rice_variable_type = "Rice::Data_Type<#{full_name}>"
|
18
|
+
|
19
|
+
includes << "#include <rice/Class.hpp>"
|
20
|
+
includes << "#include <rice/Data_Type.hpp>"
|
21
|
+
includes << "#include <rice/Constructor.hpp>"
|
22
|
+
includes << "#include \"#{node.file_name(false)}\""
|
23
|
+
|
24
|
+
class_defn = "\t#{rice_variable_type} #{rice_variable} = "
|
25
|
+
if !parent.is_a?(ExtensionBuilder)
|
26
|
+
class_defn += "Rice::define_class_under<#{full_name}>(#{parent.rice_variable}, \"#{class_name}\");"
|
27
|
+
else
|
28
|
+
class_defn += "Rice::define_class<#{full_name}>(\"#{class_name}\");"
|
29
|
+
end
|
30
|
+
|
31
|
+
body << class_defn
|
32
|
+
|
33
|
+
# Constructors
|
34
|
+
node.constructors.each do |init|
|
35
|
+
args = [full_name, init.arguments.map {|a| a.cpp_type }].flatten
|
36
|
+
body << "\t#{rice_variable}.define_constructor(Rice::Constructor<#{args.join(",")}>());"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Methods
|
40
|
+
node.methods.each do |method|
|
41
|
+
m = "define_method"
|
42
|
+
name = method.qualified_name
|
43
|
+
|
44
|
+
if method.static?
|
45
|
+
m = "define_singleton_method"
|
46
|
+
name = build_function_wrapper(method)
|
47
|
+
end
|
48
|
+
|
49
|
+
body << "\t#{rice_variable}.#{m}(\"#{Inflector.underscore(method.name)}\", &#{name});"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Nested Classes
|
53
|
+
node.classes.each do |klass|
|
54
|
+
b = ClassBuilder.new(self, klass)
|
55
|
+
b.build
|
56
|
+
builders << b
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Builders
|
3
|
+
|
4
|
+
# This class takes in all classes to be wrapped and builds
|
5
|
+
# the top-level extension Init code
|
6
|
+
class ExtensionBuilder < Base
|
7
|
+
|
8
|
+
# Need to be given the list of modules as they are a special case
|
9
|
+
attr_accessor :modules
|
10
|
+
|
11
|
+
def build
|
12
|
+
includes << "#include <rice/global_function.hpp>"
|
13
|
+
|
14
|
+
body << "extern \"C\""
|
15
|
+
body << "void Init_#{@name}() {"
|
16
|
+
|
17
|
+
# Explicitly ignore anything from the :: namespace
|
18
|
+
if @node.name != "::"
|
19
|
+
@node.functions.each do |func|
|
20
|
+
includes << "#include \"#{func.file_name(false)}\""
|
21
|
+
wrapper_name = build_function_wrapper(func)
|
22
|
+
body << "\tdefine_global_function(\"#{Inflector.underscore(func.name)}\", &#{wrapper_name});"
|
23
|
+
end
|
24
|
+
|
25
|
+
build_classes
|
26
|
+
end
|
27
|
+
|
28
|
+
build_modules
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_modules
|
32
|
+
@modules.each do |mod|
|
33
|
+
builder = ModuleBuilder.new(self, mod)
|
34
|
+
builder.build
|
35
|
+
builders << builder
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Finish up the required code before doing final output
|
40
|
+
def to_s
|
41
|
+
includes << "using namespace Rice;"
|
42
|
+
body << "}"
|
43
|
+
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
module Builders
|
3
|
+
|
4
|
+
# This class handles generating source for a requested Module
|
5
|
+
class ModuleBuilder < Base
|
6
|
+
|
7
|
+
# Initializer takes the parent object and the RbModule construction
|
8
|
+
def initialize(parent, mod)
|
9
|
+
super(mod.name, mod.node)
|
10
|
+
@module = mod
|
11
|
+
self.parent = parent
|
12
|
+
end
|
13
|
+
|
14
|
+
def build
|
15
|
+
# Using qualified name with underscores here to allow for nested modules
|
16
|
+
# of the same name
|
17
|
+
self.rice_variable = "rb_m#{self.qualified_name.gsub(/::/, "_")}"
|
18
|
+
self.rice_variable_type = "Rice::Module"
|
19
|
+
|
20
|
+
includes << "#include <rice/Module.hpp>"
|
21
|
+
|
22
|
+
mod_defn = "\t#{rice_variable_type} #{rice_variable} = "
|
23
|
+
if !parent.is_a?(ExtensionBuilder)
|
24
|
+
mod_defn += "Rice::define_module_under(#{parent.rice_variable}, \"#{name}\");"
|
25
|
+
else
|
26
|
+
mod_defn += "Rice::define_module(\"#{name}\");"
|
27
|
+
end
|
28
|
+
|
29
|
+
body << mod_defn
|
30
|
+
|
31
|
+
# If a namespace has been given to this module, find and wrap the appropriate code
|
32
|
+
if @node
|
33
|
+
build_functions
|
34
|
+
build_classes
|
35
|
+
end
|
36
|
+
|
37
|
+
# Build each inner module
|
38
|
+
@module.modules.each do |mod|
|
39
|
+
builder = ModuleBuilder.new(self, mod)
|
40
|
+
builder.build
|
41
|
+
builders << builder
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Process functions to be added to this module
|
46
|
+
def build_functions
|
47
|
+
@node.functions.each do |func|
|
48
|
+
includes << "#include \"#{func.file_name(false)}\""
|
49
|
+
|
50
|
+
func_name = Inflector.underscore(func.name)
|
51
|
+
wrapped_name = build_function_wrapper(func)
|
52
|
+
body << "\t#{self.rice_variable}.define_module_function(\"#{func_name}\", &#{wrapped_name});"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Special name handling. Qualified name is simply the name of this module
|
57
|
+
def qualified_name
|
58
|
+
if parent.is_a?(ModuleBuilder)
|
59
|
+
"#{parent.qualified_name}::#{self.name}"
|
60
|
+
else
|
61
|
+
self.name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module RbPlusPlus
|
2
|
+
|
3
|
+
# This is the starting class for Rb++ wrapping. All Rb++ projects start with this
|
4
|
+
# class:
|
5
|
+
#
|
6
|
+
# Extension.new "extension_name" do |e|
|
7
|
+
# ...
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# "extension_name" is what the resulting Ruby library will be named, aka in your code
|
11
|
+
# you will have
|
12
|
+
#
|
13
|
+
# require "extension_name"
|
14
|
+
#
|
15
|
+
# It is recommended that you use the block format of this class's initializer.
|
16
|
+
# If you want more fine-grained control of the whole process, don't use
|
17
|
+
# the block format. Instead you should do the following:
|
18
|
+
#
|
19
|
+
# e = Extension.new "extension_name"
|
20
|
+
# ...
|
21
|
+
#
|
22
|
+
# The following calls are required in both formats:
|
23
|
+
#
|
24
|
+
# #sources - The directory / array / name of C++ header files to parse.
|
25
|
+
#
|
26
|
+
# In the non-block format, the following calls are required:
|
27
|
+
#
|
28
|
+
# #working_dir - Specify the directory where the code will be generated. This needs
|
29
|
+
# to be a full path.
|
30
|
+
#
|
31
|
+
# In the non-block format, you need to manually fire the different steps of the
|
32
|
+
# code generation process, and in this order:
|
33
|
+
#
|
34
|
+
# #build - Fires the code generation process
|
35
|
+
#
|
36
|
+
# #write - Writes out the generated code into files
|
37
|
+
#
|
38
|
+
# #compile - Compiles the generated code into a Ruby extension.
|
39
|
+
#
|
40
|
+
class Extension
|
41
|
+
|
42
|
+
# Where will the generated code be put
|
43
|
+
attr_accessor :working_dir
|
44
|
+
|
45
|
+
# The list of modules to create
|
46
|
+
attr_accessor :modules
|
47
|
+
|
48
|
+
# List of extra include directives
|
49
|
+
attr_accessor :includes
|
50
|
+
|
51
|
+
# List of directories for library searching
|
52
|
+
attr_accessor :lib_paths
|
53
|
+
|
54
|
+
# List of libraries to link
|
55
|
+
attr_accessor :libraries
|
56
|
+
|
57
|
+
# Create a new Ruby extension with a given name. This name will be
|
58
|
+
# the module built into the extension.
|
59
|
+
# This constructor can be standalone or take a block.
|
60
|
+
def initialize(name, &block)
|
61
|
+
@name = name
|
62
|
+
@modules = []
|
63
|
+
@writer_mode = :multiple
|
64
|
+
@includes = []
|
65
|
+
@lib_paths = []
|
66
|
+
@libraries = []
|
67
|
+
|
68
|
+
if block
|
69
|
+
build_working_dir(&block)
|
70
|
+
block.call(self)
|
71
|
+
build
|
72
|
+
write
|
73
|
+
compile
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Define where we can find the header files to parse
|
78
|
+
# Can give an array of directories, a glob, or just a string.
|
79
|
+
# All file names should be full paths, not partial.
|
80
|
+
#
|
81
|
+
# Options can be the following:
|
82
|
+
#
|
83
|
+
# * <tt>:include_paths</tt> - An array or string of full paths to be added as -I flags
|
84
|
+
# * <tt>:library_paths</tt> - An array or string of full paths to be added as -L flags
|
85
|
+
# * <tt>:libraries</tt> - An array or string of full paths to be added as -l flags
|
86
|
+
def sources(dirs, options = {})
|
87
|
+
parser_options = {}
|
88
|
+
|
89
|
+
if (paths = options.delete(:include_paths))
|
90
|
+
@includes << paths
|
91
|
+
parser_options[:includes] = @includes
|
92
|
+
end
|
93
|
+
|
94
|
+
if (lib_paths = options.delete(:library_paths))
|
95
|
+
@lib_paths << lib_paths
|
96
|
+
end
|
97
|
+
|
98
|
+
if (libs = options.delete(:libraries))
|
99
|
+
@libraries << libs
|
100
|
+
end
|
101
|
+
|
102
|
+
@parser = RbGCCXML.parse(dirs, parser_options)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set a namespace to be the main namespace used for this extension.
|
106
|
+
# Specifing a namespace on the Extension itself will mark functions,
|
107
|
+
# class, enums, etc to be globally available to Ruby (aka not in it's own
|
108
|
+
# module)
|
109
|
+
def namespace(name)
|
110
|
+
@node = @parser.namespaces(name)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Mark that this extension needs to create a Ruby module of
|
114
|
+
# a give name. Like Extension, this can be used with or without
|
115
|
+
# a block.
|
116
|
+
def module(name, &block)
|
117
|
+
m = RbModule.new(name, @parser, &block)
|
118
|
+
@modules << m
|
119
|
+
m
|
120
|
+
end
|
121
|
+
|
122
|
+
# How should we write out the source code? This can be one of two modes:
|
123
|
+
# * <tt>:multiple</tt> (default) - Each class and module gets it's own set of hpp/cpp files
|
124
|
+
# * <tt>:single</tt> - Everything gets written to a single file
|
125
|
+
def writer_mode(mode)
|
126
|
+
raise "Unknown writer mode #{mode}" unless [:multiple, :single].include?(mode)
|
127
|
+
@writer_mode = mode
|
128
|
+
end
|
129
|
+
|
130
|
+
# Start the code generation process.
|
131
|
+
def build
|
132
|
+
raise ConfigurationError.new("Must specify working directory") unless @working_dir
|
133
|
+
raise ConfigurationError.new("Must specify which sources to wrap") unless @parser
|
134
|
+
|
135
|
+
@builder = Builders::ExtensionBuilder.new(@name, @node || @parser)
|
136
|
+
@builder.modules = @modules
|
137
|
+
@builder.build
|
138
|
+
end
|
139
|
+
|
140
|
+
# Write out the generated code into files.
|
141
|
+
# #build must be called before this step or nothing will be written out
|
142
|
+
def write
|
143
|
+
prepare_working_dir
|
144
|
+
|
145
|
+
# Create the code
|
146
|
+
writer_class = @writer_mode == :multiple ? Writers::MultipleFilesWriter : Writers::SingleFileWriter
|
147
|
+
writer_class.new(@builder, @working_dir).write
|
148
|
+
|
149
|
+
# Create the extconf.rb
|
150
|
+
extconf = Writers::ExtensionWriter.new(@builder, @working_dir)
|
151
|
+
extconf.includes = @includes
|
152
|
+
extconf.library_paths = @lib_paths
|
153
|
+
extconf.libraries = @libraries
|
154
|
+
extconf.write
|
155
|
+
end
|
156
|
+
|
157
|
+
# Compile the extension.
|
158
|
+
# This will create an rbpp_compile.log file in @working_dir. View this
|
159
|
+
# file to see the full compilation process including any compiler
|
160
|
+
# errors / warnings.
|
161
|
+
def compile
|
162
|
+
FileUtils.cd @working_dir do
|
163
|
+
system("ruby extconf.rb > rbpp_compile.log 2>&1")
|
164
|
+
system("make >> rbpp_compile.log 2>&1")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
protected
|
169
|
+
|
170
|
+
# If the working dir doesn't exist, make it
|
171
|
+
# and if it does exist, clean it out
|
172
|
+
def prepare_working_dir
|
173
|
+
FileUtils.mkdir_p @working_dir unless File.directory?(@working_dir)
|
174
|
+
FileUtils.rm_rf "#{@working_dir}/*"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Cool little eval / binding hack, from need.rb
|
178
|
+
def build_working_dir(&block)
|
179
|
+
@working_dir = File.expand_path(
|
180
|
+
File.join(File.dirname(eval("__FILE__", block.binding)), "generated"))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|