diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Readme.mdown | 24 | ||||
| -rw-r--r-- | generate.rb | 118 | ||||
| -rw-r--r-- | sample.config.rb | 45 | ||||
| -rw-r--r-- | scan.rb | 7 | ||||
| -rw-r--r-- | templates.rb | 70 |
6 files changed, 257 insertions, 8 deletions
@@ -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 + + @@ -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 + |
