summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorrealtradam <[email protected]>2022-08-31 08:29:23 -0400
committerrealtradam <[email protected]>2022-08-31 08:29:23 -0400
commitc51ded6c4efef4e0ef5a157a72ae79c402b7964b (patch)
tree46802e9bfaae9d97538ab94285c3357f7ca65c94 /src
parent8caf4473882f3eab9018b0ed4500792d459bdc98 (diff)
downloadFelBind-c51ded6c4efef4e0ef5a157a72ae79c402b7964b.tar.gz
FelBind-c51ded6c4efef4e0ef5a157a72ae79c402b7964b.zip
starting work on the backend system
Diffstat (limited to 'src')
-rw-r--r--src/backend/test.h12
-rw-r--r--src/backend/uctags.rb146
-rw-r--r--src/generate.rb444
-rw-r--r--src/sample.config.rb45
-rw-r--r--src/scan.rb85
-rw-r--r--src/templates.rb623
6 files changed, 1355 insertions, 0 deletions
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 <raylib.h>
+#include <mruby.h>
+#include <mruby/array.h>
+#include <mruby/data.h>
+#include <mruby/class.h>
+#include <mruby/numeric.h>
+#include <mruby/string.h>
+#include <mruby/compile.h>
+#include <stdlib.h>
+ }
+
+
+ $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
+