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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +123 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +11 -0
- data/bin/konpeito +6 -0
- data/konpeito.gemspec +43 -0
- data/lib/konpeito/ast/typed_ast.rb +620 -0
- data/lib/konpeito/ast/visitor.rb +78 -0
- data/lib/konpeito/cache/cache_manager.rb +230 -0
- data/lib/konpeito/cache/dependency_graph.rb +192 -0
- data/lib/konpeito/cache.rb +8 -0
- data/lib/konpeito/cli/base_command.rb +187 -0
- data/lib/konpeito/cli/build_command.rb +220 -0
- data/lib/konpeito/cli/check_command.rb +104 -0
- data/lib/konpeito/cli/config.rb +231 -0
- data/lib/konpeito/cli/deps_command.rb +128 -0
- data/lib/konpeito/cli/doctor_command.rb +340 -0
- data/lib/konpeito/cli/fmt_command.rb +199 -0
- data/lib/konpeito/cli/init_command.rb +312 -0
- data/lib/konpeito/cli/lsp_command.rb +40 -0
- data/lib/konpeito/cli/run_command.rb +150 -0
- data/lib/konpeito/cli/test_command.rb +248 -0
- data/lib/konpeito/cli/watch_command.rb +212 -0
- data/lib/konpeito/cli.rb +301 -0
- data/lib/konpeito/codegen/builtin_methods.rb +229 -0
- data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
- data/lib/konpeito/codegen/debug_info.rb +352 -0
- data/lib/konpeito/codegen/inliner.rb +486 -0
- data/lib/konpeito/codegen/jvm_backend.rb +197 -0
- data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
- data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
- data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
- data/lib/konpeito/codegen/monomorphizer.rb +359 -0
- data/lib/konpeito/codegen/profile_runtime.c +341 -0
- data/lib/konpeito/codegen/profiler.rb +99 -0
- data/lib/konpeito/compiler.rb +592 -0
- data/lib/konpeito/dependency_resolver.rb +296 -0
- data/lib/konpeito/diagnostics/collector.rb +127 -0
- data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
- data/lib/konpeito/diagnostics/renderer.rb +144 -0
- data/lib/konpeito/formatter/formatter.rb +1214 -0
- data/lib/konpeito/hir/builder.rb +7167 -0
- data/lib/konpeito/hir/nodes.rb +2465 -0
- data/lib/konpeito/lsp/document_manager.rb +820 -0
- data/lib/konpeito/lsp/server.rb +183 -0
- data/lib/konpeito/lsp/transport.rb +38 -0
- data/lib/konpeito/parser/prism_adapter.rb +65 -0
- data/lib/konpeito/platform.rb +103 -0
- data/lib/konpeito/profile/report.rb +136 -0
- data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
- data/lib/konpeito/stdlib/compression/compression.rb +72 -0
- data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
- data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
- data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
- data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
- data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
- data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
- data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
- data/lib/konpeito/stdlib/http/extconf.rb +19 -0
- data/lib/konpeito/stdlib/http/http.rb +125 -0
- data/lib/konpeito/stdlib/http/http.rbs +57 -0
- data/lib/konpeito/stdlib/http/http_native.c +440 -0
- data/lib/konpeito/stdlib/json/extconf.rb +17 -0
- data/lib/konpeito/stdlib/json/json.rb +44 -0
- data/lib/konpeito/stdlib/json/json.rbs +33 -0
- data/lib/konpeito/stdlib/json/json_native.c +286 -0
- data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
- data/lib/konpeito/stdlib/ui/ui.rb +318 -0
- data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
- data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
- data/lib/konpeito/type_checker/inferrer.rb +565 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
- data/lib/konpeito/type_checker/type_resolver.rb +276 -0
- data/lib/konpeito/type_checker/types.rb +1434 -0
- data/lib/konpeito/type_checker/unification.rb +323 -0
- data/lib/konpeito/ui/animation/animated_state.rb +80 -0
- data/lib/konpeito/ui/animation/easing.rb +59 -0
- data/lib/konpeito/ui/animation/value_tween.rb +66 -0
- data/lib/konpeito/ui/app.rb +379 -0
- data/lib/konpeito/ui/box.rb +38 -0
- data/lib/konpeito/ui/castella.rb +70 -0
- data/lib/konpeito/ui/castella_native.rb +76 -0
- data/lib/konpeito/ui/chart/area_chart.rb +305 -0
- data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
- data/lib/konpeito/ui/chart/base_chart.rb +210 -0
- data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
- data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
- data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
- data/lib/konpeito/ui/chart/line_chart.rb +289 -0
- data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
- data/lib/konpeito/ui/chart/scales.rb +77 -0
- data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
- data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
- data/lib/konpeito/ui/column.rb +271 -0
- data/lib/konpeito/ui/core.rb +2199 -0
- data/lib/konpeito/ui/dsl.rb +443 -0
- data/lib/konpeito/ui/frame.rb +171 -0
- data/lib/konpeito/ui/frame_native.rb +494 -0
- data/lib/konpeito/ui/markdown/ast.rb +124 -0
- data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
- data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
- data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
- data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
- data/lib/konpeito/ui/markdown/parser.rb +805 -0
- data/lib/konpeito/ui/markdown/renderer.rb +639 -0
- data/lib/konpeito/ui/markdown/theme.rb +165 -0
- data/lib/konpeito/ui/render_node.rb +260 -0
- data/lib/konpeito/ui/row.rb +207 -0
- data/lib/konpeito/ui/spacer.rb +18 -0
- data/lib/konpeito/ui/style.rb +799 -0
- data/lib/konpeito/ui/theme.rb +563 -0
- data/lib/konpeito/ui/themes/material.rb +35 -0
- data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
- data/lib/konpeito/ui/widgets/button.rb +103 -0
- data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
- data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
- data/lib/konpeito/ui/widgets/container.rb +91 -0
- data/lib/konpeito/ui/widgets/data_table.rb +667 -0
- data/lib/konpeito/ui/widgets/divider.rb +29 -0
- data/lib/konpeito/ui/widgets/image.rb +105 -0
- data/lib/konpeito/ui/widgets/input.rb +485 -0
- data/lib/konpeito/ui/widgets/markdown.rb +57 -0
- data/lib/konpeito/ui/widgets/modal.rb +163 -0
- data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
- data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
- data/lib/konpeito/ui/widgets/net_image.rb +100 -0
- data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
- data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
- data/lib/konpeito/ui/widgets/slider.rb +133 -0
- data/lib/konpeito/ui/widgets/switch.rb +84 -0
- data/lib/konpeito/ui/widgets/tabs.rb +157 -0
- data/lib/konpeito/ui/widgets/text.rb +110 -0
- data/lib/konpeito/ui/widgets/tree.rb +426 -0
- data/lib/konpeito/version.rb +5 -0
- data/lib/konpeito.rb +109 -0
- data/test_native_array.rb +172 -0
- data/test_native_array_class.rb +197 -0
- data/test_native_class.rb +151 -0
- data/tools/konpeito-asm/build.sh +65 -0
- data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
- 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
|