konpeito 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/lib/konpeito/codegen/inliner.rb +34 -0
  4. data/lib/konpeito/codegen/llvm_generator.rb +61 -3
  5. data/lib/konpeito/codegen/mruby_backend.rb +101 -3
  6. data/lib/konpeito/codegen/mruby_helpers.c +125 -18
  7. data/lib/konpeito/compiler.rb +14 -2
  8. data/lib/konpeito/hir/builder.rb +26 -0
  9. data/lib/konpeito/stdlib/clay/clay_native.c +24 -2
  10. data/lib/konpeito/stdlib/clay_tui/clay_tui.rb +112 -0
  11. data/lib/konpeito/stdlib/clay_tui/clay_tui.rbs +258 -0
  12. data/lib/konpeito/stdlib/clay_tui/clay_tui_native.c +452 -0
  13. data/lib/konpeito/stdlib/kui/kui.rb +345 -0
  14. data/lib/konpeito/stdlib/kui/kui.rbs +53 -0
  15. data/lib/konpeito/stdlib/kui/kui_events.rb +17 -0
  16. data/lib/konpeito/stdlib/kui/kui_events.rbs +1 -0
  17. data/lib/konpeito/stdlib/kui/kui_gui.rb +317 -0
  18. data/lib/konpeito/stdlib/kui/kui_gui.rbs +2 -0
  19. data/lib/konpeito/stdlib/kui/kui_theme.rb +143 -0
  20. data/lib/konpeito/stdlib/kui/kui_theme.rbs +9 -0
  21. data/lib/konpeito/stdlib/kui/kui_tui.rb +443 -0
  22. data/lib/konpeito/stdlib/kui/kui_tui.rbs +5 -0
  23. data/lib/konpeito/stdlib/raylib/raylib.rb +112 -0
  24. data/lib/konpeito/stdlib/raylib/raylib.rbs +288 -0
  25. data/lib/konpeito/stdlib/raylib/raylib_native.c +510 -0
  26. data/lib/konpeito/stdlib/rpg_framework/rpg_framework.rb +668 -0
  27. data/lib/konpeito/stdlib/shell/shell.rb +20 -0
  28. data/lib/konpeito/stdlib/shell/shell.rbs +59 -0
  29. data/lib/konpeito/stdlib/shell/shell_native.c +195 -0
  30. data/lib/konpeito/type_checker/rbs_loader.rb +17 -2
  31. data/lib/konpeito/version.rb +1 -1
  32. data/vendor/clay/clay_renderer_termbox2.c +215 -0
  33. data/vendor/termbox2/LICENSE +19 -0
  34. data/vendor/termbox2/termbox2.h +4307 -0
  35. data/vendor/termbox2/termbox2_impl.c +3 -0
  36. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 453b0bb32e4ef66b8b6020d262200e070caf01fb483cff4a0ebd6ceba46495b3
4
- data.tar.gz: 7b38fb2807e1649150a0febae5defcb8f964967a715770db1de857d6409f88b3
3
+ metadata.gz: 50ad4b5d35d1b8abd26ee9d941a901b99e281eb9cc7d2deed777f3ebf12b2308
4
+ data.tar.gz: fc91a16a60718a8a910776ab56f01a09d49ec2ee78e71082965392f1dee332e1
5
5
  SHA512:
6
- metadata.gz: afa5fd10db773c8e1bb3c399a39cf140b1c7577271420750368551ee80cab864683d9455860ddba185b0d53cc221df42b5beb3a7334fbc67b56747e4a7bd6e0f
7
- data.tar.gz: 7fbe956b09d489f9af313c0fa6600f81960cb02cb72d95f912a4d50d9f658b4b7bff35528d43b73c223d011d0aa5c35f6c63bf09ce5f1873c14605cd7274395e
6
+ metadata.gz: c2efa11a609aba145beef6d2b722028f657a6a4d5469864241cbdde1b43dc494b9cf814379788a8ede8d4715d0aceab76df3d64a1c6b14913a3e2f84d0787f1f
7
+ data.tar.gz: 77860386e45ac9a5b78ead9b0f02dfc3d759668fe5ffc3d97c72c4932ca297778d27192c6a698ad264f7641a988b07905dbf6a15c32f10320678999e89a604dc
data/CHANGELOG.md CHANGED
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-03-14
11
+
12
+ ### Added
13
+ - **KUI declarative UI framework**: Pure Ruby DSL for building GUI/TUI apps with a single codebase. Wraps Clay+Raylib (GUI) or ClayTUI (TUI). Widgets: `vpanel`, `hpanel`, `fixed_panel`, `label`, `label_num`, `button`, `menu_item`, `spacer`, `divider`, `progress_bar`. Theme system and unified key event abstraction.
14
+ - **ClayTUI stdlib**: Terminal UI backend using Clay layout engine + termbox2 rendering. Auto-detected via `ClayTUI` module reference.
15
+ - **KonpeitoShell stdlib**: Shell execution (`exec`, `system`), environment variables (`getenv`, `setenv`), and file I/O (`read_file`, `write_file`, `append_file`, `file_exists`) for mruby backend.
16
+ - **RPG framework stdlib**: Reusable RPG game components (tilemap, sprites, camera, battle system) for raylib-based games.
17
+ - **Stdlib auto-detection expansion**: `KonpeitoJSON`, `KonpeitoHTTP`, `KonpeitoCrypto`, `KonpeitoCompression` added to `STDLIB_MODULE_MAP` for automatic RBS injection when referenced in source code.
18
+ - **KUI auto-path resolution**: `require "kui_gui"` / `require "kui_tui"` works without `-I` flags — KUI stdlib directory is automatically included in compiler search paths.
19
+ - **KUI example apps**: Counter (GUI + TUI), minimal hello, multi-page dashboard with sidebar navigation.
20
+ - **DQ RPG demo with Clay UI**: Battle scenes, shop menus, and status displays using Clay layout integration.
21
+ - Keyword arguments documented as supported in mruby backend.
22
+
23
+ ### Fixed
24
+ - **mruby GC crash in native callbacks**: `rb_block_call` stored raw C stack pointer (`data2`) in proc env — GC tried to dereference it as `RBasic*` causing crash in `gc_mark_children`. Fixed by storing `mrb_nil_value()` instead; the native callback path reads `data2` from a global, not from proc env.
25
+ - **TUI rendering flicker**: Added per-frame string pool to `clay_tui_native.c` (64KB static buffer) to prevent Clay from holding pointers to GC-managed mruby heap memory that could be invalidated between frames.
26
+ - **Documentation references**: Fixed CLAUDE.md referencing non-existent `docs/architecture.md`.
27
+
28
+ ### Changed
29
+ - Tutorials (EN/JA) updated with KUI framework sections and all stdlib module documentation (Shell, JSON, HTTP, Crypto, Compression).
30
+
31
+ ## [0.6.0] - 2026-03-11
32
+
33
+ ### Added
34
+ - **Raylib stdlib expansion**: 119 new cfunc bindings (87 → 206 total) for 2D RPG/SLG game development
35
+ - Texture/Sprite management (10): load, unload, draw, draw_rec, draw_pro, draw_scaled, dimensions, validity check
36
+ - Audio — Sound (10): load, unload, play, stop, pause, resume, playing?, volume, pitch
37
+ - Audio — Music (13): load, unload, play, stop, pause, resume, update, playing?, volume, pitch, time_length, time_played, seek
38
+ - Audio — Device (4): init, close, ready?, master volume
39
+ - Camera2D (4): begin/end mode, world-to-screen coordinate conversion (x/y)
40
+ - File I/O (4): save/load text files, file/directory existence checks
41
+ - Font management (6): load, load_ex, unload, draw_text_ex, measure_text_ex (x/y)
42
+ - Gamepad input (7 + 21 constants): button pressed/down/released/up, axis movement/count, D-pad/face/trigger/middle button and axis constants
43
+ - Extended shapes (5): draw_rectangle_pro, draw_rectangle_rounded, gradient v/h, circle sector
44
+ - Collision detection (5): recs, circles, circle-rec, point-rec, point-circle
45
+ - ID table pattern for resource management: textures (256), sounds (128), music (32), fonts (32)
46
+ - **RPG tilemap demo**: `examples/mruby_rpg_demo/rpg_demo.rb` — 40×40 tilemap, smooth camera scrolling, 4-direction player movement with animation, sign interaction, HUD with step counter
47
+ - macOS audio framework linker flags (CoreAudio, AudioToolbox, CoreFoundation)
48
+
49
+ ### Fixed
50
+ - **Inliner CFuncCall bug**: `clone_and_rename()` now handles `CFuncCall`, `ExternConstructorCall`, and `ExternMethodCall` — parameters are correctly remapped when user functions calling `@cfunc` are inlined (previously fell through to default case, returning instructions with unmapped parameters)
51
+ - **CI lint**: use anonymous block forwarding (`&`) in `rbs_loader.rb`
52
+ - **CI test isolation**: use unique symbol names in `module_native_array_test` to avoid ELF symbol interposition on Linux
53
+
10
54
  ## [0.5.0] - 2026-03-10
11
55
 
12
56
  ### Added
@@ -542,6 +542,40 @@ module Konpeito
542
542
  result_var: new_result
543
543
  )
544
544
 
545
+ when HIR::CFuncCall
546
+ new_result = inst.result_var ? prefix + inst.result_var : nil
547
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
548
+ HIR::CFuncCall.new(
549
+ c_func_name: inst.c_func_name,
550
+ args: new_args,
551
+ cfunc_type: inst.cfunc_type,
552
+ result_var: new_result
553
+ )
554
+
555
+ when HIR::ExternConstructorCall
556
+ new_result = inst.result_var ? prefix + inst.result_var : nil
557
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
558
+ HIR::ExternConstructorCall.new(
559
+ extern_type: inst.extern_type,
560
+ c_func_name: inst.c_func_name,
561
+ args: new_args,
562
+ method_sig: inst.method_sig,
563
+ result_var: new_result
564
+ )
565
+
566
+ when HIR::ExternMethodCall
567
+ new_result = inst.result_var ? prefix + inst.result_var : nil
568
+ new_receiver = transform_value(inst.receiver, prefix, param_map)
569
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
570
+ HIR::ExternMethodCall.new(
571
+ receiver: new_receiver,
572
+ c_func_name: inst.c_func_name,
573
+ args: new_args,
574
+ extern_type: inst.extern_type,
575
+ method_sig: inst.method_sig,
576
+ result_var: new_result
577
+ )
578
+
545
579
  else
546
580
  # For other instructions, just return as-is with renamed result
547
581
  inst
@@ -69,7 +69,7 @@ module Konpeito
69
69
  end
70
70
  end
71
71
 
72
- attr_reader :profiler, :variadic_functions, :alias_renamed_methods
72
+ attr_reader :profiler, :variadic_functions, :keyword_param_functions, :alias_renamed_methods
73
73
 
74
74
  def generate(hir_program)
75
75
  @hir_program = hir_program
@@ -1126,6 +1126,17 @@ module Konpeito
1126
1126
  init_builder.store(init_builder.call(mrb_undef_fn), @mrb_qundef_global)
1127
1127
  init_builder.ret_void
1128
1128
 
1129
+ # GC arena management functions (prevent arena overflow in loops)
1130
+ @konpeito_gc_arena_save = @mod.functions.add(
1131
+ "konpeito_gc_arena_save", [], LLVM::Int32
1132
+ )
1133
+ @konpeito_gc_arena_restore = @mod.functions.add(
1134
+ "konpeito_gc_arena_restore", [LLVM::Int32], LLVM.Void
1135
+ )
1136
+ @konpeito_gc_protect_value = @mod.functions.add(
1137
+ "konpeito_gc_protect_value", [value_type], LLVM.Void
1138
+ )
1139
+
1129
1140
  # Clear @qnil etc. so the accessor methods know to load from globals
1130
1141
  @qnil = nil
1131
1142
  @qtrue = nil
@@ -1728,6 +1739,16 @@ module Konpeito
1728
1739
  end
1729
1740
  end
1730
1741
 
1742
+ # For mruby: save GC arena index so we can restore before return.
1743
+ # This prevents arena overflow when LLVM functions call each other
1744
+ # directly (bypassing mruby dispatch's arena management).
1745
+ @mruby_gc_arena_alloca = nil
1746
+ if @runtime == :mruby
1747
+ @mruby_gc_arena_alloca = @builder.alloca(LLVM::Int32, "_gc_arena")
1748
+ arena_idx = @builder.call(@konpeito_gc_arena_save)
1749
+ @builder.store(arena_idx, @mruby_gc_arena_alloca)
1750
+ end
1751
+
1731
1752
  # Insert profiling entry probe after parameter setup
1732
1753
  insert_profile_entry_probe(hir_func)
1733
1754
 
@@ -1753,6 +1774,21 @@ module Konpeito
1753
1774
  func
1754
1775
  end
1755
1776
 
1777
+ # Emit a return instruction with mruby GC arena management.
1778
+ # For the mruby backend, restores the arena to the saved index and
1779
+ # protects the return value from GC before returning.
1780
+ def emit_ret(value)
1781
+ if @mruby_gc_arena_alloca
1782
+ idx = @builder.load2(LLVM::Int32, @mruby_gc_arena_alloca, "_arena_r")
1783
+ @builder.call(@konpeito_gc_arena_restore, idx)
1784
+ # Protect the return value so it survives the arena restore
1785
+ if value.type == value_type
1786
+ @builder.call(@konpeito_gc_protect_value, value)
1787
+ end
1788
+ end
1789
+ @builder.ret(value)
1790
+ end
1791
+
1756
1792
  # Topologically sort blocks based on phi dependencies
1757
1793
  # Ensures blocks are generated after the blocks their phi nodes reference
1758
1794
  def sort_blocks_by_phi_dependencies(blocks)
@@ -4843,6 +4879,9 @@ module Konpeito
4843
4879
  saved_allocas = @variable_allocas.dup
4844
4880
  saved_in_block_callback = @in_block_callback
4845
4881
  saved_block_callback_self = @block_callback_self
4882
+ saved_mrb_cache = save_mrb_constant_cache
4883
+ saved_current_function = @current_function
4884
+ saved_gc_arena_alloca = @mruby_gc_arena_alloca
4846
4885
 
4847
4886
  # Create entry block for callback
4848
4887
  entry = callback_func.basic_blocks.append("entry")
@@ -4850,10 +4889,16 @@ module Konpeito
4850
4889
 
4851
4890
  # Reset variable tracking for callback scope.
4852
4891
  # Set @in_block_callback so nested proc creation uses GC-safe escape-cells mode.
4892
+ # Reset mruby constant cache — LLVM values are scoped per function, so
4893
+ # cached %qnil/%qtrue from the parent function are invalid here.
4894
+ # Clear GC arena alloca — callbacks don't own their own arena scope.
4853
4895
  @in_block_callback = true
4896
+ @mruby_gc_arena_alloca = nil
4854
4897
  @variables = {}
4855
4898
  @variable_types = {}
4856
4899
  @variable_allocas = {}
4900
+ reset_mrb_constant_cache
4901
+ @current_function = callback_func
4857
4902
 
4858
4903
  # Setup captured variable access through data2 pointer
4859
4904
  unless captures.empty?
@@ -5132,6 +5177,9 @@ module Konpeito
5132
5177
  @variable_allocas = saved_allocas
5133
5178
  @in_block_callback = saved_in_block_callback
5134
5179
  @block_callback_self = saved_block_callback_self
5180
+ restore_mrb_constant_cache(saved_mrb_cache)
5181
+ @current_function = saved_current_function
5182
+ @mruby_gc_arena_alloca = saved_gc_arena_alloca
5135
5183
 
5136
5184
  callback_func
5137
5185
  end
@@ -5583,6 +5631,7 @@ module Konpeito
5583
5631
  saved_block_callback_self = @block_callback_self
5584
5632
  saved_in_thread_callback = @in_thread_callback
5585
5633
  saved_thread_boxed_vars = @thread_boxed_vars
5634
+ saved_gc_arena_alloca = @mruby_gc_arena_alloca
5586
5635
 
5587
5636
  # Create entry block for callback
5588
5637
  entry = callback_func.basic_blocks.append("entry")
@@ -5599,6 +5648,7 @@ module Konpeito
5599
5648
  @in_block_callback = false
5600
5649
  @block_callback_self = nil
5601
5650
  @in_thread_callback = true
5651
+ @mruby_gc_arena_alloca = nil
5602
5652
 
5603
5653
  unless captures.empty?
5604
5654
  declare_free
@@ -5735,6 +5785,7 @@ module Konpeito
5735
5785
  @block_callback_self = saved_block_callback_self
5736
5786
  @in_thread_callback = saved_in_thread_callback
5737
5787
  @thread_boxed_vars = saved_thread_boxed_vars
5788
+ @mruby_gc_arena_alloca = saved_gc_arena_alloca
5738
5789
 
5739
5790
  callback_func
5740
5791
  end
@@ -8134,11 +8185,13 @@ module Konpeito
8134
8185
  saved_block_callback_self = @block_callback_self
8135
8186
  saved_rescue_escape_array = @rescue_escape_array
8136
8187
  saved_rescue_escape_indices = @rescue_escape_indices
8188
+ saved_gc_arena_alloca = @mruby_gc_arena_alloca
8137
8189
 
8138
8190
  # Reset variable tracking for callback scope
8139
8191
  @variables = {}
8140
8192
  @variable_types = {}
8141
8193
  @variable_allocas = {}
8194
+ @mruby_gc_arena_alloca = nil
8142
8195
 
8143
8196
  if try_hir_blocks && !try_hir_blocks.empty?
8144
8197
  # Structured try body: process HIR BasicBlock list (handles control flow inside try).
@@ -8231,6 +8284,7 @@ module Konpeito
8231
8284
  @block_callback_self = saved_block_callback_self
8232
8285
  @rescue_escape_array = saved_rescue_escape_array
8233
8286
  @rescue_escape_indices = saved_rescue_escape_indices
8287
+ @mruby_gc_arena_alloca = saved_gc_arena_alloca
8234
8288
 
8235
8289
  callback_func
8236
8290
  end
@@ -8252,6 +8306,7 @@ module Konpeito
8252
8306
  saved_block_callback_self = @block_callback_self
8253
8307
  saved_rescue_escape_array = @rescue_escape_array
8254
8308
  saved_rescue_escape_indices = @rescue_escape_indices
8309
+ saved_gc_arena_alloca = @mruby_gc_arena_alloca
8255
8310
 
8256
8311
  # In normal mode: params[0] = self. In escape mode, set @block_callback_self after unpacking.
8257
8312
  @block_callback_self = callback_func.params[0] unless escape_var_names
@@ -8264,6 +8319,7 @@ module Konpeito
8264
8319
  @variables = {}
8265
8320
  @variable_types = {}
8266
8321
  @variable_allocas = {}
8322
+ @mruby_gc_arena_alloca = nil
8267
8323
 
8268
8324
  # CRuby rescue callback: (VALUE data2, VALUE exception)
8269
8325
  exception_val = callback_func.params[1]
@@ -8393,6 +8449,7 @@ module Konpeito
8393
8449
  @block_callback_self = saved_block_callback_self
8394
8450
  @rescue_escape_array = saved_rescue_escape_array
8395
8451
  @rescue_escape_indices = saved_rescue_escape_indices
8452
+ @mruby_gc_arena_alloca = saved_gc_arena_alloca
8396
8453
 
8397
8454
  callback_func
8398
8455
  end
@@ -8532,6 +8589,7 @@ module Konpeito
8532
8589
  @block_callback_self = saved_block_callback_self
8533
8590
  @rescue_escape_array = saved_rescue_escape_array
8534
8591
  @rescue_escape_indices = saved_rescue_escape_indices
8592
+ @mruby_gc_arena_alloca = saved_gc_arena_alloca
8535
8593
 
8536
8594
  callback_func
8537
8595
  end
@@ -9593,9 +9651,9 @@ module Konpeito
9593
9651
  value, type_tag = get_value_with_type(term.value)
9594
9652
  # Box the value before returning to Ruby
9595
9653
  boxed = convert_value(value, type_tag, :value)
9596
- @builder.ret(boxed)
9654
+ emit_ret(boxed)
9597
9655
  else
9598
- @builder.ret(qnil)
9656
+ emit_ret(qnil)
9599
9657
  end
9600
9658
  when HIR::Branch
9601
9659
  condition, cond_type = get_value_with_type(term.condition)
@@ -8,6 +8,10 @@ module Konpeito
8
8
  module Codegen
9
9
  # Generates standalone executable from LLVM module using mruby runtime
10
10
  class MRubyBackend
11
+ # Stdlib modules that should be auto-defined in mruby init code
12
+ # even when no ModuleDef exists in HIR (cfunc-only modules).
13
+ STDLIB_MODULES = %w[Raylib Clay ClayTUI KonpeitoShell].freeze
14
+
11
15
  attr_reader :llvm_generator, :output_file, :module_name, :rbs_loader, :debug
12
16
 
13
17
  def initialize(llvm_generator, output_file:, module_name: nil, rbs_loader: nil, debug: false, extra_c_files: [],
@@ -62,6 +66,10 @@ module Konpeito
62
66
  clay_objs = ensure_clay_compiled
63
67
  extra_obj_files.concat(clay_objs)
64
68
 
69
+ # Compile vendored termbox2 library if ClayTUI is used
70
+ tb2_objs = ensure_termbox_compiled
71
+ extra_obj_files.concat(tb2_objs)
72
+
65
73
  obj_files = [obj_file, init_obj_file, helpers_obj_file] + extra_obj_files
66
74
 
67
75
  # Link into standalone executable
@@ -278,7 +286,9 @@ module Konpeito
278
286
  lines << ""
279
287
 
280
288
  # Define modules
289
+ defined_module_names = []
281
290
  hir.modules.each do |module_def|
291
+ defined_module_names << module_def.name.to_s
282
292
  module_var = "m#{module_def.name}"
283
293
  lines << " struct RClass *#{module_var} = mrb_define_module(mrb, \"#{module_def.name}\");"
284
294
 
@@ -319,6 +329,18 @@ module Konpeito
319
329
  end
320
330
  end
321
331
 
332
+ # Auto-define stdlib modules that have cfunc methods but no ModuleDef in HIR.
333
+ # This ensures that even if a dynamic dispatch fallback occurs (e.g., due to
334
+ # a method name mismatch), the module constant exists at runtime.
335
+ if @rbs_loader
336
+ STDLIB_MODULES.each do |mod_name|
337
+ next if defined_module_names.include?(mod_name)
338
+ next unless @rbs_loader.has_cfunc_methods?(mod_name.to_sym)
339
+
340
+ lines << " mrb_define_module(mrb, \"#{mod_name}\");"
341
+ end
342
+ end
343
+
322
344
  # Define NativeClasses with mrb_data_type
323
345
  native_classes.each do |class_name, class_type|
324
346
  lines.concat(generate_native_class_init(class_name, class_type))
@@ -434,8 +456,21 @@ module Konpeito
434
456
  else
435
457
  arity = llvm_func.params.size - 1 # Subtract self
436
458
 
459
+ # Check if this function has only keyword args (no positional params).
460
+ # If so, the kwargs hash arg is optional — caller may pass 0 args
461
+ # when no keywords are specified (e.g., `footer do ... end`).
462
+ func_base_name = mangled_name.sub(/\Arn_/, "")
463
+ kw_info = llvm_generator.keyword_param_functions[func_base_name] ||
464
+ llvm_generator.keyword_param_functions[func_base_name.to_sym]
465
+ kwargs_only = kw_info && kw_info[:regular_count] == 0 && arity == 1
466
+
437
467
  lines << "static mrb_value #{wrapper_name}(mrb_state *mrb, mrb_value self) {"
438
- if arity > 0
468
+ if kwargs_only
469
+ # Single optional kwargs hash arg — default to empty hash
470
+ lines << " mrb_value a0 = mrb_nil_value(), _block = mrb_nil_value();"
471
+ lines << " mrb_get_args(mrb, \"|o&\", &a0, &_block);"
472
+ lines << " if (mrb_nil_p(a0)) a0 = mrb_hash_new(mrb);"
473
+ elsif arity > 0
439
474
  lines << " mrb_value #{(0...arity).map { |i| "a#{i}" }.join(', ')}, _block;"
440
475
  format_str = "o" * arity + "&"
441
476
  args_list = (0...arity).map { |i| "&a#{i}" }.join(", ") + ", &_block"
@@ -933,6 +968,36 @@ module Konpeito
933
968
  )
934
969
  end
935
970
 
971
+ # Include termbox2 if ClayTUI is used
972
+ clay_tui_used = @extra_c_files.any? { |f| File.basename(f).include?("clay_tui") }
973
+ if clay_tui_used
974
+ sections << license_section(
975
+ "termbox2",
976
+ "MIT",
977
+ "Copyright (c) 2021 termbox developers",
978
+ "https://github.com/termbox/termbox2",
979
+ <<~MIT
980
+ Permission is hereby granted, free of charge, to any person obtaining a copy
981
+ of this software and associated documentation files (the "Software"), to deal
982
+ in the Software without restriction, including without limitation the rights
983
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
984
+ copies of the Software, and to permit persons to whom the Software is
985
+ furnished to do so, subject to the following conditions:
986
+
987
+ The above copyright notice and this permission notice shall be included in all
988
+ copies or substantial portions of the Software.
989
+
990
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
991
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
992
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
993
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
994
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
995
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
996
+ SOFTWARE.
997
+ MIT
998
+ )
999
+ end
1000
+
936
1001
  # Include raylib if linked
937
1002
  ffi_libs = @rbs_loader&.all_ffi_libraries || []
938
1003
  if ffi_libs.any? { |lib| lib.to_s.include?("raylib") }
@@ -1171,7 +1236,9 @@ module Konpeito
1171
1236
  # Known framework dependencies for popular libraries
1172
1237
  case lib_name
1173
1238
  when "raylib"
1174
- ["-framework", "IOKit", "-framework", "Cocoa", "-framework", "OpenGL"]
1239
+ ["-framework", "IOKit", "-framework", "Cocoa", "-framework", "OpenGL",
1240
+ "-framework", "CoreAudio", "-framework", "AudioToolbox",
1241
+ "-framework", "CoreFoundation"]
1175
1242
  when "SDL2", "sdl2"
1176
1243
  ["-framework", "IOKit", "-framework", "Cocoa", "-framework", "Carbon",
1177
1244
  "-framework", "CoreAudio", "-framework", "AudioToolbox",
@@ -1218,15 +1285,46 @@ module Konpeito
1218
1285
  [clay_impl_obj]
1219
1286
  end
1220
1287
 
1288
+ # Compile vendored termbox2 implementation if ClayTUI stdlib is used
1289
+ # Returns array of object file paths
1290
+ def ensure_termbox_compiled
1291
+ clay_tui_used = @extra_c_files.any? { |f| File.basename(f).include?("clay_tui") }
1292
+ return [] unless clay_tui_used
1293
+
1294
+ tb2_dir = File.expand_path("../../../vendor/termbox2", __dir__)
1295
+ tb2_impl_c = File.join(tb2_dir, "termbox2_impl.c")
1296
+ tb2_impl_obj = File.join(tb2_dir, "termbox2_impl.o")
1297
+
1298
+ return [] unless File.exist?(tb2_impl_c)
1299
+
1300
+ cc, cc_flags = cross_cc_with_flags
1301
+
1302
+ # Compile termbox2_impl.c (only if stale)
1303
+ unless File.exist?(tb2_impl_obj) && File.mtime(tb2_impl_obj) > File.mtime(tb2_impl_c)
1304
+ cmd = [*cc, "-c", "-O2"]
1305
+ cmd.concat(cc_flags)
1306
+ cmd += ["-o", tb2_impl_obj, tb2_impl_c]
1307
+ system(*cmd) or return []
1308
+ end
1309
+
1310
+ [tb2_impl_obj]
1311
+ end
1312
+
1221
1313
  def ffi_include_flags
1222
1314
  flags = []
1223
1315
 
1224
- # Always add vendored Clay include path if Clay stdlib is used
1316
+ # Always add vendored Clay include path if Clay/ClayTUI stdlib is used
1225
1317
  clay_dir = File.expand_path("../../../vendor/clay", __dir__)
1226
1318
  if @extra_c_files.any? { |f| File.basename(f).include?("clay") } && Dir.exist?(clay_dir)
1227
1319
  flags << "-I#{clay_dir}"
1228
1320
  end
1229
1321
 
1322
+ # Add vendored termbox2 include path if ClayTUI stdlib is used
1323
+ tb2_dir = File.expand_path("../../../vendor/termbox2", __dir__)
1324
+ if @extra_c_files.any? { |f| File.basename(f).include?("clay_tui") } && Dir.exist?(tb2_dir)
1325
+ flags << "-I#{tb2_dir}"
1326
+ end
1327
+
1230
1328
  if cross_compiling?
1231
1329
  # When cross-compiling, use cross library include paths
1232
1330
  flags << "-I#{@cross_libs_dir}/../include" if @cross_libs_dir && Dir.exist?("#{@cross_libs_dir}/../include")