kumi 0.0.27 → 0.0.28
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 +10 -0
- data/README.md +24 -9
- data/data/functions/core/arithmetic.yaml +28 -8
- data/data/functions/core/boolean.yaml +8 -3
- data/data/functions/core/comparison.yaml +12 -4
- data/data/kernels/javascript/core/arithmetic.yaml +6 -2
- data/data/kernels/ruby/core/arithmetic.yaml +7 -2
- data/golden/array_element/expected/schema_ruby.rb +1 -1
- data/golden/array_index/expected/lir_00_unoptimized.txt +2 -2
- data/golden/array_index/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/array_index/expected/lir_02_inlined.txt +2 -2
- data/golden/array_index/expected/lir_03_cse.txt +2 -2
- data/golden/array_index/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/array_index/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/array_index/expected/lir_06_const_prop.txt +2 -2
- data/golden/array_index/expected/schema_ruby.rb +1 -1
- data/golden/array_index/expected/snast.txt +2 -2
- data/golden/array_operations/expected/lir_00_unoptimized.txt +2 -2
- data/golden/array_operations/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/array_operations/expected/lir_02_inlined.txt +2 -2
- data/golden/array_operations/expected/lir_03_cse.txt +2 -2
- data/golden/array_operations/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/array_operations/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/array_operations/expected/lir_06_const_prop.txt +2 -2
- data/golden/array_operations/expected/schema_ruby.rb +1 -1
- data/golden/array_operations/expected/snast.txt +2 -2
- data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
- data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
- data/golden/decimal_explicit/expected/lir_00_unoptimized.txt +2 -2
- data/golden/decimal_explicit/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/decimal_explicit/expected/lir_02_inlined.txt +6 -6
- data/golden/decimal_explicit/expected/lir_03_cse.txt +5 -5
- data/golden/decimal_explicit/expected/lir_04_1_loop_fusion.txt +5 -5
- data/golden/decimal_explicit/expected/lir_04_loop_invcm.txt +5 -5
- data/golden/decimal_explicit/expected/lir_06_const_prop.txt +5 -5
- data/golden/decimal_explicit/expected/schema_ruby.rb +1 -1
- data/golden/decimal_explicit/expected/snast.txt +2 -2
- data/golden/element_arrays/expected/schema_ruby.rb +1 -1
- data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
- data/golden/function_overload/expected/schema_ruby.rb +1 -1
- data/golden/game_of_life/expected/schema_ruby.rb +1 -1
- data/golden/hash_keys/expected/schema_ruby.rb +1 -1
- data/golden/hash_value/expected/schema_ruby.rb +1 -1
- data/golden/hierarchical_complex/expected/lir_00_unoptimized.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_01_hoist_scalar_references.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_02_inlined.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_03_cse.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_04_1_loop_fusion.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_04_loop_invcm.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_06_const_prop.txt +3 -3
- data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
- data/golden/hierarchical_complex/expected/snast.txt +3 -3
- data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
- data/golden/input_reference/expected/schema_ruby.rb +1 -1
- data/golden/interleaved_fusion/expected/lir_00_unoptimized.txt +1 -1
- data/golden/interleaved_fusion/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/interleaved_fusion/expected/lir_02_inlined.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_03_cse.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_06_const_prop.txt +2 -2
- data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
- data/golden/interleaved_fusion/expected/snast.txt +1 -1
- data/golden/let_inline/expected/lir_00_unoptimized.txt +2 -2
- data/golden/let_inline/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/let_inline/expected/lir_02_inlined.txt +6 -6
- data/golden/let_inline/expected/lir_03_cse.txt +6 -6
- data/golden/let_inline/expected/lir_04_1_loop_fusion.txt +6 -6
- data/golden/let_inline/expected/lir_04_loop_invcm.txt +6 -6
- data/golden/let_inline/expected/lir_06_const_prop.txt +6 -6
- data/golden/let_inline/expected/schema_ruby.rb +1 -1
- data/golden/let_inline/expected/snast.txt +2 -2
- data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
- data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
- data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
- data/golden/multirank_hoisting/expected/lir_00_unoptimized.txt +2 -2
- data/golden/multirank_hoisting/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/multirank_hoisting/expected/lir_02_inlined.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_03_cse.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_04_1_loop_fusion.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_04_loop_invcm.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_06_const_prop.txt +7 -7
- data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
- data/golden/multirank_hoisting/expected/snast.txt +2 -2
- data/golden/nested_hash/expected/lir_00_unoptimized.txt +1 -1
- data/golden/nested_hash/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/nested_hash/expected/lir_02_inlined.txt +1 -1
- data/golden/nested_hash/expected/lir_03_cse.txt +1 -1
- data/golden/nested_hash/expected/lir_04_1_loop_fusion.txt +1 -1
- data/golden/nested_hash/expected/lir_04_loop_invcm.txt +1 -1
- data/golden/nested_hash/expected/lir_06_const_prop.txt +1 -1
- data/golden/nested_hash/expected/schema_ruby.rb +1 -1
- data/golden/nested_hash/expected/snast.txt +1 -1
- data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
- data/golden/roll/expected/schema_ruby.rb +1 -1
- data/golden/shift/expected/schema_ruby.rb +1 -1
- data/golden/shift_2d/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/lir_00_unoptimized.txt +2 -2
- data/golden/simple_math/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/simple_math/expected/lir_02_inlined.txt +2 -2
- data/golden/simple_math/expected/lir_03_cse.txt +2 -2
- data/golden/simple_math/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/simple_math/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/simple_math/expected/lir_06_const_prop.txt +2 -2
- data/golden/simple_math/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/snast.txt +2 -2
- data/golden/streaming_basics/expected/lir_00_unoptimized.txt +3 -3
- data/golden/streaming_basics/expected/lir_01_hoist_scalar_references.txt +3 -3
- data/golden/streaming_basics/expected/lir_02_inlined.txt +9 -9
- data/golden/streaming_basics/expected/lir_03_cse.txt +7 -7
- data/golden/streaming_basics/expected/lir_04_1_loop_fusion.txt +7 -7
- data/golden/streaming_basics/expected/lir_04_loop_invcm.txt +7 -7
- data/golden/streaming_basics/expected/lir_06_const_prop.txt +7 -7
- data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
- data/golden/streaming_basics/expected/snast.txt +3 -3
- data/golden/tuples/expected/schema_ruby.rb +1 -1
- data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
- data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +6 -6
- data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +6 -6
- data/golden/us_tax_2024/expected/lir_02_inlined.txt +71 -71
- data/golden/us_tax_2024/expected/lir_03_cse.txt +43 -43
- data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +48 -48
- data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +43 -43
- data/golden/us_tax_2024/expected/lir_06_const_prop.txt +43 -43
- data/golden/us_tax_2024/expected/schema_ruby.rb +1 -1
- data/golden/us_tax_2024/expected/snast.txt +6 -6
- data/golden/with_constants/expected/schema_ruby.rb +1 -1
- data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +1 -1
- data/lib/kumi/core/error_reporter.rb +1 -1
- data/lib/kumi/core/errors.rb +1 -1
- data/lib/kumi/core/functions/overload_resolver.rb +57 -11
- data/lib/kumi/core/functions/type_categories.rb +44 -0
- data/lib/kumi/frontends/text.rb +33 -5
- data/lib/kumi/syntax/location.rb +5 -1
- data/lib/kumi/version.rb +1 -1
- metadata +2 -1
@@ -26,20 +26,37 @@ module Kumi
|
|
26
26
|
def resolve(alias_or_id, arg_types)
|
27
27
|
s = alias_or_id.to_s
|
28
28
|
|
29
|
-
# If it's already a full function ID, validate and
|
29
|
+
# If it's already a full function ID, validate arity and type constraints
|
30
30
|
if @functions.key?(s)
|
31
31
|
validate_arity!(s, arg_types)
|
32
|
-
|
32
|
+
fn = @functions[s]
|
33
|
+
score = match_score(fn.params, arg_types)
|
34
|
+
if score > 0
|
35
|
+
return s
|
36
|
+
else
|
37
|
+
# Type constraints failed
|
38
|
+
raise ResolutionError,
|
39
|
+
"#{alias_or_id}(#{format_types(arg_types)}) - type mismatch"
|
40
|
+
end
|
33
41
|
end
|
34
42
|
|
35
43
|
# Get all candidate overloads for this alias
|
36
44
|
candidates = @alias_overloads[s]
|
37
45
|
raise ResolutionError, "unknown function #{alias_or_id}" if candidates.nil?
|
38
46
|
|
39
|
-
# Single overload -
|
47
|
+
# Single overload - validate type constraints too
|
40
48
|
if candidates.size == 1
|
41
|
-
|
42
|
-
|
49
|
+
fn_id = candidates.first
|
50
|
+
validate_arity!(fn_id, arg_types)
|
51
|
+
fn = @functions[fn_id]
|
52
|
+
score = match_score(fn.params, arg_types)
|
53
|
+
if score > 0
|
54
|
+
return fn_id
|
55
|
+
else
|
56
|
+
# Type constraints failed for the only overload
|
57
|
+
raise ResolutionError,
|
58
|
+
"#{alias_or_id}(#{format_types(arg_types)}) - type mismatch"
|
59
|
+
end
|
43
60
|
end
|
44
61
|
|
45
62
|
# Multiple overloads - find best match by type constraints (prefer exact matches)
|
@@ -56,10 +73,8 @@ module Kumi
|
|
56
73
|
end
|
57
74
|
|
58
75
|
# No match found - provide helpful error
|
59
|
-
available = candidates.map { |id| @functions[id].id }.join(", ")
|
60
76
|
raise ResolutionError,
|
61
|
-
"
|
62
|
-
"Available overloads: #{available}"
|
77
|
+
"#{alias_or_id}(#{format_types(arg_types)}) - type mismatch"
|
63
78
|
end
|
64
79
|
|
65
80
|
# Get function object by ID (already resolved)
|
@@ -99,7 +114,7 @@ module Kumi
|
|
99
114
|
|
100
115
|
def match_score(params, arg_types)
|
101
116
|
# Returns match quality: higher is better
|
102
|
-
# 0 = no match, 1 =
|
117
|
+
# 0 = no match, 1+ = match (1 for unconstrained params, higher for exact matches)
|
103
118
|
return 0 unless params_match?(params, arg_types)
|
104
119
|
|
105
120
|
# Count exact constraint matches (all arg_types are Type objects now)
|
@@ -108,11 +123,22 @@ module Kumi
|
|
108
123
|
score_type_object_match(param_dtype, arg_type)
|
109
124
|
end
|
110
125
|
|
111
|
-
exact_matches
|
126
|
+
# Return exact_matches + 1 so that: unconstrained=1, one exact=2, all exact=N+1
|
127
|
+
exact_matches + 1
|
112
128
|
end
|
113
129
|
|
114
130
|
def score_type_object_match(param_dtype, type_obj)
|
115
|
-
|
131
|
+
constraint = param_dtype&.to_s
|
132
|
+
return false unless constraint
|
133
|
+
|
134
|
+
# Check if it's a type category
|
135
|
+
if TypeCategories.category?(constraint)
|
136
|
+
return false unless type_obj.is_a?(Kumi::Core::Types::ScalarType)
|
137
|
+
return TypeCategories.includes?(constraint, type_obj.kind)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Individual scalar type constraints
|
141
|
+
case constraint
|
116
142
|
when "string"
|
117
143
|
type_obj.is_a?(Kumi::Core::Types::ScalarType) && type_obj.kind == :string
|
118
144
|
when "array"
|
@@ -131,6 +157,13 @@ module Kumi
|
|
131
157
|
def type_compatible?(param_dtype_str, arg_type)
|
132
158
|
raise ArgumentError, "arg_type must be a Type object, got #{arg_type.inspect}" unless arg_type.is_a?(Kumi::Core::Types::Type)
|
133
159
|
|
160
|
+
# Check if it's a type category
|
161
|
+
if TypeCategories.category?(param_dtype_str)
|
162
|
+
return false unless arg_type.is_a?(Kumi::Core::Types::ScalarType)
|
163
|
+
return TypeCategories.includes?(param_dtype_str, arg_type.kind)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Individual scalar type constraints
|
134
167
|
case param_dtype_str
|
135
168
|
when "string"
|
136
169
|
arg_type.is_a?(Kumi::Core::Types::ScalarType) && arg_type.kind == :string
|
@@ -156,6 +189,19 @@ module Kumi
|
|
156
189
|
"function #{fn_id} expects #{fn.params.size} arguments, got #{arg_types.size}"
|
157
190
|
end
|
158
191
|
|
192
|
+
private
|
193
|
+
|
194
|
+
def format_types(arg_types)
|
195
|
+
arg_types.map(&:to_s).join(", ")
|
196
|
+
end
|
197
|
+
|
198
|
+
def format_param_constraints(params)
|
199
|
+
params.map do |param|
|
200
|
+
dtype = param["dtype"]
|
201
|
+
dtype || "any"
|
202
|
+
end.join(", ")
|
203
|
+
end
|
204
|
+
|
159
205
|
# Custom error for function resolution failures
|
160
206
|
class ResolutionError < StandardError; end
|
161
207
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Functions
|
6
|
+
# Type categories define reusable type constraints
|
7
|
+
# Instead of hardcoding type checks scattered throughout the codebase,
|
8
|
+
# we define categories once and reference them in function definitions
|
9
|
+
class TypeCategories
|
10
|
+
# Define type categories as unions of scalar kinds
|
11
|
+
CATEGORIES = {
|
12
|
+
numeric: [:integer, :float, :decimal],
|
13
|
+
comparable: [:integer, :float, :decimal, :string],
|
14
|
+
boolean: [:boolean],
|
15
|
+
stringable: [:string],
|
16
|
+
orderable: [:integer, :float, :decimal, :string]
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def self.expand(dtype_constraint)
|
20
|
+
return dtype_constraint unless dtype_constraint.is_a?(String)
|
21
|
+
|
22
|
+
category = dtype_constraint.to_sym
|
23
|
+
CATEGORIES[category] || dtype_constraint
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.includes?(dtype_constraint, kind)
|
27
|
+
kinds = expand(dtype_constraint)
|
28
|
+
return kinds.include?(kind) if kinds.is_a?(Array)
|
29
|
+
|
30
|
+
# Fall back to string comparison for uncategorized constraints
|
31
|
+
kinds == kind.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.category?(name)
|
35
|
+
CATEGORIES.key?(name.to_sym)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.categories
|
39
|
+
CATEGORIES
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/kumi/frontends/text.rb
CHANGED
@@ -13,20 +13,48 @@ module Kumi
|
|
13
13
|
raise ArgumentError, "provide either :path or :src" if (path.nil? && src.nil?) || (path && src)
|
14
14
|
|
15
15
|
src ||= File.read(path)
|
16
|
-
file_label = path || "
|
16
|
+
file_label = path || "schema"
|
17
17
|
|
18
18
|
begin
|
19
19
|
require "kumi-parser"
|
20
|
-
ast = Kumi::Parser::TextParser.parse(src)
|
20
|
+
ast = Kumi::Parser::TextParser.parse(src, source_file: path || "schema")
|
21
21
|
Core::Analyzer::Debug.info(:parse, kind: :text, file: file_label, ok: true) if Core::Analyzer::Debug.enabled?
|
22
22
|
[ast, inputs]
|
23
23
|
rescue LoadError
|
24
24
|
raise "kumi-parser gem not available. Install: gem install kumi-parser"
|
25
25
|
rescue StandardError => e
|
26
|
-
|
27
|
-
line, col =
|
26
|
+
# Try to extract line/column from exception object first
|
27
|
+
line, col = extract_line_column(e)
|
28
28
|
snippet = code_frame(src, line, col)
|
29
|
-
|
29
|
+
|
30
|
+
# Strip file:line:col prefix from e.message if it exists (from parser)
|
31
|
+
# Also strip embedded "at FILE line=N column=M" to avoid duplication
|
32
|
+
error_message = e.message
|
33
|
+
.sub(/^\S+:\d+:\d+:\s+/, '')
|
34
|
+
.gsub(/\s+at\s+\S+\s+line=\d+\s+column=\d+/, '')
|
35
|
+
.strip
|
36
|
+
raise StandardError, "#{file_label}:#{line || '?'}:#{col || '?'}: #{error_message}\n#{snippet}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.extract_line_column(exception)
|
41
|
+
# Try to access Location object from exception
|
42
|
+
if exception.respond_to?(:location) && exception.location
|
43
|
+
loc = exception.location
|
44
|
+
if loc.respond_to?(:line) && loc.respond_to?(:column)
|
45
|
+
return [loc.line, loc.column]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Fall back to parsing error message if no Location object
|
50
|
+
extract_line_column_from_message(exception.message)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.extract_line_column_from_message(message)
|
54
|
+
if message =~ /line=(\d+)\s+column=(\d+)/
|
55
|
+
[::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i]
|
56
|
+
else
|
57
|
+
[nil, nil]
|
30
58
|
end
|
31
59
|
end
|
32
60
|
|
data/lib/kumi/syntax/location.rb
CHANGED
data/lib/kumi/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kumi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- André Muta
|
@@ -701,6 +701,7 @@ files:
|
|
701
701
|
- lib/kumi/core/functions/function_spec.rb
|
702
702
|
- lib/kumi/core/functions/loader.rb
|
703
703
|
- lib/kumi/core/functions/overload_resolver.rb
|
704
|
+
- lib/kumi/core/functions/type_categories.rb
|
704
705
|
- lib/kumi/core/functions/type_error_reporter.rb
|
705
706
|
- lib/kumi/core/functions/type_rules.rb
|
706
707
|
- lib/kumi/core/input/type_matcher.rb
|