konpeito 0.6.0 → 0.7.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/lib/konpeito/codegen/llvm_generator.rb +62 -3
- data/lib/konpeito/codegen/mruby_backend.rb +98 -2
- data/lib/konpeito/codegen/mruby_helpers.c +125 -18
- data/lib/konpeito/compiler.rb +14 -2
- data/lib/konpeito/hir/builder.rb +26 -0
- data/lib/konpeito/stdlib/clay/clay_native.c +24 -2
- data/lib/konpeito/stdlib/clay_tui/clay_tui.rb +112 -0
- data/lib/konpeito/stdlib/clay_tui/clay_tui.rbs +258 -0
- data/lib/konpeito/stdlib/clay_tui/clay_tui_native.c +452 -0
- data/lib/konpeito/stdlib/kui/kui.rb +345 -0
- data/lib/konpeito/stdlib/kui/kui.rbs +53 -0
- data/lib/konpeito/stdlib/kui/kui_events.rb +17 -0
- data/lib/konpeito/stdlib/kui/kui_events.rbs +1 -0
- data/lib/konpeito/stdlib/kui/kui_gui.rb +317 -0
- data/lib/konpeito/stdlib/kui/kui_gui.rbs +2 -0
- data/lib/konpeito/stdlib/kui/kui_theme.rb +143 -0
- data/lib/konpeito/stdlib/kui/kui_theme.rbs +9 -0
- data/lib/konpeito/stdlib/kui/kui_tui.rb +443 -0
- data/lib/konpeito/stdlib/kui/kui_tui.rbs +5 -0
- data/lib/konpeito/stdlib/rpg_framework/rpg_framework.rb +668 -0
- data/lib/konpeito/stdlib/shell/shell.rb +20 -0
- data/lib/konpeito/stdlib/shell/shell.rbs +59 -0
- data/lib/konpeito/stdlib/shell/shell_native.c +195 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +15 -0
- data/lib/konpeito/version.rb +1 -1
- data/vendor/clay/clay_renderer_termbox2.c +215 -0
- data/vendor/termbox2/LICENSE +19 -0
- data/vendor/termbox2/termbox2.h +4307 -0
- data/vendor/termbox2/termbox2_impl.c +3 -0
- metadata +22 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34d8bfa7eaccc6ae3cb5352a188346ed97f98b7b12940512292899bdfba280ef
|
|
4
|
+
data.tar.gz: a2298a81832fdd48c77153575d9a41d719e7ac29a3d0ae96800cb70e99be6893
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c97974f67c32006787858ad2c2b3fd1a8c70c5dbd97a37d115f14d062773435bf3737352e9d456a2a9f28a28b969b0008aa472861cee46feaf18044f30c6b36
|
|
7
|
+
data.tar.gz: 24bf6571db1ca03fd46ebefe7dfc02fbf86f04a825fc5c4b3272a9d46e443df9030a019b8558df55fd8ccf3e8eca7a384bb0fcf8d485dd29fe17e5ade719176e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.1] - 2026-03-14
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **rescue handler callback crash**: `generate_rescue_handler_with_global_flag_callback` was restoring `@mruby_gc_arena_alloca` without saving it first, causing `NameError` in begin/rescue/else code generation.
|
|
14
|
+
|
|
15
|
+
## [0.7.0] - 2026-03-14
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **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
|
+
- **ClayTUI stdlib**: Terminal UI backend using Clay layout engine + termbox2 rendering. Auto-detected via `ClayTUI` module reference.
|
|
20
|
+
- **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.
|
|
22
|
+
- **Stdlib auto-detection expansion**: `KonpeitoJSON`, `KonpeitoHTTP`, `KonpeitoCrypto`, `KonpeitoCompression` added to `STDLIB_MODULE_MAP` for automatic RBS injection when referenced in source code.
|
|
23
|
+
- **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
|
+
- **KUI example apps**: Counter (GUI + TUI), minimal hello, multi-page dashboard with sidebar navigation.
|
|
25
|
+
- **DQ RPG demo with Clay UI**: Battle scenes, shop menus, and status displays using Clay layout integration.
|
|
26
|
+
- Keyword arguments documented as supported in mruby backend.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- **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.
|
|
30
|
+
- **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.
|
|
31
|
+
- **Documentation references**: Fixed CLAUDE.md referencing non-existent `docs/architecture.md`.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Tutorials (EN/JA) updated with KUI framework sections and all stdlib module documentation (Shell, JSON, HTTP, Crypto, Compression).
|
|
35
|
+
|
|
10
36
|
## [0.6.0] - 2026-03-11
|
|
11
37
|
|
|
12
38
|
### Added
|
|
@@ -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
|
|
@@ -8412,6 +8469,7 @@ module Konpeito
|
|
|
8412
8469
|
saved_block_callback_self = @block_callback_self
|
|
8413
8470
|
saved_rescue_escape_array = @rescue_escape_array
|
|
8414
8471
|
saved_rescue_escape_indices = @rescue_escape_indices
|
|
8472
|
+
saved_gc_arena_alloca = @mruby_gc_arena_alloca
|
|
8415
8473
|
|
|
8416
8474
|
# In normal mode: params[0] = self. In escape mode, set @block_callback_self after unpacking.
|
|
8417
8475
|
@block_callback_self = callback_func.params[0] unless escape_var_names
|
|
@@ -8532,6 +8590,7 @@ module Konpeito
|
|
|
8532
8590
|
@block_callback_self = saved_block_callback_self
|
|
8533
8591
|
@rescue_escape_array = saved_rescue_escape_array
|
|
8534
8592
|
@rescue_escape_indices = saved_rescue_escape_indices
|
|
8593
|
+
@mruby_gc_arena_alloca = saved_gc_arena_alloca
|
|
8535
8594
|
|
|
8536
8595
|
callback_func
|
|
8537
8596
|
end
|
|
@@ -9593,9 +9652,9 @@ module Konpeito
|
|
|
9593
9652
|
value, type_tag = get_value_with_type(term.value)
|
|
9594
9653
|
# Box the value before returning to Ruby
|
|
9595
9654
|
boxed = convert_value(value, type_tag, :value)
|
|
9596
|
-
|
|
9655
|
+
emit_ret(boxed)
|
|
9597
9656
|
else
|
|
9598
|
-
|
|
9657
|
+
emit_ret(qnil)
|
|
9599
9658
|
end
|
|
9600
9659
|
when HIR::Branch
|
|
9601
9660
|
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
|
|
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") }
|
|
@@ -1220,15 +1285,46 @@ module Konpeito
|
|
|
1220
1285
|
[clay_impl_obj]
|
|
1221
1286
|
end
|
|
1222
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
|
+
|
|
1223
1313
|
def ffi_include_flags
|
|
1224
1314
|
flags = []
|
|
1225
1315
|
|
|
1226
|
-
# Always add vendored Clay include path if Clay stdlib is used
|
|
1316
|
+
# Always add vendored Clay include path if Clay/ClayTUI stdlib is used
|
|
1227
1317
|
clay_dir = File.expand_path("../../../vendor/clay", __dir__)
|
|
1228
1318
|
if @extra_c_files.any? { |f| File.basename(f).include?("clay") } && Dir.exist?(clay_dir)
|
|
1229
1319
|
flags << "-I#{clay_dir}"
|
|
1230
1320
|
end
|
|
1231
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
|
+
|
|
1232
1328
|
if cross_compiling?
|
|
1233
1329
|
# When cross-compiling, use cross library include paths
|
|
1234
1330
|
flags << "-I#{@cross_libs_dir}/../include" if @cross_libs_dir && Dir.exist?("#{@cross_libs_dir}/../include")
|
|
@@ -43,18 +43,50 @@ mrb_state *konpeito_get_mrb_state(void) {
|
|
|
43
43
|
/* ================================================================
|
|
44
44
|
* Block stack for rb_yield / rb_block_given_p / rb_block_proc
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* We maintain TWO parallel stacks:
|
|
47
|
+
*
|
|
48
|
+
* 1. konpeito_block_stack: mruby Proc VALUE (for rb_block_proc,
|
|
49
|
+
* rb_block_given_p, and fallback rb_yield via mrb_funcall).
|
|
50
|
+
*
|
|
51
|
+
* 2. konpeito_native_cb_stack: native function pointer + data pairs
|
|
52
|
+
* (for rb_yield to call callbacks directly without re-entering
|
|
53
|
+
* mrb_vm_exec, which corrupts mruby's callinfo on nested yields).
|
|
54
|
+
*
|
|
55
|
+
* rb_block_call pushes both the native callback AND the mruby proc.
|
|
56
|
+
* The mruby method wrapper pushes only the mruby proc (native_cb=NULL).
|
|
57
|
+
* rb_yield prefers the native callback when available.
|
|
49
58
|
* ================================================================ */
|
|
50
59
|
|
|
60
|
+
typedef mrb_value (*cruby_block_callback_t)(mrb_value, mrb_value, int, const mrb_value*);
|
|
61
|
+
|
|
62
|
+
typedef struct {
|
|
63
|
+
cruby_block_callback_t func;
|
|
64
|
+
mrb_value data2;
|
|
65
|
+
} konpeito_native_cb_entry;
|
|
66
|
+
|
|
51
67
|
#define KONPEITO_MAX_BLOCK_STACK 64
|
|
52
68
|
static mrb_value konpeito_block_stack[KONPEITO_MAX_BLOCK_STACK];
|
|
69
|
+
static konpeito_native_cb_entry konpeito_native_cb_stack[KONPEITO_MAX_BLOCK_STACK];
|
|
53
70
|
static int konpeito_block_stack_depth = 0;
|
|
54
71
|
|
|
72
|
+
/* Pending native callback: set by rb_block_call before mrb_funcall_with_block,
|
|
73
|
+
* consumed by konpeito_push_block in the wrapper. */
|
|
74
|
+
static cruby_block_callback_t konpeito_pending_native_func = NULL;
|
|
75
|
+
static mrb_value konpeito_pending_native_data2;
|
|
76
|
+
|
|
55
77
|
void konpeito_push_block(mrb_value block) {
|
|
56
78
|
if (konpeito_block_stack_depth < KONPEITO_MAX_BLOCK_STACK) {
|
|
57
|
-
konpeito_block_stack[konpeito_block_stack_depth
|
|
79
|
+
konpeito_block_stack[konpeito_block_stack_depth] = block;
|
|
80
|
+
/* If rb_block_call set a pending native callback, attach it here.
|
|
81
|
+
* This ensures the wrapper's push includes the native callback info. */
|
|
82
|
+
if (konpeito_pending_native_func) {
|
|
83
|
+
konpeito_native_cb_stack[konpeito_block_stack_depth].func = konpeito_pending_native_func;
|
|
84
|
+
konpeito_native_cb_stack[konpeito_block_stack_depth].data2 = konpeito_pending_native_data2;
|
|
85
|
+
konpeito_pending_native_func = NULL;
|
|
86
|
+
} else {
|
|
87
|
+
konpeito_native_cb_stack[konpeito_block_stack_depth].func = NULL;
|
|
88
|
+
}
|
|
89
|
+
konpeito_block_stack_depth++;
|
|
58
90
|
}
|
|
59
91
|
}
|
|
60
92
|
|
|
@@ -71,6 +103,37 @@ static mrb_value konpeito_get_block(void) {
|
|
|
71
103
|
return mrb_nil_value();
|
|
72
104
|
}
|
|
73
105
|
|
|
106
|
+
static konpeito_native_cb_entry *konpeito_get_native_cb(void) {
|
|
107
|
+
if (konpeito_block_stack_depth > 0) {
|
|
108
|
+
konpeito_native_cb_entry *entry = &konpeito_native_cb_stack[konpeito_block_stack_depth - 1];
|
|
109
|
+
if (entry->func) return entry;
|
|
110
|
+
}
|
|
111
|
+
return NULL;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ================================================================
|
|
115
|
+
* GC arena management
|
|
116
|
+
*
|
|
117
|
+
* mruby objects created in LLVM-generated code live on the C stack,
|
|
118
|
+
* invisible to mruby's GC. The arena protects them as roots.
|
|
119
|
+
* Each generated function saves the arena index at entry and restores
|
|
120
|
+
* it before returning, preventing arena overflow.
|
|
121
|
+
* ================================================================ */
|
|
122
|
+
|
|
123
|
+
int konpeito_gc_arena_save(void) {
|
|
124
|
+
return mrb_gc_arena_save(konpeito_mrb_state);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
void konpeito_gc_arena_restore(int idx) {
|
|
128
|
+
mrb_gc_arena_restore(konpeito_mrb_state, idx);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void konpeito_gc_protect_value(mrb_value val) {
|
|
132
|
+
if (!mrb_immediate_p(val)) {
|
|
133
|
+
mrb_gc_protect(konpeito_mrb_state, val);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
74
137
|
/* ================================================================
|
|
75
138
|
* mruby constant values (Qnil, Qtrue, Qfalse, Qundef equivalents)
|
|
76
139
|
* These are stored as mrb_value but exposed as uint64_t (VALUE) to
|
|
@@ -141,23 +204,31 @@ double rb_num2dbl(mrb_value v) {
|
|
|
141
204
|
|
|
142
205
|
/* CRuby: VALUE rb_str_new(const char *ptr, long len) */
|
|
143
206
|
mrb_value rb_str_new(const char *ptr, long len) {
|
|
144
|
-
|
|
207
|
+
mrb_value s = mrb_str_new(konpeito_mrb_state, ptr, (mrb_int)len);
|
|
208
|
+
mrb_gc_protect(konpeito_mrb_state, s);
|
|
209
|
+
return s;
|
|
145
210
|
}
|
|
146
211
|
|
|
147
212
|
/* CRuby: VALUE rb_utf8_str_new(const char *ptr, long len) */
|
|
148
213
|
/* mruby doesn't distinguish encodings */
|
|
149
214
|
mrb_value rb_utf8_str_new(const char *ptr, long len) {
|
|
150
|
-
|
|
215
|
+
mrb_value s = mrb_str_new(konpeito_mrb_state, ptr, (mrb_int)len);
|
|
216
|
+
mrb_gc_protect(konpeito_mrb_state, s);
|
|
217
|
+
return s;
|
|
151
218
|
}
|
|
152
219
|
|
|
153
220
|
/* CRuby: VALUE rb_str_new_cstr(const char *ptr) */
|
|
154
221
|
mrb_value rb_str_new_cstr(const char *ptr) {
|
|
155
|
-
|
|
222
|
+
mrb_value s = mrb_str_new_cstr(konpeito_mrb_state, ptr);
|
|
223
|
+
mrb_gc_protect(konpeito_mrb_state, s);
|
|
224
|
+
return s;
|
|
156
225
|
}
|
|
157
226
|
|
|
158
227
|
/* CRuby: VALUE rb_str_dup(VALUE str) */
|
|
159
228
|
mrb_value rb_str_dup(mrb_value str) {
|
|
160
|
-
|
|
229
|
+
mrb_value s = mrb_str_dup(konpeito_mrb_state, str);
|
|
230
|
+
mrb_gc_protect(konpeito_mrb_state, s);
|
|
231
|
+
return s;
|
|
161
232
|
}
|
|
162
233
|
|
|
163
234
|
/* CRuby: VALUE rb_str_hash(VALUE str) */
|
|
@@ -230,7 +301,9 @@ mrb_value rb_reg_new_str(mrb_value str, int64_t options) {
|
|
|
230
301
|
|
|
231
302
|
/* CRuby: VALUE rb_ary_new_capa(long capa) */
|
|
232
303
|
mrb_value rb_ary_new_capa(int64_t capa) {
|
|
233
|
-
|
|
304
|
+
mrb_value a = mrb_ary_new_capa(konpeito_mrb_state, (mrb_int)capa);
|
|
305
|
+
mrb_gc_protect(konpeito_mrb_state, a);
|
|
306
|
+
return a;
|
|
234
307
|
}
|
|
235
308
|
|
|
236
309
|
/* CRuby: VALUE rb_ary_push(VALUE ary, VALUE item) */
|
|
@@ -256,12 +329,16 @@ void rb_ary_store(mrb_value ary, int64_t idx, mrb_value val) {
|
|
|
256
329
|
|
|
257
330
|
/* CRuby: VALUE rb_ary_new(void) */
|
|
258
331
|
mrb_value rb_ary_new(void) {
|
|
259
|
-
|
|
332
|
+
mrb_value a = mrb_ary_new(konpeito_mrb_state);
|
|
333
|
+
mrb_gc_protect(konpeito_mrb_state, a);
|
|
334
|
+
return a;
|
|
260
335
|
}
|
|
261
336
|
|
|
262
337
|
/* CRuby: VALUE rb_ary_new_from_values(long n, const VALUE *elts) */
|
|
263
338
|
mrb_value rb_ary_new_from_values(int64_t n, const mrb_value *elts) {
|
|
264
|
-
|
|
339
|
+
mrb_value a = mrb_ary_new_from_values(konpeito_mrb_state, (mrb_int)n, elts);
|
|
340
|
+
mrb_gc_protect(konpeito_mrb_state, a);
|
|
341
|
+
return a;
|
|
265
342
|
}
|
|
266
343
|
|
|
267
344
|
/* CRuby: VALUE rb_ary_subseq(VALUE ary, long beg, long len) */
|
|
@@ -305,7 +382,9 @@ mrb_value rb_ary_delete_at(mrb_value ary, int64_t pos) {
|
|
|
305
382
|
|
|
306
383
|
/* CRuby: VALUE rb_hash_new(void) */
|
|
307
384
|
mrb_value rb_hash_new(void) {
|
|
308
|
-
|
|
385
|
+
mrb_value h = mrb_hash_new(konpeito_mrb_state);
|
|
386
|
+
mrb_gc_protect(konpeito_mrb_state, h);
|
|
387
|
+
return h;
|
|
309
388
|
}
|
|
310
389
|
|
|
311
390
|
/* CRuby: VALUE rb_hash_aset(VALUE hash, VALUE key, VALUE val) */
|
|
@@ -412,17 +491,33 @@ mrb_value rb_obj_class(mrb_value obj) {
|
|
|
412
491
|
/* CRuby: VALUE rb_yield(VALUE val) */
|
|
413
492
|
mrb_value rb_yield(mrb_value val) {
|
|
414
493
|
mrb_state *mrb = konpeito_mrb_state;
|
|
494
|
+
|
|
495
|
+
/* Prefer direct native callback to avoid mrb_vm_exec re-entry issues */
|
|
496
|
+
konpeito_native_cb_entry *native = konpeito_get_native_cb();
|
|
497
|
+
if (native) {
|
|
498
|
+
mrb_value argv[1];
|
|
499
|
+
argv[0] = val;
|
|
500
|
+
return native->func(val, native->data2, 1, argv);
|
|
501
|
+
}
|
|
502
|
+
|
|
415
503
|
mrb_value block = konpeito_get_block();
|
|
416
504
|
if (mrb_nil_p(block)) {
|
|
417
505
|
mrb_raise(mrb, E_RUNTIME_ERROR, "no block given (yield)");
|
|
418
506
|
return mrb_nil_value();
|
|
419
507
|
}
|
|
420
|
-
return mrb_funcall(
|
|
508
|
+
return mrb_funcall(mrb, block, "call", 1, val);
|
|
421
509
|
}
|
|
422
510
|
|
|
423
511
|
/* CRuby: VALUE rb_yield_values2(int argc, const VALUE *argv) */
|
|
424
512
|
mrb_value rb_yield_values2(int argc, const mrb_value *argv) {
|
|
425
513
|
mrb_state *mrb = konpeito_mrb_state;
|
|
514
|
+
|
|
515
|
+
konpeito_native_cb_entry *native = konpeito_get_native_cb();
|
|
516
|
+
if (native) {
|
|
517
|
+
mrb_value yielded = (argc > 0) ? argv[0] : mrb_nil_value();
|
|
518
|
+
return native->func(yielded, native->data2, argc, argv);
|
|
519
|
+
}
|
|
520
|
+
|
|
426
521
|
mrb_value block = konpeito_get_block();
|
|
427
522
|
if (mrb_nil_p(block)) {
|
|
428
523
|
mrb_raise(mrb, E_RUNTIME_ERROR, "no block given (yield)");
|
|
@@ -456,8 +551,6 @@ mrb_value rb_block_proc(void) {
|
|
|
456
551
|
* the original callback pointer and data in the proc's environment.
|
|
457
552
|
* ================================================================ */
|
|
458
553
|
|
|
459
|
-
typedef mrb_value (*cruby_block_callback_t)(mrb_value, mrb_value, int, const mrb_value*);
|
|
460
|
-
|
|
461
554
|
/* Adapter: called as mruby cfunc, delegates to CRuby-style callback */
|
|
462
555
|
static mrb_value konpeito_cfunc_adapter(mrb_state *mrb, mrb_value self) {
|
|
463
556
|
mrb_value cb_ptr_val = mrb_proc_cfunc_env_get(mrb, 0);
|
|
@@ -481,15 +574,29 @@ mrb_value rb_block_call(mrb_value obj, mrb_sym mid, int argc, const mrb_value *a
|
|
|
481
574
|
return mrb_funcall_argv(konpeito_mrb_state, obj, mid, argc, argv);
|
|
482
575
|
}
|
|
483
576
|
|
|
484
|
-
/* Create a proc that wraps the CRuby callback via environment
|
|
577
|
+
/* Create a proc that wraps the CRuby callback via environment.
|
|
578
|
+
* This is needed for rb_block_proc() and as fallback for rb_yield()
|
|
579
|
+
* when the block was not pushed via rb_block_call (e.g., mruby Proc). */
|
|
485
580
|
mrb_value env[2];
|
|
486
581
|
env[0] = mrb_cptr_value(konpeito_mrb_state, proc);
|
|
487
|
-
env
|
|
582
|
+
/* Store nil instead of data2 in the proc env. data2 may be a raw C
|
|
583
|
+
* stack pointer (pointer-to-alloca capture mode) which is NOT a valid
|
|
584
|
+
* mrb_value. If GC marks the proc's env it would try to dereference
|
|
585
|
+
* that raw pointer as an RBasic* → crash in gc_mark_children.
|
|
586
|
+
* The native callback path reads data2 from konpeito_pending_native_data2
|
|
587
|
+
* (set below), so env[1] is never read in practice. */
|
|
588
|
+
env[1] = mrb_nil_value();
|
|
488
589
|
struct RProc *block_proc = mrb_proc_new_cfunc_with_env(
|
|
489
590
|
konpeito_mrb_state, konpeito_cfunc_adapter, 2, env);
|
|
490
591
|
mrb_value block_val = mrb_obj_value(block_proc);
|
|
491
592
|
|
|
492
|
-
|
|
593
|
+
/* Set pending native callback so the wrapper's konpeito_push_block call
|
|
594
|
+
* will attach the native function pointer. rb_yield will then call the
|
|
595
|
+
* native callback directly, bypassing mrb_vm_exec re-entry. */
|
|
596
|
+
konpeito_pending_native_func = (cruby_block_callback_t)proc;
|
|
597
|
+
konpeito_pending_native_data2 = data2;
|
|
598
|
+
mrb_value result = mrb_funcall_with_block(konpeito_mrb_state, obj, mid, argc, argv, block_val);
|
|
599
|
+
return result;
|
|
493
600
|
}
|
|
494
601
|
|
|
495
602
|
/* CRuby: VALUE rb_block_call_kw(...) */
|
data/lib/konpeito/compiler.rb
CHANGED
|
@@ -116,8 +116,14 @@ module Konpeito
|
|
|
116
116
|
|
|
117
117
|
def parse
|
|
118
118
|
log "Resolving dependencies for #{source_file}..."
|
|
119
|
+
# Include KUI stdlib in search paths so `require "kui_gui"` / `require "kui_tui"`
|
|
120
|
+
# resolves automatically without needing `-I` for the KUI directory.
|
|
121
|
+
kui_stdlib_dir = File.expand_path("stdlib/kui", __dir__)
|
|
122
|
+
all_require_paths = @require_paths.dup
|
|
123
|
+
all_require_paths << kui_stdlib_dir unless all_require_paths.include?(kui_stdlib_dir)
|
|
124
|
+
|
|
119
125
|
resolver = DependencyResolver.new(
|
|
120
|
-
base_paths:
|
|
126
|
+
base_paths: all_require_paths,
|
|
121
127
|
verbose: verbose,
|
|
122
128
|
cache_manager: @cache_manager
|
|
123
129
|
)
|
|
@@ -653,7 +659,13 @@ module Konpeito
|
|
|
653
659
|
# Map of stdlib modules: module name pattern => stdlib directory name
|
|
654
660
|
STDLIB_MODULE_MAP = {
|
|
655
661
|
"Raylib" => "raylib",
|
|
656
|
-
"Clay" => "clay"
|
|
662
|
+
"Clay" => "clay",
|
|
663
|
+
"ClayTUI" => "clay_tui",
|
|
664
|
+
"KonpeitoShell" => "shell",
|
|
665
|
+
"KonpeitoJSON" => "json",
|
|
666
|
+
"KonpeitoHTTP" => "http",
|
|
667
|
+
"KonpeitoCrypto" => "crypto",
|
|
668
|
+
"KonpeitoCompression" => "compression"
|
|
657
669
|
}.freeze
|
|
658
670
|
|
|
659
671
|
# Scan AST for known stdlib module references and auto-add their RBS paths
|