konpeito 0.1.2 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53a3bbb24175ed554f9f560c38e6f8138206da3fb6385f87358e9dfa3eed4f10
4
- data.tar.gz: 80026dca6352dcb9effa9e7bac8e7ebc58e517ae84cb1f15627935fbb3d5fee4
3
+ metadata.gz: b559f8a6de1d213b466939fb5fbde0a797a6808a7ce664f28d55ed0496696dde
4
+ data.tar.gz: 6809762994527a50fcf96c1644f24cde5f2324b668e1dc646bf32077ea679af0
5
5
  SHA512:
6
- metadata.gz: 4233ae433c5730c64da97806275ca0300d80f77e5af8c338cf2185f54205d69b99091447a097506b5d2944dac437396a8a4e04ae9a97a90df60f8a93d60e730d
7
- data.tar.gz: 911c32b9ac29989060b08fe6fc352074a02038ae20f8b516723e56a263bf602b0ee70206cf41a3bbe682044ab4c7511552fa255ef9d7b137da952dcea271f860
6
+ metadata.gz: c8c43cca2f5e3812ee706a9b2b2c833ad9036d98e997b4481ce701d0edfa98b03a967f935c2b6aec7b8b2dfb19d27225b0e7a6f5e091ecc993a0786cec3cc2d7
7
+ data.tar.gz: 27acd8361e403febf7fd4e45b5ab832f1c767deedf329b8e164c8b62f7f1aa127c0ea75eeccaee7d5ef6767ea7f45c787baa526461bfa6dbf6ba034f90815bff
data/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ 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
+ ## [0.1.3] - 2026-02-18
9
+
10
+ ### Fixed
11
+ - JVM backend: resolve class method descriptor mismatch when called from instance methods (pre-register singleton method descriptors)
12
+ - Documentation: correct JWM and Skija GitHub URLs in README
13
+
14
+ ### Added
15
+ - Documentation: Castella UI calculator demo with Style composition
16
+
8
17
  ## [0.1.2] - 2026-02-17
9
18
 
10
19
  ### Fixed
@@ -99,6 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
108
  - `%a{extern}` - external C struct wrappers
100
109
  - `%a{simd}` - SIMD vectorization
101
110
 
111
+ [0.1.3]: https://github.com/i2y/konpeito/compare/v0.1.2...v0.1.3
102
112
  [0.1.2]: https://github.com/i2y/konpeito/compare/v0.1.1...v0.1.2
103
113
  [0.1.1]: https://github.com/i2y/konpeito/compare/v0.1.0...v0.1.1
104
114
  [0.1.0]: https://github.com/i2y/konpeito/releases/tag/v0.1.0
data/README.md CHANGED
@@ -135,7 +135,7 @@ The JVM backend supports seamless Java interop — call Java libraries directly
135
135
 
136
136
  ## Castella UI
137
137
 
138
- A reactive GUI framework for the JVM backend, powered by [JWM](https://github.com/nicenote/jwm) + [Skija](https://github.com/nicenote/skija).
138
+ A reactive GUI framework for the JVM backend, powered by [JWM](https://github.com/HumbleUI/JWM) + [Skija](https://github.com/HumbleUI/Skija).
139
139
 
140
140
  ### DSL
141
141
 
@@ -230,6 +230,144 @@ column(spacing: 12.0) {
230
230
  }
231
231
  ```
232
232
 
233
+ ### Calculator Demo
234
+
235
+ A port of the original Python Castella calculator. Flex layout, button kinds (`KIND_DANGER`, `KIND_WARNING`, `KIND_SUCCESS`), reactive `state()`, and class methods called from instance method callbacks — all in ~130 lines:
236
+
237
+ <p align="center">
238
+ <img src="docs/screenshots/calc.png" alt="Calculator" width="320" />
239
+ </p>
240
+
241
+ ```ruby
242
+ class Calc < Component
243
+ def initialize
244
+ super
245
+ @display = state("0")
246
+ @lhs = 0.0
247
+ @current_op = ""
248
+ @is_refresh = true
249
+ end
250
+
251
+ # --- Calculator logic ---
252
+
253
+ def press_number(label)
254
+ if @display.value == "0" || @is_refresh
255
+ @display.set(label)
256
+ else
257
+ @display.set(@display.value + label)
258
+ end
259
+ @is_refresh = false
260
+ end
261
+
262
+ def press_dot
263
+ if @is_refresh
264
+ @display.set("0.")
265
+ @is_refresh = false
266
+ return
267
+ end
268
+ if !@display.value.include?(".")
269
+ @display.set(@display.value + ".")
270
+ end
271
+ end
272
+
273
+ def all_clear
274
+ @display.set("0")
275
+ @lhs = 0.0
276
+ @current_op = ""
277
+ @is_refresh = true
278
+ end
279
+
280
+ def self.calc(lhs, op, rhs)
281
+ if op == "+"
282
+ lhs + rhs
283
+ elsif op == "-"
284
+ lhs - rhs
285
+ elsif op == "\u00D7"
286
+ lhs * rhs
287
+ elsif op == "\u00F7"
288
+ if rhs == 0.0
289
+ 0.0
290
+ else
291
+ lhs / rhs
292
+ end
293
+ else
294
+ rhs
295
+ end
296
+ end
297
+
298
+ def self.format_result(val)
299
+ if val == val.to_i.to_f
300
+ val.to_i.to_s
301
+ else
302
+ val.to_s
303
+ end
304
+ end
305
+
306
+ def press_operator(new_op)
307
+ rhs = @display.value.to_f
308
+ if @current_op != ""
309
+ result = Calc.calc(@lhs, @current_op, rhs)
310
+ @display.set(Calc.format_result(result))
311
+ @lhs = result
312
+ else
313
+ @lhs = rhs
314
+ end
315
+ if new_op == "="
316
+ @current_op = ""
317
+ else
318
+ @current_op = new_op
319
+ end
320
+ @is_refresh = true
321
+ end
322
+
323
+ # --- View ---
324
+
325
+ def view
326
+ grid = Style.new.spacing(4.0)
327
+ btn = Style.new.font_size(32.0)
328
+ op = btn + Style.new.kind(KIND_WARNING)
329
+ ac = btn + Style.new.kind(KIND_DANGER).flex(3)
330
+ eq = btn + Style.new.kind(KIND_SUCCESS)
331
+ wide = btn + Style.new.flex(2)
332
+
333
+ column(spacing: 4.0, padding: 4.0) {
334
+ text @display.value, font_size: 48.0, align: :right, kind: KIND_INFO, height: 72.0
335
+ row(grid) {
336
+ button("AC", ac) { all_clear }
337
+ button("\u00F7", op) { press_operator("\u00F7") }
338
+ }
339
+ row(grid) {
340
+ button("7", btn) { press_number("7") }
341
+ button("8", btn) { press_number("8") }
342
+ button("9", btn) { press_number("9") }
343
+ button("\u00D7", op) { press_operator("\u00D7") }
344
+ }
345
+ row(grid) {
346
+ button("4", btn) { press_number("4") }
347
+ button("5", btn) { press_number("5") }
348
+ button("6", btn) { press_number("6") }
349
+ button("-", op) { press_operator("-") }
350
+ }
351
+ row(grid) {
352
+ button("1", btn) { press_number("1") }
353
+ button("2", btn) { press_number("2") }
354
+ button("3", btn) { press_number("3") }
355
+ button("+", op) { press_operator("+") }
356
+ }
357
+ row(grid) {
358
+ button("0", wide) { press_number("0") }
359
+ button(".", btn) { press_dot }
360
+ button("=", eq) { press_operator("=") }
361
+ }
362
+ }
363
+ end
364
+ end
365
+
366
+ frame = JWMFrame.new("Castella Calculator", 320, 480)
367
+ app = App.new(frame, Calc.new)
368
+ app.run
369
+ ```
370
+
233
371
  ## Performance
234
372
 
235
373
  Konpeito shines in compute-heavy, typed loops where unboxed arithmetic and backend optimizations kick in. All benchmarks compare against Ruby 4.0.1 with YJIT enabled on Apple M4 Max.
@@ -3278,6 +3278,24 @@ module Konpeito
3278
3278
  instructions << { "op" => "invokestatic", "owner" => "java/lang/Boolean",
3279
3279
  "name" => "valueOf", "descriptor" => "(Z)Ljava/lang/Boolean;" }
3280
3280
  type = :value
3281
+ # Unbox Object to match expected primitive return type (static methods only).
3282
+ # Instance method descriptors are always normalized to Object return, so
3283
+ # unboxing would be incorrect — the value might not be the expected wrapper type.
3284
+ elsif !@generating_instance_method && @current_function_return_type == :double && type == :value
3285
+ instructions << { "op" => "checkcast", "type" => "java/lang/Double" }
3286
+ instructions << { "op" => "invokevirtual", "owner" => "java/lang/Double",
3287
+ "name" => "doubleValue", "descriptor" => "()D" }
3288
+ type = :double
3289
+ elsif !@generating_instance_method && @current_function_return_type == :i64 && type == :value
3290
+ instructions << { "op" => "checkcast", "type" => "java/lang/Long" }
3291
+ instructions << { "op" => "invokevirtual", "owner" => "java/lang/Long",
3292
+ "name" => "longValue", "descriptor" => "()J" }
3293
+ type = :i64
3294
+ elsif !@generating_instance_method && @current_function_return_type == :i8 && type == :value
3295
+ instructions << { "op" => "checkcast", "type" => "java/lang/Boolean" }
3296
+ instructions << { "op" => "invokevirtual", "owner" => "java/lang/Boolean",
3297
+ "name" => "booleanValue", "descriptor" => "()Z" }
3298
+ type = :i8
3281
3299
  end
3282
3300
 
3283
3301
  instructions << case type
@@ -4029,6 +4047,33 @@ module Konpeito
4029
4047
 
4030
4048
  @method_descriptors[key] = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4031
4049
  end
4050
+
4051
+ # Pre-register singleton (class) method descriptors
4052
+ (class_def.singleton_methods || []).each do |method_name|
4053
+ key = "#{class_name}.#{method_name}"
4054
+ next if @method_descriptors.key?(key)
4055
+
4056
+ func = find_class_singleton_method(class_name, method_name.to_s)
4057
+ next unless func
4058
+
4059
+ rbs_param_types = resolve_rbs_param_types(class_name, method_name.to_s, true)
4060
+ params_desc = func.params.each_with_index.map do |p, i|
4061
+ rbs_t = rbs_param_types && i < rbs_param_types.size ? rbs_param_types[i] : nil
4062
+ t = (rbs_t && rbs_t != :value) ? rbs_t : param_type(p)
4063
+ t = :value if t == :void
4064
+ type_to_descriptor(t)
4065
+ end.join
4066
+
4067
+ rbs_ret = resolve_rbs_return_type(class_name, method_name.to_s, true)
4068
+ if rbs_ret && rbs_ret != :value
4069
+ ret_type = rbs_ret
4070
+ else
4071
+ frt = function_return_type(func)
4072
+ ret_type = (frt == :void) ? :value : frt
4073
+ end
4074
+
4075
+ @method_descriptors[key] = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4076
+ end
4032
4077
  end
4033
4078
 
4034
4079
  def resolve_class_fields(class_def)
@@ -4407,9 +4452,17 @@ module Konpeito
4407
4452
  @current_generating_func_name = func.name.to_s
4408
4453
  reset_function_state(func)
4409
4454
 
4410
- # Pre-determine return type so generate_return knows if method returns Object
4411
- pre_ret = function_return_type(func)
4412
- @current_function_return_type = (pre_ret == :void) ? :value : pre_ret
4455
+ # Pre-determine return type so generate_return knows if method returns Object.
4456
+ # If a descriptor was pre-registered (from RBS), use its return type to ensure
4457
+ # the generated return instruction matches the pre-registered descriptor.
4458
+ pre_registered = @method_descriptors["#{class_def.name}.#{func.name}"]
4459
+ if pre_registered
4460
+ pre_ret = parse_descriptor_return_type(pre_registered)
4461
+ else
4462
+ pre_ret = function_return_type(func)
4463
+ pre_ret = :value if pre_ret == :void
4464
+ end
4465
+ @current_function_return_type = pre_ret
4413
4466
 
4414
4467
  # Try to resolve param types from RBS if HIR types are unresolved
4415
4468
  rbs_param_types = resolve_rbs_param_types(class_def.name.to_s, func.name.to_s, true)
@@ -4435,9 +4488,36 @@ module Konpeito
4435
4488
  instructions << default_return(ret_type)
4436
4489
  end
4437
4490
 
4438
- # Build descriptor from actual param types (which may have been corrected from RBS)
4439
- params_desc = func.params.map { |p| type_to_descriptor(@variable_types[p.name.to_s] || :value) }.join
4440
- descriptor = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4491
+ # Build descriptor: if pre-registered, use that descriptor directly for consistency.
4492
+ # The pre-registered descriptor was computed from param_type/RBS before code gen,
4493
+ # matching what the call site will use. The code-gen-derived descriptor may differ
4494
+ # because detect_return_type_from_instructions can miss returns in branching code.
4495
+ if pre_registered
4496
+ descriptor = pre_registered
4497
+ # Ensure final return instruction matches the pre-registered return type
4498
+ pre_ret_type = parse_descriptor_return_type(pre_registered)
4499
+ if pre_ret_type != :void && pre_ret_type != :value
4500
+ # Replace trailing void return with typed default return if needed
4501
+ if instructions.last && instructions.last["op"] == "return"
4502
+ instructions.pop
4503
+ case pre_ret_type
4504
+ when :i64
4505
+ instructions << { "op" => "lconst_0" }
4506
+ instructions << { "op" => "lreturn" }
4507
+ when :double
4508
+ instructions << { "op" => "dconst_0" }
4509
+ instructions << { "op" => "dreturn" }
4510
+ when :i8
4511
+ instructions << { "op" => "iconst_0" }
4512
+ instructions << { "op" => "ireturn" }
4513
+ end
4514
+ end
4515
+ end
4516
+ else
4517
+ # Build descriptor from actual param types (which may have been corrected from RBS)
4518
+ params_desc = func.params.map { |p| type_to_descriptor(@variable_types[p.name.to_s] || :value) }.join
4519
+ descriptor = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4520
+ end
4441
4521
 
4442
4522
  # When renaming for conflict avoidance, use prefixed JVM name
4443
4523
  jvm_name = if rename_prefix
@@ -5396,6 +5476,10 @@ module Konpeito
5396
5476
 
5397
5477
  return [] unless target_func
5398
5478
 
5479
+ # Look up the registered descriptor first to determine expected param types
5480
+ registered = @method_descriptors["#{class_name}.#{method_name}"]
5481
+ registered_param_types = registered ? parse_descriptor_param_types(registered) : nil
5482
+
5399
5483
  # Resolve param types: prefer RBS types over HIR types (which may be unresolved TypeVars)
5400
5484
  rbs_param_types = resolve_rbs_param_types(class_name, method_name, true)
5401
5485
 
@@ -5416,18 +5500,17 @@ module Konpeito
5416
5500
  end
5417
5501
  arg_types << :hash
5418
5502
  elsif i < args.size
5419
- param_t = param_type(param)
5420
- # Fallback to RBS type if param type is unresolved
5421
- if param_t == :value && rbs_param_types && i < rbs_param_types.size && rbs_param_types[i] != :value
5422
- # RBS resolved what HM couldn't — undo typevar fallback
5423
- @typevar_fallback_count -= 1 if @typevar_fallback_count > 0
5424
- param_t = rbs_param_types[i]
5425
- end
5426
- # Also try inferring from arg itself
5427
- if param_t == :value
5428
- arg_var = extract_var_name(args[i])
5429
- param_t = arg_var ? (@variable_types[arg_var] || :value) : (infer_type_from_hir(args[i]) || :value)
5430
- end
5503
+ # Use registered descriptor's param type if available (most accurate)
5504
+ param_t = if registered_param_types && i < registered_param_types.size
5505
+ registered_param_types[i]
5506
+ else
5507
+ pt = param_type(param)
5508
+ if pt == :value && rbs_param_types && i < rbs_param_types.size && rbs_param_types[i] != :value
5509
+ rbs_param_types[i]
5510
+ else
5511
+ pt
5512
+ end
5513
+ end
5431
5514
  instructions.concat(load_value(args[i], param_t))
5432
5515
  arg_types << param_t
5433
5516
  else
@@ -5439,7 +5522,6 @@ module Konpeito
5439
5522
  end
5440
5523
 
5441
5524
  # Build descriptor — prefer registered descriptor for consistency
5442
- registered = @method_descriptors["#{class_name}.#{method_name}"]
5443
5525
  if registered
5444
5526
  descriptor = registered
5445
5527
  ret_type = parse_descriptor_return_type(registered)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Konpeito
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: konpeito
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yasushi Itoh