diff options
| author | realtradam <[email protected]> | 2022-08-31 08:29:23 -0400 |
|---|---|---|
| committer | realtradam <[email protected]> | 2022-08-31 08:29:23 -0400 |
| commit | c51ded6c4efef4e0ef5a157a72ae79c402b7964b (patch) | |
| tree | 46802e9bfaae9d97538ab94285c3357f7ca65c94 /src | |
| parent | 8caf4473882f3eab9018b0ed4500792d459bdc98 (diff) | |
| download | FelBind-c51ded6c4efef4e0ef5a157a72ae79c402b7964b.tar.gz FelBind-c51ded6c4efef4e0ef5a157a72ae79c402b7964b.zip | |
starting work on the backend system
Diffstat (limited to 'src')
| -rw-r--r-- | src/backend/test.h | 12 | ||||
| -rw-r--r-- | src/backend/uctags.rb | 146 | ||||
| -rw-r--r-- | src/generate.rb | 444 | ||||
| -rw-r--r-- | src/sample.config.rb | 45 | ||||
| -rw-r--r-- | src/scan.rb | 85 | ||||
| -rw-r--r-- | src/templates.rb | 623 |
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 + |
