From 87727cdfbcb3d93f2a3216ab8dc20f40d5cfe985 Mon Sep 17 00:00:00 2001 From: realtradam Date: Thu, 25 May 2023 15:02:05 -0400 Subject: basic params --- .gitignore | 11 +- .rubocop.yml | 13 + .tool-versions | 1 - CHANGELOG.md | 5 + FelBind.gemspec | 39 +++ Gemfile | 7 +- Gemfile.lock | 35 ++- LICENSE.txt | 21 ++ README.md | 33 +++ Rakefile | 27 +- bin/console | 15 + bin/setup | 8 + exe/felbind | 78 ++++++ ideas/stuff.rb | 80 ++++++ lib/FelBind.rb | 156 +++++++++++ lib/FelBind/backend/test.rb | 4 + lib/FelBind/backend/uctags.rb | 102 +++++++ lib/FelBind/frontend/mruby.rb | 502 ++++++++++++++++++++++++++++++++++ lib/FelBind/generate.rb | 444 ++++++++++++++++++++++++++++++ lib/FelBind/sample.config.rb | 45 +++ lib/FelBind/scan.rb | 85 ++++++ lib/FelBind/templates.rb | 623 ++++++++++++++++++++++++++++++++++++++++++ lib/FelBind/version.rb | 5 + planning/structure.png | Bin 0 -> 212489 bytes sig/FelBind.rbs | 4 + src/backend/test.h | 12 - src/backend/test.rb | 4 - src/backend/uctags.rb | 102 ------- src/generate.rb | 444 ------------------------------ src/sample.config.rb | 45 --- src/scan.rb | 85 ------ src/templates.rb | 623 ------------------------------------------ 32 files changed, 2314 insertions(+), 1344 deletions(-) create mode 100644 .rubocop.yml delete mode 100644 .tool-versions create mode 100644 CHANGELOG.md create mode 100644 FelBind.gemspec create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100755 bin/console create mode 100755 bin/setup create mode 100755 exe/felbind create mode 100644 ideas/stuff.rb create mode 100644 lib/FelBind.rb create mode 100644 lib/FelBind/backend/test.rb create mode 100644 lib/FelBind/backend/uctags.rb create mode 100644 lib/FelBind/frontend/mruby.rb create mode 100644 lib/FelBind/generate.rb create mode 100644 lib/FelBind/sample.config.rb create mode 100644 lib/FelBind/scan.rb create mode 100644 lib/FelBind/templates.rb create mode 100644 lib/FelBind/version.rb create mode 100644 planning/structure.png create mode 100644 sig/FelBind.rbs delete mode 100644 src/backend/test.h delete mode 100644 src/backend/test.rb delete mode 100644 src/backend/uctags.rb delete mode 100644 src/generate.rb delete mode 100644 src/sample.config.rb delete mode 100644 src/scan.rb delete mode 100644 src/templates.rb diff --git a/.gitignore b/.gitignore index 2f6fcd2..e4502a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,13 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +/*.json json.json raylib.h chipmunk.h @@ -9,4 +19,3 @@ temp temp.c build build/* -planning/*.png diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..e3462a7 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,13 @@ +AllCops: + TargetRubyVersion: 2.6 + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + Enabled: true + EnforcedStyle: double_quotes + +Layout/LineLength: + Max: 120 diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 0b2d858..0000000 --- a/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -ruby 3.1.2 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d3fd5d9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## [Unreleased] + +## [0.1.0] - 2022-09-15 + +- Initial release diff --git a/FelBind.gemspec b/FelBind.gemspec new file mode 100644 index 0000000..83dbe12 --- /dev/null +++ b/FelBind.gemspec @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require_relative "lib/FelBind/version" + +Gem::Specification.new do |spec| + spec.name = "FelBind" + spec.version = FelBind::VERSION + spec.authors = ["realtradam"] + spec.email = ["github@tradam.dev"] + + spec.summary = "Write a short summary, because RubyGems requires one." #TODO + #spec.description = "TODO: Write a longer description or delete this line." + spec.homepage = "https://github.com/realtradam/FelBind"#"TODO: Put your gem's website or public repo URL here." + spec.license = "MIT" + spec.required_ruby_version = ">= 2.6.0" + + spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage#"TODO: Put your gem's public repo URL here." + #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) + end + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + # Uncomment to register a new dependency of your gem + # spec.add_dependency "example-gem", "~> 1.0" + + # For more information and examples about making a new gem, check out our + # guide at: https://bundler.io/guides/creating_gem.html +end diff --git a/Gemfile b/Gemfile index 527fc7f..d7e57ed 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,9 @@ source "https://rubygems.org" -# gem "rails" +# Specify your gem's dependencies in FelBind.gemspec +gemspec -gem "cast", "~> 0.3.1" +gem "rake", "~> 13.0" + +gem "rubocop", "~> 1.21" diff --git a/Gemfile.lock b/Gemfile.lock index c32dc74..602c7ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,13 +1,42 @@ +PATH + remote: . + specs: + FelBind (0.1.0) + GEM remote: https://rubygems.org/ specs: - cast (0.3.1) + ast (2.4.2) + json (2.6.3) + parallel (1.22.1) + parser (3.2.1.0) + ast (~> 2.4.1) + rainbow (3.1.1) + rake (13.0.6) + regexp_parser (2.7.0) + rexml (3.2.5) + rubocop (1.45.1) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.2.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.24.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.26.0) + parser (>= 3.2.1.0) + ruby-progressbar (1.11.0) + unicode-display_width (2.4.2) PLATFORMS x86_64-linux DEPENDENCIES - cast (~> 0.3.1) + FelBind! + rake (~> 13.0) + rubocop (~> 1.21) BUNDLED WITH - 2.3.20 + 2.4.6 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..40cd76e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 realtradam + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8210486 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# FelBind + +Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/FelBind`. To experiment with that code, run `bin/console` for an interactive prompt. + +TODO: Delete this and the text above, and describe your gem + +## Installation + +Install the gem and add to the application's Gemfile by executing: + + $ bundle add FelBind + +If bundler is not being used to manage dependencies, install the gem by executing: + + $ gem install FelBind + +## Usage + +TODO: Write usage instructions here + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/FelBind. + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile index 986a40b..1924143 100644 --- a/Rakefile +++ b/Rakefile @@ -1,25 +1,8 @@ -require_relative 'scan.rb' -require_relative 'generate.rb' +# frozen_string_literal: true -desc 'create parsed file with ctags' -task :scan do - # for each file in target directory - # parse file - # output to build/parse - Dir.mkdir('build') unless File.exists?('build') - Dir.each_child('target') do |file| - Scan.scan("target/#{file}", 'build/parsed.json') - end -end +require "bundler/gem_tasks" +require "rubocop/rake_task" -desc 'build bindings from the parsed file' -task :generate do - # read parse file - # output to build/bind - Generate.generate('build/parsed.json', '../FelFlameEngine/mrbgems/mruby-raylib/src/bind.c') -end +RuboCop::RakeTask.new -task :make_gem do - # read bind file - # output to build/gem -end +task default: :rubocop diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..871d911 --- /dev/null +++ b/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "FelBind" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/exe/felbind b/exe/felbind new file mode 100755 index 0000000..efb432a --- /dev/null +++ b/exe/felbind @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +require 'fileutils' +require 'FelBind' + +def print_help + commands = [ + " # Prints Help", + " help", + "", + " # prints out intermediate", + " parse - ", + " Valid Backends:", + " -uctags (default)", + "", + " # Creates bindings gem", + " build - ", + " Valid Frontends:", + " -mruby (default)", + ] + + commands.each { |cmd| puts cmd } +end + +if (ARGV.size == 0) || (ARGV[0] == 'help') + print_help + return +end + +if ARGV[0] == 'parse' + file_path = '' + if ARGV[1].start_with? '-' + if ARGV[1] != '-uctags' + puts 'invalid backend' + return + else + file_path = ARGV[2] + end + else + file_path = ARGV[1] + end + + if !File.exist?(file_path) + puts "invalid file path" + return + end + + pp FelBind::Backends::UCTags.parse(file_path) +elsif ARGV[0] == 'build' + file_path = '' + dest_dir = '' + if ARGV[1] == '-mruby' + file_path = ARGV[2] + dest_dir = ARGV[3] + else + file_path = ARGV[1] + dest_dir = ARGV[2] + end + + if !File.exist?(file_path) + puts "invalid file path" + return + end + + puts "TEST" + FileUtils.mkpath(dest_dir) unless File.exist?(dest_dir) + + #if !File.directory? + # puts "invalid destination" + # return + #end + + FelBind::Frontend::Mruby.build(file_path, dest_dir) +else + print_help + return +end + diff --git a/ideas/stuff.rb b/ideas/stuff.rb new file mode 100644 index 0000000..276a101 --- /dev/null +++ b/ideas/stuff.rb @@ -0,0 +1,80 @@ +# frozen_string_literal:true + +# --- + +mgem = FelBind::BindGem.new(gem_name: "basic_example") + +mgem.add_class("BasicExample") + +mgem.add_function(class_name: "BasicExample", function_name: "say_hello") do |func| + func.content = "printf(\"Hello World\n\");" + func.return_call do |rc| + rc.type = "nil" + end +end + +puts mgem.build + +# --- + +mgem = FelBind::BindGem.new + +mgem.add_class("ArgumentsAndReturnExample") + +mgem.add_function(class: "ArgumentsAndReturnExample", name: "multiply_numbers") do |func| + func.get_args do |args| + args.int "first_input" + args.int "second_input" + end + + func.return_call do |rc| + rc.type = "int" + rc.val = "#{func.arg("first_input")} * #{func.arg("second_input")}" + end +end + +# --- + +mgem = BindGem.new + +mgem.add_class("KeywordArgumentsExample") + +mgem.add_function(class: "KeywordArgumentsExample", name: "multiply_numbers") do |func| + func.get_kwargs do |kwargs| + kwargs.args x: "int", y: "int" + end + + func.return_call do |rc| + rc.rtype = "int" + rc.val = "#{func.kwarg("x")} * #{func.kwarg("y")}" + end +end + +# --- + +mgem = BindGem.new + +mgem.add_class("Color") + +mgem.add_struct(class: "Color") do |struct| + struct.initializer = true + struct.member( + name: "r", + ctype: "char", + rtype: "int", + accessor: true + ) + struct.member( + name: "g", + ctype: "char", + rtype: "int", + accessor: true + ) + struct.member( + name: "b", + ctype: "char", + rtype: "int", + accessor: true + ) +end + diff --git a/lib/FelBind.rb b/lib/FelBind.rb new file mode 100644 index 0000000..1ca1cff --- /dev/null +++ b/lib/FelBind.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +=begin +require_relative "FelBind/version" +require_relative "FelBind/backend/uctags.rb" +require_relative "FelBind/frontend/mruby.rb" +=end + +module FelBind + class Error < StandardError; end + # Your code goes here... + + # Binding C to mruby + class BindGem + attr_accessor :gem_name + + def initialize(gem_name:) + self.gem_name = gem_name + end + + def add_class(name) + class_names.push name + end + + def add_function(class_name:, function_name:, &block) + functions.push Function.new(class_name: class_name, function_name: function_name) + block.call(functions.last) + end + + private + + def functions + @functions ||= [] + end + + def class_names + @class_names ||= [] + end + + # function + class Function + attr_accessor :content, :name, :class_name, :return_call_val, :args + + def initialize(class_name:, function_name:) + self.class_name = class_name + self.name = function_name + end + + def return_call(&block) + self.return_call_val = ReturnCall.new + block.call(return_call_val) + end + + def build_get_vars + result = "" + expect = "" + addresses = "" + args.arguments.each do |param| + if param.first == :int + result += "mrb_int #{arg(param.last)};\n" + expect += "i" + addresses += ", &#{arg(param.last)}" + end + end + addresses.delete_prefix! ", " + result += "mrb_get_args(mrb, \"#{expect}\", #{addresses});\n" + end + + def build + function = "static mrb_value\n" + function += "felbind_#{name}(mrb_state *mrb, mrb_value self){\n" + function += build_get_vars + function += "#{content}\n" + function += "#{return_call_val.build}" + function += "}\n" + function + end + + def arg(name) + "felflame_var_#{name}" + end + + def get_args(&block) + self.args = Args.new + block.call(args) + end + + # args + class Args + def arguments + @arguments ||= [] + end + + def int(name) + arguments.push [:int, name] + end + end + + # return call + class ReturnCall + attr_accessor :type, :val + + def build + if type == "nil" + "return mrb_nil_value();\n" + elsif type == "int" + "return mrb_fixnum_value(#{val});\n" + end + end + end + end + end + + # bind gem + class BindGem + def build + result = "" + result += insert_includes + functions.each do |func_obj| + result += func_obj.build + end + result += build_init + result += build_final + result + end + + private + + def insert_includes + "#include \n#include \n" + end + + def build_init + result = "" + result += "void mrb_#{gem_name}_gem_init(mrb_state* mrb) {\n" + class_names.each do |class_name| + result += "struct RClass *#{class_name}_class = mrb_define_module(mrb, \"#{class_name}\");\n" + end + functions.each do |func| + result += "mrb_define_class_method(mrb, #{func.class_name}_class, \"#{func.name}\", felbind_#{func.name}," + if(func.args.arguments.size.zero?) + result += " MRB_ARGS_NONE()" + else + result += " MRB_ARGS_REQ(#{func.args.arguments.size})" + end + result += ");\n" + end + result += "}\n" + result + end + + def build_final + "void mrb_#{gem_name}_gem_final(mrb_state* mrb) {}\n" + end + end +end diff --git a/lib/FelBind/backend/test.rb b/lib/FelBind/backend/test.rb new file mode 100644 index 0000000..956ccca --- /dev/null +++ b/lib/FelBind/backend/test.rb @@ -0,0 +1,4 @@ +require_relative './uctags.rb' + +pp FelBind::Backends::UCTags.parse('test.h') +#pp FelBind::Backends::UCTags.parse('raylib.h') diff --git a/lib/FelBind/backend/uctags.rb b/lib/FelBind/backend/uctags.rb new file mode 100644 index 0000000..010773e --- /dev/null +++ b/lib/FelBind/backend/uctags.rb @@ -0,0 +1,102 @@ +require 'json' + +module FelBind + module Backends + class Intermediate < Hash + + def initialize + self[:GemName] = '' + self[:Typedefs] = {} + self[:CFunctions] = {} + self[:CStructs] = {} + end + + def add_function(name:, params:) + func = {} + self[:CFunctions][name] = func + func[:RubyName] = name + func[:Params] = {} + params.each do |param| + temp = param.rpartition(' ') + func[:Params][temp.last] = [temp.last, temp.first] + end + end + + def add_struct(name:) + self[:CStructs][name] = {} + end + + def add_struct_param(struct_name:, param_name:, type:) + struct = self[:CStructs][struct_name] + struct[:Accessors] = [] + struct[:Accessors].push [ + param_name, + { + RubyName: param_name, + Type: type, + GetterSkip: false, + SetterSkip: false, + }, + ] + end + + def add_typedef(typedef:, c_type:) + self[:Typedefs][typedef] = c_type + 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 + intermediate.add_function(name: tag["name"], params: tag["signature"][1...-1].split(',')) + elsif tag["kind"] == "typedef" + if tag["typeref"].split(':').first == "typename" + # is a typedef + puts 'its an alias(typedef)' #TODO remove + intermediate.add_typedef(typedef: tag["name"], c_type: tag["typeref"].split(':').last) + elsif tag["typeref"].split(':').first == "struct" + # is a struct + puts 'its a struct' #TODO remove + intermediate.add_struct(name: tag["name"]) + else + puts "warning: no match" #TODO better errors + end + elsif tag['kind'] == 'member' + # is struct param + puts 'its a struct param' #TODO remove + intermediate.add_struct_param(struct_name: tag["scope"], param_name: tag["name"], type: tag["typeref"].split(":").last) + else + puts "warning: no match" #TODO better errors + end + end + + return intermediate + end + end + end + end +end diff --git a/lib/FelBind/frontend/mruby.rb b/lib/FelBind/frontend/mruby.rb new file mode 100644 index 0000000..4d2fb87 --- /dev/null +++ b/lib/FelBind/frontend/mruby.rb @@ -0,0 +1,502 @@ +#require 'optparse' +require 'json' +require 'active_record' # use to make strings to snake_case. probably overkill +#require_relative './templates.rb' +#LibraryName = 'Test' + +module FelBind + module Frontend + class Mruby < Hash + class << self + + end + + attr_accessor :intermediate + + def initialize(file_path) + # TODO wierd Struct_Deinit thing in the flowchart + # ignored it for now + self[:headers] = "" + self[:structs] = [] + # This is how each array item looks + #{ + # struct_type_init: "", + # struct_getter_method: [], + # struct_setter_method: [], + # struct_initializer: "", + #} + self[:initializer] = { + modules: { name: "", code: "" }, + classes: [] + { + name: "", + + }, + } + + self[:methods] = "" + + self.intermediate = JSON.parse(File.read(file_path)) + end + + def gen_headers + self[:headers] + end + + + # module Methods + # class << self + + # def build + # end + + # def initialize_vars + # end + + # end + # end + + # module Structs + # end + end + end +end + +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/lib/FelBind/generate.rb b/lib/FelBind/generate.rb new file mode 100644 index 0000000..4fa41be --- /dev/null +++ b/lib/FelBind/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/lib/FelBind/sample.config.rb b/lib/FelBind/sample.config.rb new file mode 100644 index 0000000..91d883b --- /dev/null +++ b/lib/FelBind/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/lib/FelBind/scan.rb b/lib/FelBind/scan.rb new file mode 100644 index 0000000..9f9aede --- /dev/null +++ b/lib/FelBind/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/lib/FelBind/templates.rb b/lib/FelBind/templates.rb new file mode 100644 index 0000000..7d88876 --- /dev/null +++ b/lib/FelBind/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/lib/FelBind/version.rb b/lib/FelBind/version.rb new file mode 100644 index 0000000..48c42e3 --- /dev/null +++ b/lib/FelBind/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module FelBind + VERSION = "0.1.0" +end diff --git a/planning/structure.png b/planning/structure.png new file mode 100644 index 0000000..6996f20 Binary files /dev/null and b/planning/structure.png differ diff --git a/sig/FelBind.rbs b/sig/FelBind.rbs new file mode 100644 index 0000000..38e8ac4 --- /dev/null +++ b/sig/FelBind.rbs @@ -0,0 +1,4 @@ +module FelBind + VERSION: String + # See the writing guide of rbs: https://github.com/ruby/rbs#guides +end diff --git a/src/backend/test.h b/src/backend/test.h deleted file mode 100644 index c7b5ea9..0000000 --- a/src/backend/test.h +++ /dev/null @@ -1,12 +0,0 @@ -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/test.rb b/src/backend/test.rb deleted file mode 100644 index 956ccca..0000000 --- a/src/backend/test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require_relative './uctags.rb' - -pp FelBind::Backends::UCTags.parse('test.h') -#pp FelBind::Backends::UCTags.parse('raylib.h') diff --git a/src/backend/uctags.rb b/src/backend/uctags.rb deleted file mode 100644 index 010773e..0000000 --- a/src/backend/uctags.rb +++ /dev/null @@ -1,102 +0,0 @@ -require 'json' - -module FelBind - module Backends - class Intermediate < Hash - - def initialize - self[:GemName] = '' - self[:Typedefs] = {} - self[:CFunctions] = {} - self[:CStructs] = {} - end - - def add_function(name:, params:) - func = {} - self[:CFunctions][name] = func - func[:RubyName] = name - func[:Params] = {} - params.each do |param| - temp = param.rpartition(' ') - func[:Params][temp.last] = [temp.last, temp.first] - end - end - - def add_struct(name:) - self[:CStructs][name] = {} - end - - def add_struct_param(struct_name:, param_name:, type:) - struct = self[:CStructs][struct_name] - struct[:Accessors] = [] - struct[:Accessors].push [ - param_name, - { - RubyName: param_name, - Type: type, - GetterSkip: false, - SetterSkip: false, - }, - ] - end - - def add_typedef(typedef:, c_type:) - self[:Typedefs][typedef] = c_type - 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 - intermediate.add_function(name: tag["name"], params: tag["signature"][1...-1].split(',')) - elsif tag["kind"] == "typedef" - if tag["typeref"].split(':').first == "typename" - # is a typedef - puts 'its an alias(typedef)' #TODO remove - intermediate.add_typedef(typedef: tag["name"], c_type: tag["typeref"].split(':').last) - elsif tag["typeref"].split(':').first == "struct" - # is a struct - puts 'its a struct' #TODO remove - intermediate.add_struct(name: tag["name"]) - else - puts "warning: no match" #TODO better errors - end - elsif tag['kind'] == 'member' - # is struct param - puts 'its a struct param' #TODO remove - intermediate.add_struct_param(struct_name: tag["scope"], param_name: tag["name"], type: tag["typeref"].split(":").last) - else - puts "warning: no match" #TODO better errors - end - end - - return intermediate - end - end - end - end -end diff --git a/src/generate.rb b/src/generate.rb deleted file mode 100644 index 4fa41be..0000000 --- a/src/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/src/sample.config.rb b/src/sample.config.rb deleted file mode 100644 index 91d883b..0000000 --- a/src/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/src/scan.rb b/src/scan.rb deleted file mode 100644 index 9f9aede..0000000 --- a/src/scan.rb +++ /dev/null @@ -1,85 +0,0 @@ -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 deleted file mode 100644 index 7d88876..0000000 --- a/src/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