diff options
Diffstat (limited to 'mrbgems/raylib/tools/gen_raylib.rb')
| -rw-r--r-- | mrbgems/raylib/tools/gen_raylib.rb | 402 |
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)" |
