konpeito 0.1.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +75 -0
  4. data/CONTRIBUTING.md +123 -0
  5. data/LICENSE +21 -0
  6. data/README.md +257 -0
  7. data/Rakefile +11 -0
  8. data/bin/konpeito +6 -0
  9. data/konpeito.gemspec +43 -0
  10. data/lib/konpeito/ast/typed_ast.rb +620 -0
  11. data/lib/konpeito/ast/visitor.rb +78 -0
  12. data/lib/konpeito/cache/cache_manager.rb +230 -0
  13. data/lib/konpeito/cache/dependency_graph.rb +192 -0
  14. data/lib/konpeito/cache.rb +8 -0
  15. data/lib/konpeito/cli/base_command.rb +187 -0
  16. data/lib/konpeito/cli/build_command.rb +220 -0
  17. data/lib/konpeito/cli/check_command.rb +104 -0
  18. data/lib/konpeito/cli/config.rb +231 -0
  19. data/lib/konpeito/cli/deps_command.rb +128 -0
  20. data/lib/konpeito/cli/doctor_command.rb +340 -0
  21. data/lib/konpeito/cli/fmt_command.rb +199 -0
  22. data/lib/konpeito/cli/init_command.rb +312 -0
  23. data/lib/konpeito/cli/lsp_command.rb +40 -0
  24. data/lib/konpeito/cli/run_command.rb +150 -0
  25. data/lib/konpeito/cli/test_command.rb +248 -0
  26. data/lib/konpeito/cli/watch_command.rb +212 -0
  27. data/lib/konpeito/cli.rb +301 -0
  28. data/lib/konpeito/codegen/builtin_methods.rb +229 -0
  29. data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
  30. data/lib/konpeito/codegen/debug_info.rb +352 -0
  31. data/lib/konpeito/codegen/inliner.rb +486 -0
  32. data/lib/konpeito/codegen/jvm_backend.rb +197 -0
  33. data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
  34. data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
  35. data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
  36. data/lib/konpeito/codegen/monomorphizer.rb +359 -0
  37. data/lib/konpeito/codegen/profile_runtime.c +341 -0
  38. data/lib/konpeito/codegen/profiler.rb +99 -0
  39. data/lib/konpeito/compiler.rb +592 -0
  40. data/lib/konpeito/dependency_resolver.rb +296 -0
  41. data/lib/konpeito/diagnostics/collector.rb +127 -0
  42. data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
  43. data/lib/konpeito/diagnostics/renderer.rb +144 -0
  44. data/lib/konpeito/formatter/formatter.rb +1214 -0
  45. data/lib/konpeito/hir/builder.rb +7167 -0
  46. data/lib/konpeito/hir/nodes.rb +2465 -0
  47. data/lib/konpeito/lsp/document_manager.rb +820 -0
  48. data/lib/konpeito/lsp/server.rb +183 -0
  49. data/lib/konpeito/lsp/transport.rb +38 -0
  50. data/lib/konpeito/parser/prism_adapter.rb +65 -0
  51. data/lib/konpeito/platform.rb +103 -0
  52. data/lib/konpeito/profile/report.rb +136 -0
  53. data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
  54. data/lib/konpeito/stdlib/compression/compression.rb +72 -0
  55. data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
  56. data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
  57. data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
  58. data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
  59. data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
  60. data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
  61. data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
  62. data/lib/konpeito/stdlib/http/extconf.rb +19 -0
  63. data/lib/konpeito/stdlib/http/http.rb +125 -0
  64. data/lib/konpeito/stdlib/http/http.rbs +57 -0
  65. data/lib/konpeito/stdlib/http/http_native.c +440 -0
  66. data/lib/konpeito/stdlib/json/extconf.rb +17 -0
  67. data/lib/konpeito/stdlib/json/json.rb +44 -0
  68. data/lib/konpeito/stdlib/json/json.rbs +33 -0
  69. data/lib/konpeito/stdlib/json/json_native.c +286 -0
  70. data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
  71. data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
  72. data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
  73. data/lib/konpeito/stdlib/ui/ui.rb +318 -0
  74. data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
  75. data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
  76. data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
  77. data/lib/konpeito/type_checker/inferrer.rb +565 -0
  78. data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
  79. data/lib/konpeito/type_checker/type_resolver.rb +276 -0
  80. data/lib/konpeito/type_checker/types.rb +1434 -0
  81. data/lib/konpeito/type_checker/unification.rb +323 -0
  82. data/lib/konpeito/ui/animation/animated_state.rb +80 -0
  83. data/lib/konpeito/ui/animation/easing.rb +59 -0
  84. data/lib/konpeito/ui/animation/value_tween.rb +66 -0
  85. data/lib/konpeito/ui/app.rb +379 -0
  86. data/lib/konpeito/ui/box.rb +38 -0
  87. data/lib/konpeito/ui/castella.rb +70 -0
  88. data/lib/konpeito/ui/castella_native.rb +76 -0
  89. data/lib/konpeito/ui/chart/area_chart.rb +305 -0
  90. data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
  91. data/lib/konpeito/ui/chart/base_chart.rb +210 -0
  92. data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
  93. data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
  94. data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
  95. data/lib/konpeito/ui/chart/line_chart.rb +289 -0
  96. data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
  97. data/lib/konpeito/ui/chart/scales.rb +77 -0
  98. data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
  99. data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
  100. data/lib/konpeito/ui/column.rb +271 -0
  101. data/lib/konpeito/ui/core.rb +2199 -0
  102. data/lib/konpeito/ui/dsl.rb +443 -0
  103. data/lib/konpeito/ui/frame.rb +171 -0
  104. data/lib/konpeito/ui/frame_native.rb +494 -0
  105. data/lib/konpeito/ui/markdown/ast.rb +124 -0
  106. data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
  107. data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
  108. data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
  109. data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
  110. data/lib/konpeito/ui/markdown/parser.rb +805 -0
  111. data/lib/konpeito/ui/markdown/renderer.rb +639 -0
  112. data/lib/konpeito/ui/markdown/theme.rb +165 -0
  113. data/lib/konpeito/ui/render_node.rb +260 -0
  114. data/lib/konpeito/ui/row.rb +207 -0
  115. data/lib/konpeito/ui/spacer.rb +18 -0
  116. data/lib/konpeito/ui/style.rb +799 -0
  117. data/lib/konpeito/ui/theme.rb +563 -0
  118. data/lib/konpeito/ui/themes/material.rb +35 -0
  119. data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
  120. data/lib/konpeito/ui/widgets/button.rb +103 -0
  121. data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
  122. data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
  123. data/lib/konpeito/ui/widgets/container.rb +91 -0
  124. data/lib/konpeito/ui/widgets/data_table.rb +667 -0
  125. data/lib/konpeito/ui/widgets/divider.rb +29 -0
  126. data/lib/konpeito/ui/widgets/image.rb +105 -0
  127. data/lib/konpeito/ui/widgets/input.rb +485 -0
  128. data/lib/konpeito/ui/widgets/markdown.rb +57 -0
  129. data/lib/konpeito/ui/widgets/modal.rb +163 -0
  130. data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
  131. data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
  132. data/lib/konpeito/ui/widgets/net_image.rb +100 -0
  133. data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
  134. data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
  135. data/lib/konpeito/ui/widgets/slider.rb +133 -0
  136. data/lib/konpeito/ui/widgets/switch.rb +84 -0
  137. data/lib/konpeito/ui/widgets/tabs.rb +157 -0
  138. data/lib/konpeito/ui/widgets/text.rb +110 -0
  139. data/lib/konpeito/ui/widgets/tree.rb +426 -0
  140. data/lib/konpeito/version.rb +5 -0
  141. data/lib/konpeito.rb +109 -0
  142. data/test_native_array.rb +172 -0
  143. data/test_native_array_class.rb +197 -0
  144. data/test_native_class.rb +151 -0
  145. data/tools/konpeito-asm/build.sh +65 -0
  146. data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
  147. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  148. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  149. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  150. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  151. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  152. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  153. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  154. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  155. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  156. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  157. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  158. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  159. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  160. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  161. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  162. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  163. data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
  164. data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
  165. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
  166. data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
  167. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
  168. data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
  169. data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
  170. data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
  171. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
  172. data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
  173. data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
  174. data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
  175. data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
  176. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
  177. data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
  178. data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
  179. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
  180. metadata +267 -0
@@ -0,0 +1,323 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types"
4
+
5
+ module Konpeito
6
+ module TypeChecker
7
+ # Type variable for Hindley-Milner inference
8
+ class TypeVar
9
+ @@counter = 0
10
+
11
+ attr_reader :id
12
+ attr_accessor :instance # Bound type after unification
13
+
14
+ def initialize(name = nil)
15
+ @id = @@counter += 1
16
+ @name = name || "τ#{@id}"
17
+ @instance = nil
18
+ end
19
+
20
+ def to_s
21
+ if @instance
22
+ @instance.to_s
23
+ else
24
+ @name
25
+ end
26
+ end
27
+
28
+ def inspect
29
+ "#<TypeVar #{@name}#{@instance ? " = #{@instance}" : ""}>"
30
+ end
31
+
32
+ def prune
33
+ # Follow the chain of instantiated type variables
34
+ if @instance.is_a?(TypeVar)
35
+ @instance = @instance.prune
36
+ end
37
+ @instance || self
38
+ end
39
+
40
+ def ==(other)
41
+ other.is_a?(TypeVar) && @id == other.id
42
+ end
43
+
44
+ def hash
45
+ @id.hash
46
+ end
47
+
48
+ alias eql? ==
49
+ end
50
+
51
+ # Function type for HM inference
52
+ class FunctionType
53
+ attr_reader :param_types, :return_type, :rest_param_type
54
+
55
+ def initialize(param_types, return_type, rest_param_type: nil)
56
+ @param_types = param_types
57
+ @return_type = return_type
58
+ @rest_param_type = rest_param_type # TypeVar for *args element type (nil if no rest)
59
+ end
60
+
61
+ def to_s
62
+ params = @param_types.map(&:to_s).join(", ")
63
+ params += ", *#{@rest_param_type}" if @rest_param_type
64
+ "(#{params}) -> #{@return_type}"
65
+ end
66
+
67
+ def ==(other)
68
+ other.is_a?(FunctionType) &&
69
+ @param_types == other.param_types &&
70
+ @return_type == other.return_type &&
71
+ @rest_param_type == other.rest_param_type
72
+ end
73
+ end
74
+
75
+ # Unification error
76
+ class UnificationError < Error
77
+ attr_reader :type1, :type2, :node, :context
78
+
79
+ def initialize(type1, type2, node: nil, context: nil)
80
+ @type1 = type1
81
+ @type2 = type2
82
+ @node = node # Prism node where error occurred
83
+ @context = context # Additional context (e.g., "in argument 1 of method 'foo'")
84
+ super("Cannot unify #{type1} with #{type2}")
85
+ end
86
+ end
87
+
88
+ # Unification algorithm
89
+ class Unifier
90
+ def initialize
91
+ @substitution = {}
92
+ end
93
+
94
+ # Unify two types, updating substitutions
95
+ #
96
+ # nil compatibility: In Ruby, all reference types are implicitly nullable.
97
+ # Unlike Kotlin (which distinguishes T vs T?), Ruby has no non-nullable types.
98
+ # We treat nil as a subtype of every type — unification succeeds, but the
99
+ # concrete type is always preserved (nil never overwrites type information).
100
+ def unify(t1, t2)
101
+ t1 = prune(t1)
102
+ t2 = prune(t2)
103
+
104
+ if t1.is_a?(TypeVar)
105
+ if t1 != t2
106
+ if occurs_in?(t1, t2)
107
+ raise UnificationError.new(t1, t2)
108
+ end
109
+ # If TypeVar is already bound to a ClassInstance and t2 is also ClassInstance,
110
+ # check for subtype/LUB before rebinding
111
+ if t1.instance.is_a?(Types::ClassInstance) && t2.is_a?(Types::ClassInstance) && t1.instance.name != t2.name
112
+ existing = t1.instance
113
+ if numeric_widening?(existing, t2)
114
+ # Integer → Float widening (Java/Kotlin widening primitive conversion)
115
+ t1.instance = t2
116
+ elsif numeric_widening?(t2, existing)
117
+ # Float already bound, Integer is compatible — keep Float
118
+ elsif t2.subtype_of?(existing)
119
+ # New type is subtype of existing — keep existing (supertype)
120
+ elsif existing.subtype_of?(t2)
121
+ # Existing is subtype of new — widen to new
122
+ t1.instance = t2
123
+ else
124
+ lub = Types::ClassInstance.find_lub(existing, t2)
125
+ if lub
126
+ t1.instance = lub
127
+ else
128
+ raise UnificationError.new(existing, t2)
129
+ end
130
+ end
131
+ else
132
+ t1.instance = t2
133
+ end
134
+ end
135
+ elsif t2.is_a?(TypeVar)
136
+ unify(t2, t1)
137
+ elsif t1.is_a?(FunctionType) && t2.is_a?(FunctionType)
138
+ if t1.param_types.size != t2.param_types.size
139
+ raise UnificationError.new(t1, t2)
140
+ end
141
+ t1.param_types.zip(t2.param_types).each do |p1, p2|
142
+ unify(p1, p2)
143
+ end
144
+ if t1.rest_param_type && t2.rest_param_type
145
+ unify(t1.rest_param_type, t2.rest_param_type)
146
+ end
147
+ unify(t1.return_type, t2.return_type)
148
+ elsif t1.is_a?(Types::ClassInstance) && t2.is_a?(Types::ClassInstance)
149
+ if t1.name != t2.name
150
+ # TrueClass/FalseClass are boolean-compatible
151
+ if boolean_compatible?(t1, t2)
152
+ # Both are boolean types — compatible
153
+ # Integer → Float widening (Java/Kotlin widening primitive conversion)
154
+ elsif numeric_widening?(t1, t2) || numeric_widening?(t2, t1)
155
+ # Integer is safely widened to Float — compatible
156
+ # Check subtype relationship before failing
157
+ elsif t1.subtype_of?(t2)
158
+ # t1 is a subtype of t2 — compatible (keep supertype)
159
+ elsif t2.subtype_of?(t1)
160
+ # t2 is a subtype of t1 — compatible (keep supertype)
161
+ else
162
+ # Try to find LUB (Least Upper Bound)
163
+ lub = Types::ClassInstance.find_lub(t1, t2)
164
+ if lub && lub.name != :Object
165
+ # Found a meaningful common ancestor
166
+ else
167
+ raise UnificationError.new(t1, t2)
168
+ end
169
+ end
170
+ else
171
+ # Same class name — unify type arguments if any
172
+ t1.type_args.zip(t2.type_args).each do |a1, a2|
173
+ unify(a1, a2)
174
+ end
175
+ end
176
+ elsif t1 == t2
177
+ # Same type, nothing to do
178
+ elsif t1 == Types::UNTYPED || t2 == Types::UNTYPED
179
+ # UNTYPED unifies with anything (escape hatch)
180
+ elsif t1 == Types::NIL || t2 == Types::NIL
181
+ # nil is a subtype of every type in Ruby (implicit nullable).
182
+ # Compatible, but no type information is lost — the concrete side is preserved.
183
+ elsif boolean_compatible?(t1, t2)
184
+ # TrueClass, FalseClass, and Bool are all boolean-compatible.
185
+ # Ruby has separate TrueClass/FalseClass but they're used interchangeably.
186
+ elsif singleton_value_compatible?(t1, t2)
187
+ # ClassSingleton (value constant like EXPANDING) is compatible with its base type.
188
+ # e.g., singleton(EXPANDING) ↔ Integer when EXPANDING = 2
189
+ else
190
+ raise UnificationError.new(t1, t2)
191
+ end
192
+ end
193
+
194
+ # Check if both types are boolean-compatible (TrueClass, FalseClass, Bool)
195
+ def boolean_compatible?(t1, t2)
196
+ bool_types = Set[:TrueClass, :FalseClass]
197
+ (t1.is_a?(Types::ClassInstance) && bool_types.include?(t1.name) &&
198
+ t2.is_a?(Types::ClassInstance) && bool_types.include?(t2.name)) ||
199
+ (t1 == Types::BOOL && t2.is_a?(Types::ClassInstance) && bool_types.include?(t2.name)) ||
200
+ (t2 == Types::BOOL && t1.is_a?(Types::ClassInstance) && bool_types.include?(t1.name))
201
+ end
202
+
203
+ # Check if a ClassSingleton (value constant) is compatible with a concrete type.
204
+ # Value constants (EXPANDING = 2) produce ClassSingleton but should unify with Integer.
205
+ def singleton_value_compatible?(t1, t2)
206
+ (t1.is_a?(Types::ClassSingleton) && t2.is_a?(Types::ClassInstance)) ||
207
+ (t2.is_a?(Types::ClassSingleton) && t1.is_a?(Types::ClassInstance))
208
+ end
209
+
210
+ # Check if `from` can be widened to `to` (Java/Kotlin-style widening primitive conversion).
211
+ # Integer → Float is safe (no precision loss for typical values).
212
+ # Float → Integer is NOT supported (requires explicit conversion).
213
+ def numeric_widening?(from, to)
214
+ from.is_a?(Types::ClassInstance) && to.is_a?(Types::ClassInstance) &&
215
+ from.name == :Integer && to.name == :Float
216
+ end
217
+
218
+ # Prune: follow type variable chain
219
+ def prune(t)
220
+ if t.is_a?(TypeVar) && t.instance
221
+ t.instance = prune(t.instance)
222
+ t.instance
223
+ else
224
+ t
225
+ end
226
+ end
227
+
228
+ # Occurs check: prevent infinite types
229
+ def occurs_in?(tvar, type)
230
+ type = prune(type)
231
+ return true if tvar == type
232
+
233
+ if type.is_a?(FunctionType)
234
+ type.param_types.any? { |p| occurs_in?(tvar, p) } ||
235
+ (type.rest_param_type && occurs_in?(tvar, type.rest_param_type)) ||
236
+ occurs_in?(tvar, type.return_type)
237
+ elsif type.is_a?(Types::ClassInstance)
238
+ type.type_args.any? { |a| occurs_in?(tvar, a) }
239
+ else
240
+ false
241
+ end
242
+ end
243
+
244
+ # Apply substitution to get final type
245
+ def apply(type)
246
+ type = prune(type)
247
+
248
+ case type
249
+ when TypeVar
250
+ type.instance ? apply(type.instance) : type
251
+ when FunctionType
252
+ FunctionType.new(
253
+ type.param_types.map { |p| apply(p) },
254
+ apply(type.return_type),
255
+ rest_param_type: type.rest_param_type ? apply(type.rest_param_type) : nil
256
+ )
257
+ when Types::ClassInstance
258
+ if type.type_args.empty?
259
+ type
260
+ else
261
+ Types::ClassInstance.new(
262
+ type.name,
263
+ type.type_args.map { |a| apply(a) }
264
+ )
265
+ end
266
+ else
267
+ type
268
+ end
269
+ end
270
+ end
271
+
272
+ # Type scheme (polymorphic type with quantified variables)
273
+ class TypeScheme
274
+ attr_reader :type_vars, :type
275
+
276
+ def initialize(type_vars, type)
277
+ @type_vars = type_vars
278
+ @type = type
279
+ end
280
+
281
+ # Instantiate: replace bound variables with fresh ones
282
+ def instantiate
283
+ mapping = {}
284
+ @type_vars.each do |tv|
285
+ mapping[tv.id] = TypeVar.new
286
+ end
287
+ substitute(@type, mapping)
288
+ end
289
+
290
+ private
291
+
292
+ def substitute(type, mapping)
293
+ case type
294
+ when TypeVar
295
+ if type.instance
296
+ substitute(type.instance, mapping)
297
+ elsif mapping[type.id]
298
+ mapping[type.id]
299
+ else
300
+ type
301
+ end
302
+ when FunctionType
303
+ FunctionType.new(
304
+ type.param_types.map { |p| substitute(p, mapping) },
305
+ substitute(type.return_type, mapping),
306
+ rest_param_type: type.rest_param_type ? substitute(type.rest_param_type, mapping) : nil
307
+ )
308
+ when Types::ClassInstance
309
+ if type.type_args.empty?
310
+ type
311
+ else
312
+ Types::ClassInstance.new(
313
+ type.name,
314
+ type.type_args.map { |a| substitute(a, mapping) }
315
+ )
316
+ end
317
+ else
318
+ type
319
+ end
320
+ end
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,80 @@
1
+ # AnimatedState - observable value that transitions smoothly
2
+ # Inherits from ObservableBase so Components can subscribe
3
+
4
+ class AnimatedState < ObservableBase
5
+ def initialize(initial_value, duration, easing_fn)
6
+ super()
7
+ @value = initial_value
8
+ @target = initial_value
9
+ @duration = duration # milliseconds
10
+ @easing_fn = easing_fn # Symbol
11
+ @tween = nil
12
+ @animating = false
13
+ end
14
+
15
+ def value
16
+ @value
17
+ end
18
+
19
+ def target
20
+ @target
21
+ end
22
+
23
+ def animating?
24
+ @animating
25
+ end
26
+
27
+ # Set a new target — begins animation from current value
28
+ def set(new_target)
29
+ if new_target == @target && !@animating
30
+ return
31
+ end
32
+ @target = new_target
33
+ @tween = ValueTween.new(@value, @target, @duration, @easing_fn)
34
+ @animating = true
35
+ # Register with App's animation loop
36
+ app = App.current
37
+ if app != nil
38
+ app.register_animation(self)
39
+ end
40
+ end
41
+
42
+ # Immediately jump to a value (no animation)
43
+ def set_immediate(new_value)
44
+ @value = new_value
45
+ @target = new_value
46
+ @tween = nil
47
+ @animating = false
48
+ notify_observers
49
+ end
50
+
51
+ # Call each frame with delta time in ms
52
+ def tick(dt)
53
+ return false if !@animating || @tween == nil
54
+ @tween.tick(dt)
55
+ @value = @tween.current
56
+ if @tween.finished?
57
+ @value = @target
58
+ @animating = false
59
+ @tween = nil
60
+ end
61
+ notify_observers
62
+ @animating
63
+ end
64
+
65
+ def duration
66
+ @duration
67
+ end
68
+
69
+ def duration=(d)
70
+ @duration = d
71
+ end
72
+
73
+ def easing_fn
74
+ @easing_fn
75
+ end
76
+
77
+ def easing_fn=(e)
78
+ @easing_fn = e
79
+ end
80
+ end
@@ -0,0 +1,59 @@
1
+ # Easing functions for animation
2
+ # All functions take t in [0.0, 1.0] and return a value in [0.0, 1.0]
3
+
4
+ EASING_PI = 3.14159265358979323846
5
+
6
+ module Easing
7
+ def self.linear(t)
8
+ t
9
+ end
10
+
11
+ def self.ease_in(t)
12
+ t * t
13
+ end
14
+
15
+ def self.ease_out(t)
16
+ 1.0 - (1.0 - t) * (1.0 - t)
17
+ end
18
+
19
+ def self.ease_in_out(t)
20
+ if t < 0.5
21
+ 2.0 * t * t
22
+ else
23
+ 1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
24
+ end
25
+ end
26
+
27
+ def self.ease_in_cubic(t)
28
+ t * t * t
29
+ end
30
+
31
+ def self.ease_out_cubic(t)
32
+ v = 1.0 - t
33
+ 1.0 - v * v * v
34
+ end
35
+
36
+ def self.ease_in_out_cubic(t)
37
+ if t < 0.5
38
+ 4.0 * t * t * t
39
+ else
40
+ v = -2.0 * t + 2.0
41
+ 1.0 - v * v * v / 2.0
42
+ end
43
+ end
44
+
45
+ def self.bounce(t)
46
+ if t < 1.0 / 2.75
47
+ 7.5625 * t * t
48
+ elsif t < 2.0 / 2.75
49
+ t2 = t - 1.5 / 2.75
50
+ 7.5625 * t2 * t2 + 0.75
51
+ elsif t < 2.5 / 2.75
52
+ t2 = t - 2.25 / 2.75
53
+ 7.5625 * t2 * t2 + 0.9375
54
+ else
55
+ t2 = t - 2.625 / 2.75
56
+ 7.5625 * t2 * t2 + 0.984375
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,66 @@
1
+ # ValueTween - interpolates between two numeric values over a duration
2
+ # Frame-based: call tick(dt) each frame to advance
3
+
4
+ class ValueTween
5
+ def initialize(from_val, to_val, duration, easing_fn)
6
+ @from_val = from_val
7
+ @to_val = to_val
8
+ @duration = duration # milliseconds
9
+ @easing_fn = easing_fn # Symbol: :linear, :ease_in, etc.
10
+ @elapsed = 0.0
11
+ @current = from_val
12
+ @finished = false
13
+ end
14
+
15
+ def tick(dt)
16
+ return if @finished
17
+ @elapsed = @elapsed + dt
18
+ if @elapsed >= @duration
19
+ @elapsed = @duration
20
+ @finished = true
21
+ end
22
+ t = @elapsed / @duration
23
+ eased = apply_easing(t)
24
+ @current = @from_val + (@to_val - @from_val) * eased
25
+ end
26
+
27
+ def current
28
+ @current
29
+ end
30
+
31
+ def finished?
32
+ @finished
33
+ end
34
+
35
+ def reset(from_val, to_val)
36
+ @from_val = from_val
37
+ @to_val = to_val
38
+ @elapsed = 0.0
39
+ @current = from_val
40
+ @finished = false
41
+ end
42
+
43
+ private
44
+
45
+ def apply_easing(t)
46
+ if @easing_fn == :linear
47
+ Easing.linear(t)
48
+ elsif @easing_fn == :ease_in
49
+ Easing.ease_in(t)
50
+ elsif @easing_fn == :ease_out
51
+ Easing.ease_out(t)
52
+ elsif @easing_fn == :ease_in_out
53
+ Easing.ease_in_out(t)
54
+ elsif @easing_fn == :ease_in_cubic
55
+ Easing.ease_in_cubic(t)
56
+ elsif @easing_fn == :ease_out_cubic
57
+ Easing.ease_out_cubic(t)
58
+ elsif @easing_fn == :ease_in_out_cubic
59
+ Easing.ease_in_out_cubic(t)
60
+ elsif @easing_fn == :bounce
61
+ Easing.bounce(t)
62
+ else
63
+ t
64
+ end
65
+ end
66
+ end