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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +24 -9
  4. data/data/functions/core/arithmetic.yaml +28 -8
  5. data/data/functions/core/boolean.yaml +8 -3
  6. data/data/functions/core/comparison.yaml +12 -4
  7. data/data/kernels/javascript/core/arithmetic.yaml +6 -2
  8. data/data/kernels/ruby/core/arithmetic.yaml +7 -2
  9. data/golden/array_element/expected/schema_ruby.rb +1 -1
  10. data/golden/array_index/expected/lir_00_unoptimized.txt +2 -2
  11. data/golden/array_index/expected/lir_01_hoist_scalar_references.txt +2 -2
  12. data/golden/array_index/expected/lir_02_inlined.txt +2 -2
  13. data/golden/array_index/expected/lir_03_cse.txt +2 -2
  14. data/golden/array_index/expected/lir_04_1_loop_fusion.txt +2 -2
  15. data/golden/array_index/expected/lir_04_loop_invcm.txt +2 -2
  16. data/golden/array_index/expected/lir_06_const_prop.txt +2 -2
  17. data/golden/array_index/expected/schema_ruby.rb +1 -1
  18. data/golden/array_index/expected/snast.txt +2 -2
  19. data/golden/array_operations/expected/lir_00_unoptimized.txt +2 -2
  20. data/golden/array_operations/expected/lir_01_hoist_scalar_references.txt +2 -2
  21. data/golden/array_operations/expected/lir_02_inlined.txt +2 -2
  22. data/golden/array_operations/expected/lir_03_cse.txt +2 -2
  23. data/golden/array_operations/expected/lir_04_1_loop_fusion.txt +2 -2
  24. data/golden/array_operations/expected/lir_04_loop_invcm.txt +2 -2
  25. data/golden/array_operations/expected/lir_06_const_prop.txt +2 -2
  26. data/golden/array_operations/expected/schema_ruby.rb +1 -1
  27. data/golden/array_operations/expected/snast.txt +2 -2
  28. data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
  29. data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
  30. data/golden/decimal_explicit/expected/lir_00_unoptimized.txt +2 -2
  31. data/golden/decimal_explicit/expected/lir_01_hoist_scalar_references.txt +2 -2
  32. data/golden/decimal_explicit/expected/lir_02_inlined.txt +6 -6
  33. data/golden/decimal_explicit/expected/lir_03_cse.txt +5 -5
  34. data/golden/decimal_explicit/expected/lir_04_1_loop_fusion.txt +5 -5
  35. data/golden/decimal_explicit/expected/lir_04_loop_invcm.txt +5 -5
  36. data/golden/decimal_explicit/expected/lir_06_const_prop.txt +5 -5
  37. data/golden/decimal_explicit/expected/schema_ruby.rb +1 -1
  38. data/golden/decimal_explicit/expected/snast.txt +2 -2
  39. data/golden/element_arrays/expected/schema_ruby.rb +1 -1
  40. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
  41. data/golden/function_overload/expected/schema_ruby.rb +1 -1
  42. data/golden/game_of_life/expected/schema_ruby.rb +1 -1
  43. data/golden/hash_keys/expected/schema_ruby.rb +1 -1
  44. data/golden/hash_value/expected/schema_ruby.rb +1 -1
  45. data/golden/hierarchical_complex/expected/lir_00_unoptimized.txt +3 -3
  46. data/golden/hierarchical_complex/expected/lir_01_hoist_scalar_references.txt +3 -3
  47. data/golden/hierarchical_complex/expected/lir_02_inlined.txt +3 -3
  48. data/golden/hierarchical_complex/expected/lir_03_cse.txt +3 -3
  49. data/golden/hierarchical_complex/expected/lir_04_1_loop_fusion.txt +3 -3
  50. data/golden/hierarchical_complex/expected/lir_04_loop_invcm.txt +3 -3
  51. data/golden/hierarchical_complex/expected/lir_06_const_prop.txt +3 -3
  52. data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
  53. data/golden/hierarchical_complex/expected/snast.txt +3 -3
  54. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
  55. data/golden/input_reference/expected/schema_ruby.rb +1 -1
  56. data/golden/interleaved_fusion/expected/lir_00_unoptimized.txt +1 -1
  57. data/golden/interleaved_fusion/expected/lir_01_hoist_scalar_references.txt +1 -1
  58. data/golden/interleaved_fusion/expected/lir_02_inlined.txt +2 -2
  59. data/golden/interleaved_fusion/expected/lir_03_cse.txt +2 -2
  60. data/golden/interleaved_fusion/expected/lir_04_1_loop_fusion.txt +2 -2
  61. data/golden/interleaved_fusion/expected/lir_04_loop_invcm.txt +2 -2
  62. data/golden/interleaved_fusion/expected/lir_06_const_prop.txt +2 -2
  63. data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
  64. data/golden/interleaved_fusion/expected/snast.txt +1 -1
  65. data/golden/let_inline/expected/lir_00_unoptimized.txt +2 -2
  66. data/golden/let_inline/expected/lir_01_hoist_scalar_references.txt +2 -2
  67. data/golden/let_inline/expected/lir_02_inlined.txt +6 -6
  68. data/golden/let_inline/expected/lir_03_cse.txt +6 -6
  69. data/golden/let_inline/expected/lir_04_1_loop_fusion.txt +6 -6
  70. data/golden/let_inline/expected/lir_04_loop_invcm.txt +6 -6
  71. data/golden/let_inline/expected/lir_06_const_prop.txt +6 -6
  72. data/golden/let_inline/expected/schema_ruby.rb +1 -1
  73. data/golden/let_inline/expected/snast.txt +2 -2
  74. data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
  75. data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
  76. data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
  77. data/golden/multirank_hoisting/expected/lir_00_unoptimized.txt +2 -2
  78. data/golden/multirank_hoisting/expected/lir_01_hoist_scalar_references.txt +2 -2
  79. data/golden/multirank_hoisting/expected/lir_02_inlined.txt +7 -7
  80. data/golden/multirank_hoisting/expected/lir_03_cse.txt +7 -7
  81. data/golden/multirank_hoisting/expected/lir_04_1_loop_fusion.txt +7 -7
  82. data/golden/multirank_hoisting/expected/lir_04_loop_invcm.txt +7 -7
  83. data/golden/multirank_hoisting/expected/lir_06_const_prop.txt +7 -7
  84. data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
  85. data/golden/multirank_hoisting/expected/snast.txt +2 -2
  86. data/golden/nested_hash/expected/lir_00_unoptimized.txt +1 -1
  87. data/golden/nested_hash/expected/lir_01_hoist_scalar_references.txt +1 -1
  88. data/golden/nested_hash/expected/lir_02_inlined.txt +1 -1
  89. data/golden/nested_hash/expected/lir_03_cse.txt +1 -1
  90. data/golden/nested_hash/expected/lir_04_1_loop_fusion.txt +1 -1
  91. data/golden/nested_hash/expected/lir_04_loop_invcm.txt +1 -1
  92. data/golden/nested_hash/expected/lir_06_const_prop.txt +1 -1
  93. data/golden/nested_hash/expected/schema_ruby.rb +1 -1
  94. data/golden/nested_hash/expected/snast.txt +1 -1
  95. data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
  96. data/golden/roll/expected/schema_ruby.rb +1 -1
  97. data/golden/shift/expected/schema_ruby.rb +1 -1
  98. data/golden/shift_2d/expected/schema_ruby.rb +1 -1
  99. data/golden/simple_math/expected/lir_00_unoptimized.txt +2 -2
  100. data/golden/simple_math/expected/lir_01_hoist_scalar_references.txt +2 -2
  101. data/golden/simple_math/expected/lir_02_inlined.txt +2 -2
  102. data/golden/simple_math/expected/lir_03_cse.txt +2 -2
  103. data/golden/simple_math/expected/lir_04_1_loop_fusion.txt +2 -2
  104. data/golden/simple_math/expected/lir_04_loop_invcm.txt +2 -2
  105. data/golden/simple_math/expected/lir_06_const_prop.txt +2 -2
  106. data/golden/simple_math/expected/schema_ruby.rb +1 -1
  107. data/golden/simple_math/expected/snast.txt +2 -2
  108. data/golden/streaming_basics/expected/lir_00_unoptimized.txt +3 -3
  109. data/golden/streaming_basics/expected/lir_01_hoist_scalar_references.txt +3 -3
  110. data/golden/streaming_basics/expected/lir_02_inlined.txt +9 -9
  111. data/golden/streaming_basics/expected/lir_03_cse.txt +7 -7
  112. data/golden/streaming_basics/expected/lir_04_1_loop_fusion.txt +7 -7
  113. data/golden/streaming_basics/expected/lir_04_loop_invcm.txt +7 -7
  114. data/golden/streaming_basics/expected/lir_06_const_prop.txt +7 -7
  115. data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
  116. data/golden/streaming_basics/expected/snast.txt +3 -3
  117. data/golden/tuples/expected/schema_ruby.rb +1 -1
  118. data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
  119. data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +6 -6
  120. data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +6 -6
  121. data/golden/us_tax_2024/expected/lir_02_inlined.txt +71 -71
  122. data/golden/us_tax_2024/expected/lir_03_cse.txt +43 -43
  123. data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +48 -48
  124. data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +43 -43
  125. data/golden/us_tax_2024/expected/lir_06_const_prop.txt +43 -43
  126. data/golden/us_tax_2024/expected/schema_ruby.rb +1 -1
  127. data/golden/us_tax_2024/expected/snast.txt +6 -6
  128. data/golden/with_constants/expected/schema_ruby.rb +1 -1
  129. data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +1 -1
  130. data/lib/kumi/core/error_reporter.rb +1 -1
  131. data/lib/kumi/core/errors.rb +1 -1
  132. data/lib/kumi/core/functions/overload_resolver.rb +57 -11
  133. data/lib/kumi/core/functions/type_categories.rb +44 -0
  134. data/lib/kumi/frontends/text.rb +33 -5
  135. data/lib/kumi/syntax/location.rb +5 -1
  136. data/lib/kumi/version.rb +1 -1
  137. 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 return it
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
- return s
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 - use it directly
47
+ # Single overload - validate type constraints too
40
48
  if candidates.size == 1
41
- validate_arity!(candidates.first, arg_types)
42
- return candidates.first
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
- "no overload of '#{alias_or_id}' matches argument types #{arg_types.inspect}. " \
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 = matches with unconstrained params, 2 = exact match
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
- case param_dtype&.to_s
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
@@ -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 || "(string)"
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
- loc = (e.respond_to?(:location) && e.location) || {}
27
- line, col = loc.values_at(:line, :column)
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
- raise StandardError, "#{file_label}:#{line || '?'}:#{col || '?'}: #{e.message}\n#{snippet}"
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
 
@@ -1,5 +1,9 @@
1
1
  module Kumi
2
2
  module Syntax
3
- Location = Struct.new(:file, :line, :column, keyword_init: true)
3
+ class Location < Struct.new(:file, :line, :column, keyword_init: true)
4
+ def to_s
5
+ "#{file} line=#{line} column=#{column}"
6
+ end
7
+ end
4
8
  end
5
9
  end
data/lib/kumi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- VERSION = "0.0.27"
4
+ VERSION = "0.0.28"
5
5
  end
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.27
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