summaryrefslogtreecommitdiffhomepage
path: root/mrbgems/raylib/tools/gen_raylib.rb
diff options
context:
space:
mode:
Diffstat (limited to 'mrbgems/raylib/tools/gen_raylib.rb')
-rw-r--r--mrbgems/raylib/tools/gen_raylib.rb402
1 files changed, 402 insertions, 0 deletions
diff --git a/mrbgems/raylib/tools/gen_raylib.rb b/mrbgems/raylib/tools/gen_raylib.rb
new file mode 100644
index 0000000..794c668
--- /dev/null
+++ b/mrbgems/raylib/tools/gen_raylib.rb
@@ -0,0 +1,402 @@
+#!/usr/bin/env ruby
+# Generates C mruby bindings for ALL supported raylib functions/structs/enums
+# from raylib's official parser output (raylib_api.json).
+#
+# ruby gen_raylib.rb <raylib_api.json> <out.c>
+#
+# Supported marshaling:
+# scalars (int/uint/char/short/long/float/double/bool), const char* (string),
+# raylib structs by value (wrapped mruby Data objects), and single struct
+# pointers (passed by reference -> inout). Functions using other pointer kinds
+# (primitive arrays, void*, char**, callbacks, varargs, pointer/array returns)
+# are skipped and listed in a comment at the top of the output.
+require 'json'
+
+json_path, out_path, raymath_path = ARGV
+abort "usage: gen_raylib.rb <api.json> <out.c> [raymath_api.json]" unless json_path && out_path
+
+# Load a raylib API json, tolerating a known raylib 6.0 bug: the
+# LoadDirectoryFilesEx description contains literal unescaped double-quotes
+# ("*.*", "FILES*", "DIRS*") that the parser copies verbatim, breaking strict
+# JSON. We escape those embedded quotes and retry. Safe on already-valid json:
+# the escaped form (\"X\") does not contain the unescaped substring "X".
+def load_api(path)
+ raw = File.read(path)
+ return JSON.parse(raw) rescue JSON.parse(raw
+ .gsub('"*.*"', '\"*.*\"')
+ .gsub('"FILES*"', '\"FILES*\"')
+ .gsub('"DIRS*"', '\"DIRS*\"'))
+end
+
+api = load_api(json_path)
+raymath = (raymath_path && File.exist?(raymath_path)) ? load_api(raymath_path) : nil
+
+# Functions present in raylib_api.json but not compiled in every raylib platform
+# build (e.g. PLATFORM_WEB), which would cause link errors. Kept minimal.
+SKIP_FUNCTIONS = %w[
+ GetClipboardImage
+].to_h { |n| [n, true] }
+
+STRUCTS = api['structs'].map { |s| s['name'] }
+STRUCT_SET = STRUCTS.to_h { |n| [n, true] }
+ALIASES = api['aliases'].to_h { |a| [a['name'], a['type']] } # Texture2D -> Texture
+CALLBACKS = api['callbacks'].to_h { |c| [c['name'], true] }
+
+INT_TYPES = %w[int char short long size_t int8_t int16_t int32_t int64_t
+ uint8_t uint16_t uint32_t uint64_t].to_h { |t| [t, true] }
+
+def base_struct(type)
+ t = type.gsub('const', '').gsub('*', '').strip
+ t = ALIASES[t] || t
+ STRUCT_SET[t] ? t : nil
+end
+
+def pointer?(type) = type.include?('*')
+
+# Classify a type for marshaling. Returns [:kind, base_struct_or_nil].
+def classify(type, as_return: false)
+ t = type.strip
+ return [:void, nil] if t == 'void'
+ return [:bool, nil] if t == 'bool'
+ return [:float, nil] if t == 'float' || t == 'double'
+ return [:string, nil] if t == 'const char *' || (as_return && t == 'char *')
+
+ unless pointer?(t)
+ base = t.sub(/^unsigned /, '').sub(/^signed /, '')
+ return [:int, nil] if INT_TYPES[base] || t == 'unsigned int' || t == 'unsigned char' ||
+ t == 'unsigned short' || t == 'unsigned long' || base == 'unsigned'
+ s = base_struct(t)
+ return [:struct, s] if s
+ return [:unsupported, nil]
+ end
+
+ # pointer types
+ return [:unsupported, nil] if t.include?('**')
+ return [:unsupported, nil] if t.include?('(') # function pointer
+ s = base_struct(t)
+ return [:unsupported, nil] if s.nil? || as_return # struct* return unsupported
+ [:structptr, s]
+end
+
+def snake(name)
+ name.gsub(/(\d)([A-Z][a-z])/, '\1_\2') # Vector2Add -> Vector2_Add (keeps Mode2D)
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
+ .gsub(/([a-z])([A-Z])/, '\1_\2')
+ .downcase
+end
+
+# Ruby method name for a function (Is* -> predicate?)
+def ruby_method(name)
+ if name =~ /\AIs([A-Z].*)\z/
+ snake($1) + '?'
+ else
+ snake(name)
+ end
+end
+
+# --- field accessor support ---
+def field_kind(type)
+ k, s = classify(type)
+ return [k, s] if %i[int float bool].include?(k)
+ return [:struct, s] if k == :struct
+ [:unsupported, nil]
+end
+
+out = +""
+out << "/* AUTO-GENERATED by tools/gen_raylib.rb — do not edit. */\n"
+out << "#include <string.h>\n#include <mruby.h>\n#include <mruby/string.h>\n"
+out << "#include <mruby/class.h>\n#include <mruby/data.h>\n#include <mruby/variable.h>\n"
+out << "#include <mruby/array.h>\n"
+out << "#include <raylib.h>\n"
+out << "#include <raymath.h>\n" if raymath
+out << "\n"
+
+out << "static void rl_struct_free(mrb_state *mrb, void *p){ if (p) mrb_free(mrb, p); }\n\n"
+
+# Per-struct: data type, wrap/ptr helpers (declared early so functions can use them)
+STRUCTS.each do |name|
+ out << "static const mrb_data_type rl_dt_#{name} = { \"Rl::#{name}\", rl_struct_free };\n"
+end
+out << "\n"
+STRUCTS.each do |name|
+ out << <<~C
+ static mrb_value rl_wrap_#{name}(mrb_state *mrb, #{name} v){
+ #{name} *p = (#{name}*)mrb_malloc(mrb, sizeof(#{name})); *p = v;
+ struct RClass *m = mrb_module_get(mrb, "Rl");
+ struct RClass *c = mrb_class_get_under(mrb, m, "#{name}");
+ return mrb_obj_value(mrb_data_object_alloc(mrb, c, p, &rl_dt_#{name}));
+ }
+ static #{name} *rl_ptr_#{name}(mrb_state *mrb, mrb_value o){
+ return (#{name}*)mrb_data_get_ptr(mrb, o, &rl_dt_#{name});
+ }
+ C
+end
+out << "\n"
+
+# Helper to resolve a struct's helper base name through aliases.
+def hbase(type) = base_struct(type)
+
+# --- struct initialize + field accessors ---
+struct_defs = api['structs']
+struct_defs.each do |st|
+ name = st['name']
+ fields = st['fields']
+ supported = fields.map { |f| [f, field_kind(f['type'])] }.select { |_, (k, _)| k != :unsupported }
+
+ # constructor
+ fmt = +"|"
+ decls = []
+ assigns = []
+ argptrs = []
+ supported.each_with_index do |(f, (k, s)), i|
+ v = "a#{i}"
+ case k
+ when :int then fmt << 'i'; decls << "mrb_int #{v} = 0;"; argptrs << "&#{v}"; assigns << "p->#{f['name']} = (#{f['type']})#{v};"
+ when :float then fmt << 'f'; decls << "mrb_float #{v} = 0;"; argptrs << "&#{v}"; assigns << "p->#{f['name']} = (#{f['type']})#{v};"
+ when :bool then fmt << 'b'; decls << "mrb_bool #{v} = 0;"; argptrs << "&#{v}"; assigns << "p->#{f['name']} = #{v};"
+ when :struct then fmt << 'o'; decls << "mrb_value #{v} = mrb_nil_value();"; argptrs << "&#{v}"
+ assigns << "if (!mrb_nil_p(#{v})) p->#{f['name']} = *rl_ptr_#{hbase(f['type'])}(mrb, #{v});"
+ end
+ end
+ out << "static mrb_value rl_init_#{name}(mrb_state *mrb, mrb_value self){\n"
+ out << " #{name} *p = (#{name}*)mrb_malloc(mrb, sizeof(#{name})); memset(p, 0, sizeof(#{name}));\n"
+ out << " mrb_data_init(self, p, &rl_dt_#{name});\n"
+ unless supported.empty?
+ out << " #{decls.join(' ')}\n"
+ out << " mrb_get_args(mrb, \"#{fmt}\"#{argptrs.empty? ? '' : ', ' + argptrs.join(', ')});\n"
+ out << " #{assigns.join("\n ")}\n"
+ end
+ out << " return self;\n}\n"
+
+ # accessors
+ supported.each do |(f, (k, s))|
+ fn = f['name']
+ case k
+ when :int
+ out << "static mrb_value rl_g_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ return mrb_fixnum_value(rl_ptr_#{name}(mrb,self)->#{fn}); }\n"
+ out << "static mrb_value rl_s_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ mrb_int v; mrb_get_args(mrb,\"i\",&v); rl_ptr_#{name}(mrb,self)->#{fn}=(#{f['type']})v; return mrb_fixnum_value(v); }\n"
+ when :float
+ out << "static mrb_value rl_g_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ return mrb_float_value(mrb, rl_ptr_#{name}(mrb,self)->#{fn}); }\n"
+ out << "static mrb_value rl_s_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ mrb_float v; mrb_get_args(mrb,\"f\",&v); rl_ptr_#{name}(mrb,self)->#{fn}=(#{f['type']})v; return mrb_float_value(mrb,v); }\n"
+ when :bool
+ out << "static mrb_value rl_g_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ return mrb_bool_value(rl_ptr_#{name}(mrb,self)->#{fn}); }\n"
+ out << "static mrb_value rl_s_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ mrb_bool v; mrb_get_args(mrb,\"b\",&v); rl_ptr_#{name}(mrb,self)->#{fn}=v; return mrb_bool_value(v); }\n"
+ when :struct
+ hb = hbase(f['type'])
+ out << "static mrb_value rl_g_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ return rl_wrap_#{hb}(mrb, rl_ptr_#{name}(mrb,self)->#{fn}); }\n"
+ out << "static mrb_value rl_s_#{name}_#{fn}(mrb_state *mrb, mrb_value self){ mrb_value v; mrb_get_args(mrb,\"o\",&v); rl_ptr_#{name}(mrb,self)->#{fn}=*rl_ptr_#{hb}(mrb,v); return v; }\n"
+ end
+ end
+end
+out << "\n"
+
+# --- functions ---
+skipped = []
+fn_regs = []
+seen_fns = {}
+emit_function = lambda do |fn|
+ name = fn['name']
+ return if seen_fns[name]
+ if SKIP_FUNCTIONS[name]
+ skipped << "#{name} (platform)"; return
+ end
+ seen_fns[name] = true
+ params = fn['params'] || []
+ rkind, rstruct = classify(fn['returnType'], as_return: true)
+
+ # skip on unsupported return / params
+ if rkind == :unsupported
+ skipped << "#{name} (ret #{fn['returnType']})"; return
+ end
+ bad = false
+ pinfo = params.map do |p|
+ t = p['type']
+ if t == '...' || t == 'va_list' || CALLBACKS[t.gsub('*','').strip]
+ bad = true; break
+ end
+ k, s = classify(t)
+ if k == :unsupported
+ bad = true; break
+ end
+ [k, s]
+ end
+ if bad || pinfo.nil?
+ skipped << "#{name} (params)"; return
+ end
+
+ cname = "rl_fn_#{name}"
+ fmt = +""
+ decls = []
+ argptrs = []
+ callargs = []
+ pinfo.each_with_index do |(k, s), i|
+ v = "a#{i}"
+ case k
+ when :int then fmt << 'i'; decls << "mrb_int #{v};"; argptrs << "&#{v}"; callargs << "(#{params[i]['type']})#{v}"
+ when :float then fmt << 'f'; decls << "mrb_float #{v};"; argptrs << "&#{v}"; callargs << "(#{params[i]['type']})#{v}"
+ when :bool then fmt << 'b'; decls << "mrb_bool #{v};"; argptrs << "&#{v}"; callargs << "#{v}"
+ when :string then fmt << 'z!'; decls << "const char *#{v} = NULL;"; argptrs << "&#{v}"; callargs << "#{v}"
+ when :struct then fmt << 'o'; decls << "mrb_value #{v};"; argptrs << "&#{v}"; callargs << "(*rl_ptr_#{s}(mrb,#{v}))"
+ when :structptr then fmt << 'o'; decls << "mrb_value #{v};"; argptrs << "&#{v}"; callargs << "rl_ptr_#{s}(mrb,#{v})"
+ end
+ end
+
+ body = +""
+ body << "static mrb_value #{cname}(mrb_state *mrb, mrb_value self){\n"
+ body << " #{decls.join(' ')}\n" unless decls.empty?
+ body << " mrb_get_args(mrb, \"#{fmt}\"#{argptrs.empty? ? '' : ', ' + argptrs.join(', ')});\n" unless fmt.empty?
+ call = "#{name}(#{callargs.join(', ')})"
+ case rkind
+ when :void then body << " #{call};\n return mrb_nil_value();\n"
+ when :bool then body << " return mrb_bool_value(#{call});\n"
+ when :int then body << " return mrb_fixnum_value(#{call});\n"
+ when :float then body << " return mrb_float_value(mrb, #{call});\n"
+ when :string then body << " const char *r = #{call};\n return r ? mrb_str_new_cstr(mrb, r) : mrb_nil_value();\n"
+ when :struct then body << " return rl_wrap_#{rstruct}(mrb, #{call});\n"
+ end
+ body << "}\n"
+ out << body
+
+ fn_regs << %( mrb_define_module_function(mrb, rl, "#{ruby_method(name)}", #{cname}, MRB_ARGS_REQ(#{params.size}));)
+end
+
+api['functions'].each { |fn| emit_function.call(fn) }
+raymath['functions'].each { |fn| emit_function.call(fn) } if raymath
+
+# --- hand-written shader uniform setters ---
+# SetShaderValue / SetShaderValueV take a `const void *value` + a uniform-type
+# tag, which the generic marshaller can't express. We accept a Ruby Numeric or
+# Array of Numerics and pack it into the right C buffer based on `uniform_type`.
+# (These were in the skip list as "(params)"; remove them now that they're bound.)
+skipped.reject! { |s| s.start_with?('SetShaderValue ', 'SetShaderValueV ') }
+out << <<~'C'
+
+ /* ---- hand-written shader uniform setters ---- */
+ static int rl_uniform_comps(mrb_int t){
+ switch (t) {
+ case SHADER_UNIFORM_VEC2: case SHADER_UNIFORM_IVEC2: return 2;
+ case SHADER_UNIFORM_VEC3: case SHADER_UNIFORM_IVEC3: return 3;
+ case SHADER_UNIFORM_VEC4: case SHADER_UNIFORM_IVEC4: return 4;
+ default: return 1; /* FLOAT, INT, SAMPLER2D */
+ }
+ }
+ static mrb_bool rl_uniform_is_int(mrb_int t){
+ return (t == SHADER_UNIFORM_INT || t == SHADER_UNIFORM_SAMPLER2D ||
+ (t >= SHADER_UNIFORM_IVEC2 && t <= SHADER_UNIFORM_IVEC4));
+ }
+ /* Pack `count` * `comps` scalars from a Ruby value (scalar, flat Array, or
+ Array of Arrays) into buf. Returns 0 on success, -1 on arity mismatch. */
+ static int rl_pack_uniform(mrb_state *mrb, mrb_value v, void *buf,
+ int comps, int count, mrb_bool is_int){
+ float *f = (float*)buf; int *ip = (int*)buf;
+ int total = comps * count, k = 0;
+ if (!mrb_array_p(v)) {
+ if (total != 1) return -1;
+ if (is_int) ip[0] = (int)mrb_as_int(mrb, v); else f[0] = (float)mrb_as_float(mrb, v);
+ return 0;
+ }
+ mrb_int n = RARRAY_LEN(v);
+ for (mrb_int i = 0; i < n; i++) {
+ mrb_value e = mrb_ary_ref(mrb, v, i);
+ if (mrb_array_p(e)) {
+ mrb_int m = RARRAY_LEN(e);
+ for (mrb_int j = 0; j < m; j++, k++) {
+ if (k >= total) return -1;
+ mrb_value s = mrb_ary_ref(mrb, e, j);
+ if (is_int) ip[k] = (int)mrb_as_int(mrb, s); else f[k] = (float)mrb_as_float(mrb, s);
+ }
+ } else {
+ if (k >= total) return -1;
+ if (is_int) ip[k] = (int)mrb_as_int(mrb, e); else f[k] = (float)mrb_as_float(mrb, e);
+ k++;
+ }
+ }
+ return (k == total) ? 0 : -1;
+ }
+ static mrb_value rl_fn_SetShaderValue(mrb_state *mrb, mrb_value self){
+ mrb_value sh, val; mrb_int loc, utype;
+ mrb_get_args(mrb, "oioi", &sh, &loc, &val, &utype);
+ int comps = rl_uniform_comps(utype);
+ mrb_bool is_int = rl_uniform_is_int(utype);
+ int buf[4]; /* 4 ints or 4 floats, same size */
+ if (rl_pack_uniform(mrb, val, buf, comps, 1, is_int) != 0)
+ mrb_raisef(mrb, E_ARGUMENT_ERROR, "shader uniform expects %d component(s)", comps);
+ SetShaderValue(*rl_ptr_Shader(mrb, sh), (int)loc, buf, (int)utype);
+ return mrb_nil_value();
+ }
+ static mrb_value rl_fn_SetShaderValueV(mrb_state *mrb, mrb_value self){
+ mrb_value sh, val; mrb_int loc, utype, count;
+ mrb_get_args(mrb, "oioii", &sh, &loc, &val, &utype, &count);
+ int comps = rl_uniform_comps(utype);
+ mrb_bool is_int = rl_uniform_is_int(utype);
+ if (count < 1) mrb_raise(mrb, E_ARGUMENT_ERROR, "count must be >= 1");
+ void *buf = mrb_malloc(mrb, (size_t)comps * (size_t)count * sizeof(int));
+ int rc = rl_pack_uniform(mrb, val, buf, comps, (int)count, is_int);
+ if (rc != 0) { mrb_free(mrb, buf);
+ mrb_raisef(mrb, E_ARGUMENT_ERROR, "shader uniform array expects %d*%d values",
+ comps, (int)count); }
+ SetShaderValueV(*rl_ptr_Shader(mrb, sh), (int)loc, buf, (int)utype, (int)count);
+ mrb_free(mrb, buf);
+ return mrb_nil_value();
+ }
+C
+fn_regs << %( mrb_define_module_function(mrb, rl, "set_shader_value", rl_fn_SetShaderValue, MRB_ARGS_REQ(4));)
+fn_regs << %( mrb_define_module_function(mrb, rl, "set_shader_value_v", rl_fn_SetShaderValueV, MRB_ARGS_REQ(5));)
+
+# --- enums + defines (constants) ---
+enum_regs = []
+api['enums'].each do |e|
+ e['values'].each do |v|
+ enum_regs << %( mrb_define_const(mrb, rl, "#{v['name']}", mrb_fixnum_value(#{v['value']}));)
+ end
+end
+
+define_regs = []
+color_consts = []
+api['defines'].each do |d|
+ case d['type']
+ when 'INT' then define_regs << %( mrb_define_const(mrb, rl, "#{d['name']}", mrb_fixnum_value(#{d['value']}));)
+ when 'FLOAT' then define_regs << %( mrb_define_const(mrb, rl, "#{d['name']}", mrb_float_value(mrb, #{d['value'].to_f}));)
+ when 'STRING' then define_regs << %( mrb_define_const(mrb, rl, "#{d['name']}", mrb_str_new_cstr(mrb, #{d['value'].inspect}));)
+ when 'COLOR'
+ if d['value'] =~ /\{\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\}/
+ color_consts << %( mrb_define_const(mrb, rl, "#{d['name']}", rl_wrap_Color(mrb, (Color){#{$1},#{$2},#{$3},#{$4}}));)
+ end
+ end
+end
+
+# --- init function ---
+out << "\nvoid rl_define_generated(mrb_state *mrb){\n"
+out << " struct RClass *rl = mrb_define_module(mrb, \"Rl\");\n"
+out << " struct RClass *obj = mrb->object_class;\n (void)obj;\n"
+# struct classes + accessors
+struct_defs.each do |st|
+ name = st['name']
+ out << " {\n struct RClass *c = mrb_define_class_under(mrb, rl, \"#{name}\", mrb->object_class);\n"
+ out << " MRB_SET_INSTANCE_TT(c, MRB_TT_DATA);\n"
+ out << " mrb_define_method(mrb, c, \"initialize\", rl_init_#{name}, MRB_ARGS_OPT(16));\n"
+ fields = st['fields']
+ fields.each do |f|
+ k, _ = field_kind(f['type'])
+ next if k == :unsupported
+ fn = f['name']
+ out << " mrb_define_method(mrb, c, \"#{fn}\", rl_g_#{name}_#{fn}, MRB_ARGS_NONE());\n"
+ out << " mrb_define_method(mrb, c, \"#{fn}=\", rl_s_#{name}_#{fn}, MRB_ARGS_REQ(1));\n"
+ end
+ out << " }\n"
+end
+out << "\n"
+out << enum_regs.join("\n") << "\n\n"
+out << define_regs.join("\n") << "\n\n"
+out << color_consts.join("\n") << "\n\n"
+out << fn_regs.join("\n") << "\n"
+out << "}\n"
+
+# Report skipped functions as a comment.
+total = api['functions'].size + (raymath ? raymath['functions'].size : 0)
+bound = total - skipped.size
+header = "/* Generated: #{bound}/#{total} functions bound (raylib#{raymath ? ' + raymath' : ''}). */\n"
+header << "/* Skipped (#{skipped.size}): #{skipped.join('; ')} */\n\n"
+
+File.write(out_path, header + out)
+warn "gen_raylib: wrote #{out_path} (#{bound}/#{total} functions, #{STRUCTS.size} structs)"