kumi 0.0.10 → 0.0.12
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +23 -0
- data/CLAUDE.md +7 -231
- data/README.md +5 -5
- data/docs/SYNTAX.md +66 -0
- data/docs/VECTOR_SEMANTICS.md +286 -0
- data/docs/features/hierarchical-broadcasting.md +67 -1
- data/docs/features/input-declaration-system.md +16 -0
- data/docs/features/s-expression-printer.md +2 -2
- data/lib/kumi/analyzer.rb +34 -12
- data/lib/kumi/compiler.rb +2 -12
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +157 -64
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +1 -1
- data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +47 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +123 -101
- data/lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb +293 -0
- data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +993 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +2 -2
- data/lib/kumi/core/analyzer/passes/scope_resolution_pass.rb +346 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +2 -1
- data/lib/kumi/core/analyzer/passes/toposorter.rb +9 -3
- data/lib/kumi/core/analyzer/passes/type_checker.rb +3 -3
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/core/analyzer/passes/{type_inferencer.rb → type_inferencer_pass.rb} +4 -4
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +2 -2
- data/lib/kumi/core/analyzer/plans.rb +52 -0
- data/lib/kumi/core/analyzer/structs/access_plan.rb +20 -0
- data/lib/kumi/core/analyzer/structs/input_meta.rb +29 -0
- data/lib/kumi/core/compiler/access_builder.rb +36 -0
- data/lib/kumi/core/compiler/access_planner.rb +219 -0
- data/lib/kumi/core/compiler/accessors/base.rb +69 -0
- data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +84 -0
- data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +55 -0
- data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +73 -0
- data/lib/kumi/core/compiler/accessors/read_accessor.rb +41 -0
- data/lib/kumi/core/compiler_base.rb +2 -2
- data/lib/kumi/core/error_reporter.rb +6 -5
- data/lib/kumi/core/errors.rb +4 -0
- data/lib/kumi/core/explain.rb +157 -205
- data/lib/kumi/core/export/node_builders.rb +2 -2
- data/lib/kumi/core/export/node_serializers.rb +1 -1
- data/lib/kumi/core/function_registry/collection_functions.rb +21 -10
- data/lib/kumi/core/function_registry/conditional_functions.rb +14 -4
- data/lib/kumi/core/function_registry/function_builder.rb +142 -55
- data/lib/kumi/core/function_registry/logical_functions.rb +5 -5
- data/lib/kumi/core/function_registry/stat_functions.rb +2 -2
- data/lib/kumi/core/function_registry.rb +126 -108
- data/lib/kumi/core/input/validator.rb +1 -1
- data/lib/kumi/core/ir/execution_engine/combinators.rb +117 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +336 -0
- data/lib/kumi/core/ir/execution_engine/values.rb +46 -0
- data/lib/kumi/core/ir/execution_engine.rb +50 -0
- data/lib/kumi/core/ir.rb +58 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +2 -2
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +0 -12
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +36 -15
- data/lib/kumi/core/ruby_parser/input_builder.rb +30 -9
- data/lib/kumi/core/ruby_parser/parser.rb +1 -1
- data/lib/kumi/core/ruby_parser/schema_builder.rb +2 -2
- data/lib/kumi/core/ruby_parser/sugar.rb +7 -0
- data/lib/kumi/core/types/validator.rb +1 -1
- data/lib/kumi/registry.rb +14 -79
- data/lib/kumi/runtime/executable.rb +213 -0
- data/lib/kumi/schema.rb +14 -3
- data/lib/kumi/schema_metadata.rb +2 -2
- data/lib/kumi/support/ir_dump.rb +491 -0
- data/lib/kumi/support/s_expression_printer.rb +1 -1
- data/lib/kumi/syntax/location.rb +5 -0
- data/lib/kumi/syntax/node.rb +0 -1
- data/lib/kumi/syntax/root.rb +2 -2
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +6 -15
- metadata +37 -19
- data/lib/kumi/core/cascade_executor_builder.rb +0 -132
- data/lib/kumi/core/compiled_schema.rb +0 -43
- data/lib/kumi/core/compiler/expression_compiler.rb +0 -146
- data/lib/kumi/core/compiler/function_invoker.rb +0 -55
- data/lib/kumi/core/compiler/path_traversal_compiler.rb +0 -158
- data/lib/kumi/core/compiler/reference_compiler.rb +0 -46
- data/lib/kumi/core/evaluation_wrapper.rb +0 -40
- data/lib/kumi/core/nested_structure_utils.rb +0 -78
- data/lib/kumi/core/schema_instance.rb +0 -115
- data/lib/kumi/core/vectorized_function_builder.rb +0 -88
- data/lib/kumi/js/compiler.rb +0 -878
- data/lib/kumi/js/function_registry.rb +0 -333
- data/migrate_to_core_iterative.rb +0 -938
@@ -3,92 +3,179 @@
|
|
3
3
|
module Kumi
|
4
4
|
module Core
|
5
5
|
module FunctionRegistry
|
6
|
-
# Utility class to reduce repetition in function definitions
|
7
6
|
class FunctionBuilder
|
8
|
-
|
9
|
-
|
7
|
+
# Rich, defaulted function entry
|
8
|
+
class Entry
|
9
|
+
# NOTE: Keep ctor args minimal; everything else has sensible defaults.
|
10
|
+
attr_reader :fn, :arity, :param_types, :return_type, :description,
|
11
|
+
:reducer, :structure_function, :param_modes, :param_info
|
10
12
|
|
11
|
-
|
13
|
+
# param_modes: nil | ->(argc){[:elem,:scalar,...]} | {fixed: [...], variadic: :elem|:scalar}
|
14
|
+
# param_info: nil | ->(argc){[specs]} | {fixed: [...], variadic: {…}}
|
15
|
+
# where a spec is: { name:, type:, mode:, required:, default:, doc: }
|
16
|
+
def initialize(
|
17
|
+
fn:,
|
18
|
+
arity: nil, # Integer (>=0) or -1 / nil for variadic
|
19
|
+
param_types: nil, # defaults to [:any] * arity (when fixed)
|
20
|
+
return_type: :any,
|
21
|
+
description: "",
|
22
|
+
reducer: false,
|
23
|
+
structure_function: false,
|
24
|
+
param_modes: nil,
|
25
|
+
param_info: nil
|
26
|
+
)
|
27
|
+
@fn = fn
|
28
|
+
@arity = arity
|
29
|
+
@param_types = param_types || default_param_types(arity)
|
30
|
+
@return_type = return_type
|
31
|
+
@description = description
|
32
|
+
@reducer = !!reducer
|
33
|
+
@structure_function = !!structure_function
|
34
|
+
@param_modes = normalize_param_modes(param_modes, arity)
|
35
|
+
@param_info = normalize_param_info(param_info, arity, @param_types)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Concrete modes for a call site
|
39
|
+
def param_modes_for(argc)
|
40
|
+
pm = @param_modes
|
41
|
+
return pm.call(argc) if pm.respond_to?(:call)
|
42
|
+
|
43
|
+
fixed = Array(pm[:fixed] || [])
|
44
|
+
return fixed.first(argc) if argc <= fixed.size
|
45
|
+
|
46
|
+
fixed + Array.new(argc - fixed.size, pm.fetch(:variadic, :elem))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Concrete param specs for a call site
|
50
|
+
# → [{name:, type:, mode:, required:, default:, doc:}, ...]
|
51
|
+
def param_specs_for(argc)
|
52
|
+
base = if @param_info.respond_to?(:call)
|
53
|
+
@param_info.call(argc)
|
54
|
+
else
|
55
|
+
fixed = Array(@param_info[:fixed] || [])
|
56
|
+
if argc <= fixed.size
|
57
|
+
fixed.first(argc)
|
58
|
+
else
|
59
|
+
fixed + Array.new(argc - fixed.size, @param_info.fetch(:variadic, {}))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
modes = param_modes_for(argc)
|
64
|
+
types = expand_types_for(argc)
|
65
|
+
|
66
|
+
base.each_with_index.map do |spec, i|
|
67
|
+
{
|
68
|
+
name: spec[:name] || auto_name(i),
|
69
|
+
type: spec[:type] || types[i] || :any,
|
70
|
+
mode: spec[:mode] || modes[i] || :elem,
|
71
|
+
required: spec.key?(:required) ? spec[:required] : true,
|
72
|
+
default: spec[:default],
|
73
|
+
doc: spec[:doc] || ""
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def default_param_types(arity)
|
81
|
+
if arity.is_a?(Integer) && arity >= 0
|
82
|
+
Array.new(arity, :any)
|
83
|
+
else
|
84
|
+
[] # variadic → types resolved per call
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def expand_types_for(argc)
|
89
|
+
if @param_types.nil? || @param_types.empty?
|
90
|
+
Array.new(argc, :any)
|
91
|
+
elsif @param_types.length >= argc
|
92
|
+
@param_types.first(argc)
|
93
|
+
else
|
94
|
+
@param_types + Array.new(argc - @param_types.length, @param_types.last || :any)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def normalize_param_modes(pm, arity)
|
99
|
+
return pm if pm
|
100
|
+
|
101
|
+
# Default: everything element-wise/broadcastable
|
102
|
+
->(argc) { Array.new(argc, :elem) }
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalize_param_info(info, arity, types)
|
106
|
+
return info if info
|
107
|
+
|
108
|
+
# Default: synthesize from types/modes at call time
|
109
|
+
->(argc) { Array.new(argc) { {} } }
|
110
|
+
end
|
111
|
+
|
112
|
+
def auto_name(i) = :"arg#{i + 1}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# ===== Helper constructors (unchanged usage; now benefit from defaults) =====
|
116
|
+
|
117
|
+
def self.comparison(_name, description, op)
|
12
118
|
Entry.new(
|
13
|
-
fn: ->(a, b) { a.public_send(
|
14
|
-
arity: 2,
|
15
|
-
|
16
|
-
return_type: :boolean,
|
17
|
-
description: description
|
119
|
+
fn: ->(a, b) { a.public_send(op, b) },
|
120
|
+
arity: 2, param_types: %i[float float],
|
121
|
+
return_type: :boolean, description: description
|
18
122
|
)
|
19
123
|
end
|
20
124
|
|
21
|
-
def self.equality(_name, description,
|
125
|
+
def self.equality(_name, description, op)
|
22
126
|
Entry.new(
|
23
|
-
fn: ->(a, b) { a.public_send(
|
24
|
-
arity: 2,
|
25
|
-
|
26
|
-
return_type: :boolean,
|
27
|
-
description: description
|
127
|
+
fn: ->(a, b) { a.public_send(op, b) },
|
128
|
+
arity: 2, param_types: %i[any any],
|
129
|
+
return_type: :boolean, description: description
|
28
130
|
)
|
29
131
|
end
|
30
132
|
|
31
|
-
def self.math_binary(_name, description,
|
133
|
+
def self.math_binary(_name, description, op, return_type: :float)
|
32
134
|
Entry.new(
|
33
|
-
fn:
|
34
|
-
|
35
|
-
|
36
|
-
arity: 2,
|
37
|
-
param_types: %i[float float],
|
38
|
-
return_type: return_type,
|
39
|
-
description: description
|
135
|
+
fn: ->(a, b) { a.public_send(op, b) },
|
136
|
+
arity: 2, param_types: %i[float float],
|
137
|
+
return_type: return_type, description: description
|
40
138
|
)
|
41
139
|
end
|
42
140
|
|
43
|
-
def self.math_unary(_name, description,
|
141
|
+
def self.math_unary(_name, description, op, return_type: :float)
|
44
142
|
Entry.new(
|
45
|
-
fn: proc(&
|
46
|
-
arity: 1,
|
47
|
-
|
48
|
-
return_type: return_type,
|
49
|
-
description: description
|
143
|
+
fn: proc(&op),
|
144
|
+
arity: 1, param_types: [:float],
|
145
|
+
return_type: return_type, description: description
|
50
146
|
)
|
51
147
|
end
|
52
148
|
|
53
|
-
def self.string_unary(_name, description,
|
149
|
+
def self.string_unary(_name, description, op)
|
54
150
|
Entry.new(
|
55
|
-
fn: ->(
|
56
|
-
arity: 1,
|
57
|
-
|
58
|
-
return_type: :string,
|
59
|
-
description: description
|
151
|
+
fn: ->(s) { s.to_s.public_send(op) },
|
152
|
+
arity: 1, param_types: [:string],
|
153
|
+
return_type: :string, description: description
|
60
154
|
)
|
61
155
|
end
|
62
156
|
|
63
|
-
def self.string_binary(_name, description,
|
157
|
+
def self.string_binary(_name, description, op, return_type: :string)
|
64
158
|
Entry.new(
|
65
|
-
fn: ->(
|
66
|
-
arity: 2,
|
67
|
-
|
68
|
-
return_type: return_type,
|
69
|
-
description: description
|
159
|
+
fn: ->(s, x) { s.to_s.public_send(op, x.to_s) },
|
160
|
+
arity: 2, param_types: %i[string string],
|
161
|
+
return_type: return_type, description: description
|
70
162
|
)
|
71
163
|
end
|
72
164
|
|
73
|
-
def self.logical_variadic(_name, description,
|
165
|
+
def self.logical_variadic(_name, description, op)
|
74
166
|
Entry.new(
|
75
|
-
fn: ->(
|
76
|
-
arity: -1,
|
77
|
-
|
78
|
-
return_type: :boolean,
|
79
|
-
description: description
|
167
|
+
fn: ->(*conds) { conds.flatten.public_send(op) },
|
168
|
+
arity: -1, param_types: [:boolean],
|
169
|
+
return_type: :boolean, description: description
|
80
170
|
)
|
81
171
|
end
|
82
172
|
|
83
|
-
def self.collection_unary(_name, description,
|
173
|
+
def self.collection_unary(_name, description, op, return_type: :boolean, reducer: false, structure_function: false)
|
84
174
|
Entry.new(
|
85
|
-
fn: proc(&
|
86
|
-
arity: 1,
|
87
|
-
|
88
|
-
|
89
|
-
description: description,
|
90
|
-
reducer: reducer,
|
91
|
-
structure_function: structure_function
|
175
|
+
fn: proc(&op),
|
176
|
+
arity: 1, param_types: [Kumi::Core::Types.array(:any)],
|
177
|
+
return_type: return_type, description: description,
|
178
|
+
reducer: reducer, structure_function: structure_function
|
92
179
|
)
|
93
180
|
end
|
94
181
|
end
|
@@ -173,9 +173,9 @@ module Kumi
|
|
173
173
|
),
|
174
174
|
|
175
175
|
# Collection logical operations
|
176
|
-
all?: FunctionBuilder.collection_unary(:all?, "Check if all elements in collection are truthy", :all
|
177
|
-
any?: FunctionBuilder.collection_unary(:any?, "Check if any element in collection is truthy", :any
|
178
|
-
none?: FunctionBuilder.collection_unary(:none?, "Check if no elements in collection are truthy", :none
|
176
|
+
all?: FunctionBuilder.collection_unary(:all?, "Check if all elements in collection are truthy", :all?, reducer: true),
|
177
|
+
any?: FunctionBuilder.collection_unary(:any?, "Check if any element in collection is truthy", :any?, reducer: true),
|
178
|
+
none?: FunctionBuilder.collection_unary(:none?, "Check if no elements in collection are truthy", :none?, reducer: true),
|
179
179
|
|
180
180
|
# Element-wise AND for cascades - works on arrays with same structure
|
181
181
|
cascade_and: FunctionBuilder::Entry.new(
|
@@ -188,9 +188,9 @@ module Kumi
|
|
188
188
|
end
|
189
189
|
|
190
190
|
return false if conditions.empty?
|
191
|
-
return conditions.first if conditions.length == 1
|
192
191
|
|
193
|
-
#
|
192
|
+
# Always process uniformly, even for single conditions
|
193
|
+
# This ensures DeclarationReferences are evaluated properly
|
194
194
|
result = conditions.first
|
195
195
|
conditions[1..].each_with_index do |condition, i|
|
196
196
|
puts " Combining result with condition[#{i + 1}]" if ENV["DEBUG_CASCADE"]
|
@@ -8,7 +8,7 @@ module Kumi
|
|
8
8
|
{
|
9
9
|
# Statistical Functions
|
10
10
|
avg: FunctionBuilder::Entry.new(
|
11
|
-
fn: ->(array) { array.sum.to_f / array.size },
|
11
|
+
fn: ->(array) { array.empty? ? nil : array.sum.to_f / array.size },
|
12
12
|
arity: 1,
|
13
13
|
param_types: [Kumi::Core::Types.array(:float)],
|
14
14
|
return_type: :float,
|
@@ -17,7 +17,7 @@ module Kumi
|
|
17
17
|
),
|
18
18
|
|
19
19
|
mean: FunctionBuilder::Entry.new(
|
20
|
-
fn: ->(array) { array.sum.to_f / array.size },
|
20
|
+
fn: ->(array) { array.empty? ? nil : array.sum.to_f / array.size },
|
21
21
|
arity: 1,
|
22
22
|
param_types: [Kumi::Core::Types.array(:float)],
|
23
23
|
return_type: :float,
|
@@ -2,18 +2,15 @@
|
|
2
2
|
|
3
3
|
module Kumi
|
4
4
|
module Core
|
5
|
-
# Internal function registry
|
6
|
-
# Use Kumi::Registry for the public interface to register custom functions
|
5
|
+
# Internal function registry (single source of truth).
|
7
6
|
module FunctionRegistry
|
8
|
-
# Re-export the Entry struct from FunctionBuilder for compatibility
|
9
7
|
Entry = FunctionBuilder::Entry
|
10
8
|
|
11
|
-
# Core operators that are always available
|
12
9
|
CORE_OPERATORS = %i[== > < >= <= != between?].freeze
|
13
10
|
|
14
|
-
# Build
|
11
|
+
# Build core functions once
|
15
12
|
CORE_FUNCTIONS = {}.tap do |registry|
|
16
|
-
|
13
|
+
[
|
17
14
|
ComparisonFunctions.definitions,
|
18
15
|
MathFunctions.definitions,
|
19
16
|
StringFunctions.definitions,
|
@@ -22,137 +19,158 @@ module Kumi
|
|
22
19
|
ConditionalFunctions.definitions,
|
23
20
|
TypeFunctions.definitions,
|
24
21
|
StatFunctions.definitions
|
25
|
-
]
|
22
|
+
].each do |defs|
|
23
|
+
defs.each do |name, entry|
|
24
|
+
raise ArgumentError, "Duplicate core function: #{name}" if registry.key?(name)
|
26
25
|
|
27
|
-
|
28
|
-
module_definitions.each do |name, definition|
|
29
|
-
if registry.key?(name)
|
30
|
-
raise ArgumentError, "Function #{name.inspect} is already registered. Found duplicate in function registry."
|
31
|
-
end
|
32
|
-
|
33
|
-
registry[name] = definition
|
26
|
+
registry[name] = entry
|
34
27
|
end
|
35
28
|
end
|
36
29
|
end.freeze
|
37
30
|
|
38
|
-
@
|
39
|
-
@
|
31
|
+
@lock = Mutex.new
|
32
|
+
@functions = CORE_FUNCTIONS.transform_values(&:dup)
|
33
|
+
@frozen = false
|
40
34
|
|
41
|
-
|
42
|
-
# Internal interface for registering custom functions
|
43
|
-
def register(name, &block)
|
44
|
-
raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
|
35
|
+
class FrozenError < RuntimeError; end
|
45
36
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
def register_with_metadata(name, fn_lambda, arity:, param_types: [:any], return_type: :any, description: nil,
|
52
|
-
inverse: nil, reducer: false)
|
53
|
-
raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
|
54
|
-
|
55
|
-
@functions[name] = Entry.new(
|
56
|
-
fn: fn_lambda,
|
57
|
-
arity: arity,
|
58
|
-
param_types: param_types,
|
59
|
-
return_type: return_type,
|
60
|
-
description: description,
|
61
|
-
inverse: inverse,
|
62
|
-
reducer: reducer
|
63
|
-
)
|
64
|
-
end
|
37
|
+
class << self
|
38
|
+
def auto_register(*mods)
|
39
|
+
mods.each do |mod|
|
40
|
+
mod.public_instance_methods(false).each do |m|
|
41
|
+
next if function?(m)
|
65
42
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
next if supported?(method_name)
|
43
|
+
register(m) { |*args| mod.new.public_send(m, *args) }
|
44
|
+
end
|
45
|
+
mod.singleton_methods(false).each do |m|
|
46
|
+
next if function?(m)
|
71
47
|
|
72
|
-
|
73
|
-
|
48
|
+
fn = mod.method(m)
|
49
|
+
register(m) { |*args| fn.call(*args) }
|
74
50
|
end
|
75
51
|
end
|
76
52
|
end
|
77
|
-
end
|
78
53
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
54
|
+
#
|
55
|
+
# Lifecycle
|
56
|
+
#
|
57
|
+
def reset!
|
58
|
+
@lock.synchronize do
|
59
|
+
@functions = CORE_FUNCTIONS.transform_values(&:dup)
|
60
|
+
@frozen = false
|
61
|
+
end
|
62
|
+
end
|
86
63
|
|
87
|
-
|
88
|
-
|
64
|
+
def freeze!
|
65
|
+
@lock.synchronize do
|
66
|
+
@functions.each_value(&:freeze)
|
67
|
+
@functions.freeze
|
68
|
+
@frozen = true
|
69
|
+
end
|
70
|
+
end
|
89
71
|
|
90
|
-
|
91
|
-
|
92
|
-
|
72
|
+
def frozen?
|
73
|
+
@frozen
|
74
|
+
end
|
93
75
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
76
|
+
#
|
77
|
+
# Registration
|
78
|
+
#
|
79
|
+
# Unified entry point; used by both public and internal callers.
|
80
|
+
def register(name, fn_or = nil, **meta, &block)
|
81
|
+
fn = fn_or || block
|
82
|
+
raise ArgumentError, "block or Proc required" unless fn.is_a?(Proc)
|
83
|
+
|
84
|
+
defaults = {
|
85
|
+
arity: fn.arity,
|
86
|
+
param_types: [:any],
|
87
|
+
return_type: :any,
|
88
|
+
description: nil,
|
89
|
+
param_modes: nil,
|
90
|
+
reducer: false,
|
91
|
+
structure_function: false
|
92
|
+
}
|
93
|
+
register_with_metadata(name, fn, **defaults, **meta)
|
94
|
+
end
|
103
95
|
|
104
|
-
|
105
|
-
|
106
|
-
|
96
|
+
# Back-compat explicit API
|
97
|
+
def register_with_metadata(name, fn, arity:, param_types: [:any], return_type: :any,
|
98
|
+
description: nil, param_modes: nil, reducer: false,
|
99
|
+
structure_function: false)
|
100
|
+
@lock.synchronize do
|
101
|
+
raise FrozenError, "registry is frozen" if @frozen
|
102
|
+
raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
|
103
|
+
|
104
|
+
@functions[name] = Entry.new(
|
105
|
+
fn: fn,
|
106
|
+
arity: arity,
|
107
|
+
param_types: param_types,
|
108
|
+
return_type: return_type,
|
109
|
+
description: description,
|
110
|
+
param_modes: param_modes,
|
111
|
+
reducer: reducer,
|
112
|
+
structure_function: structure_function
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
107
116
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
117
|
+
#
|
118
|
+
# Queries
|
119
|
+
#
|
120
|
+
def function?(name)
|
121
|
+
@functions.key?(name)
|
122
|
+
end
|
123
|
+
alias supported? function?
|
112
124
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
125
|
+
def operator?(name)
|
126
|
+
name.is_a?(Symbol) && function?(name) && CORE_OPERATORS.include?(name)
|
127
|
+
end
|
117
128
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
129
|
+
def entry(name)
|
130
|
+
@functions[name]
|
131
|
+
end
|
122
132
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
133
|
+
def fetch(name)
|
134
|
+
ent = entry(name)
|
135
|
+
raise Kumi::Errors::UnknownFunction, "Unknown function: #{name}" unless ent
|
127
136
|
|
128
|
-
|
129
|
-
|
130
|
-
end
|
137
|
+
ent.fn
|
138
|
+
end
|
131
139
|
|
132
|
-
|
133
|
-
|
134
|
-
|
140
|
+
def signature(name)
|
141
|
+
ent = entry(name) or raise Kumi::Errors::UnknownFunction, "Unknown function: #{name}"
|
142
|
+
{ arity: ent.arity, param_types: ent.param_types, return_type: ent.return_type, description: ent.description }
|
143
|
+
end
|
135
144
|
|
136
|
-
|
137
|
-
|
138
|
-
|
145
|
+
def reducer?(name)
|
146
|
+
ent = entry(name)
|
147
|
+
ent ? !!ent.reducer : false
|
148
|
+
end
|
139
149
|
|
140
|
-
|
141
|
-
|
142
|
-
|
150
|
+
def structure_function?(name)
|
151
|
+
ent = entry(name)
|
152
|
+
ent ? !!ent.structure_function : false
|
153
|
+
end
|
143
154
|
|
144
|
-
|
145
|
-
|
146
|
-
|
155
|
+
def all_functions
|
156
|
+
@functions.keys
|
157
|
+
end
|
158
|
+
alias all all_functions
|
147
159
|
|
148
|
-
|
149
|
-
|
150
|
-
|
160
|
+
def functions
|
161
|
+
@functions.dup
|
162
|
+
end
|
151
163
|
|
152
|
-
|
153
|
-
|
164
|
+
# Introspection helpers
|
165
|
+
def comparison_operators = ComparisonFunctions.definitions.keys
|
166
|
+
def math_operations = MathFunctions.definitions.keys
|
167
|
+
def string_operations = StringFunctions.definitions.keys
|
168
|
+
def logical_operations = LogicalFunctions.definitions.keys
|
169
|
+
def collection_operations = CollectionFunctions.definitions.keys
|
170
|
+
def conditional_operations = ConditionalFunctions.definitions.keys
|
171
|
+
def type_operations = TypeFunctions.definitions.keys
|
172
|
+
def stat_operations = StatFunctions.definitions.keys
|
154
173
|
end
|
155
174
|
end
|
156
175
|
end
|
157
176
|
end
|
158
|
-
# end
|
@@ -39,7 +39,7 @@ module Kumi
|
|
39
39
|
end
|
40
40
|
|
41
41
|
private_class_method def self.should_validate_type?(meta)
|
42
|
-
meta[:type] && meta[:type] != :any
|
42
|
+
meta[:type] && meta[:type] != :any && !(meta[:children] && !meta[:children].empty?)
|
43
43
|
end
|
44
44
|
|
45
45
|
private_class_method def self.should_validate_domain?(meta)
|