From c51ded6c4efef4e0ef5a157a72ae79c402b7964b Mon Sep 17 00:00:00 2001 From: realtradam Date: Wed, 31 Aug 2022 08:29:23 -0400 Subject: starting work on the backend system --- .tool-versions | 1 + Gemfile | 2 +- Gemfile.lock | 21 +- generate.rb | 444 ---------------------------------- planning/structure.puml | 224 +++++++++-------- sample.config.rb | 45 ---- scan.rb | 83 ------- src/backend/test.h | 12 + src/backend/uctags.rb | 146 ++++++++++++ src/generate.rb | 444 ++++++++++++++++++++++++++++++++++ src/sample.config.rb | 45 ++++ src/scan.rb | 85 +++++++ src/templates.rb | 623 ++++++++++++++++++++++++++++++++++++++++++++++++ templates.rb | 623 ------------------------------------------------ 14 files changed, 1486 insertions(+), 1312 deletions(-) create mode 100644 .tool-versions delete mode 100644 generate.rb delete mode 100644 sample.config.rb delete mode 100644 scan.rb create mode 100644 src/backend/test.h create mode 100644 src/backend/uctags.rb create mode 100644 src/generate.rb create mode 100644 src/sample.config.rb create mode 100644 src/scan.rb create mode 100644 src/templates.rb delete mode 100644 templates.rb diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..0b2d858 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.1.2 diff --git a/Gemfile b/Gemfile index f4b9259..527fc7f 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,4 @@ source "https://rubygems.org" # gem "rails" -gem "activerecord", "~> 7.0" +gem "cast", "~> 0.3.1" diff --git a/Gemfile.lock b/Gemfile.lock index 8184b71..c32dc74 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,28 +1,13 @@ GEM remote: https://rubygems.org/ specs: - activemodel (7.0.2.3) - activesupport (= 7.0.2.3) - activerecord (7.0.2.3) - activemodel (= 7.0.2.3) - activesupport (= 7.0.2.3) - activesupport (7.0.2.3) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - concurrent-ruby (1.1.10) - i18n (1.10.0) - concurrent-ruby (~> 1.0) - minitest (5.15.0) - tzinfo (2.0.4) - concurrent-ruby (~> 1.0) + cast (0.3.1) PLATFORMS x86_64-linux DEPENDENCIES - activerecord (~> 7.0) + cast (~> 0.3.1) BUNDLED WITH - 2.3.7 + 2.3.20 diff --git a/generate.rb b/generate.rb deleted file mode 100644 index 4fa41be..0000000 --- a/generate.rb +++ /dev/null @@ -1,444 +0,0 @@ -#require 'optparse' -require 'json' -require 'active_record' # use to make strings to snake_case. probably overkill -require_relative './templates.rb' -LibraryName = 'Test' - -class Generate - class << self -=begin - 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' -=end - def generate(file, destination) - glue = JSON.parse(File.read(file)) - - # configuration - #Template.treated_as_int |= ['unsigned char'] - - $phase1 = {} - $phase2 = {} - $phase3 = {} - $phase4 = {} - $phase5 = {} - $complete_phase1 = {} - $complete_phase2 = {} - $complete_phase3 = {} - $complete_phase4 = {} - $complete_phase5 = {} - - $result = "" - $includes = %{ -#include -#include -#include -#include -#include -#include -#include -#include -#include - } - - - $defines = "" - $init_body = "" - Template.parse_struct_types(glue.last) - - - # convert types - # TODO need to make this built in - # functionality(with scanner + generator) - glue.first.keys.each do |k| - rpart = k.rpartition(' ') - - #glue.first[ mappings[k] ] = glue.first.delete(k) if mappings[k] - if 'Texture2D' == rpart.first - glue.first["Texture #{rpart.last}"] = glue.first.delete(k) - elsif 'RenderTexture2D' == rpart.first - glue.first["RenderTexture #{rpart.last}"] = glue.first.delete(k) - end - end - - glue.first.each do |params| - params[1].map! do |param| - rpart = param.rpartition(' ') - - if ['Texture2D'].include? rpart.first - "Texture #{rpart.last}" - elsif ['RenderTexture2D'].include? rpart.first - "RenderTexture #{rpart.last}" - else - param - end - end - end - # for displaying statistics - glue.first.each do |func, params| - if (func.rpartition(' ').first == 'void') && (params[0] == 'void') - $phase1[func] = params - elsif (Template.non_struct_types =~ func.rpartition(' ').first) && (params[0] == 'void') - $phase2[func] = params - else - no_struct_param = true - params.each do |param| - if !(Template.non_struct_types =~ param.rpartition(' ').first) - no_struct_param = false - break - end - end - if no_struct_param - if Template.non_struct_types =~ func.rpartition(' ').first - $phase3[func] = params - else - $phase4[func] = params - end - else - $phase5[func] = params - end - end - end - # also for display statistics - def debug_mark_binding(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 - - $all_params = [] - $bound_params = [] - # generates structs, accessors, and initializers - glue.last.each do |struct, params| - $defines += Template.init_struct_wrapper(struct) - $init_body += Template.init_class(struct, LibraryName.downcase) - init_vars = '' - - params.each do |param| - $all_params.push param - param_datatype, _space, param_name = param.rpartition(' ') - - #next unless Template.non_struct_types =~ param_datatype - - # getter - # take no params - # unwrap struct - # return(using correct type conversion) - body = Template.unwrap_struct("#{struct} *struct_#{struct.downcase}", 'self', "mrb_#{struct}_struct", struct) - - # if non struct - if Template.non_struct_types_all =~ param_datatype - $bound_params.push param - if Template.non_struct_types =~ param_datatype - body += "return #{Template.to_mrb(param_datatype, "struct_#{struct.downcase}->#{param_name}")};\n" - else # pointer - body += "return #{Template.to_mrb(param_datatype, "struct_#{struct.downcase}->#{param_name}")};\n" - end - # elseif struct TODO - elsif Template.struct_types_all =~ param_datatype - if Template.struct_types =~ param_datatype - next # TODO - Template.wrap_struct(var_name, target, mrb_type, type) - else # pointer - next # TODO - end - # init var - # unwrap struct - # initialize struct as ruby object - # set to var - # end - else - next - end - $defines += Template.function("#{struct}_get_#{param_name}", body) - $init_body += Template.init_function("#{struct.downcase}_class", Template::MRuby.rubify_func_name(param_name), "#{struct}_get_#{param_name}", "MRB_ARGS_NONE()") - - # setter - # init var of correct type - # take 1 arg param - # unwrap struct - # set value in struct - # return same value - body = '' - # TODO check this and why it is kwargs? - body += Template::C.initialize_variables_for_kwargs([param], glue.last, "Struct: #{struct}") - body += Template.get_args({ "#{param_name}": "#{param_datatype}" }) - body += Template.unwrap_struct("#{struct} *struct_#{struct.downcase}", 'self', "mrb_#{struct}_struct", struct) - - if Template.non_struct_types_all =~ param_datatype - # if its a pointer - if Template.non_struct_types_pointer =~ param_datatype - body += "*struct_#{struct.downcase}->#{param_name} = #{Template::C.convention_parameter(param_name)};\n" - body += "return #{Template.to_mrb(param_datatype.delete_suffix(' *'), Template::C.convention_parameter(param_name))};\n" - else - body += "struct_#{struct.downcase}->#{param_name} = #{Template::C.convention_parameter(param_name)};\n" - body += "return #{Template.to_mrb(param_datatype, Template::C.convention_parameter(param_name))};\n" - end - elsif Template.struct_types_all =~ param_datatype - next - if Template.struct_types_pointer =~ param_datatype - #TODO - else - #TODO - end - end - - - $defines += Template.function("#{struct}_set_#{param_name}", body) - $init_body += Template.init_function("#{struct.downcase}_class", "#{Template::MRuby.rubify_func_name(param_name)}=", "#{struct}_set_#{param_name}", "MRB_ARGS_REQ(1)") - - - end - - ## initializer - # init the struct(using mrb to allocate) - # get values - # assign values to struct - # wrap struct - # return self - body = '' - body += Template.get_module(LibraryName) - body += Template.get_class(struct, LibraryName.downcase) - body += "#{struct} *wrapped_value = (#{struct} *)mrb_malloc(mrb, sizeof(#{struct}));\n" - #body += "*wrapped_value = {0};\n" #{func_name}(" - - init_array_body = '' - unwrapped_kwargs = '' - params.each_with_index do |param, index| - temp = param - temp_rpart = temp.rpartition(' ') - #if temp_rpart.first == 'const char *' - # temp = 'char *' + temp_rpart.last - #end - #init_var_body += temp + ";\n" - init_array_body += "mrb_intern_lit(mrb, \"#{temp_rpart.last}\"),\n" - #unwrapped_kwargs += Tplt.unwrap_kwarg(index, "#{temp_rpart.last} = #{Tplt.to_c(temp_rpart.first, "kw_values[#{index}]")};", nil, "#{temp_rpart.last} Argument Missing") - if Template.non_struct_types =~ temp_rpart.first - unwrapped_kwargs += Template::C.unwrap_kwarg(index, "wrapped_value->#{temp_rpart.last} = #{Template.to_c(temp_rpart.first, "kw_values[#{index}]")};\n", nil, "Missing kwarg: #{temp_rpart.last.underscore}") - else - # this is for structs or "undetermined" types - # doesnt work yet - next - #unwrapped_kwargs += Tplt.unwrap_kwarg(index, "wrapped_value->#{temp_rpart.last} = (#{temp_rpart.first})kw_values[#{index}];\n") - end - end - body += Template::C.get_kwargs(params)#params.length, '', init_array_body) - body += unwrapped_kwargs - - body += "mrb_data_init(self, wrapped_value, &mrb_#{struct}_struct);\n" - body += 'return self;' - $defines += Template.function("#{struct}_initialize", body) - $init_body += Template.init_function("#{struct.downcase}_class", "initialize", "#{struct}_initialize", "MRB_ARGS_OPT(1)") - - end - - # generates functions - glue.first.each do |func, params| - # func = function name with return type - # func_datatype = function return type - # func_name = function name - # params = array of params with their data types(void means none) - rpart = func.rpartition(' ') - func_datatype = rpart.first - func_name = rpart.last - - # TODO make a skip detector(what functions to skip bindings) - skip = false - #puts "FUNCTION" - #puts "#{func.rpartition(' ').first}| + |#{func.rpartition(' ').last}" - params.each do |param| - #puts "#{param.rpartition(' ').first}| - |#{param.rpartition(' ').last}" - unless (Template.all_valid_types =~ param.rpartition(' ').first) || ("void" == param) - skip = true - break - end - #if param.chars.include? '*' - # unless /^char \*$/ =~ param.rpartition(' ').first - # skip = true - # break - # end - #end - end - next if skip - #next if ['SetTraceLogCallback', 'SetSaveFileTextCallback', 'SetSaveFileDataCallback', 'SetLoadFileTextCallback', 'SetLoadFileDataCallback', 'SetCameraMode', 'GetWorldToScreenEx', 'GetWorldToScreen', 'GetMouseRay', 'GetCameraMatrix', 'DrawBillboardRec', 'DrawBillboardPro', 'DrawBillboard'].include? func_name - - # since void * can be anything just skip functions - # (by default) that use it - next if ['void *'].include? func_datatype - unless Template.all_valid_types =~ func_datatype - puts "// \"#{func_datatype}\" is not a function return datatype that can be currently autobound. From function: \"#{func_name}\"\n\n" - next - end - - body = '' - - # use kwargs - if params.count > 1 - body += Template::C.initialize_variables_for_kwargs(params, glue.last, func_name) - - body += Template::C.get_kwargs(params) - - body += Template::C.parse_kwargs(params) - body += "\n" # formatting - # use args - elsif params.first != 'void' - body += Template::C.initialize_variables_for_args(params, glue.last, func_name) - param_rpart = params.first.rpartition(' ') - body += Template.get_args({ "#{param_rpart.last}": "#{param_rpart.first}" }) - body += Template::C.parse_args(params) - end - - body += Template::C.initialize_return_var(func_datatype, func_name) - body += "\n" # formatting - - body += Template.format_set_method_call(func_datatype, func_name, params, Template.struct_types =~ func_datatype.gsub(/ *\*+$/,'')) - - body += Template.format_return(func_datatype, func_name) - - $defines += "\n//#{func}" - $defines += Template.function(func_name, body) - $init_body += Template.init_module_function(LibraryName.downcase, Template::MRuby.rubify_func_name(func_name, params), func_name, "MRB_ARGS_OPT(1)") - - debug_mark_binding(func, params) - #puts body - # TODO CONTINUE HERE - #puts "// --- NEXT ---" - #next -=begin - # if phase 1 or 2 - if (func_datatype == 'void' && params[0] == 'void') || ((Tplt.non_struct_types.include? func_datatype) && (params[0] == 'void')) - body = Tplt.return_format(func, params) - #$defines += 'PHASE 1\n' - $defines += "\n//#{func}" - $defines += Tplt.function(func_name, body) - $init_body += Tplt.init_module_function(LibraryName.downcase, Tplt.rubify_func_name(func_name), func_name, "MRB_ARGS_NONE()") - - debug_mark_binding(func, params) - else Tplt.non_struct_types.include? func_datatype # accept params - # detecting if there is no struct param(wont need this in the future) - no_struct_param = true - params.each do |param| - if !(Tplt.non_struct_types.include? param.rpartition(' ').first) - no_struct_param = false - break - end - end - if no_struct_param - #if true# Tplt.non_struct_types.include? func.rpartition(' ').first - #$phase3[func] = params - # --- - body = '' - #body = Tplt.return_format(func, params) - init_var_body = '' - init_array_body = '' - unwrapped_kwargs = '' - params.each_with_index do |param, index| - temp = param - temp_rpart = temp.rpartition(' ') - if temp_rpart.first == 'const char *' - temp = 'char *' + temp_rpart.last - end - init_var_body += temp + ";\n" - init_array_body += "mrb_intern_lit(mrb, \"#{temp_rpart.last}\"),\n" - unwrapped_kwargs += Tplt.unwrap_kwarg(index, "#{temp_rpart.last} = #{Tplt.to_c(temp_rpart.first, "kw_values[#{index}]")};", nil, "#{temp_rpart.last} Argument Missing") - end - - # if return isnt regular types, add struct to init - unless Tplt.non_struct_types.include? func_datatype - #init_var_body += "#{func_datatype} *wrapped_value = {0};\n" - end - - body = Tplt.get_kwargs(params.length, init_var_body, init_array_body) - body += unwrapped_kwargs - - # if return isnt regular types, use struct return format - if Tplt.non_struct_types.include? func_datatype - body += Tplt.return_format(func, params) - else - body += Tplt.get_module(LibraryName) - body += Tplt.get_class(func_datatype, LibraryName.downcase) - body += "#{func_datatype} *wrapped_value = (#{func_datatype} *)mrb_malloc(mrb, sizeof(#{func_datatype}));\n" - body += "*wrapped_value = #{func_name}(" - params.each do |param| - temp_rpart = param.rpartition(' ') - body += "#{temp_rpart.last}, " - end - body.delete_suffix!(', ') - body += ");\n" - body += "return mrb_obj_value(Data_Wrap_Struct(mrb, #{func_datatype.downcase}_mrb_class, &mrb_#{func_datatype}_struct, wrapped_value));" - end - - $defines += "\n//#{func}" - $defines += Tplt.function(func_name, body) - $init_body += Tplt.init_module_function(LibraryName.downcase, Tplt.rubify_func_name(func_name), func_name, "MRB_ARGS_OPT(1)") # opt stuff isnt correct, need to look at this again - # --- - #puts func - debug_mark_binding(func, params) - #end - else - #$phase5[func] = params - end - end -end -raise 'end of testing' -=end - end - $init_body.prepend(Template.define_module(LibraryName)) - - $result = %{ -#{$includes} -#{$defines} -#{Template.base(LibraryName.downcase, $init_body, nil)} - } - - - #pp ($phase3.keys - $complete_phase3.keys) - #puts - #pp $complete_phase3 - - all_completed = $complete_phase1.keys | $complete_phase2.keys | $complete_phase3.keys | $complete_phase4.keys | $complete_phase5.keys - all = $phase1.keys | $phase2.keys | $phase3.keys | $phase4.keys | $phase5.keys - - $result += "/* Unbound:\n" - (all - all_completed).each do |unbound| - $result += "#{unbound}\n" - end - $result += "*/\n" - - $result += "//Bound Functions: #{$complete_phase1.length + $complete_phase2.length + $complete_phase3.length + $complete_phase4.length + $complete_phase5.length} / #{$phase1.length + $phase2.length + $phase3.length + $phase4.length + $phase5.length}\n//---\n" - $result += "//Struct Accessors: #{$bound_params.length} / #{$all_params.length}\n//---\n" - - $result += "\n" - - puts $result - - puts '/*' - puts "UNBOUND:" - pp $all_params - $bound_params - puts - puts "BOUND:" - pp $bound_params - puts '*/' - - File.write(destination, $result) - end - end -end diff --git a/planning/structure.puml b/planning/structure.puml index 67cc77f..817a3bd 100644 --- a/planning/structure.puml +++ b/planning/structure.puml @@ -1,107 +1,107 @@ @startuml - state Gem { - state Headers - state Structs #red { - state Struct_Type_Init - state Struct_Deinit #chocolate - state "Struct Getter Method" as Struct_Getter #lawngreen { - state fork_is_struct_getter <> - state "Unwrap Struct\nand Read Value" as Unwrap_Struct_Getter - state "Return Value" as Return_Struct_Getter - state "Handle If Value\nIs Struct" as Handle_Struct_Value_Getter - - [*] -d-> Unwrap_Struct_Getter - Unwrap_Struct_Getter -d-> fork_is_struct_getter - fork_is_struct_getter -d-> Return_Struct_Getter - fork_is_struct_getter -d-> Handle_Struct_Value_Getter - Handle_Struct_Value_Getter -d-> Return_Struct_Getter - } - state "Struct Setter Method" as Struct_Setter #darkorchid { - state "Initialize Vars" as Initialize_Vars_Setter - state "Get and Assign\nArg" as Get_Arg_Setter - state "Unwrap And Set" as Unwrap_Struct_Setter - state "Return Var" as Return_Setter - - [*] -d-> Initialize_Vars_Setter - Initialize_Vars_Setter -d-> Get_Arg_Setter - Get_Arg_Setter -d-> Unwrap_Struct_Setter - Unwrap_Struct_Setter -d-> Return_Setter - } - state Struct_Initializer as "Struct Initializer" #gold { - state "Initialize Vars" as Initialize_Vars_Struct_Init - state fork_kw_args_struct_init <> - state "Get and Assign\nArg" as Get_Arg_Struct_Init - state "Get and Assign\nKwargs" as Get_Kwargs_Struct_Init - state "Build Struct" as Build_Struct_Struct_Init - state "Wrap Struct" as Wrap_Struct_Struct_Init - state "Return Ruby Object" as Return_Struct_Init - - [*] -d-> Initialize_Vars_Struct_Init - - Initialize_Vars_Struct_Init -d-> fork_kw_args_struct_init - - fork_kw_args_struct_init -d-> Get_Arg_Struct_Init - - fork_kw_args_struct_init -d-> Get_Kwargs_Struct_Init - - Get_Arg_Struct_Init -d-> Build_Struct_Struct_Init - Get_Kwargs_Struct_Init -d-> Build_Struct_Struct_Init - - Build_Struct_Struct_Init -d-> Wrap_Struct_Struct_Init - - Wrap_Struct_Struct_Init -d-> Return_Struct_Init - } - - Struct_Type_Init --> Struct_Deinit +state Gem { + state Headers + state Structs #red { + state Struct_Type_Init + state Struct_Deinit #chocolate + state "Struct Getter Method" as Struct_Getter #lawngreen { + state fork_is_struct_getter <> + state "Unwrap Struct\nand Read Value" as Unwrap_Struct_Getter + state "Return Value" as Return_Struct_Getter + state "Handle If Value\nIs Struct" as Handle_Struct_Value_Getter + [*] -d-> Unwrap_Struct_Getter + Unwrap_Struct_Getter -d-> fork_is_struct_getter + fork_is_struct_getter -d-> Return_Struct_Getter + fork_is_struct_getter -d-> Handle_Struct_Value_Getter + Handle_Struct_Value_Getter -d-> Return_Struct_Getter } - state Methods #lightblue { - state "Initialize Vars" as Initialize_Vars_C_Function - state fork_kw_args_c_function <> - state "Get and Assign Arg" as Get_Arg_C_Function - state "Get and Assign Kwargs" as Get_Kwargs_C_Function - state "Call Function" as Call_C_Function - state fork_wrap_struct_c_function <> - state "Wrap Struct" as Wrap_Struct_C_Function - state "Return Value" as Return_C_Function - - [*] -d-> Initialize_Vars_C_Function - [*] -d-> Call_C_Function - - Initialize_Vars_C_Function -d-> fork_kw_args_c_function - - fork_kw_args_c_function -d-> Get_Arg_C_Function - - fork_kw_args_c_function -d-> Get_Kwargs_C_Function - - Get_Arg_C_Function -d-> Call_C_Function - Get_Kwargs_C_Function -d-> Call_C_Function - - Call_C_Function -d-> fork_wrap_struct_c_function - fork_wrap_struct_c_function -d-> Wrap_Struct_C_Function - - fork_wrap_struct_c_function -d-> Return_C_Function - Wrap_Struct_C_Function -d-> Return_C_Function + state "Struct Setter Method" as Struct_Setter #darkorchid { + state "Initialize Vars" as Initialize_Vars_Setter + state "Get and Assign\nArg" as Get_Arg_Setter + state "Unwrap And Set" as Unwrap_Struct_Setter + state "Return Var" as Return_Setter + + [*] -d-> Initialize_Vars_Setter + Initialize_Vars_Setter -d-> Get_Arg_Setter + Get_Arg_Setter -d-> Unwrap_Struct_Setter + Unwrap_Struct_Setter -d-> Return_Setter } - state Gem_Initializer as "Gem Initializer" { - state Define_Module as "Define Module" #crimson - state Define_Struct_Classes as "Define Classes" #darkorange - state Define_Struct_Methods as "Define Struct Methods" #hotpink - state Define_Functions as "Define Functions" #lightblue + state Struct_Initializer as "Struct Initializer" #gold { + state "Initialize Vars" as Initialize_Vars_Struct_Init + state fork_kw_args_struct_init <> + state "Get and Assign\nArg" as Get_Arg_Struct_Init + state "Get and Assign\nKwargs" as Get_Kwargs_Struct_Init + state "Build Struct" as Build_Struct_Struct_Init + state "Wrap Struct" as Wrap_Struct_Struct_Init + state "Return Ruby Object" as Return_Struct_Init + + [*] -d-> Initialize_Vars_Struct_Init + + Initialize_Vars_Struct_Init -d-> fork_kw_args_struct_init + + fork_kw_args_struct_init -d-> Get_Arg_Struct_Init + + fork_kw_args_struct_init -d-> Get_Kwargs_Struct_Init + + Get_Arg_Struct_Init -d-> Build_Struct_Struct_Init + Get_Kwargs_Struct_Init -d-> Build_Struct_Struct_Init + + Build_Struct_Struct_Init -d-> Wrap_Struct_Struct_Init + + Wrap_Struct_Struct_Init -d-> Return_Struct_Init } - state Gem_Finalizer as "Gem Finalizer" - - - Headers -d-> Structs - Structs -d-> Methods - Methods -d-> Gem_Initializer - Gem_Initializer -d-> Gem_Finalizer - - Define_Module -d-> Define_Struct_Classes - Define_Struct_Classes -r-> Define_Struct_Methods - Define_Struct_Classes -d-> Define_Functions + + Struct_Type_Init --> Struct_Deinit + } + state Methods #lightblue { + state "Initialize Vars" as Initialize_Vars_C_Function + state fork_kw_args_c_function <> + state "Get and Assign Arg" as Get_Arg_C_Function + state "Get and Assign Kwargs" as Get_Kwargs_C_Function + state "Call Function" as Call_C_Function + state fork_wrap_struct_c_function <> + state "Wrap Struct" as Wrap_Struct_C_Function + state "Return Value" as Return_C_Function + + [*] -d-> Initialize_Vars_C_Function + [*] -d-> Call_C_Function + + Initialize_Vars_C_Function -d-> fork_kw_args_c_function + + fork_kw_args_c_function -d-> Get_Arg_C_Function + + fork_kw_args_c_function -d-> Get_Kwargs_C_Function + + Get_Arg_C_Function -d-> Call_C_Function + Get_Kwargs_C_Function -d-> Call_C_Function + + Call_C_Function -d-> fork_wrap_struct_c_function + fork_wrap_struct_c_function -d-> Wrap_Struct_C_Function + + fork_wrap_struct_c_function -d-> Return_C_Function + Wrap_Struct_C_Function -d-> Return_C_Function + } + state Gem_Initializer as "Gem Initializer" { + state Define_Module as "Define Module" #crimson + state Define_Struct_Classes as "Define Classes" #darkorange + state Define_Struct_Methods as "Define Struct Methods" #hotpink + state Define_Functions as "Define Functions" #lightblue + } + state Gem_Finalizer as "Gem Finalizer" + + + Headers -d-> Structs + Structs -d-> Methods + Methods -d-> Gem_Initializer + Gem_Initializer -d-> Gem_Finalizer + + Define_Module -d-> Define_Struct_Classes + Define_Struct_Classes -r-> Define_Struct_Methods + Define_Struct_Classes -d-> Define_Functions +} state Build_State as "Build State Hash" { state BS_C_Gemname as "Gem Name" #crimson : string @@ -137,7 +137,35 @@ state Build_State as "Build State Hash" { BS_C_Function_Name -d[hidden]-> BS_C_Struct_Name } +state UCTags as "Universal Ctags Identifier" { + state UCT_KindIsProto as "Tag Kind Is\nPrototype" + state UCT_KindIsTypedef as "Tag Kind Is\nTypedef" + state UCT_KindIsMember as "Tag Kind Is\nMember" + + state UCT_TyperefIsTypename as "Tag Typeref is\n'typename:{something}'" + state UCT_TyperefIsStruct as "Tag Typeref is\n'struct:{something}'" + + state UCT_IsFunction as "It's a Function" + state UCT_IsStructParam as "It's a Struct\nParameter" + state UCT_IsTypedef as "It's an Alias\n(Typedef)" + state UCT_IsStruct as "It's a Struct" + + [*] -d-> UCT_KindIsProto + [*] -d-> UCT_KindIsTypedef + [*] -d-> UCT_KindIsMember + + UCT_KindIsProto -d-> UCT_IsFunction + UCT_KindIsMember -d-> UCT_IsStructParam + + UCT_KindIsTypedef -d-> UCT_TyperefIsTypename + UCT_KindIsTypedef -d-> UCT_TyperefIsStruct + UCT_TyperefIsTypename -d-> UCT_IsStruct + UCT_TyperefIsStruct -d-> UCT_IsTypedef + +} + + + - @enduml diff --git a/sample.config.rb b/sample.config.rb deleted file mode 100644 index 91d883b..0000000 --- a/sample.config.rb +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index 38dd95b..0000000 --- a/scan.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'json' - -#file_to_scan = 'raylib.h' - -class Scan - class << self - # ctags --list-kinds=c - # p function prototypes - # s structure names - # z function parameters inside function or prototype definitions - # m struct, and union members - def ctag(file) - `ctags --output-format=json --c-kinds=pm --fields=+S --language-force=c #{file}` - end - #File.write('json.json', parse) - #$garbage = [] - - def param_strip(signature) - signature[1...-1].split(',') - end - - - def parse_header(path) - parse = `ctags --output-format=json --c-kinds=pm --fields=+S --language-force=c #{path}` - structs = {} - functions = {} - failed = [] - parse.each_line do |line| - json_line = JSON.parse line - if json_line['kind'] == 'prototype' - 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']] ||= [] - structs[json_line['scope']].push "#{json_line['typeref'].delete_prefix('typename:')} #{json_line['name']}" - else - failed.push json_line - end - elsif json_line['kind'] == 'struct' - structs[json_line['name']] = json_line - else - failed.push json_line - end - end - [functions, structs, failed] - end - - - def debug_show(type, hash) - puts "#{type.upcase}:" - puts '---' - hash.each do |key, params| - puts "#{type.capitalize}: #{key}" - params.each do |param| - puts param - end - puts '---' - end - puts - end - - def scan(file, destination) - functions, structs, failed = parse_header(file) - debug_show('functions', functions) - debug_show('structs', structs) - - if !failed.empty? - puts "-- Failed: --" - pp failed - puts - end - - puts "Functions: #{functions.size}" - puts "Structs: #{structs.size}" - puts "Failed: #{failed.size}" - puts - - result = [functions, structs] - - File.write(destination, JSON.generate(result)) - end - end -end diff --git a/src/backend/test.h b/src/backend/test.h new file mode 100644 index 0000000..c7b5ea9 --- /dev/null +++ b/src/backend/test.h @@ -0,0 +1,12 @@ +void InitWindow(int width, int height, const char *title); + +typedef struct Rectangle { + float height; // Rectangle height +} Rectangle; + +typedef struct Texture { + unsigned int id; // OpenGL texture id + int format; // Data format (PixelFormat type) +} Texture; + +typedef Texture Texture2D; diff --git a/src/backend/uctags.rb b/src/backend/uctags.rb new file mode 100644 index 0000000..0cfa3ee --- /dev/null +++ b/src/backend/uctags.rb @@ -0,0 +1,146 @@ +require 'json' + +module FelBind + module Backends + class Intermediate < Hash + + def initialize + self[:GemName] = '' + self[:Typedefs] = {} + self[:CFunctions] = {} + self[:CStructs] = {} + end + + def add_function(name:, ruby_name: , ruby_class:, param_as_self: '') + #TODO + end + + def add_struct + #TODO + end + + def add_typedef + #TODO + end + + def add_struct_param + #TODO + end + + end + + module UCTags + class << self + + # ctags --list-kinds=c + # --c-kinds: + # p function prototypes + # (s structure names) + # (z function parameters inside function or prototype definitions) + # m struct, and union members + # t typedef + # --fields: + # S signature + def ctag(file) + `ctags --output-format=json --c-kinds=pmt --fields=+S --language-force=c #{file}` + end + + def parse(file) + ctags_output = self.ctag(file).each_line.map do |tag| + JSON.parse tag + end + intermediate = FelBind::Backends::Intermediate.new + + ctags_output.each do |tag| + if tag["kind"] == "prototype" + # its a function + puts 'its a function' #TODO remove + elsif tag["kind"] == "typedef" + if tag["typeref"].split(':').first == "typename" + # is a typedef + puts 'its an alias(typedef)' #TODO remove + elsif tag["typeref"].split(':').first == "struct" + # is a struct + puts 'its a struct' #TODO remove + else + puts "warning: no match" #TODO better errors + end + elsif tag['kind'] == 'member' + # is struct param + puts 'its a param' #TODO remove + else + puts "warning: no match" #TODO better errors + end + end + + end + +=begin +def param_strip(signature) + signature[1...-1].split(',') + end + + def parse_header(path) + parse = `ctags --output-format=json --c-kinds=pm --fields=+S --language-force=c #{path}` + structs = {} + functions = {} + failed = [] + parse.each_line do |line| + json_line = JSON.parse line + if json_line['kind'] == 'prototype' + 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']] ||= [] + structs[json_line['scope']].push "#{json_line['typeref'].delete_prefix('typename:')} #{json_line['name']}" + else + failed.push json_line + end + elsif json_line['kind'] == 'struct' + structs[json_line['name']] = json_line + else + failed.push json_line + end + end + [functions, structs, failed] + end + + + def debug_show(type, hash) + puts "#{type.upcase}:" + puts '---' + hash.each do |key, params| + puts "#{type.capitalize}: #{key}" + params.each do |param| + puts param + end + puts '---' + end + puts + end + + def scan(file, destination) + functions, structs, failed = parse_header(file) + debug_show('functions', functions) + debug_show('structs', structs) + + if !failed.empty? + puts "-- Failed: --" + pp failed + puts + end + + puts "Functions: #{functions.size}" + puts "Structs: #{structs.size}" + puts "Failed: #{failed.size}" + puts + + result = [functions, structs] + + File.write(destination, JSON.generate(result)) + end +=end + end + end + end +end diff --git a/src/generate.rb b/src/generate.rb new file mode 100644 index 0000000..4fa41be --- /dev/null +++ b/src/generate.rb @@ -0,0 +1,444 @@ +#require 'optparse' +require 'json' +require 'active_record' # use to make strings to snake_case. probably overkill +require_relative './templates.rb' +LibraryName = 'Test' + +class Generate + class << self +=begin + 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' +=end + def generate(file, destination) + glue = JSON.parse(File.read(file)) + + # configuration + #Template.treated_as_int |= ['unsigned char'] + + $phase1 = {} + $phase2 = {} + $phase3 = {} + $phase4 = {} + $phase5 = {} + $complete_phase1 = {} + $complete_phase2 = {} + $complete_phase3 = {} + $complete_phase4 = {} + $complete_phase5 = {} + + $result = "" + $includes = %{ +#include +#include +#include +#include +#include +#include +#include +#include +#include + } + + + $defines = "" + $init_body = "" + Template.parse_struct_types(glue.last) + + + # convert types + # TODO need to make this built in + # functionality(with scanner + generator) + glue.first.keys.each do |k| + rpart = k.rpartition(' ') + + #glue.first[ mappings[k] ] = glue.first.delete(k) if mappings[k] + if 'Texture2D' == rpart.first + glue.first["Texture #{rpart.last}"] = glue.first.delete(k) + elsif 'RenderTexture2D' == rpart.first + glue.first["RenderTexture #{rpart.last}"] = glue.first.delete(k) + end + end + + glue.first.each do |params| + params[1].map! do |param| + rpart = param.rpartition(' ') + + if ['Texture2D'].include? rpart.first + "Texture #{rpart.last}" + elsif ['RenderTexture2D'].include? rpart.first + "RenderTexture #{rpart.last}" + else + param + end + end + end + # for displaying statistics + glue.first.each do |func, params| + if (func.rpartition(' ').first == 'void') && (params[0] == 'void') + $phase1[func] = params + elsif (Template.non_struct_types =~ func.rpartition(' ').first) && (params[0] == 'void') + $phase2[func] = params + else + no_struct_param = true + params.each do |param| + if !(Template.non_struct_types =~ param.rpartition(' ').first) + no_struct_param = false + break + end + end + if no_struct_param + if Template.non_struct_types =~ func.rpartition(' ').first + $phase3[func] = params + else + $phase4[func] = params + end + else + $phase5[func] = params + end + end + end + # also for display statistics + def debug_mark_binding(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 + + $all_params = [] + $bound_params = [] + # generates structs, accessors, and initializers + glue.last.each do |struct, params| + $defines += Template.init_struct_wrapper(struct) + $init_body += Template.init_class(struct, LibraryName.downcase) + init_vars = '' + + params.each do |param| + $all_params.push param + param_datatype, _space, param_name = param.rpartition(' ') + + #next unless Template.non_struct_types =~ param_datatype + + # getter + # take no params + # unwrap struct + # return(using correct type conversion) + body = Template.unwrap_struct("#{struct} *struct_#{struct.downcase}", 'self', "mrb_#{struct}_struct", struct) + + # if non struct + if Template.non_struct_types_all =~ param_datatype + $bound_params.push param + if Template.non_struct_types =~ param_datatype + body += "return #{Template.to_mrb(param_datatype, "struct_#{struct.downcase}->#{param_name}")};\n" + else # pointer + body += "return #{Template.to_mrb(param_datatype, "struct_#{struct.downcase}->#{param_name}")};\n" + end + # elseif struct TODO + elsif Template.struct_types_all =~ param_datatype + if Template.struct_types =~ param_datatype + next # TODO + Template.wrap_struct(var_name, target, mrb_type, type) + else # pointer + next # TODO + end + # init var + # unwrap struct + # initialize struct as ruby object + # set to var + # end + else + next + end + $defines += Template.function("#{struct}_get_#{param_name}", body) + $init_body += Template.init_function("#{struct.downcase}_class", Template::MRuby.rubify_func_name(param_name), "#{struct}_get_#{param_name}", "MRB_ARGS_NONE()") + + # setter + # init var of correct type + # take 1 arg param + # unwrap struct + # set value in struct + # return same value + body = '' + # TODO check this and why it is kwargs? + body += Template::C.initialize_variables_for_kwargs([param], glue.last, "Struct: #{struct}") + body += Template.get_args({ "#{param_name}": "#{param_datatype}" }) + body += Template.unwrap_struct("#{struct} *struct_#{struct.downcase}", 'self', "mrb_#{struct}_struct", struct) + + if Template.non_struct_types_all =~ param_datatype + # if its a pointer + if Template.non_struct_types_pointer =~ param_datatype + body += "*struct_#{struct.downcase}->#{param_name} = #{Template::C.convention_parameter(param_name)};\n" + body += "return #{Template.to_mrb(param_datatype.delete_suffix(' *'), Template::C.convention_parameter(param_name))};\n" + else + body += "struct_#{struct.downcase}->#{param_name} = #{Template::C.convention_parameter(param_name)};\n" + body += "return #{Template.to_mrb(param_datatype, Template::C.convention_parameter(param_name))};\n" + end + elsif Template.struct_types_all =~ param_datatype + next + if Template.struct_types_pointer =~ param_datatype + #TODO + else + #TODO + end + end + + + $defines += Template.function("#{struct}_set_#{param_name}", body) + $init_body += Template.init_function("#{struct.downcase}_class", "#{Template::MRuby.rubify_func_name(param_name)}=", "#{struct}_set_#{param_name}", "MRB_ARGS_REQ(1)") + + + end + + ## initializer + # init the struct(using mrb to allocate) + # get values + # assign values to struct + # wrap struct + # return self + body = '' + body += Template.get_module(LibraryName) + body += Template.get_class(struct, LibraryName.downcase) + body += "#{struct} *wrapped_value = (#{struct} *)mrb_malloc(mrb, sizeof(#{struct}));\n" + #body += "*wrapped_value = {0};\n" #{func_name}(" + + init_array_body = '' + unwrapped_kwargs = '' + params.each_with_index do |param, index| + temp = param + temp_rpart = temp.rpartition(' ') + #if temp_rpart.first == 'const char *' + # temp = 'char *' + temp_rpart.last + #end + #init_var_body += temp + ";\n" + init_array_body += "mrb_intern_lit(mrb, \"#{temp_rpart.last}\"),\n" + #unwrapped_kwargs += Tplt.unwrap_kwarg(index, "#{temp_rpart.last} = #{Tplt.to_c(temp_rpart.first, "kw_values[#{index}]")};", nil, "#{temp_rpart.last} Argument Missing") + if Template.non_struct_types =~ temp_rpart.first + unwrapped_kwargs += Template::C.unwrap_kwarg(index, "wrapped_value->#{temp_rpart.last} = #{Template.to_c(temp_rpart.first, "kw_values[#{index}]")};\n", nil, "Missing kwarg: #{temp_rpart.last.underscore}") + else + # this is for structs or "undetermined" types + # doesnt work yet + next + #unwrapped_kwargs += Tplt.unwrap_kwarg(index, "wrapped_value->#{temp_rpart.last} = (#{temp_rpart.first})kw_values[#{index}];\n") + end + end + body += Template::C.get_kwargs(params)#params.length, '', init_array_body) + body += unwrapped_kwargs + + body += "mrb_data_init(self, wrapped_value, &mrb_#{struct}_struct);\n" + body += 'return self;' + $defines += Template.function("#{struct}_initialize", body) + $init_body += Template.init_function("#{struct.downcase}_class", "initialize", "#{struct}_initialize", "MRB_ARGS_OPT(1)") + + end + + # generates functions + glue.first.each do |func, params| + # func = function name with return type + # func_datatype = function return type + # func_name = function name + # params = array of params with their data types(void means none) + rpart = func.rpartition(' ') + func_datatype = rpart.first + func_name = rpart.last + + # TODO make a skip detector(what functions to skip bindings) + skip = false + #puts "FUNCTION" + #puts "#{func.rpartition(' ').first}| + |#{func.rpartition(' ').last}" + params.each do |param| + #puts "#{param.rpartition(' ').first}| - |#{param.rpartition(' ').last}" + unless (Template.all_valid_types =~ param.rpartition(' ').first) || ("void" == param) + skip = true + break + end + #if param.chars.include? '*' + # unless /^char \*$/ =~ param.rpartition(' ').first + # skip = true + # break + # end + #end + end + next if skip + #next if ['SetTraceLogCallback', 'SetSaveFileTextCallback', 'SetSaveFileDataCallback', 'SetLoadFileTextCallback', 'SetLoadFileDataCallback', 'SetCameraMode', 'GetWorldToScreenEx', 'GetWorldToScreen', 'GetMouseRay', 'GetCameraMatrix', 'DrawBillboardRec', 'DrawBillboardPro', 'DrawBillboard'].include? func_name + + # since void * can be anything just skip functions + # (by default) that use it + next if ['void *'].include? func_datatype + unless Template.all_valid_types =~ func_datatype + puts "// \"#{func_datatype}\" is not a function return datatype that can be currently autobound. From function: \"#{func_name}\"\n\n" + next + end + + body = '' + + # use kwargs + if params.count > 1 + body += Template::C.initialize_variables_for_kwargs(params, glue.last, func_name) + + body += Template::C.get_kwargs(params) + + body += Template::C.parse_kwargs(params) + body += "\n" # formatting + # use args + elsif params.first != 'void' + body += Template::C.initialize_variables_for_args(params, glue.last, func_name) + param_rpart = params.first.rpartition(' ') + body += Template.get_args({ "#{param_rpart.last}": "#{param_rpart.first}" }) + body += Template::C.parse_args(params) + end + + body += Template::C.initialize_return_var(func_datatype, func_name) + body += "\n" # formatting + + body += Template.format_set_method_call(func_datatype, func_name, params, Template.struct_types =~ func_datatype.gsub(/ *\*+$/,'')) + + body += Template.format_return(func_datatype, func_name) + + $defines += "\n//#{func}" + $defines += Template.function(func_name, body) + $init_body += Template.init_module_function(LibraryName.downcase, Template::MRuby.rubify_func_name(func_name, params), func_name, "MRB_ARGS_OPT(1)") + + debug_mark_binding(func, params) + #puts body + # TODO CONTINUE HERE + #puts "// --- NEXT ---" + #next +=begin + # if phase 1 or 2 + if (func_datatype == 'void' && params[0] == 'void') || ((Tplt.non_struct_types.include? func_datatype) && (params[0] == 'void')) + body = Tplt.return_format(func, params) + #$defines += 'PHASE 1\n' + $defines += "\n//#{func}" + $defines += Tplt.function(func_name, body) + $init_body += Tplt.init_module_function(LibraryName.downcase, Tplt.rubify_func_name(func_name), func_name, "MRB_ARGS_NONE()") + + debug_mark_binding(func, params) + else Tplt.non_struct_types.include? func_datatype # accept params + # detecting if there is no struct param(wont need this in the future) + no_struct_param = true + params.each do |param| + if !(Tplt.non_struct_types.include? param.rpartition(' ').first) + no_struct_param = false + break + end + end + if no_struct_param + #if true# Tplt.non_struct_types.include? func.rpartition(' ').first + #$phase3[func] = params + # --- + body = '' + #body = Tplt.return_format(func, params) + init_var_body = '' + init_array_body = '' + unwrapped_kwargs = '' + params.each_with_index do |param, index| + temp = param + temp_rpart = temp.rpartition(' ') + if temp_rpart.first == 'const char *' + temp = 'char *' + temp_rpart.last + end + init_var_body += temp + ";\n" + init_array_body += "mrb_intern_lit(mrb, \"#{temp_rpart.last}\"),\n" + unwrapped_kwargs += Tplt.unwrap_kwarg(index, "#{temp_rpart.last} = #{Tplt.to_c(temp_rpart.first, "kw_values[#{index}]")};", nil, "#{temp_rpart.last} Argument Missing") + end + + # if return isnt regular types, add struct to init + unless Tplt.non_struct_types.include? func_datatype + #init_var_body += "#{func_datatype} *wrapped_value = {0};\n" + end + + body = Tplt.get_kwargs(params.length, init_var_body, init_array_body) + body += unwrapped_kwargs + + # if return isnt regular types, use struct return format + if Tplt.non_struct_types.include? func_datatype + body += Tplt.return_format(func, params) + else + body += Tplt.get_module(LibraryName) + body += Tplt.get_class(func_datatype, LibraryName.downcase) + body += "#{func_datatype} *wrapped_value = (#{func_datatype} *)mrb_malloc(mrb, sizeof(#{func_datatype}));\n" + body += "*wrapped_value = #{func_name}(" + params.each do |param| + temp_rpart = param.rpartition(' ') + body += "#{temp_rpart.last}, " + end + body.delete_suffix!(', ') + body += ");\n" + body += "return mrb_obj_value(Data_Wrap_Struct(mrb, #{func_datatype.downcase}_mrb_class, &mrb_#{func_datatype}_struct, wrapped_value));" + end + + $defines += "\n//#{func}" + $defines += Tplt.function(func_name, body) + $init_body += Tplt.init_module_function(LibraryName.downcase, Tplt.rubify_func_name(func_name), func_name, "MRB_ARGS_OPT(1)") # opt stuff isnt correct, need to look at this again + # --- + #puts func + debug_mark_binding(func, params) + #end + else + #$phase5[func] = params + end + end +end +raise 'end of testing' +=end + end + $init_body.prepend(Template.define_module(LibraryName)) + + $result = %{ +#{$includes} +#{$defines} +#{Template.base(LibraryName.downcase, $init_body, nil)} + } + + + #pp ($phase3.keys - $complete_phase3.keys) + #puts + #pp $complete_phase3 + + all_completed = $complete_phase1.keys | $complete_phase2.keys | $complete_phase3.keys | $complete_phase4.keys | $complete_phase5.keys + all = $phase1.keys | $phase2.keys | $phase3.keys | $phase4.keys | $phase5.keys + + $result += "/* Unbound:\n" + (all - all_completed).each do |unbound| + $result += "#{unbound}\n" + end + $result += "*/\n" + + $result += "//Bound Functions: #{$complete_phase1.length + $complete_phase2.length + $complete_phase3.length + $complete_phase4.length + $complete_phase5.length} / #{$phase1.length + $phase2.length + $phase3.length + $phase4.length + $phase5.length}\n//---\n" + $result += "//Struct Accessors: #{$bound_params.length} / #{$all_params.length}\n//---\n" + + $result += "\n" + + puts $result + + puts '/*' + puts "UNBOUND:" + pp $all_params - $bound_params + puts + puts "BOUND:" + pp $bound_params + puts '*/' + + File.write(destination, $result) + end + end +end diff --git a/src/sample.config.rb b/src/sample.config.rb new file mode 100644 index 0000000..91d883b --- /dev/null +++ b/src/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/src/scan.rb b/src/scan.rb new file mode 100644 index 0000000..9f9aede --- /dev/null +++ b/src/scan.rb @@ -0,0 +1,85 @@ +require 'json' +require 'cast' + +#file_to_scan = 'raylib.h' +module FelBind + class Scan + class << self + # ctags --list-kinds=c + # p function prototypes + # s structure names + # z function parameters inside function or prototype definitions + # m struct, and union members + def ctag(file) + `ctags --output-format=json --c-kinds=pm --fields=+S --language-force=c #{file}` + end + #File.write('json.json', parse) + #$garbage = [] + + def param_strip(signature) + signature[1...-1].split(',') + end + + + def parse_header(path) + parse = `ctags --output-format=json --c-kinds=pm --fields=+S --language-force=c #{path}` + structs = {} + functions = {} + failed = [] + parse.each_line do |line| + json_line = JSON.parse line + if json_line['kind'] == 'prototype' + 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']] ||= [] + structs[json_line['scope']].push "#{json_line['typeref'].delete_prefix('typename:')} #{json_line['name']}" + else + failed.push json_line + end + elsif json_line['kind'] == 'struct' + structs[json_line['name']] = json_line + else + failed.push json_line + end + end + [functions, structs, failed] + end + + + def debug_show(type, hash) + puts "#{type.upcase}:" + puts '---' + hash.each do |key, params| + puts "#{type.capitalize}: #{key}" + params.each do |param| + puts param + end + puts '---' + end + puts + end + + def scan(file, destination) + functions, structs, failed = parse_header(file) + debug_show('functions', functions) + debug_show('structs', structs) + + if !failed.empty? + puts "-- Failed: --" + pp failed + puts + end + + puts "Functions: #{functions.size}" + puts "Structs: #{structs.size}" + puts "Failed: #{failed.size}" + puts + + result = [functions, structs] + + File.write(destination, JSON.generate(result)) + end + end + end +end diff --git a/src/templates.rb b/src/templates.rb new file mode 100644 index 0000000..7d88876 --- /dev/null +++ b/src/templates.rb @@ -0,0 +1,623 @@ +module Template # Template + # methods that convert something from ruby-land to c-land + module C + class << self + def to_c_function_name(function_name:) + "mrb_#{function_name}" + end + + def to_getter_name(struct_name:, variable_name:) + "mrb_#{struct_name}_get_#{variable_name}" + end + + def to_setter_name(struct_name:, variable_name:) + "mrb_#{struct_name}_set_#{variable_name}" + end + + def to_initializer_name(struct_name:) + "mrb_#{struct_name}_initialize" + end + + def format_type(param_datatype) + if Template.treated_as_int =~ param_datatype + 'int' + elsif Template.treated_as_int_pointer =~ param_datatype + 'int' + elsif Template.treated_as_bool =~ param_datatype + 'bool' + elsif Template.treated_as_bool_pointer =~ param_datatype + 'bool' + elsif Template.treated_as_float =~ param_datatype + 'float' + elsif Template.treated_as_float_pointer =~ param_datatype + 'float' + elsif Template.treated_as_string =~ param_datatype + 'char *' + # Ignore for now + #elsif Template.treated_as_string_pointer =~ param_datatype + # 'char *' + elsif Template.struct_types =~ param_datatype + "#{param_datatype}" + elsif Template.struct_types_pointer =~ param_datatype + "#{param_datatype.gsub(/ *\*+$/,'')}" + else + nil # cannot be formated + end + end + + def format_mrb_type(param_datatype) + if Template.treated_as_int =~ param_datatype + 'mrb_int' + elsif Template.treated_as_int_pointer =~ param_datatype + 'mrb_int' + elsif Template.treated_as_bool =~ param_datatype + 'mrb_bool' + elsif Template.treated_as_bool_pointer =~ param_datatype + 'mrb_bool' + elsif Template.treated_as_float =~ param_datatype + 'mrb_float' + elsif Template.treated_as_float_pointer =~ param_datatype + 'mrb_float' + elsif Template.treated_as_string =~ param_datatype + 'char *' + # Ignore for now + #elsif Template.treated_as_string_pointer =~ param_datatype + # 'char *' + elsif Template.struct_types =~ param_datatype + "mrb_value" #"#{param_datatype}" + elsif Template.struct_types_pointer =~ param_datatype + "mrb_value" #"#{param_datatype.gsub(/ *\*+$/,'')}" + else + nil # cannot be formated + end + end + + def convention_parameter(param) + "parameter_#{param}" + end + + def convention_mrb_parameter(param) + "parameter_mrb_#{param}" + end + + def convention_return_variable(func_name) + "return_of_#{func_name}" + end + + def initialize_variables_for_kwargs(params, structs, func_name=nil) + result = '' + return result if params.first == 'void' + params.each do |param| + rpart = param.rpartition(' ') + format = Template::C.format_type(rpart.first) + if format + result += format + " #{'*' if Template.struct_types =~ rpart.first.gsub(/ *\*+$/,'')}#{Template::C.convention_parameter(rpart.last)};\n" + elsif !func_name.nil? + puts "// \"#{rpart.first}\" is not a parameter datatype that can be currently autobound. From function: \"#{func_name}\" and param: #{rpart.last}\n\n" + #raise + end + end + result + "\n" + end + + #def needs_mrb_conversion?(datatype) + # test = Template::C.datatype_to_arg_flag(datatype) + # if ['i','b','z','f'].include? test + # false + # elsif ['o'].include? test + # true + # else + # nil + # end + #end + + def initialize_variables_for_args(params, structs, func_name=nil) + result = '' + return result if params.first == 'void' + params.each do |param| + rpart = param.rpartition(' ') + format = Template::C.format_mrb_type(rpart.first) + if format + if Template.struct_types =~ rpart.first || Template.struct_types_pointer =~ rpart.first + result += format + " #{Template::C.convention_mrb_parameter(rpart.last)};\n" + else + result += format + " #{Template::C.convention_parameter(rpart.last)};\n" + end + elsif !func_name.nil? + puts "// \"#{rpart.first}\" is not a parameter datatype that can be currently autobound. From function: \"#{func_name}\" and param: #{rpart.last}\n\n" + #raise + end + end + result + "\n" + end + + def initialize_return_var(func_datatype, func_name) + return '' if func_datatype == 'void' + result = '' + depointer_datatype = func_datatype.delete_suffix(' *') + if Template.struct_types =~ depointer_datatype + result += Template.get_module('Test') + result += Template.get_class(depointer_datatype, 'Test') + result += "#{depointer_datatype} *#{Template::C.convention_return_variable(func_name)} = (#{depointer_datatype} *)mrb_malloc(mrb, sizeof(#{depointer_datatype}));\n" + else + result += "#{func_datatype} #{Template::C.convention_return_variable(func_name)};\n" + end + result + end + + def get_kwargs(params) + init_array_body = '' + params.each do |param| + rpart = param.rpartition(' ') + init_array_body += "mrb_intern_lit(mrb, \"#{rpart.last.underscore}\"),\n" + end + init_array_body.delete_suffix!(",\n") + %{uint32_t kw_num = #{params.length}; +const mrb_sym kw_names[] = { +#{init_array_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 parse_kwargs(params) + result = '' + skipped = 0 + params.each_with_index do |param, index| + rpart = param.rpartition(' ') + datatype, _space, var_name = param.rpartition(' ') + if Template.struct_types_all =~ datatype + + unwrap = Template.unwrap_struct(Template::C.convention_parameter(var_name), "kw_values[#{index - skipped}]", "mrb_#{datatype.delete_suffix(' *')}_struct", datatype.delete_suffix(' *')) + + result += Template::C.unwrap_kwarg(index - skipped,unwrap, nil, "Missing kwarg: #{var_name.underscore}") + elsif Template.non_struct_types_all =~ datatype + unwrap = "#{Template::C.convention_parameter(var_name)} = #{Template.to_c(datatype, "kw_values[#{index - skipped}]")};" + result += Template::C.unwrap_kwarg(index - skipped, unwrap, nil, "#{var_name.underscore}") + else + skipped += 1 + next + end + end + result + end + + # unwrap structs + # convert floats? + # ignore string + # ignore boolean + # ignore int + def parse_args(params) + result = '' + params.each do |param| + datatype, _space, var_name = param.rpartition(' ') + next unless Template.struct_types =~ datatype || Template.struct_types_pointer =~ datatype + format = Template::C.format_type(datatype) + if format + # init var + result += format + " *#{Template::C.convention_parameter(var_name)};\n" + # unwrap var + result += Template.unwrap_struct(Template::C.convention_parameter(var_name), Template::C.convention_mrb_parameter(var_name), "mrb_#{datatype.delete_suffix(' *')}_struct", datatype.delete_suffix(' *')) + else + # error + end + end + result + end + + def unwrap_kwarg(kwarg_iter, body_if_defined, body_if_undefined = nil, no_argument_error_message = 'Missing Keyword Argument') + %{ +if (mrb_undef_p(kw_values[#{kwarg_iter}])) { +#{body_if_undefined || "mrb_load_string(mrb, \"raise ArgumentError.new \\\"#{no_argument_error_message}\\\"\");"} +} else { +#{body_if_defined} +} + } + end + + end + end + + # methods that convert something from c-land to ruby-land + module MRuby + class << self + # convert a C function name to be + # formatted like a Ruby method name + def rubify_func_name(function, params=[]) + func = function.underscore + if func.start_with? 'is_' + func = func.delete_prefix('is_') + '?' + elsif func.start_with? 'set_' + func = func.delete_prefix('set_') + '=' if params.count == 1 && params.first != 'void' + end + func.delete_prefix('get_') + end + + def to_c_function_name(function_name:) + rubify_func_name(function_name) + end + + def to_getter_name(struct_name:, variable_name: nil) + rubify_func_name(variable_name) + end + + def to_setter_name(struct_name:, variable_name: nil) + rubify_func_name(variable_name) + '=' + end + + def to_initializer_name(struct_name: nil) + "initialize" + end + end + end + + + class << self + + # could be unsigned + attr_writer :treated_as_int + def treated_as_int + @treated_as_int ||= /^((un)?signed )?int$|^((un)?signed )?long$|^((un)?signed )?short$|^((un)?signed )char$/ + end + attr_writer :treated_as_int_pointer + def treated_as_int_pointer + @treated_as_int_pointer ||= /^((un)?signed )?int \*$|^((un)?signed )?long \*$|^((un)?signed )?short \*$|^((un)?signed )char \*$/ + end + + attr_writer :treated_as_bool + def treated_as_bool + @treated_as_bool ||= /^bool$/ + end + attr_writer :treated_as_bool_pointer + def treated_as_bool_pointer + @treated_as_bool_pointer ||= /^bool \*$/ + end + + attr_writer :treated_as_float + def treated_as_float + @treated_as_float ||= /^float$|^double$/ + end + attr_writer :treated_as_float_pointer + def treated_as_float_pointer + @treated_as_float_pointer ||= /^float \*$|^double \*$/ + end + + attr_writer :treated_as_string + def treated_as_string + @treated_as_string ||= /^(const )?char \*$/ + end + # Ignore for now + #attr_writer :treated_as_string_pointer + #def treated_as_string_pointer + # @treated_as_string_pointer ||= /^(const )?char \*\*$/ + #end + + attr_writer :treated_as_void + def treated_as_void + @treated_as_void ||= /^void$/ + end + + def non_struct_types + @non_struct_types ||= Regexp.union(treated_as_int, treated_as_bool, treated_as_float, treated_as_string, treated_as_void) + end + def non_struct_types_pointer + @non_struct_types_pointer ||= Regexp.union(treated_as_int_pointer, treated_as_bool_pointer, treated_as_float_pointer)#, treated_as_string_pointer) + end + def non_struct_types_all + @non_struct_types_all ||= Regexp.union(non_struct_types, non_struct_types_pointer) + end + + attr_writer :struct_types + def struct_types + if @struct_types + @struct_types + else + raise "Struct types were not parsed\nRun 'parse_struct_types' first" + end + end + attr_writer :struct_types_pointer + def struct_types_pointer + if @struct_types_pointer + @struct_types_pointer + else + raise "Struct types were not parsed\nRun 'parse_struct_types' first" + end + end + attr_writer :struct_types_all + def struct_types_all + @struct_types_all ||= Regexp.union(struct_types, struct_types_pointer) + end + + def parse_struct_types(structs) + struct_types = structs.keys + struct_types_pointer = struct_types.map do |string| + "^#{string} \\*$" + end + struct_types.map! do |string| + "^#{string}$" + end + @struct_types_pointer = /#{struct_types_pointer.join('|')}/ + @struct_types = /#{struct_types.join('|')}/ + end + + def valid_types + @valid_types ||= Regexp.union(non_struct_types, struct_types) + end + def valid_types_pointer + @valid_types_pointer ||= Regexp.union(non_struct_types_pointer, struct_types_pointer) + end + def all_valid_types + @all_valid_types ||= Regexp.union(valid_types, valid_types_pointer) + end + + 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 format_method_call(func_datatype, func_name, params, is_struct=false) + result = '' + #if params.first == 'void' + # result += "return #{'*' if is_struct}#{Template::C.convention_return_variable(func_name)} = " + #end + result += "#{func_name}(" + unless params.first == 'void' + params.each do |param| + rpart = param.rpartition(' ') + result += "#{'*' if Template.struct_types =~ rpart.first}#{"(#{rpart.first})&" if Template.non_struct_types_pointer =~ rpart.first}#{Template::C.convention_parameter(rpart.last)}, " + end + end + result.delete_suffix(', ') + ")" + end + + def format_set_method_call(func_datatype, func_name, params, is_struct=false) + result = format_method_call(func_datatype, func_name, params, is_struct) + ";\n" + unless func_datatype == 'void' + if Template.struct_types_pointer =~ func_datatype + result = '*' + result + end + result = "#{Template::C.convention_return_variable(func_name)} = #{result}" + if is_struct + result = '*' + result + end + end + result + end + + def format_return(func_datatype, func_name) + "return #{Template.to_mrb(func_datatype, Template::C.convention_return_variable(func_name))};" + end + + def init_module(module_name) + "struct RClass *#{module_name.downcase}_module = mrb_define_module(mrb, \"#{module_name}\");" + end + + def get_module(module_name) + "struct RClass *#{module_name.downcase}_mrb_module = mrb_module_get(mrb, \"#{module_name}\");\n" + end + + def get_class(class_name, defined_under) + "struct RClass *#{class_name.downcase}_mrb_class = mrb_class_get_under(mrb, #{defined_under.downcase}_mrb_module, mrb_#{class_name}_struct.struct_name);\n" + 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 + + # define under needs the C name, not the ruby name which may be confusing + def init_class(class_name, define_under, is_struct_wrapper = true) + %{ + struct RClass *#{class_name.downcase}_class = mrb_define_class_under(mrb, #{define_under}, \"#{class_name}\", mrb->object_class);#{ + if is_struct_wrapper + "\nMRB_SET_INSTANCE_TT(#{class_name.downcase}_class, MRB_TT_DATA);" + end +} + } + 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_args(req_arg_hash, opt_arg_hash=nil) + raise 'opt_arg_hash feature not implemented yet' if opt_arg_hash + result = '' + tail = '' + flags = '' + req_arg_hash.each do |var_name, var_datatype| + #if var_datatype != 'unsigned char' + #if Template.non_struct_types =~ var_datatype + #result += "#{Template::C.format_type(var_datatype)} #{Template::C.convention_parameter(var_name)};\n" + #else + # result += "mrb_int #{var_name};\n" + #end + if Template.struct_types_all =~ var_datatype + tail += ", &#{Template::C.convention_mrb_parameter(var_name)}" + else + tail += ", &#{Template::C.convention_parameter(var_name)}" + end + flags += datatype_to_arg_flag(var_datatype) + end + result += "mrb_get_args(mrb, \"#{flags}\"#{tail});\n" + end + + def datatype_to_arg_flag(datatype) + if Template.treated_as_int =~ datatype || Template.treated_as_int_pointer =~ datatype + 'i' + elsif Template.treated_as_bool =~ datatype || Template.treated_as_bool_pointer =~ datatype + 'b' + elsif Template.treated_as_float =~ datatype || Template.treated_as_float_pointer =~ datatype + 'f' + elsif Template.treated_as_string =~ datatype + 'z' + elsif Template.struct_types =~ datatype || Template.struct_types_pointer =~ datatype + 'o' + else # failed to match + nil + end + end + + def unwrap_struct(var_name, target, mrb_type, type) + %{#{var_name} = DATA_GET_PTR(mrb, #{target}, &#{mrb_type}, #{type});\n} + end + + def wrap_struct(var_name, target, mrb_type, type) + %{ + #{var_name} = (#{type} *)DATA_PTR(#{target}); + if(#{var_name}) #{'{'} mrb_free(mrb, #{var_name}); #{'}'} + mrb_data_init(#{target}, NULL, &#{mrb_type}); +#{var_name} = (#{type} *)mrb_malloc(mrb, sizeof(#{type})); + } + end + + def define_module(module_name) + %{struct RClass *#{module_name.downcase} = mrb_define_module(mrb, "#{module_name}"); + } + end + + # for converting mrb to C + def to_c(type, variable) + if (Template.treated_as_int =~ type) || (Template.treated_as_bool =~ type) + "mrb_as_int(mrb, #{variable})" + elsif (Template.treated_as_int_pointer =~ type) || (Template.treated_as_bool_pointer =~ type) + "mrb_as_int(mrb, #{variable})" + elsif Template.treated_as_float =~ type + "mrb_as_float(mrb, #{variable})" + elsif Template.treated_as_float_pointer =~ type + "mrb_as_float(mrb, #{variable})" + elsif Template.treated_as_string =~ type + "mrb_str_to_cstr(mrb, #{variable})" + else + "#{type} and #{variable}" + end + end + + # for converting C to mrb + def to_mrb(type, variable) + if Template.treated_as_int =~ type + "mrb_fixnum_value(#{variable})" + elsif Template.treated_as_float =~ type + "mrb_float_value(mrb, #{variable})" + elsif Template.treated_as_bool =~ type + "mrb_bool_value(#{variable})" + elsif Template.treated_as_int_pointer =~ type + "mrb_fixnum_value(*#{variable})" + elsif Template.treated_as_float_pointer =~ type + "mrb_float_value(mrb, *#{variable})" + elsif Template.treated_as_bool_pointer =~ type + "mrb_bool_value(*#{variable})" + elsif Template.treated_as_string =~ type + "mrb_str_new_cstr(mrb, #{variable})" + elsif Template.treated_as_void =~ type + 'mrb_nil_value()' + elsif Template.struct_types =~ type + "mrb_obj_value(Data_Wrap_Struct(mrb, #{type.downcase}_mrb_class, &mrb_#{type}_struct, #{variable}))" + elsif Template.struct_types_pointer =~ type + "mrb_obj_value(Data_Wrap_Struct(mrb, #{type.delete_suffix(' *').downcase}_mrb_class, &mrb_#{type.delete_suffix(' *')}_struct, #{variable}))" + end + end + + + # generate a return of a ruby bound C function + def return_format(function, params) + func_rpart = function.rpartition(' ') + func_datatype = func_rpart.first + func_name = func_rpart.last + result = '' + if func_datatype == 'void' + if params.first == 'void' + result = "#{func_name}();\nreturn mrb_nil_value();" + else + result = "#{func_name}(" + result += params.first.rpartition(' ').last + + params.drop(1).each do |param| + result += ", #{param.rpartition(' ').last}" + end + result += ");\nreturn mrb_nil_value();" + end + elsif params.first == 'void' + result = "return " + Tplt.to_mrb(func_datatype, "#{func_name}()") + ';' + else + temp_params = params.first.rpartition(' ').last + + params.drop(1).each do |param| + temp_params += ", #{param.rpartition(' ').last}" + end + result = 'return ' + Tplt.to_mrb(func_datatype, "#{func_name}(#{temp_params})") + ';' + end + result + end + + # doesnt seem correct? + def return_format_struct(function) + func_rpart = function.rpartition(' ') + func_datatype = func_rpart.first.delete_suffix(' *') + func_name = func_rpart.last + "return mrb_obj_value(Data_Wrap_Struct(mrb, #{func_datatype.downcase}_mrb_class, &mrb_#{func_datatype}_struct, return_value));" + end + + def make_mrb_obj_from_struct(mrb_var, func, struct_var) + func_rpart = func.rpartition(' ') + func_datatype = func_rpart.first.delete_suffix(' *') + func_name = func_rpart.last + "mrb_data_init(#{mrb_var}, #{struct_var}, &mrb_#{func_rpart.first}_struct);\n" + end + + # wrapping an existing struct to be used by ruby + def init_struct_wrapper(struct, free_body = nil) + %{ + #{"void mrb_helper_#{struct}_free(mrb_state*, void*);" if free_body} + + static const struct mrb_data_type mrb_#{struct}_struct = { + "#{struct}", + #{ + if free_body + "mrb_helper_#{struct}_free" + else + "mrb_free" + end +} + }; + #{ + if free_body + + %{ + void + mrb_helper_#{struct}_free(mrb_state* mrb, void*ptr) { + #{struct} *struct_data = (#{struct}*)ptr; + #{free_body} +mrb_free(mrb, ptr); + } + } + end + } + } + end + + end +end + diff --git a/templates.rb b/templates.rb deleted file mode 100644 index 7d88876..0000000 --- a/templates.rb +++ /dev/null @@ -1,623 +0,0 @@ -module Template # Template - # methods that convert something from ruby-land to c-land - module C - class << self - def to_c_function_name(function_name:) - "mrb_#{function_name}" - end - - def to_getter_name(struct_name:, variable_name:) - "mrb_#{struct_name}_get_#{variable_name}" - end - - def to_setter_name(struct_name:, variable_name:) - "mrb_#{struct_name}_set_#{variable_name}" - end - - def to_initializer_name(struct_name:) - "mrb_#{struct_name}_initialize" - end - - def format_type(param_datatype) - if Template.treated_as_int =~ param_datatype - 'int' - elsif Template.treated_as_int_pointer =~ param_datatype - 'int' - elsif Template.treated_as_bool =~ param_datatype - 'bool' - elsif Template.treated_as_bool_pointer =~ param_datatype - 'bool' - elsif Template.treated_as_float =~ param_datatype - 'float' - elsif Template.treated_as_float_pointer =~ param_datatype - 'float' - elsif Template.treated_as_string =~ param_datatype - 'char *' - # Ignore for now - #elsif Template.treated_as_string_pointer =~ param_datatype - # 'char *' - elsif Template.struct_types =~ param_datatype - "#{param_datatype}" - elsif Template.struct_types_pointer =~ param_datatype - "#{param_datatype.gsub(/ *\*+$/,'')}" - else - nil # cannot be formated - end - end - - def format_mrb_type(param_datatype) - if Template.treated_as_int =~ param_datatype - 'mrb_int' - elsif Template.treated_as_int_pointer =~ param_datatype - 'mrb_int' - elsif Template.treated_as_bool =~ param_datatype - 'mrb_bool' - elsif Template.treated_as_bool_pointer =~ param_datatype - 'mrb_bool' - elsif Template.treated_as_float =~ param_datatype - 'mrb_float' - elsif Template.treated_as_float_pointer =~ param_datatype - 'mrb_float' - elsif Template.treated_as_string =~ param_datatype - 'char *' - # Ignore for now - #elsif Template.treated_as_string_pointer =~ param_datatype - # 'char *' - elsif Template.struct_types =~ param_datatype - "mrb_value" #"#{param_datatype}" - elsif Template.struct_types_pointer =~ param_datatype - "mrb_value" #"#{param_datatype.gsub(/ *\*+$/,'')}" - else - nil # cannot be formated - end - end - - def convention_parameter(param) - "parameter_#{param}" - end - - def convention_mrb_parameter(param) - "parameter_mrb_#{param}" - end - - def convention_return_variable(func_name) - "return_of_#{func_name}" - end - - def initialize_variables_for_kwargs(params, structs, func_name=nil) - result = '' - return result if params.first == 'void' - params.each do |param| - rpart = param.rpartition(' ') - format = Template::C.format_type(rpart.first) - if format - result += format + " #{'*' if Template.struct_types =~ rpart.first.gsub(/ *\*+$/,'')}#{Template::C.convention_parameter(rpart.last)};\n" - elsif !func_name.nil? - puts "// \"#{rpart.first}\" is not a parameter datatype that can be currently autobound. From function: \"#{func_name}\" and param: #{rpart.last}\n\n" - #raise - end - end - result + "\n" - end - - #def needs_mrb_conversion?(datatype) - # test = Template::C.datatype_to_arg_flag(datatype) - # if ['i','b','z','f'].include? test - # false - # elsif ['o'].include? test - # true - # else - # nil - # end - #end - - def initialize_variables_for_args(params, structs, func_name=nil) - result = '' - return result if params.first == 'void' - params.each do |param| - rpart = param.rpartition(' ') - format = Template::C.format_mrb_type(rpart.first) - if format - if Template.struct_types =~ rpart.first || Template.struct_types_pointer =~ rpart.first - result += format + " #{Template::C.convention_mrb_parameter(rpart.last)};\n" - else - result += format + " #{Template::C.convention_parameter(rpart.last)};\n" - end - elsif !func_name.nil? - puts "// \"#{rpart.first}\" is not a parameter datatype that can be currently autobound. From function: \"#{func_name}\" and param: #{rpart.last}\n\n" - #raise - end - end - result + "\n" - end - - def initialize_return_var(func_datatype, func_name) - return '' if func_datatype == 'void' - result = '' - depointer_datatype = func_datatype.delete_suffix(' *') - if Template.struct_types =~ depointer_datatype - result += Template.get_module('Test') - result += Template.get_class(depointer_datatype, 'Test') - result += "#{depointer_datatype} *#{Template::C.convention_return_variable(func_name)} = (#{depointer_datatype} *)mrb_malloc(mrb, sizeof(#{depointer_datatype}));\n" - else - result += "#{func_datatype} #{Template::C.convention_return_variable(func_name)};\n" - end - result - end - - def get_kwargs(params) - init_array_body = '' - params.each do |param| - rpart = param.rpartition(' ') - init_array_body += "mrb_intern_lit(mrb, \"#{rpart.last.underscore}\"),\n" - end - init_array_body.delete_suffix!(",\n") - %{uint32_t kw_num = #{params.length}; -const mrb_sym kw_names[] = { -#{init_array_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 parse_kwargs(params) - result = '' - skipped = 0 - params.each_with_index do |param, index| - rpart = param.rpartition(' ') - datatype, _space, var_name = param.rpartition(' ') - if Template.struct_types_all =~ datatype - - unwrap = Template.unwrap_struct(Template::C.convention_parameter(var_name), "kw_values[#{index - skipped}]", "mrb_#{datatype.delete_suffix(' *')}_struct", datatype.delete_suffix(' *')) - - result += Template::C.unwrap_kwarg(index - skipped,unwrap, nil, "Missing kwarg: #{var_name.underscore}") - elsif Template.non_struct_types_all =~ datatype - unwrap = "#{Template::C.convention_parameter(var_name)} = #{Template.to_c(datatype, "kw_values[#{index - skipped}]")};" - result += Template::C.unwrap_kwarg(index - skipped, unwrap, nil, "#{var_name.underscore}") - else - skipped += 1 - next - end - end - result - end - - # unwrap structs - # convert floats? - # ignore string - # ignore boolean - # ignore int - def parse_args(params) - result = '' - params.each do |param| - datatype, _space, var_name = param.rpartition(' ') - next unless Template.struct_types =~ datatype || Template.struct_types_pointer =~ datatype - format = Template::C.format_type(datatype) - if format - # init var - result += format + " *#{Template::C.convention_parameter(var_name)};\n" - # unwrap var - result += Template.unwrap_struct(Template::C.convention_parameter(var_name), Template::C.convention_mrb_parameter(var_name), "mrb_#{datatype.delete_suffix(' *')}_struct", datatype.delete_suffix(' *')) - else - # error - end - end - result - end - - def unwrap_kwarg(kwarg_iter, body_if_defined, body_if_undefined = nil, no_argument_error_message = 'Missing Keyword Argument') - %{ -if (mrb_undef_p(kw_values[#{kwarg_iter}])) { -#{body_if_undefined || "mrb_load_string(mrb, \"raise ArgumentError.new \\\"#{no_argument_error_message}\\\"\");"} -} else { -#{body_if_defined} -} - } - end - - end - end - - # methods that convert something from c-land to ruby-land - module MRuby - class << self - # convert a C function name to be - # formatted like a Ruby method name - def rubify_func_name(function, params=[]) - func = function.underscore - if func.start_with? 'is_' - func = func.delete_prefix('is_') + '?' - elsif func.start_with? 'set_' - func = func.delete_prefix('set_') + '=' if params.count == 1 && params.first != 'void' - end - func.delete_prefix('get_') - end - - def to_c_function_name(function_name:) - rubify_func_name(function_name) - end - - def to_getter_name(struct_name:, variable_name: nil) - rubify_func_name(variable_name) - end - - def to_setter_name(struct_name:, variable_name: nil) - rubify_func_name(variable_name) + '=' - end - - def to_initializer_name(struct_name: nil) - "initialize" - end - end - end - - - class << self - - # could be unsigned - attr_writer :treated_as_int - def treated_as_int - @treated_as_int ||= /^((un)?signed )?int$|^((un)?signed )?long$|^((un)?signed )?short$|^((un)?signed )char$/ - end - attr_writer :treated_as_int_pointer - def treated_as_int_pointer - @treated_as_int_pointer ||= /^((un)?signed )?int \*$|^((un)?signed )?long \*$|^((un)?signed )?short \*$|^((un)?signed )char \*$/ - end - - attr_writer :treated_as_bool - def treated_as_bool - @treated_as_bool ||= /^bool$/ - end - attr_writer :treated_as_bool_pointer - def treated_as_bool_pointer - @treated_as_bool_pointer ||= /^bool \*$/ - end - - attr_writer :treated_as_float - def treated_as_float - @treated_as_float ||= /^float$|^double$/ - end - attr_writer :treated_as_float_pointer - def treated_as_float_pointer - @treated_as_float_pointer ||= /^float \*$|^double \*$/ - end - - attr_writer :treated_as_string - def treated_as_string - @treated_as_string ||= /^(const )?char \*$/ - end - # Ignore for now - #attr_writer :treated_as_string_pointer - #def treated_as_string_pointer - # @treated_as_string_pointer ||= /^(const )?char \*\*$/ - #end - - attr_writer :treated_as_void - def treated_as_void - @treated_as_void ||= /^void$/ - end - - def non_struct_types - @non_struct_types ||= Regexp.union(treated_as_int, treated_as_bool, treated_as_float, treated_as_string, treated_as_void) - end - def non_struct_types_pointer - @non_struct_types_pointer ||= Regexp.union(treated_as_int_pointer, treated_as_bool_pointer, treated_as_float_pointer)#, treated_as_string_pointer) - end - def non_struct_types_all - @non_struct_types_all ||= Regexp.union(non_struct_types, non_struct_types_pointer) - end - - attr_writer :struct_types - def struct_types - if @struct_types - @struct_types - else - raise "Struct types were not parsed\nRun 'parse_struct_types' first" - end - end - attr_writer :struct_types_pointer - def struct_types_pointer - if @struct_types_pointer - @struct_types_pointer - else - raise "Struct types were not parsed\nRun 'parse_struct_types' first" - end - end - attr_writer :struct_types_all - def struct_types_all - @struct_types_all ||= Regexp.union(struct_types, struct_types_pointer) - end - - def parse_struct_types(structs) - struct_types = structs.keys - struct_types_pointer = struct_types.map do |string| - "^#{string} \\*$" - end - struct_types.map! do |string| - "^#{string}$" - end - @struct_types_pointer = /#{struct_types_pointer.join('|')}/ - @struct_types = /#{struct_types.join('|')}/ - end - - def valid_types - @valid_types ||= Regexp.union(non_struct_types, struct_types) - end - def valid_types_pointer - @valid_types_pointer ||= Regexp.union(non_struct_types_pointer, struct_types_pointer) - end - def all_valid_types - @all_valid_types ||= Regexp.union(valid_types, valid_types_pointer) - end - - 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 format_method_call(func_datatype, func_name, params, is_struct=false) - result = '' - #if params.first == 'void' - # result += "return #{'*' if is_struct}#{Template::C.convention_return_variable(func_name)} = " - #end - result += "#{func_name}(" - unless params.first == 'void' - params.each do |param| - rpart = param.rpartition(' ') - result += "#{'*' if Template.struct_types =~ rpart.first}#{"(#{rpart.first})&" if Template.non_struct_types_pointer =~ rpart.first}#{Template::C.convention_parameter(rpart.last)}, " - end - end - result.delete_suffix(', ') + ")" - end - - def format_set_method_call(func_datatype, func_name, params, is_struct=false) - result = format_method_call(func_datatype, func_name, params, is_struct) + ";\n" - unless func_datatype == 'void' - if Template.struct_types_pointer =~ func_datatype - result = '*' + result - end - result = "#{Template::C.convention_return_variable(func_name)} = #{result}" - if is_struct - result = '*' + result - end - end - result - end - - def format_return(func_datatype, func_name) - "return #{Template.to_mrb(func_datatype, Template::C.convention_return_variable(func_name))};" - end - - def init_module(module_name) - "struct RClass *#{module_name.downcase}_module = mrb_define_module(mrb, \"#{module_name}\");" - end - - def get_module(module_name) - "struct RClass *#{module_name.downcase}_mrb_module = mrb_module_get(mrb, \"#{module_name}\");\n" - end - - def get_class(class_name, defined_under) - "struct RClass *#{class_name.downcase}_mrb_class = mrb_class_get_under(mrb, #{defined_under.downcase}_mrb_module, mrb_#{class_name}_struct.struct_name);\n" - 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 - - # define under needs the C name, not the ruby name which may be confusing - def init_class(class_name, define_under, is_struct_wrapper = true) - %{ - struct RClass *#{class_name.downcase}_class = mrb_define_class_under(mrb, #{define_under}, \"#{class_name}\", mrb->object_class);#{ - if is_struct_wrapper - "\nMRB_SET_INSTANCE_TT(#{class_name.downcase}_class, MRB_TT_DATA);" - end -} - } - 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_args(req_arg_hash, opt_arg_hash=nil) - raise 'opt_arg_hash feature not implemented yet' if opt_arg_hash - result = '' - tail = '' - flags = '' - req_arg_hash.each do |var_name, var_datatype| - #if var_datatype != 'unsigned char' - #if Template.non_struct_types =~ var_datatype - #result += "#{Template::C.format_type(var_datatype)} #{Template::C.convention_parameter(var_name)};\n" - #else - # result += "mrb_int #{var_name};\n" - #end - if Template.struct_types_all =~ var_datatype - tail += ", &#{Template::C.convention_mrb_parameter(var_name)}" - else - tail += ", &#{Template::C.convention_parameter(var_name)}" - end - flags += datatype_to_arg_flag(var_datatype) - end - result += "mrb_get_args(mrb, \"#{flags}\"#{tail});\n" - end - - def datatype_to_arg_flag(datatype) - if Template.treated_as_int =~ datatype || Template.treated_as_int_pointer =~ datatype - 'i' - elsif Template.treated_as_bool =~ datatype || Template.treated_as_bool_pointer =~ datatype - 'b' - elsif Template.treated_as_float =~ datatype || Template.treated_as_float_pointer =~ datatype - 'f' - elsif Template.treated_as_string =~ datatype - 'z' - elsif Template.struct_types =~ datatype || Template.struct_types_pointer =~ datatype - 'o' - else # failed to match - nil - end - end - - def unwrap_struct(var_name, target, mrb_type, type) - %{#{var_name} = DATA_GET_PTR(mrb, #{target}, &#{mrb_type}, #{type});\n} - end - - def wrap_struct(var_name, target, mrb_type, type) - %{ - #{var_name} = (#{type} *)DATA_PTR(#{target}); - if(#{var_name}) #{'{'} mrb_free(mrb, #{var_name}); #{'}'} - mrb_data_init(#{target}, NULL, &#{mrb_type}); -#{var_name} = (#{type} *)mrb_malloc(mrb, sizeof(#{type})); - } - end - - def define_module(module_name) - %{struct RClass *#{module_name.downcase} = mrb_define_module(mrb, "#{module_name}"); - } - end - - # for converting mrb to C - def to_c(type, variable) - if (Template.treated_as_int =~ type) || (Template.treated_as_bool =~ type) - "mrb_as_int(mrb, #{variable})" - elsif (Template.treated_as_int_pointer =~ type) || (Template.treated_as_bool_pointer =~ type) - "mrb_as_int(mrb, #{variable})" - elsif Template.treated_as_float =~ type - "mrb_as_float(mrb, #{variable})" - elsif Template.treated_as_float_pointer =~ type - "mrb_as_float(mrb, #{variable})" - elsif Template.treated_as_string =~ type - "mrb_str_to_cstr(mrb, #{variable})" - else - "#{type} and #{variable}" - end - end - - # for converting C to mrb - def to_mrb(type, variable) - if Template.treated_as_int =~ type - "mrb_fixnum_value(#{variable})" - elsif Template.treated_as_float =~ type - "mrb_float_value(mrb, #{variable})" - elsif Template.treated_as_bool =~ type - "mrb_bool_value(#{variable})" - elsif Template.treated_as_int_pointer =~ type - "mrb_fixnum_value(*#{variable})" - elsif Template.treated_as_float_pointer =~ type - "mrb_float_value(mrb, *#{variable})" - elsif Template.treated_as_bool_pointer =~ type - "mrb_bool_value(*#{variable})" - elsif Template.treated_as_string =~ type - "mrb_str_new_cstr(mrb, #{variable})" - elsif Template.treated_as_void =~ type - 'mrb_nil_value()' - elsif Template.struct_types =~ type - "mrb_obj_value(Data_Wrap_Struct(mrb, #{type.downcase}_mrb_class, &mrb_#{type}_struct, #{variable}))" - elsif Template.struct_types_pointer =~ type - "mrb_obj_value(Data_Wrap_Struct(mrb, #{type.delete_suffix(' *').downcase}_mrb_class, &mrb_#{type.delete_suffix(' *')}_struct, #{variable}))" - end - end - - - # generate a return of a ruby bound C function - def return_format(function, params) - func_rpart = function.rpartition(' ') - func_datatype = func_rpart.first - func_name = func_rpart.last - result = '' - if func_datatype == 'void' - if params.first == 'void' - result = "#{func_name}();\nreturn mrb_nil_value();" - else - result = "#{func_name}(" - result += params.first.rpartition(' ').last - - params.drop(1).each do |param| - result += ", #{param.rpartition(' ').last}" - end - result += ");\nreturn mrb_nil_value();" - end - elsif params.first == 'void' - result = "return " + Tplt.to_mrb(func_datatype, "#{func_name}()") + ';' - else - temp_params = params.first.rpartition(' ').last - - params.drop(1).each do |param| - temp_params += ", #{param.rpartition(' ').last}" - end - result = 'return ' + Tplt.to_mrb(func_datatype, "#{func_name}(#{temp_params})") + ';' - end - result - end - - # doesnt seem correct? - def return_format_struct(function) - func_rpart = function.rpartition(' ') - func_datatype = func_rpart.first.delete_suffix(' *') - func_name = func_rpart.last - "return mrb_obj_value(Data_Wrap_Struct(mrb, #{func_datatype.downcase}_mrb_class, &mrb_#{func_datatype}_struct, return_value));" - end - - def make_mrb_obj_from_struct(mrb_var, func, struct_var) - func_rpart = func.rpartition(' ') - func_datatype = func_rpart.first.delete_suffix(' *') - func_name = func_rpart.last - "mrb_data_init(#{mrb_var}, #{struct_var}, &mrb_#{func_rpart.first}_struct);\n" - end - - # wrapping an existing struct to be used by ruby - def init_struct_wrapper(struct, free_body = nil) - %{ - #{"void mrb_helper_#{struct}_free(mrb_state*, void*);" if free_body} - - static const struct mrb_data_type mrb_#{struct}_struct = { - "#{struct}", - #{ - if free_body - "mrb_helper_#{struct}_free" - else - "mrb_free" - end -} - }; - #{ - if free_body - - %{ - void - mrb_helper_#{struct}_free(mrb_state* mrb, void*ptr) { - #{struct} *struct_data = (#{struct}*)ptr; - #{free_body} -mrb_free(mrb, ptr); - } - } - end - } - } - end - - end -end - -- cgit v1.2.3