konpeito 0.7.1 → 0.9.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -2
  3. data/lib/konpeito/codegen/llvm_generator.rb +121 -11
  4. data/lib/konpeito/codegen/mruby_backend.rb +44 -10
  5. data/lib/konpeito/codegen/mruby_helpers.c +107 -0
  6. data/lib/konpeito/stdlib/clay/clay.rb +26 -0
  7. data/lib/konpeito/stdlib/clay/clay.rbs +86 -0
  8. data/lib/konpeito/stdlib/clay/clay_native.c +371 -2
  9. data/lib/konpeito/stdlib/clay_tui/clay_tui.rb +41 -0
  10. data/lib/konpeito/stdlib/clay_tui/clay_tui.rbs +117 -0
  11. data/lib/konpeito/stdlib/clay_tui/clay_tui_native.c +263 -0
  12. data/lib/konpeito/stdlib/game_framework/game_framework.rb +1552 -0
  13. data/lib/konpeito/stdlib/kui/kui.rb +158 -96
  14. data/lib/konpeito/stdlib/kui/kui.rbs +180 -0
  15. data/lib/konpeito/stdlib/kui/kui_charts.rb +409 -0
  16. data/lib/konpeito/stdlib/kui/kui_charts.rbs +8 -0
  17. data/lib/konpeito/stdlib/kui/kui_containers.rb +202 -0
  18. data/lib/konpeito/stdlib/kui/kui_data.rb +284 -0
  19. data/lib/konpeito/stdlib/kui/kui_events.rb +27 -0
  20. data/lib/konpeito/stdlib/kui/kui_forms.rb +563 -0
  21. data/lib/konpeito/stdlib/kui/kui_gui.rb +360 -20
  22. data/lib/konpeito/stdlib/kui/kui_interactive.rb +428 -0
  23. data/lib/konpeito/stdlib/kui/kui_layouts.rb +150 -0
  24. data/lib/konpeito/stdlib/kui/kui_markdown.rb +1765 -0
  25. data/lib/konpeito/stdlib/kui/kui_markdown.rbs +6 -0
  26. data/lib/konpeito/stdlib/kui/kui_nav.rb +158 -0
  27. data/lib/konpeito/stdlib/kui/kui_overlay.rb +355 -0
  28. data/lib/konpeito/stdlib/kui/kui_state.rb +20 -0
  29. data/lib/konpeito/stdlib/kui/kui_state.rbs +5 -0
  30. data/lib/konpeito/stdlib/kui/kui_theme.rb +376 -6
  31. data/lib/konpeito/stdlib/kui/kui_theme.rbs +6 -1
  32. data/lib/konpeito/stdlib/kui/kui_tui.rb +281 -14
  33. data/lib/konpeito/stdlib/raylib/raylib.rb +27 -0
  34. data/lib/konpeito/stdlib/raylib/raylib.rbs +81 -0
  35. data/lib/konpeito/stdlib/raylib/raylib_native.c +29 -0
  36. data/lib/konpeito/version.rb +1 -1
  37. data/vendor/clay/clay_renderer_raylib.c +1 -1
  38. metadata +15 -2
  39. data/lib/konpeito/stdlib/rpg_framework/rpg_framework.rb +0 -668
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34d8bfa7eaccc6ae3cb5352a188346ed97f98b7b12940512292899bdfba280ef
4
- data.tar.gz: a2298a81832fdd48c77153575d9a41d719e7ac29a3d0ae96800cb70e99be6893
3
+ metadata.gz: d513cebc5d3b0574c814b92901fbe2f4154e4ffc905da31c095abf9375bcaf34
4
+ data.tar.gz: 8becb035b3dc4e7aa9f2cb482fa658fcf4551ceeb734fba5c7727eb87e06babd
5
5
  SHA512:
6
- metadata.gz: 7c97974f67c32006787858ad2c2b3fd1a8c70c5dbd97a37d115f14d062773435bf3737352e9d456a2a9f28a28b969b0008aa472861cee46feaf18044f30c6b36
7
- data.tar.gz: 24bf6571db1ca03fd46ebefe7dfc02fbf86f04a825fc5c4b3272a9d46e443df9030a019b8558df55fd8ccf3e8eca7a384bb0fcf8d485dd29fe17e5ade719176e
6
+ metadata.gz: e37567409349ff5fe6c87802b89ffe2ecef3cd7b4b74fc332df6534e5c5e144b0d527d9f2d67d39c532dd117ebe35aa654e2779a4c10f6191f96b0a8fa9bb168
7
+ data.tar.gz: aef79cd03516814b259747bf9eaa0b449d692ca6568b6ecf9c4939e05ce279d7343d65caf8ceccc74e63094839e318bf5a855ea8b2d75e54f83d6e1e7f5ef6e8
data/CHANGELOG.md CHANGED
@@ -5,7 +5,70 @@ All notable changes to Konpeito will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.9.0] - 2026-03-16
9
+
10
+ ### Added
11
+ - **KUI widget expansion** (25→66 widgets): badge, avatar, progress_steps, number_stepper,
12
+ segmented_control, accordion, dropdown, tooltip, toast, alert/confirm dialog, slider,
13
+ switch, rating, textarea, color_picker, date_picker, grid, zstack, scaffold, wrap_panel,
14
+ nav_bar, bottom_nav, drawer, bottom_sheet, list_section, sortable_header, timeline,
15
+ skeleton, carousel_dots, and nested yield fix in mruby backend
16
+ - **KUI Markdown renderer** with live window resize support
17
+ - **KUI chart rendering**: bar, line, and pie charts
18
+ - **KUI Castella-style theming**: style composition and theme presets
19
+ (dark, light, solarized, etc.)
20
+ - **Basic IME support**: UTF-8 text buffers and CJK font loading
21
+ - **TUI Markdown demo** and TUI build resize stubs
22
+ - **Pomodoro Timer** KUI utility app
23
+ - **KUI framework extensions** and two new example apps (café menu, dashboard)
24
+
25
+ ### Fixed
26
+ - **case/when with integer constants** crashed with SIGSEGV
27
+ - **Large draw functions** caused SIGSEGV in Pomodoro Timer (resolved by function splitting)
28
+ - **Unboxed captures in escape-cells** caused GC SIGSEGV (now boxed before capture)
29
+ - **Nested yield+block** caused SIGSEGV (re-enabled yield target stack)
30
+
31
+ ### Changed
32
+ - KUI tutorial updated with new widget features
33
+ - KUI documentation clarified as immediate-mode UI framework
34
+
35
+ ## [0.8.0] - 2026-03-14
36
+
37
+ ### Added
38
+ - **KUI widget expansion**: text_input, checkbox, radio, toggle, spinner, status_bar,
39
+ selectable_list, list_item, table (header/row/cell), modal, tab_bar, tab_button,
40
+ tab_content — all with keyboard focus support and click-to-focus
41
+ - **KUI focus system**: Tab/arrow key navigation across all interactive widgets with
42
+ focus highlight and Enter key activation
43
+ - **KUI extended key input**: `kui_char_pressed()`, `kui_mod_pressed()`,
44
+ Delete/Home/End/PgUp/PgDn/F1-F12 key constants
45
+ - **KUI text buffer API**: `kui_textbuf_copy`, `kui_textbuf_render` for dynamic content
46
+ - **KUI GUI backend**: Full GUI implementations for all new widgets (Clay+Raylib)
47
+ - **Game framework expansion**: tween/easing (linear, quad, cubic, bounce, elastic),
48
+ screen shake, scene transitions (fade), FSM, timer system, parallax scrolling,
49
+ simple physics (AABB, gravity, friction), particle system, object pool, grid/tile
50
+ utilities, debug overlay (FPS, collision rects), gamepad abstraction, save/load
51
+ - **Game showcase demo**: "Coin Dash" score-attack platformer demonstrating
52
+ game framework API (physics, particles, tween, FSM, parallax, Camera2D)
53
+ - **Physics demo**: Platformer demo showcasing NativeArray-based physics
54
+ - **KUI form demo**: Text input, checkbox, radio, toggle showcase
55
+ - **KUI tabs demo**: Tab bar, table, modal dialog, selectable list, status bar showcase
56
+ - **KUI showcase demo**: Rich GUI app demonstrating all v0.8.0 widgets in a 4-tab layout
57
+
58
+ ### Fixed
59
+ - **NativeArray SEGV as function argument**: `convert_value()` lacked
60
+ `[:native_array, :value]` case, causing raw LLVM pointers where Ruby VALUEs
61
+ were expected
62
+ - **NativeArray SEGV in compound expressions**: `get_effective_unboxed_type()`
63
+ only checked `@variable_types` for LoadLocal nodes, causing incorrect float path
64
+ for integer arithmetic in if conditions
65
+ - **KUI text input isolation**: Only focused text_input processes keyboard input
66
+ - **KUI toggle flip-back**: Filter out termbox2 mouse release events (key 65509)
67
+ - **KUI modal layout**: Reduced TUI padding from 16 to 1 char cells
68
+ - **mruby keyword argument handling**: Optional keyword hash with `MRB_ARGS_ARG`
69
+ - **KUI scroll containers not scrolling**: `Clay_GetScrollOffset()` was not called
70
+ in `konpeito_clay_scroll` / `konpeito_clay_tui_scroll`, so `childOffset` remained
71
+ zero and scroll position was never visually applied
9
72
 
10
73
  ## [0.7.1] - 2026-03-14
11
74
 
@@ -18,7 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
81
  - **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.
19
82
  - **ClayTUI stdlib**: Terminal UI backend using Clay layout engine + termbox2 rendering. Auto-detected via `ClayTUI` module reference.
20
83
  - **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.
21
- - **RPG framework stdlib**: Reusable RPG game components (tilemap, sprites, camera, battle system) for raylib-based games.
84
+ - **Game framework stdlib**: Reusable 2D game components (tilemap, sprites, camera, battle system) for raylib-based games.
22
85
  - **Stdlib auto-detection expansion**: `KonpeitoJSON`, `KonpeitoHTTP`, `KonpeitoCrypto`, `KonpeitoCompression` added to `STDLIB_MODULE_MAP` for automatic RBS injection when referenced in source code.
23
86
  - **KUI auto-path resolution**: `require "kui_gui"` / `require "kui_tui"` works without `-I` flags — KUI stdlib directory is automatically included in compiler search paths.
24
87
  - **KUI example apps**: Counter (GUI + TUI), minimal hello, multi-page dashboard with sidebar navigation.
@@ -341,6 +404,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
341
404
  - `%a{extern}` - external C struct wrappers
342
405
  - `%a{simd}` - SIMD vectorization
343
406
 
407
+ [0.9.0]: https://github.com/i2y/konpeito/compare/v0.8.0...v0.9.0
408
+ [0.8.0]: https://github.com/i2y/konpeito/compare/v0.7.1...v0.8.0
409
+ [0.7.1]: https://github.com/i2y/konpeito/compare/v0.7.0...v0.7.1
410
+ [0.7.0]: https://github.com/i2y/konpeito/compare/v0.6.0...v0.7.0
411
+ [0.6.0]: https://github.com/i2y/konpeito/compare/v0.5.0...v0.6.0
412
+ [0.5.0]: https://github.com/i2y/konpeito/compare/v0.4.2...v0.5.0
413
+ [0.4.2]: https://github.com/i2y/konpeito/compare/v0.4.1...v0.4.2
344
414
  [0.4.1]: https://github.com/i2y/konpeito/compare/v0.4.0...v0.4.1
345
415
  [0.4.0]: https://github.com/i2y/konpeito/compare/v0.3.1...v0.4.0
346
416
  [0.3.1]: https://github.com/i2y/konpeito/compare/v0.3.0...v0.3.1
@@ -823,6 +823,13 @@ module Konpeito
823
823
  # We'll use rb_yield_values2 instead: VALUE rb_yield_values2(int argc, const VALUE *argv)
824
824
  @rb_yield_values2 = @mod.functions.add("rb_yield_values2", [LLVM::Int32, LLVM::Pointer(value_type)], value_type)
825
825
 
826
+ # Nested yield: yield to the enclosing method's block from inside a block callback
827
+ @konpeito_yield_to_method_block = @mod.functions.add("konpeito_yield_to_method_block", [value_type], value_type)
828
+ @konpeito_yield_to_method_block_values = @mod.functions.add("konpeito_yield_to_method_block_values",
829
+ [LLVM::Int32, LLVM::Pointer(value_type)], value_type)
830
+ @konpeito_push_yield_target = @mod.functions.add("konpeito_push_yield_target", [], LLVM::Type.void)
831
+ @konpeito_pop_yield_target = @mod.functions.add("konpeito_pop_yield_target", [], LLVM::Type.void)
832
+
826
833
  # int rb_block_given_p(void) - check if block is given
827
834
  @rb_block_given_p = @mod.functions.add("rb_block_given_p", [], LLVM::Int32)
828
835
 
@@ -1597,6 +1604,24 @@ module Konpeito
1597
1604
  boxed_value = func.params[i + 1] # +1 to skip self
1598
1605
  type_tag = @variable_types[param.name]
1599
1606
 
1607
+ if type_tag == :native_array
1608
+ # NativeArray parameter: the VALUE is actually a pointer address (via ptr2int).
1609
+ # Reconstruct the pointer and store directly in @variables (not alloca).
1610
+ # generate_load_local reads NativeArray values from @variables, not allocas.
1611
+ array_ptr = @builder.int2ptr(boxed_value, ptr_type, "#{param.name}_ptr")
1612
+ @variables[param.name] = array_ptr
1613
+ # Propagate element type and length from the parameter's HM type if available
1614
+ if param.type.is_a?(TypeChecker::Types::NativeArrayType)
1615
+ elem_type = param.type.element_type
1616
+ @variables["#{param.name}_len"] = LLVM::Int64.from_i(param.type.size || 0)
1617
+ if elem_type && elem_type != :Int64 && elem_type != :Float64
1618
+ @native_array_class_types ||= {}
1619
+ @native_array_class_types[param.name] = elem_type
1620
+ end
1621
+ end
1622
+ next
1623
+ end
1624
+
1600
1625
  # Unbox parameter if it's a numeric type
1601
1626
  value_to_store = case type_tag
1602
1627
  when :i64
@@ -1749,6 +1774,15 @@ module Konpeito
1749
1774
  @builder.store(arena_idx, @mruby_gc_arena_alloca)
1750
1775
  end
1751
1776
 
1777
+ # For mruby: if this function has yield inside block callbacks (nested yield),
1778
+ # push the current method's block as the yield target so that nested callbacks
1779
+ # can yield to the correct block instead of the innermost callback.
1780
+ @needs_yield_target_pop = false
1781
+ if @runtime == :mruby && function_has_nested_yield?(hir_func)
1782
+ @builder.call(@konpeito_push_yield_target)
1783
+ @needs_yield_target_pop = true
1784
+ end
1785
+
1752
1786
  # Insert profiling entry probe after parameter setup
1753
1787
  insert_profile_entry_probe(hir_func)
1754
1788
 
@@ -1778,6 +1812,10 @@ module Konpeito
1778
1812
  # For the mruby backend, restores the arena to the saved index and
1779
1813
  # protects the return value from GC before returning.
1780
1814
  def emit_ret(value)
1815
+ # Pop yield target before return if this function pushed one
1816
+ if @needs_yield_target_pop
1817
+ @builder.call(@konpeito_pop_yield_target)
1818
+ end
1781
1819
  if @mruby_gc_arena_alloca
1782
1820
  idx = @builder.load2(LLVM::Int32, @mruby_gc_arena_alloca, "_arena_r")
1783
1821
  @builder.call(@konpeito_gc_arena_restore, idx)
@@ -1789,6 +1827,32 @@ module Konpeito
1789
1827
  @builder.ret(value)
1790
1828
  end
1791
1829
 
1830
+ # Check if a function has yield inside any block callback body (nested yield).
1831
+ # Direct yield in the function body does NOT count (that uses rb_yield normally).
1832
+ def function_has_nested_yield?(hir_func)
1833
+ hir_func.body.each do |bb|
1834
+ bb.instructions.each do |inst|
1835
+ if inst.is_a?(HIR::Call) && inst.block
1836
+ return true if block_body_contains_yield?(inst.block)
1837
+ end
1838
+ end
1839
+ end
1840
+ false
1841
+ end
1842
+
1843
+ # Recursively check if a block body contains a Yield instruction.
1844
+ def block_body_contains_yield?(block_def)
1845
+ block_def.body.each do |bb|
1846
+ bb.instructions.each do |inst|
1847
+ return true if inst.is_a?(HIR::Yield)
1848
+ if inst.is_a?(HIR::Call) && inst.block
1849
+ return true if block_body_contains_yield?(inst.block)
1850
+ end
1851
+ end
1852
+ end
1853
+ false
1854
+ end
1855
+
1792
1856
  # Topologically sort blocks based on phi dependencies
1793
1857
  # Ensures blocks are generated after the blocks their phi nodes reference
1794
1858
  def sort_blocks_by_phi_dependencies(blocks)
@@ -2004,6 +2068,10 @@ module Konpeito
2004
2068
  [LLVM::Int64, :i64]
2005
2069
  elsif float_type?(ruby_type)
2006
2070
  [LLVM::Double, :double]
2071
+ elsif ruby_type.is_a?(TypeChecker::Types::NativeArrayType)
2072
+ # NativeArray parameters: alloca holds VALUE (i64) but type is :native_array
2073
+ # The actual pointer is reconstructed from the VALUE in parameter setup
2074
+ [value_type, :native_array]
2007
2075
  else
2008
2076
  [value_type, :value]
2009
2077
  end
@@ -3029,6 +3097,9 @@ module Konpeito
3029
3097
  when [:native_class, :value]
3030
3098
  # NativeClass struct pointer to VALUE (i64)
3031
3099
  @builder.ptr2int(value, LLVM::Int64)
3100
+ when [:native_array, :value]
3101
+ # NativeArray pointer to VALUE (i64) — encode pointer address as integer
3102
+ @builder.ptr2int(value, LLVM::Int64)
3032
3103
  when [:i64, :double]
3033
3104
  @builder.si2fp(value, LLVM::Double)
3034
3105
  when [:double, :i64]
@@ -4789,11 +4860,19 @@ module Konpeito
4789
4860
  if use_escape_cells
4790
4861
  esc_ary = @builder.call(@rb_ary_new_capa, LLVM::Int64.from_i(all_captures.size), "blk_esc_ary")
4791
4862
  all_captures.each do |cap|
4863
+ cap_type = capture_types[cap.name] || :value
4792
4864
  val = if @variable_allocas[cap.name]
4793
4865
  @builder.load2(value_type, @variable_allocas[cap.name], "blk_esc_#{cap.name}")
4794
4866
  else
4795
4867
  qnil
4796
4868
  end
4869
+ # Box unboxed values before pushing to the escape-cells Ruby Array.
4870
+ # The GC scans array elements as mrb_values; a raw unboxed integer
4871
+ # (e.g., from NativeArray) without fixnum tag bits would be
4872
+ # misinterpreted as a heap pointer → SIGSEGV in gc_mark_children.
4873
+ if cap_type != :value
4874
+ val = convert_value(val, cap_type, :value)
4875
+ end
4797
4876
  @builder.call(@rb_ary_push, esc_ary, val)
4798
4877
  end
4799
4878
 
@@ -4891,8 +4970,9 @@ module Konpeito
4891
4970
  # Set @in_block_callback so nested proc creation uses GC-safe escape-cells mode.
4892
4971
  # Reset mruby constant cache — LLVM values are scoped per function, so
4893
4972
  # cached %qnil/%qtrue from the parent function are invalid here.
4894
- # Clear GC arena alloca — callbacks don't own their own arena scope.
4895
4973
  @in_block_callback = true
4974
+
4975
+ # Clear GC arena alloca — callbacks run under the caller's arena scope.
4896
4976
  @mruby_gc_arena_alloca = nil
4897
4977
  @variables = {}
4898
4978
  @variable_types = {}
@@ -4911,13 +4991,21 @@ module Konpeito
4911
4991
  captures.each_with_index do |capture, i|
4912
4992
  val = @builder.call(@rb_ary_entry, ary_val,
4913
4993
  LLVM::Int64.from_i(i), "esc_#{capture.name}")
4994
+ # Values were boxed when pushed to the escape-cells array
4995
+ # (to prevent GC from misinterpreting raw unboxed integers as
4996
+ # heap pointers). Unbox them back to their original type.
4997
+ cap_type = capture_types[capture.name] || :value
4998
+ if cap_type != :value
4999
+ val = convert_value(val, :value, cap_type)
5000
+ end
4914
5001
  # Store in an alloca so LoadLocal/StoreLocal can use it normally.
4915
5002
  # Writes to this alloca stay local (no back-write to the GC array),
4916
5003
  # which is correct for snapshots from a returned outer scope.
4917
- cap_alloca = @builder.alloca(value_type, "esc_alloca_#{capture.name}")
5004
+ llvm_type = type_tag_to_llvm_type(cap_type)
5005
+ cap_alloca = @builder.alloca(llvm_type, "esc_alloca_#{capture.name}")
4918
5006
  @builder.store(val, cap_alloca)
4919
5007
  @variable_allocas[capture.name] = cap_alloca
4920
- @variable_types[capture.name] = capture_types[capture.name] || :value
5008
+ @variable_types[capture.name] = cap_type
4921
5009
  end
4922
5010
  else
4923
5011
  # data2 is a pointer to array of VALUE* (pointers to captured variables).
@@ -5124,10 +5212,7 @@ module Konpeito
5124
5212
  # Return the last value in the block, or Qnil
5125
5213
  # For while_exit blocks, the last instruction loads the break value
5126
5214
  if last_instr
5127
- result_val = last_instr
5128
- # Box unboxed values before returning
5129
- result_type = :value # Default
5130
- @builder.ret(result_val)
5215
+ @builder.ret(last_instr)
5131
5216
  else
5132
5217
  @builder.ret(qnil)
5133
5218
  end
@@ -7372,6 +7457,15 @@ module Konpeito
7372
7457
  return type_tag if type_tag == :i64 || type_tag == :double
7373
7458
  end
7374
7459
 
7460
+ # Check @variable_types for any instruction with result_var.
7461
+ # This is critical for sub-expressions (e.g. NativeArrayGet, Call) whose
7462
+ # HIR type may be an unresolved TypeVar but whose actual generated type
7463
+ # is correctly tracked in @variable_types.
7464
+ if hir_value.is_a?(HIR::Instruction) && hir_value.result_var
7465
+ type_tag = @variable_types[hir_value.result_var]
7466
+ return type_tag if type_tag == :i64 || type_tag == :double
7467
+ end
7468
+
7375
7469
  # Check HIR type
7376
7470
  hir_type = get_type(hir_value)
7377
7471
  if integer_type?(hir_type)
@@ -7948,15 +8042,20 @@ module Konpeito
7948
8042
  end
7949
8043
 
7950
8044
  def generate_yield(inst)
8045
+ # When yield is inside a block callback, use the yield target stack
8046
+ # to reach the enclosing method's block instead of the innermost callback.
8047
+ yield_fn = @in_block_callback ? @konpeito_yield_to_method_block : @rb_yield
8048
+ yield_values_fn = @in_block_callback ? @konpeito_yield_to_method_block_values : @rb_yield_values2
8049
+
7951
8050
  result = if inst.args.empty?
7952
8051
  # yield with no arguments - pass Qnil
7953
- @builder.call(@rb_yield, qnil)
8052
+ @builder.call(yield_fn, qnil)
7954
8053
  elsif inst.args.size == 1
7955
8054
  # yield with single argument — must be boxed VALUE for rb_yield
7956
8055
  arg_value = get_value_as_ruby(inst.args.first)
7957
- @builder.call(@rb_yield, arg_value)
8056
+ @builder.call(yield_fn, arg_value)
7958
8057
  else
7959
- # yield with multiple arguments - use rb_yield_values2
8058
+ # yield with multiple arguments
7960
8059
  argc = LLVM::Int32.from_i(inst.args.size)
7961
8060
 
7962
8061
  # Allocate array on stack
@@ -7970,7 +8069,7 @@ module Konpeito
7970
8069
 
7971
8070
  # Cast to VALUE*
7972
8071
  argv_ptr = @builder.bit_cast(argv, LLVM::Pointer(value_type))
7973
- @builder.call(@rb_yield_values2, argc, argv_ptr)
8072
+ @builder.call(yield_values_fn, argc, argv_ptr)
7974
8073
  end
7975
8074
 
7976
8075
  @variables[inst.result_var] = result if inst.result_var
@@ -8855,6 +8954,10 @@ module Konpeito
8855
8954
  # String === arg: Use rb_str_equal
8856
8955
  return inline_string_case_eq(receiver, arg)
8857
8956
  elsif receiver_hir.is_a?(HIR::ConstantLookup)
8957
+ if is_integer_type?(receiver_hir.type)
8958
+ # Integer constant (e.g. OP_ADD = 1) === arg: VALUE equality check
8959
+ return inline_value_case_eq(receiver, arg)
8960
+ end
8858
8961
  # Class/Module === arg: Use rb_obj_is_kind_of
8859
8962
  return inline_class_case_eq(receiver, arg)
8860
8963
  end
@@ -8892,6 +8995,13 @@ module Konpeito
8892
8995
  @builder.select(result_bool, qtrue, qfalse)
8893
8996
  end
8894
8997
 
8998
+ # Inline VALUE equality check for integer constants (e.g. OP_ADD = 1)
8999
+ # Since CRuby/mruby Fixnums are immediate values, VALUE equality == numeric equality
9000
+ def inline_value_case_eq(receiver, arg)
9001
+ eq_bool = @builder.icmp(:eq, receiver, arg)
9002
+ @builder.select(eq_bool, qtrue, qfalse)
9003
+ end
9004
+
8895
9005
  # Inline String === arg comparison
8896
9006
  def inline_string_case_eq(receiver, arg)
8897
9007
  # Use rb_str_equal which returns Qtrue/Qfalse
@@ -78,13 +78,17 @@ module Konpeito
78
78
  # Generate license file alongside the executable
79
79
  generate_license_file
80
80
  ensure
81
- # Clean up intermediate files
81
+ # Clean up intermediate files (keep .ll for debugging if ENV['KONPEITO_KEEP_IR'] is set)
82
+ keep_ir = ENV['KONPEITO_KEEP_IR']
82
83
  all_temps = [ir_file, obj_file, init_c_file, init_obj_file, helpers_obj_file] + extra_obj_files
83
84
  all_temps.each do |f|
85
+ next if keep_ir && f&.end_with?('.ll')
84
86
  FileUtils.rm_f(f) if f && File.exist?(f)
85
87
  end
86
88
  # Also clean optimized IR if it was generated
87
- FileUtils.rm_f("#{ir_file}.opt.ll") if File.exist?("#{ir_file}.opt.ll")
89
+ unless keep_ir
90
+ FileUtils.rm_f("#{ir_file}.opt.ll") if File.exist?("#{ir_file}.opt.ll")
91
+ end
88
92
  end
89
93
  end
90
94
 
@@ -125,6 +129,8 @@ module Konpeito
125
129
  lines << "/* Block stack for rb_yield/rb_block_given_p support */"
126
130
  lines << "extern void konpeito_push_block(mrb_value block);"
127
131
  lines << "extern void konpeito_pop_block(void);"
132
+ lines << "extern void konpeito_push_yield_target(void);"
133
+ lines << "extern void konpeito_pop_yield_target(void);"
128
134
  lines << ""
129
135
 
130
136
  # Collect NativeClasses from RBS loader
@@ -302,7 +308,7 @@ module Konpeito
302
308
 
303
309
  arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
304
310
  wrapper = "mrb_wrap_#{mangled_name}"
305
- mrb_args = arity_to_mrb_args(arity)
311
+ mrb_args = effective_mrb_args(mangled_name, arity)
306
312
  lines << " mrb_define_method(mrb, #{module_var}, \"#{method_name}\", #{wrapper}, #{mrb_args});"
307
313
  end
308
314
 
@@ -316,7 +322,7 @@ module Konpeito
316
322
 
317
323
  arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
318
324
  wrapper = "mrb_wrap_#{mangled_name}"
319
- mrb_args = arity_to_mrb_args(arity)
325
+ mrb_args = effective_mrb_args(mangled_name, arity)
320
326
  lines << " mrb_define_class_method(mrb, #{module_var}, \"#{method_name}\", #{wrapper}, #{mrb_args});"
321
327
  end
322
328
 
@@ -372,7 +378,7 @@ module Konpeito
372
378
 
373
379
  arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
374
380
  wrapper = "mrb_wrap_#{mangled_name}"
375
- mrb_args = arity_to_mrb_args(arity)
381
+ mrb_args = effective_mrb_args(mangled_name, arity)
376
382
  lines << " mrb_define_method(mrb, #{class_var}, \"#{method_name}\", #{wrapper}, #{mrb_args});"
377
383
  end
378
384
 
@@ -386,7 +392,7 @@ module Konpeito
386
392
 
387
393
  arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
388
394
  wrapper = "mrb_wrap_#{mangled_name}"
389
- mrb_args = arity_to_mrb_args(arity)
395
+ mrb_args = effective_mrb_args(mangled_name, arity)
390
396
  lines << " mrb_define_class_method(mrb, #{class_var}, \"#{method_name}\", #{wrapper}, #{mrb_args});"
391
397
  end
392
398
 
@@ -408,7 +414,7 @@ module Konpeito
408
414
 
409
415
  arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
410
416
  wrapper = "mrb_wrap_#{mangled_name}"
411
- mrb_args = arity_to_mrb_args(arity)
417
+ mrb_args = effective_mrb_args(mangled_name, arity)
412
418
  lines << " mrb_define_method(mrb, mrb->object_class, \"#{func_def.name}\", #{wrapper}, #{mrb_args});"
413
419
  end
414
420
 
@@ -456,13 +462,14 @@ module Konpeito
456
462
  else
457
463
  arity = llvm_func.params.size - 1 # Subtract self
458
464
 
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`).
465
+ # Check if this function has keyword args.
466
+ # The kwargs hash arg should be optional — caller may pass only
467
+ # positional args when no keywords are specified.
462
468
  func_base_name = mangled_name.sub(/\Arn_/, "")
463
469
  kw_info = llvm_generator.keyword_param_functions[func_base_name] ||
464
470
  llvm_generator.keyword_param_functions[func_base_name.to_sym]
465
471
  kwargs_only = kw_info && kw_info[:regular_count] == 0 && arity == 1
472
+ has_mixed_kwargs = kw_info && kw_info[:regular_count] > 0 && arity > 1
466
473
 
467
474
  lines << "static mrb_value #{wrapper_name}(mrb_state *mrb, mrb_value self) {"
468
475
  if kwargs_only
@@ -470,6 +477,15 @@ module Konpeito
470
477
  lines << " mrb_value a0 = mrb_nil_value(), _block = mrb_nil_value();"
471
478
  lines << " mrb_get_args(mrb, \"|o&\", &a0, &_block);"
472
479
  lines << " if (mrb_nil_p(a0)) a0 = mrb_hash_new(mrb);"
480
+ elsif has_mixed_kwargs
481
+ # Positional args + optional kwargs hash — last arg defaults to empty hash
482
+ kw_hash_idx = arity - 1
483
+ req_count = kw_info[:regular_count]
484
+ lines << " mrb_value #{(0...arity).map { |i| "a#{i} = mrb_nil_value()" }.join(', ')}, _block = mrb_nil_value();"
485
+ format_str = "o" * req_count + "|o&"
486
+ args_list = (0...arity).map { |i| "&a#{i}" }.join(", ") + ", &_block"
487
+ lines << " mrb_get_args(mrb, \"#{format_str}\", #{args_list});"
488
+ lines << " if (mrb_nil_p(a#{kw_hash_idx})) a#{kw_hash_idx} = mrb_hash_new(mrb);"
473
489
  elsif arity > 0
474
490
  lines << " mrb_value #{(0...arity).map { |i| "a#{i}" }.join(', ')}, _block;"
475
491
  format_str = "o" * arity + "&"
@@ -804,6 +820,24 @@ module Konpeito
804
820
  end
805
821
  end
806
822
 
823
+ # Compute mrb_args for a function, accounting for optional keyword hash.
824
+ # Functions with keyword params can be called with or without the keyword
825
+ # hash argument, so the kwargs arg must be registered as optional.
826
+ def effective_mrb_args(mangled_name, arity)
827
+ return arity_to_mrb_args(arity) if arity <= 0
828
+
829
+ func_base_name = mangled_name.sub(/\Arn_/, "")
830
+ kw_info = llvm_generator.keyword_param_functions[func_base_name] ||
831
+ llvm_generator.keyword_param_functions[func_base_name.to_sym]
832
+ if kw_info
833
+ # Keyword hash is optional — use MRB_ARGS_ARG(required, optional)
834
+ req = kw_info[:regular_count]
835
+ "MRB_ARGS_ARG(#{req}, #{arity - req})"
836
+ else
837
+ arity_to_mrb_args(arity)
838
+ end
839
+ end
840
+
807
841
  def resolve_superclass_mrb_expr(superclass, non_native_classes)
808
842
  return "mrb->object_class" unless superclass
809
843
 
@@ -96,6 +96,90 @@ void konpeito_pop_block(void) {
96
96
  }
97
97
  }
98
98
 
99
+ /* Forward declarations for yield target stack and nested yield support */
100
+ static mrb_value konpeito_get_block(void);
101
+ static konpeito_native_cb_entry *konpeito_get_native_cb(void);
102
+ mrb_value rb_yield(mrb_value val);
103
+ mrb_value rb_yield_values2(int argc, const mrb_value *argv);
104
+
105
+ /* ================================================================
106
+ * Yield target stack for nested yield
107
+ *
108
+ * When a method receives a block and its body (via nested block
109
+ * callbacks) calls yield, the global block stack top points to the
110
+ * innermost callback, not the method's block.
111
+ *
112
+ * The yield target stack captures the block at method entry so that
113
+ * yield inside any nested callback can find the correct target.
114
+ * ================================================================ */
115
+
116
+ static mrb_value konpeito_yield_target_block[KONPEITO_MAX_BLOCK_STACK];
117
+ static konpeito_native_cb_entry konpeito_yield_target_native[KONPEITO_MAX_BLOCK_STACK];
118
+ static int konpeito_yield_target_depth = 0;
119
+
120
+ void konpeito_push_yield_target(void) {
121
+ if (konpeito_yield_target_depth < KONPEITO_MAX_BLOCK_STACK) {
122
+ /* Capture the current top of block_stack as the yield target */
123
+ konpeito_yield_target_block[konpeito_yield_target_depth] = konpeito_get_block();
124
+ konpeito_native_cb_entry *native = konpeito_get_native_cb();
125
+ if (native) {
126
+ konpeito_yield_target_native[konpeito_yield_target_depth] = *native;
127
+ } else {
128
+ konpeito_yield_target_native[konpeito_yield_target_depth].func = NULL;
129
+ }
130
+ konpeito_yield_target_depth++;
131
+ }
132
+ }
133
+
134
+ void konpeito_pop_yield_target(void) {
135
+ if (konpeito_yield_target_depth > 0) {
136
+ konpeito_yield_target_depth--;
137
+ }
138
+ }
139
+
140
+ /* Yield to the enclosing method's block (not the immediate callback).
141
+ * Used by yield inside block callbacks to propagate correctly. */
142
+ mrb_value konpeito_yield_to_method_block(mrb_value val) {
143
+ mrb_state *mrb = konpeito_mrb_state;
144
+
145
+ if (konpeito_yield_target_depth > 0) {
146
+ konpeito_native_cb_entry *target =
147
+ &konpeito_yield_target_native[konpeito_yield_target_depth - 1];
148
+ if (target->func) {
149
+ mrb_value argv[1];
150
+ argv[0] = val;
151
+ return target->func(val, target->data2, 1, argv);
152
+ }
153
+ mrb_value block = konpeito_yield_target_block[konpeito_yield_target_depth - 1];
154
+ if (!mrb_nil_p(block)) {
155
+ return mrb_funcall(mrb, block, "call", 1, val);
156
+ }
157
+ }
158
+
159
+ /* Fallback to normal yield */
160
+ return rb_yield(val);
161
+ }
162
+
163
+ mrb_value konpeito_yield_to_method_block_values(int argc, const mrb_value *argv) {
164
+ mrb_state *mrb = konpeito_mrb_state;
165
+
166
+ if (konpeito_yield_target_depth > 0) {
167
+ konpeito_native_cb_entry *target =
168
+ &konpeito_yield_target_native[konpeito_yield_target_depth - 1];
169
+ if (target->func) {
170
+ mrb_value yielded = (argc > 0) ? argv[0] : mrb_nil_value();
171
+ return target->func(yielded, target->data2, argc, argv);
172
+ }
173
+ mrb_value block = konpeito_yield_target_block[konpeito_yield_target_depth - 1];
174
+ if (!mrb_nil_p(block)) {
175
+ mrb_sym call_sym = mrb_intern_cstr(mrb, "call");
176
+ return mrb_funcall_argv(mrb, block, call_sym, argc, argv);
177
+ }
178
+ }
179
+
180
+ return rb_yield_values2(argc, argv);
181
+ }
182
+
99
183
  static mrb_value konpeito_get_block(void) {
100
184
  if (konpeito_block_stack_depth > 0) {
101
185
  return konpeito_block_stack[konpeito_block_stack_depth - 1];
@@ -1052,3 +1136,26 @@ void konpeito_mrb_data_init(mrb_value obj, void *ptr, const mrb_data_type *type)
1052
1136
 
1053
1137
  /* mrb_top_self wrapper */
1054
1138
  mrb_value konpeito_mrb_top_self(mrb_state *mrb) { return mrb_top_self(mrb); }
1139
+
1140
+ /* ================================================================
1141
+ * Live resize bridge — calls Ruby _kui_resize_frame() from C
1142
+ * ================================================================ */
1143
+
1144
+ /* Forward declaration from clay_native.c */
1145
+ typedef void (*clay_frame_fn)(void);
1146
+ extern void konpeito_clay_set_resize_frame_fn(clay_frame_fn fn);
1147
+
1148
+ static void konpeito_resize_frame_bridge(void) {
1149
+ mrb_state *mrb = konpeito_mrb_state;
1150
+ if (!mrb) return;
1151
+ int ai = mrb_gc_arena_save(mrb);
1152
+ mrb_funcall(mrb, mrb_top_self(mrb), "_kui_resize_frame", 0);
1153
+ if (mrb->exc) {
1154
+ mrb->exc = NULL; /* swallow errors during resize */
1155
+ }
1156
+ mrb_gc_arena_restore(mrb, ai);
1157
+ }
1158
+
1159
+ void konpeito_register_resize_callback(void) {
1160
+ konpeito_clay_set_resize_frame_fn(konpeito_resize_frame_bridge);
1161
+ }
@@ -26,6 +26,7 @@ module Clay
26
26
  def self.layout(dir, pl, pr, pt, pb, gap, swt, swv, sht, shv, ax, ay) end
27
27
  def self.bg(r, g, b, a, corner_radius) end
28
28
  def self.border(r, g, b, a, top, right, bottom, left, corner_radius) end
29
+ def self.set_custom(chart_id) end
29
30
  def self.scroll(horizontal, vertical) end
30
31
  def self.floating(ox, oy, z, att_elem, att_parent) end
31
32
 
@@ -33,6 +34,7 @@ module Clay
33
34
  def self.text(text, font_id, font_size, r, g, b, a, wrap) end
34
35
  def self.set_measure_text_raylib() end
35
36
  def self.load_font(path, size) end
37
+ def self.load_font_cjk(path, size) end
36
38
 
37
39
  # Render Command Access
38
40
  def self.cmd_type(index) end
@@ -48,10 +50,18 @@ module Clay
48
50
  def self.cmd_font_id(index) end
49
51
  def self.cmd_font_size(index) end
50
52
  def self.cmd_corner_radius(index) end
53
+ def self.cmd_ix(index) end
54
+ def self.cmd_iy(index) end
55
+ def self.cmd_iw(index) end
56
+ def self.cmd_ih(index) end
57
+ def self.cmd_custom_data(index) end
51
58
  def self.cmd_border_width_top(index) end
52
59
 
53
60
  # Bulk Rendering
54
61
  def self.render_raylib() end
62
+ def self.set_bg_color(r, g, b) end
63
+ def self.is_resizing() end
64
+ def self.register_resize_callback() end
55
65
 
56
66
  # Scroll
57
67
  def self.update_scroll(dx, dy, dt) end
@@ -63,4 +73,20 @@ module Clay
63
73
  def self.sizing_percent() end
64
74
  def self.left_to_right() end
65
75
  def self.top_to_bottom() end
76
+
77
+ # Text Buffer System
78
+ def self.textbuf_clear(id) end
79
+ def self.textbuf_copy(dst, src) end
80
+ def self.textbuf_putchar(id, ch) end
81
+ def self.textbuf_backspace(id) end
82
+ def self.textbuf_delete(id) end
83
+ def self.textbuf_cursor_left(id) end
84
+ def self.textbuf_cursor_right(id) end
85
+ def self.textbuf_cursor_home(id) end
86
+ def self.textbuf_cursor_end(id) end
87
+ def self.textbuf_len(id) end
88
+ def self.textbuf_cursor(id) end
89
+ def self.textbuf_render(id, fid, fsz, r, g, b) end
90
+ def self.textbuf_render_range(id, start_pos, end_pos, fid, fsz, r, g, b) end
91
+ def self.text_char(ch, fid, fsz, r, g, b) end
66
92
  end