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
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
|