summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Readme.mdown24
-rw-r--r--generate.rb118
-rw-r--r--sample.config.rb45
-rw-r--r--scan.rb7
-rw-r--r--templates.rb70
6 files changed, 257 insertions, 8 deletions
diff --git a/.gitignore b/.gitignore
index c5bff83..eb84171 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ raylib.h
result.txt
output
output/*
+glue.json
diff --git a/Readme.mdown b/Readme.mdown
index 0c7a56e..ca1546a 100644
--- a/Readme.mdown
+++ b/Readme.mdown
@@ -5,18 +5,28 @@ A binding assistant and generator for C/C++ to mruby
### How I plan for it to work:
-1. Run the scanner to scan the code and collect all functions and place them into a generation config file
-2. Edit the configuration to customize how things should be bound
+1. Run the scanner which will generate a glue.json file. This json file will contain all functions(and their params) as well as all structs(and their params)
+2. Create a configuration file where you can reference specific functions and how you want their bindings to be generated differently
- for example, under what module or class a function should belong
- if a certain param should use self instead of passing in something
- ignore some functions if you dont need them
- insert bindings you made yourself
-3. Run the configuration file - this generates the resulting code and header files
+3. Run the generator with the configuration file - this generates the resulting binding code
Todo:
- [X] parse C files for function and struct declarations
-- [ ] design DSL for configuration file
-- [ ] code configuration DSL to output valid C code
-- [ ] make C parser generate configuration file
-- [ ] do it for C++
+- [X] plan for DSL for configuration file
+- [ ] create generator's default output
+ - [X] phase 1 - bind returnless, paramless functions
+ - [ ] phase 2 - bind standard type return functions(e.g string or int), but still paramless
+ - bool
+ - int
+ - float
+ - double
+ - string
+ - [ ] phase 3 - bind standard type return or params
+ - [ ] phase 4 - bind struct construction(returning struct objects)
+ - [ ] phase 5 - bind struct params(unwrapping structs)
+- [ ] have generator use config DSL file to customize bindings
+- [ ] do it all again for C++
diff --git a/generate.rb b/generate.rb
new file mode 100644
index 0000000..5320db7
--- /dev/null
+++ b/generate.rb
@@ -0,0 +1,118 @@
+require 'optparse'
+require 'json'
+require 'set'
+require 'active_support/core_ext/string'
+require_relative './templates.rb'
+
+options = {}
+OptionParser.new do |parser|
+ parser.banner = "Usage: example.rb [options]"
+
+ parser.on("-gGLUE", "--glue=GLUE", "Path to file(defaults to ./glue.rb)") do |glue|
+ options[:glue] = glue
+ end
+
+ parser.on('-cCONFIG', '--config=CONFIG', 'Path to config file') do |config|
+ options[:config] = config
+ end
+end.parse!
+
+options[:glue] ||= './glue.json'
+glue = JSON.parse(File.read(options[:glue]))
+
+bound = {}
+
+phase1 = {}
+phase2 = {}
+phase3 = {}
+phase4 = {}
+phase5 = {}
+complete_phase1 = {}
+complete_phase2 = {}
+complete_phase3 = {}
+complete_phase4 = {}
+complete_phase5 = {}
+
+result = ""
+includes = %{
+#include <raylib.h>
+#include <mruby.h>
+#include <mruby/array.h>
+#include <mruby/class.h>
+#include <mruby/numeric.h>
+#include <mruby/string.h>
+#include <stdlib.h>
+}
+defines = ""
+init_body = ""
+standard_types = ['bool', 'int', 'float', 'double', 'float', 'const char *', 'unsigned int', 'void']
+
+# for displaying statistics
+glue.first.each do |func, params|
+ if (func.rpartition(' ').first == 'void') && (params[0] == 'void')
+ phase1[func] = params
+ elsif (standard_types.include? func.rpartition(' ').first) && (params[0] == 'void')
+ phase2[func] = params
+ else
+ no_struct_param = true
+ params.each do |param|
+ if !(standard_types.include? param.rpartition(' ').first)
+ no_struct_param = false
+ break
+ end
+ end
+ if no_struct_param
+ if standard_types.include? func.rpartition(' ').first
+ phase3[func] = params
+ else
+ phase4[func] = params
+ end
+ else
+ phase5[func] = params
+ end
+ end
+end
+
+# generates functions
+glue.first.each do |func, params|
+ # for now dont worry about params or returns
+ if func.rpartition(' ').first != 'void' || params[0] != 'void'
+ next
+ else
+ bound[func] = params
+ if phase1.include? func
+ complete_phase1[func] = params
+ elsif phase2.include? func
+ complete_phase2[func] = params
+ elsif phase3.include? func
+ complete_phase3[func] = params
+ elsif phase4.include? func
+ complete_phase4[func] = params
+ elsif phase5.include? func
+ complete_phase5[func] = params
+ end
+ end
+ body = "#{func.split(' ').last}();\nreturn mrb_nil_value();"
+ defines += Template.function(func.split(' ').last, body)
+
+ init_body += Template.init_module_function('test', func.split(' ').last.underscore, func.split(' ').last, "MRB_ARGS_NONE()")
+end
+
+init_body.prepend(Template.define_module('Test'))
+
+result = %{
+ #{includes}
+#{defines}
+#{Template.base('test', init_body, nil)}
+}
+
+result += "//Bound Functions: #{bound.length} / #{phase1.length + phase2.length + phase3.length + phase4.length + phase5.length}\n"
+
+result += "//Phase 1 Functions: #{complete_phase1.length} / #{phase1.length}\n"
+result += "//Phase 2 Functions: #{complete_phase2.length} / #{phase2.length}\n"
+result += "//Phase 3 Functions: #{complete_phase3.length} / #{phase3.length}\n"
+result += "//Phase 4 Functions: #{complete_phase4.length} / #{phase4.length}\n"
+result += "//Phase 5 Functions: #{complete_phase5.length} / #{phase5.length}\n"
+
+
+puts result
diff --git a/sample.config.rb b/sample.config.rb
new file mode 100644
index 0000000..91d883b
--- /dev/null
+++ b/sample.config.rb
@@ -0,0 +1,45 @@
+require 'felbind'
+
+FelBind::Config.set('Raylib') do |config|
+ # what namespace to make the bindings under
+ config.namespace = 'Raylib'
+
+ config.func('DrawLineV') << {
+ # use vars inside of the struc as params rather then the struct
+ # as a param and place them into a struct later when passing into func
+ # this avoids using mruby struct wrapping
+ dont_wrap: ['color'],
+
+ # default setting, converts functions names to snakecase
+ ruby_name_conversion: true
+ }
+
+ config.func('DrawText') << {
+ # will be under Raylib::String because of namespace
+ define_under_module: 'String',
+
+ # default(because of ruby_name_conversion)
+ override_func_name: 'draw_text'
+ }
+
+ config.func('DrawRectangleRec') << {
+ # define as a function used by Rectangle objects
+ define_under_obj: 'Raylib::Rectangle',
+
+ # Unwrap "self" rather then accept a parameter
+ use_self_for: 'rec'
+ }
+
+ # do not bind these at all
+ config.func_ignore << [
+ 'TextCopy',
+ 'TextIsEqual',
+ 'TextLength'
+ ]
+
+ config.struct_ignore << [
+ 'Vector3'
+ ]
+end
+
+
diff --git a/scan.rb b/scan.rb
index 70a2893..6a6d230 100644
--- a/scan.rb
+++ b/scan.rb
@@ -21,7 +21,7 @@ def parse_header(path)
parse.each_line do |line|
json_line = JSON.parse line
if json_line['kind'] == 'prototype'
- functions[json_line['name']] = param_strip(json_line['signature'])
+ functions["#{json_line['typeref'].sub(/^[^ ][^ ]* /,'')} #{json_line['name']}"] = param_strip(json_line['signature'])
elsif json_line['kind'] == 'member'
if json_line['scopeKind'] == 'struct'
structs[json_line['scope']] ||= []
@@ -65,3 +65,8 @@ puts "Functions: #{functions.size}"
puts "Structs: #{structs.size}"
puts "Garbage: #{$garbage.size}(should be 0)"
puts
+
+result = [functions, structs]
+
+File.write('glue.json', JSON.generate(result))
+
diff --git a/templates.rb b/templates.rb
new file mode 100644
index 0000000..177769d
--- /dev/null
+++ b/templates.rb
@@ -0,0 +1,70 @@
+module Template
+ class << self
+ def base(gem_name, init_body, final_body)
+ %{
+void
+mrb_mruby_#{gem_name}_gem_init(mrb_state* mrb) {
+#{init_body}
+}
+
+void
+mrb_mruby_#{gem_name}_gem_final(mrb_state* mrb) {
+#{final_body}
+}
+ }
+ end
+
+ def init_module_function(module_name, function_name, mrb_function_name, mrb_args)
+ %{
+ mrb_define_module_function(mrb, #{module_name}, "#{function_name}", mrb_#{mrb_function_name}, #{mrb_args});
+ }
+ end
+
+ def function(function_name, body)
+ %{
+static mrb_value
+mrb_#{function_name}(mrb_state* mrb, mrb_value self) {
+#{body}
+}
+ }
+ end
+
+ def init_function(class_name, function_name, mrb_function_name, mrb_args)
+ %{mrb_define_method(mrb, #{class_name}, "#{function_name}", mrb_#{mrb_function_name}, #{mrb_args});
+ }
+ end
+
+ def get_kwargs(kwarg_num, body)
+ %{
+uint32_t kw_num = #{kwarg_num};
+const mrb_sym kw_names[] = {
+#{body}
+};
+mrb_value kw_values[kw_num];
+const mrb_kwargs kwargs = { kw_num, 0, kw_names, kw_values, NULL };
+mrb_get_args(mrb, "|:", &kwargs);
+ }
+ end
+
+ def unwrap_kwarg(kwarg_iter, body_if_defined, body_if_undefined)
+ %{
+if (mrb_undef_p(kw_values[#{kwarg_iter}])) {
+#{body_if_defined}
+} else {
+#{body_if_undefined}
+}
+ }
+ end
+
+ def unwrap_struct(var_name, target, mrb_type, type)
+ %{#{var_name} = DATA_GET_PTR(mrb, #{target}, &#{mrb_type}, #{type})}
+ end
+
+ def define_module(module_name)
+ %{struct RClass *#{module_name.downcase} = mrb_define_module(mrb, "#{module_name}");
+ }
+ end
+
+ end
+end
+