kumi 0.0.7 → 0.0.9
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/CLAUDE.md +1 -1
- data/README.md +21 -5
- data/docs/AST.md +7 -0
- data/docs/features/README.md +7 -0
- data/docs/features/s-expression-printer.md +77 -0
- data/examples/game_of_life.rb +1 -1
- data/examples/static_analysis_errors.rb +7 -7
- data/lib/kumi/analyzer.rb +15 -15
- data/lib/kumi/compiler.rb +6 -6
- data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
- data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
- data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
- data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
- data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
- data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
- data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
- data/lib/kumi/core/atom_unsat_solver.rb +396 -0
- data/lib/kumi/core/compiled_schema.rb +43 -0
- data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
- data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
- data/lib/kumi/core/domain/range_analyzer.rb +85 -0
- data/lib/kumi/core/domain/validator.rb +82 -0
- data/lib/kumi/core/domain/violation_formatter.rb +42 -0
- data/lib/kumi/core/error_reporter.rb +166 -0
- data/lib/kumi/core/error_reporting.rb +97 -0
- data/lib/kumi/core/errors.rb +120 -0
- data/lib/kumi/core/evaluation_wrapper.rb +40 -0
- data/lib/kumi/core/explain.rb +295 -0
- data/lib/kumi/core/export/deserializer.rb +41 -0
- data/lib/kumi/core/export/errors.rb +14 -0
- data/lib/kumi/core/export/node_builders.rb +142 -0
- data/lib/kumi/core/export/node_registry.rb +54 -0
- data/lib/kumi/core/export/node_serializers.rb +158 -0
- data/lib/kumi/core/export/serializer.rb +25 -0
- data/lib/kumi/core/export.rb +35 -0
- data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
- data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
- data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
- data/lib/kumi/core/function_registry/function_builder.rb +95 -0
- data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
- data/lib/kumi/core/function_registry/math_functions.rb +74 -0
- data/lib/kumi/core/function_registry/string_functions.rb +57 -0
- data/lib/kumi/core/function_registry/type_functions.rb +53 -0
- data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
- data/lib/kumi/core/input/type_matcher.rb +97 -0
- data/lib/kumi/core/input/validator.rb +51 -0
- data/lib/kumi/core/input/violation_creator.rb +52 -0
- data/lib/kumi/core/json_schema/generator.rb +65 -0
- data/lib/kumi/core/json_schema/validator.rb +27 -0
- data/lib/kumi/core/json_schema.rb +16 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
- data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
- data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
- data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
- data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
- data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
- data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
- data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
- data/lib/kumi/core/ruby_parser/parser.rb +71 -0
- data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
- data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
- data/lib/kumi/core/ruby_parser.rb +12 -0
- data/lib/kumi/core/schema_instance.rb +111 -0
- data/lib/kumi/core/types/builder.rb +23 -0
- data/lib/kumi/core/types/compatibility.rb +96 -0
- data/lib/kumi/core/types/formatter.rb +26 -0
- data/lib/kumi/core/types/inference.rb +42 -0
- data/lib/kumi/core/types/normalizer.rb +72 -0
- data/lib/kumi/core/types/validator.rb +37 -0
- data/lib/kumi/core/types.rb +66 -0
- data/lib/kumi/core/vectorization_metadata.rb +110 -0
- data/lib/kumi/errors.rb +1 -112
- data/lib/kumi/registry.rb +37 -0
- data/lib/kumi/schema.rb +5 -5
- data/lib/kumi/schema_metadata.rb +3 -3
- data/lib/kumi/support/s_expression_printer.rb +161 -0
- data/lib/kumi/syntax/array_expression.rb +6 -6
- data/lib/kumi/syntax/call_expression.rb +4 -4
- data/lib/kumi/syntax/cascade_expression.rb +4 -4
- data/lib/kumi/syntax/case_expression.rb +4 -4
- data/lib/kumi/syntax/declaration_reference.rb +4 -4
- data/lib/kumi/syntax/hash_expression.rb +4 -4
- data/lib/kumi/syntax/input_declaration.rb +5 -5
- data/lib/kumi/syntax/input_element_reference.rb +5 -5
- data/lib/kumi/syntax/input_reference.rb +5 -5
- data/lib/kumi/syntax/literal.rb +4 -4
- data/lib/kumi/syntax/node.rb +34 -34
- data/lib/kumi/syntax/root.rb +6 -6
- data/lib/kumi/syntax/trait_declaration.rb +4 -4
- data/lib/kumi/syntax/value_declaration.rb +4 -4
- data/lib/kumi/version.rb +1 -1
- data/migrate_to_core_iterative.rb +938 -0
- data/scripts/generate_function_docs.rb +9 -9
- metadata +77 -72
- data/lib/kumi/analyzer/analysis_state.rb +0 -37
- data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
- data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -246
- data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
- data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
- data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
- data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -109
- data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
- data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
- data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
- data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -404
- data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
- data/lib/kumi/atom_unsat_solver.rb +0 -394
- data/lib/kumi/compiled_schema.rb +0 -41
- data/lib/kumi/constraint_relationship_solver.rb +0 -638
- data/lib/kumi/domain/enum_analyzer.rb +0 -53
- data/lib/kumi/domain/range_analyzer.rb +0 -83
- data/lib/kumi/domain/validator.rb +0 -80
- data/lib/kumi/domain/violation_formatter.rb +0 -40
- data/lib/kumi/error_reporter.rb +0 -164
- data/lib/kumi/error_reporting.rb +0 -95
- data/lib/kumi/evaluation_wrapper.rb +0 -38
- data/lib/kumi/explain.rb +0 -293
- data/lib/kumi/export/deserializer.rb +0 -39
- data/lib/kumi/export/errors.rb +0 -12
- data/lib/kumi/export/node_builders.rb +0 -140
- data/lib/kumi/export/node_registry.rb +0 -52
- data/lib/kumi/export/node_serializers.rb +0 -156
- data/lib/kumi/export/serializer.rb +0 -23
- data/lib/kumi/export.rb +0 -33
- data/lib/kumi/function_registry/collection_functions.rb +0 -200
- data/lib/kumi/function_registry/comparison_functions.rb +0 -31
- data/lib/kumi/function_registry/conditional_functions.rb +0 -36
- data/lib/kumi/function_registry/function_builder.rb +0 -93
- data/lib/kumi/function_registry/logical_functions.rb +0 -42
- data/lib/kumi/function_registry/math_functions.rb +0 -72
- data/lib/kumi/function_registry/string_functions.rb +0 -54
- data/lib/kumi/function_registry/type_functions.rb +0 -51
- data/lib/kumi/input/type_matcher.rb +0 -95
- data/lib/kumi/input/validator.rb +0 -49
- data/lib/kumi/input/violation_creator.rb +0 -50
- data/lib/kumi/json_schema/generator.rb +0 -63
- data/lib/kumi/json_schema/validator.rb +0 -25
- data/lib/kumi/json_schema.rb +0 -14
- data/lib/kumi/ruby_parser/build_context.rb +0 -25
- data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
- data/lib/kumi/ruby_parser/dsl.rb +0 -12
- data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
- data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
- data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
- data/lib/kumi/ruby_parser/input_builder.rb +0 -125
- data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
- data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
- data/lib/kumi/ruby_parser/nested_input.rb +0 -15
- data/lib/kumi/ruby_parser/parser.rb +0 -69
- data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
- data/lib/kumi/ruby_parser/sugar.rb +0 -261
- data/lib/kumi/ruby_parser.rb +0 -10
- data/lib/kumi/schema_instance.rb +0 -109
- data/lib/kumi/types/builder.rb +0 -21
- data/lib/kumi/types/compatibility.rb +0 -94
- data/lib/kumi/types/formatter.rb +0 -24
- data/lib/kumi/types/inference.rb +0 -40
- data/lib/kumi/types/normalizer.rb +0 -70
- data/lib/kumi/types/validator.rb +0 -35
- data/lib/kumi/types.rb +0 -64
- data/lib/kumi/vectorization_metadata.rb +0 -108
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Export
|
6
|
+
module NodeSerializers
|
7
|
+
# Root node: top-level container
|
8
|
+
def serialize_root(node)
|
9
|
+
{
|
10
|
+
type: "root",
|
11
|
+
inputs: node.inputs.map { |input| serialize_node(input) },
|
12
|
+
attributes: node.attributes.map { |attr| serialize_node(attr) },
|
13
|
+
traits: node.traits.map { |trait| serialize_node(trait) }
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Field Declaration: preserves type info for analyzer
|
18
|
+
def serialize_field_declaration(node)
|
19
|
+
{
|
20
|
+
name: node.name.to_s,
|
21
|
+
name_type: node.name.class.name,
|
22
|
+
field_type: serialize_type(node.type),
|
23
|
+
domain: serialize_domain(node.domain)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Attribute Declaration: preserves name and expression tree
|
28
|
+
def serialize_attribute_declaration(node)
|
29
|
+
{
|
30
|
+
name: node.name.to_s,
|
31
|
+
name_type: node.name.class.name,
|
32
|
+
expression: serialize_node(node.expression)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Trait Declaration: preserves name and expression tree
|
37
|
+
def serialize_trait_declaration(node)
|
38
|
+
{
|
39
|
+
name: node.name.to_s,
|
40
|
+
name_type: node.name.class.name,
|
41
|
+
expression: serialize_node(node.expression)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Call Expression: critical for dependency analysis
|
46
|
+
def serialize_call_expression(node)
|
47
|
+
{
|
48
|
+
function_name: node.fn_name.to_s,
|
49
|
+
function_name_type: node.fn_name.class.name,
|
50
|
+
arguments: node.args.map { |arg| serialize_node(arg) }
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Literal: preserve exact value and Ruby type
|
55
|
+
def serialize_literal(node)
|
56
|
+
{
|
57
|
+
value: node.value,
|
58
|
+
ruby_type: node.value.class.name
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Field Reference: critical for dependency resolution
|
63
|
+
def serialize_field_reference(node)
|
64
|
+
{
|
65
|
+
field_name: node.name.to_s,
|
66
|
+
name_type: node.name.class.name
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# DeclarationReference Reference: critical for dependency resolution
|
71
|
+
def serialize_binding_reference(node)
|
72
|
+
{
|
73
|
+
binding_name: node.name.to_s,
|
74
|
+
name_type: node.name.class.name
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
# List Expression: preserve order and elements
|
79
|
+
def serialize_list_expression(node)
|
80
|
+
{
|
81
|
+
elements: node.elements.map { |element| serialize_node(element) }
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Cascade Expression: preserve condition/result pairs
|
86
|
+
def serialize_cascade_expression(node)
|
87
|
+
{
|
88
|
+
cases: node.cases.map { |case_node| serialize_node(case_node) }
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
# When Case Expression: individual case in cascade
|
93
|
+
def serialize_when_case_expression(node)
|
94
|
+
{
|
95
|
+
condition: serialize_node(node.condition),
|
96
|
+
result: serialize_node(node.result)
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def serialize_type(type)
|
103
|
+
case type
|
104
|
+
when Symbol
|
105
|
+
{ type: "symbol", value: type.to_s }
|
106
|
+
when Hash
|
107
|
+
if type.key?(:array)
|
108
|
+
{ type: "array", element_type: serialize_type(type[:array]) }
|
109
|
+
elsif type.key?(:hash)
|
110
|
+
{ type: "hash", key_type: serialize_type(type[:hash][0]), value_type: serialize_type(type[:hash][1]) }
|
111
|
+
else
|
112
|
+
{ type: "hash", value: type }
|
113
|
+
end
|
114
|
+
when String, Integer, Float, TrueClass, FalseClass, NilClass
|
115
|
+
{ type: "literal", value: type }
|
116
|
+
else
|
117
|
+
{ type: "unknown", value: type.to_s }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def serialize_domain(domain)
|
122
|
+
return nil unless domain
|
123
|
+
|
124
|
+
case domain
|
125
|
+
when Range
|
126
|
+
{ type: "range", min: domain.min, max: domain.max, exclude_end: domain.exclude_end? }
|
127
|
+
when Array
|
128
|
+
{ type: "array", values: domain }
|
129
|
+
else
|
130
|
+
{ type: "custom", value: domain.to_s }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def serialize_node(node)
|
135
|
+
type_name = NodeRegistry.type_name_for(node)
|
136
|
+
|
137
|
+
base_data = {
|
138
|
+
type: type_name,
|
139
|
+
**send("serialize_#{type_name}", node)
|
140
|
+
}
|
141
|
+
|
142
|
+
add_location_if_present(base_data, node) if @include_locations
|
143
|
+
base_data
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_location_if_present(data, node)
|
147
|
+
return unless node.respond_to?(:loc) && node.loc
|
148
|
+
|
149
|
+
data[:location] = {
|
150
|
+
line: node.loc.line,
|
151
|
+
column: node.loc.column,
|
152
|
+
file: node.loc.file
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Export
|
6
|
+
class Serializer
|
7
|
+
include NodeSerializers
|
8
|
+
|
9
|
+
def initialize(pretty: false, include_locations: false)
|
10
|
+
@pretty = pretty
|
11
|
+
@include_locations = include_locations
|
12
|
+
end
|
13
|
+
|
14
|
+
def serialize(syntax_root)
|
15
|
+
json_data = {
|
16
|
+
kumi_version: VERSION,
|
17
|
+
ast: serialize_root(syntax_root)
|
18
|
+
}
|
19
|
+
|
20
|
+
@pretty ? JSON.pretty_generate(json_data) : JSON.generate(json_data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Kumi
|
6
|
+
module Core
|
7
|
+
module Export
|
8
|
+
# Core interface - only depends on Syntax::Root
|
9
|
+
def self.to_json(syntax_root, **options)
|
10
|
+
Serializer.new(**options).serialize(syntax_root)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_json(json_string, **options)
|
14
|
+
Deserializer.new(**options).deserialize(json_string)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Convenience methods
|
18
|
+
def self.to_file(syntax_root, filepath, **options)
|
19
|
+
File.write(filepath, to_json(syntax_root, **options))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.from_file(filepath, **options)
|
23
|
+
from_json(File.read(filepath), **options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Validation without import
|
27
|
+
def self.valid?(json_string)
|
28
|
+
from_json(json_string)
|
29
|
+
true
|
30
|
+
rescue StandardError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# Collection manipulation and query functions
|
7
|
+
module CollectionFunctions
|
8
|
+
def self.definitions
|
9
|
+
{
|
10
|
+
# Collection queries (these are reducers - they reduce arrays to scalars)
|
11
|
+
empty?: FunctionBuilder.collection_unary(:empty?, "Check if collection is empty", :empty?, reducer: true),
|
12
|
+
size: FunctionBuilder.collection_unary(:size, "Get collection size", :size, return_type: :integer, reducer: true),
|
13
|
+
length: FunctionBuilder.collection_unary(:length, "Get collection length", :length, return_type: :integer, reducer: true),
|
14
|
+
|
15
|
+
# Element access
|
16
|
+
first: FunctionBuilder::Entry.new(
|
17
|
+
fn: lambda(&:first),
|
18
|
+
arity: 1,
|
19
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
20
|
+
return_type: :any,
|
21
|
+
description: "Get first element of collection",
|
22
|
+
reducer: true
|
23
|
+
),
|
24
|
+
|
25
|
+
last: FunctionBuilder::Entry.new(
|
26
|
+
fn: lambda(&:last),
|
27
|
+
arity: 1,
|
28
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
29
|
+
return_type: :any,
|
30
|
+
description: "Get last element of collection",
|
31
|
+
reducer: true
|
32
|
+
),
|
33
|
+
|
34
|
+
# Mathematical operations on collections
|
35
|
+
sum: FunctionBuilder::Entry.new(
|
36
|
+
fn: lambda(&:sum),
|
37
|
+
arity: 1,
|
38
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
39
|
+
return_type: :float,
|
40
|
+
description: "Sum all numeric elements in collection",
|
41
|
+
reducer: true
|
42
|
+
),
|
43
|
+
|
44
|
+
min: FunctionBuilder::Entry.new(
|
45
|
+
fn: lambda(&:min),
|
46
|
+
arity: 1,
|
47
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
48
|
+
return_type: :float,
|
49
|
+
description: "Find minimum value in numeric collection",
|
50
|
+
reducer: true
|
51
|
+
),
|
52
|
+
|
53
|
+
max: FunctionBuilder::Entry.new(
|
54
|
+
fn: lambda(&:max),
|
55
|
+
arity: 1,
|
56
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
57
|
+
return_type: :float,
|
58
|
+
description: "Find maximum value in numeric collection",
|
59
|
+
reducer: true
|
60
|
+
),
|
61
|
+
|
62
|
+
# Collection operations
|
63
|
+
include?: FunctionBuilder::Entry.new(
|
64
|
+
fn: ->(collection, element) { collection.include?(element) },
|
65
|
+
arity: 2,
|
66
|
+
param_types: [Kumi::Core::Types.array(:any), :any],
|
67
|
+
return_type: :boolean,
|
68
|
+
description: "Check if collection includes element"
|
69
|
+
),
|
70
|
+
|
71
|
+
reverse: FunctionBuilder::Entry.new(
|
72
|
+
fn: lambda(&:reverse),
|
73
|
+
arity: 1,
|
74
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
75
|
+
return_type: Kumi::Core::Types.array(:any),
|
76
|
+
description: "Reverse collection order"
|
77
|
+
),
|
78
|
+
|
79
|
+
sort: FunctionBuilder::Entry.new(
|
80
|
+
fn: lambda(&:sort),
|
81
|
+
arity: 1,
|
82
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
83
|
+
return_type: Kumi::Core::Types.array(:any),
|
84
|
+
description: "Sort collection"
|
85
|
+
),
|
86
|
+
|
87
|
+
unique: FunctionBuilder::Entry.new(
|
88
|
+
fn: lambda(&:uniq),
|
89
|
+
arity: 1,
|
90
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
91
|
+
return_type: Kumi::Core::Types.array(:any),
|
92
|
+
description: "Remove duplicate elements from collection"
|
93
|
+
),
|
94
|
+
|
95
|
+
# Array transformation functions
|
96
|
+
flatten: FunctionBuilder::Entry.new(
|
97
|
+
fn: lambda(&:flatten),
|
98
|
+
arity: 1,
|
99
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
100
|
+
return_type: Kumi::Core::Types.array(:any),
|
101
|
+
description: "Flatten nested arrays into a single array"
|
102
|
+
),
|
103
|
+
|
104
|
+
# Mathematical transformation functions
|
105
|
+
map_multiply: FunctionBuilder::Entry.new(
|
106
|
+
fn: ->(collection, factor) { collection.map { |x| x * factor } },
|
107
|
+
arity: 2,
|
108
|
+
param_types: [Kumi::Core::Types.array(:float), :float],
|
109
|
+
return_type: Kumi::Core::Types.array(:float),
|
110
|
+
description: "Multiply each element by factor"
|
111
|
+
),
|
112
|
+
|
113
|
+
map_add: FunctionBuilder::Entry.new(
|
114
|
+
fn: ->(collection, value) { collection.map { |x| x + value } },
|
115
|
+
arity: 2,
|
116
|
+
param_types: [Kumi::Core::Types.array(:float), :float],
|
117
|
+
return_type: Kumi::Core::Types.array(:float),
|
118
|
+
description: "Add value to each element"
|
119
|
+
),
|
120
|
+
|
121
|
+
# Conditional transformation functions
|
122
|
+
map_conditional: FunctionBuilder::Entry.new(
|
123
|
+
fn: lambda { |collection, condition_value, true_value, false_value|
|
124
|
+
collection.map { |x| x == condition_value ? true_value : false_value }
|
125
|
+
},
|
126
|
+
arity: 4,
|
127
|
+
param_types: %i[array any any any],
|
128
|
+
return_type: :array,
|
129
|
+
description: "Transform elements based on condition: if element == condition_value then true_value else false_value"
|
130
|
+
),
|
131
|
+
|
132
|
+
# Range/index functions for grid operations
|
133
|
+
build_array: FunctionBuilder::Entry.new(
|
134
|
+
fn: lambda { |size, &generator|
|
135
|
+
(0...size).map { |i| generator ? generator.call(i) : i }
|
136
|
+
},
|
137
|
+
arity: 1,
|
138
|
+
param_types: [:integer],
|
139
|
+
return_type: Kumi::Core::Types.array(:any),
|
140
|
+
description: "Build array of given size with index values"
|
141
|
+
),
|
142
|
+
|
143
|
+
range: FunctionBuilder::Entry.new(
|
144
|
+
fn: ->(start, finish) { (start...finish).to_a },
|
145
|
+
arity: 2,
|
146
|
+
param_types: %i[integer integer],
|
147
|
+
return_type: Kumi::Core::Types.array(:integer),
|
148
|
+
description: "Generate range of integers from start to finish (exclusive)"
|
149
|
+
),
|
150
|
+
|
151
|
+
# Array slicing and grouping for rendering
|
152
|
+
each_slice: FunctionBuilder::Entry.new(
|
153
|
+
fn: ->(array, size) { array.each_slice(size).to_a },
|
154
|
+
arity: 2,
|
155
|
+
param_types: %i[array integer],
|
156
|
+
return_type: Kumi::Core::Types.array(:array),
|
157
|
+
description: "Group array elements into subarrays of given size"
|
158
|
+
),
|
159
|
+
|
160
|
+
join: FunctionBuilder::Entry.new(
|
161
|
+
fn: lambda { |array, separator = ""|
|
162
|
+
array.map(&:to_s).join(separator.to_s)
|
163
|
+
},
|
164
|
+
arity: 2,
|
165
|
+
param_types: %i[array string],
|
166
|
+
return_type: :string,
|
167
|
+
description: "Join array elements into string with separator"
|
168
|
+
),
|
169
|
+
|
170
|
+
# Transform each subarray to string and join the results
|
171
|
+
map_join_rows: FunctionBuilder::Entry.new(
|
172
|
+
fn: lambda { |array_of_arrays, row_separator = "", column_separator = "\n"|
|
173
|
+
array_of_arrays.map { |row| row.join(row_separator.to_s) }.join(column_separator.to_s)
|
174
|
+
},
|
175
|
+
arity: 3,
|
176
|
+
param_types: [Kumi::Core::Types.array(:array), :string, :string],
|
177
|
+
return_type: :string,
|
178
|
+
description: "Join 2D array into string with row and column separators"
|
179
|
+
),
|
180
|
+
|
181
|
+
# Higher-order collection functions (limited to common patterns)
|
182
|
+
map_with_index: FunctionBuilder::Entry.new(
|
183
|
+
fn: ->(collection) { collection.map.with_index.to_a },
|
184
|
+
arity: 1,
|
185
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
186
|
+
return_type: Kumi::Core::Types.array(:any),
|
187
|
+
description: "Map collection elements to [element, index] pairs"
|
188
|
+
),
|
189
|
+
|
190
|
+
indices: FunctionBuilder::Entry.new(
|
191
|
+
fn: ->(collection) { (0...collection.size).to_a },
|
192
|
+
arity: 1,
|
193
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
194
|
+
return_type: Kumi::Core::Types.array(:integer),
|
195
|
+
description: "Generate array of indices for the collection"
|
196
|
+
)
|
197
|
+
}
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# Comparison and equality functions
|
7
|
+
module ComparisonFunctions
|
8
|
+
def self.definitions
|
9
|
+
{
|
10
|
+
# Equality operators
|
11
|
+
:== => FunctionBuilder.equality(:==, "Equality comparison", :==),
|
12
|
+
:!= => FunctionBuilder.equality(:!=, "Inequality comparison", :!=),
|
13
|
+
|
14
|
+
# Comparison operators
|
15
|
+
:> => FunctionBuilder.comparison(:>, "Greater than comparison", :>),
|
16
|
+
:< => FunctionBuilder.comparison(:<, "Less than comparison", :<),
|
17
|
+
:>= => FunctionBuilder.comparison(:>=, "Greater than or equal comparison", :>=),
|
18
|
+
:<= => FunctionBuilder.comparison(:<=, "Less than or equal comparison", :<=),
|
19
|
+
|
20
|
+
# Range comparison
|
21
|
+
:between? => FunctionBuilder::Entry.new(
|
22
|
+
fn: ->(value, min, max) { value.between?(min, max) },
|
23
|
+
arity: 3,
|
24
|
+
param_types: %i[float float float],
|
25
|
+
return_type: :boolean,
|
26
|
+
description: "Check if value is between min and max"
|
27
|
+
)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# Conditional and control flow functions
|
7
|
+
module ConditionalFunctions
|
8
|
+
def self.definitions
|
9
|
+
{
|
10
|
+
conditional: FunctionBuilder::Entry.new(
|
11
|
+
fn: ->(condition, true_value, false_value) { condition ? true_value : false_value },
|
12
|
+
arity: 3,
|
13
|
+
param_types: %i[boolean any any],
|
14
|
+
return_type: :any,
|
15
|
+
description: "Ternary conditional operator"
|
16
|
+
),
|
17
|
+
|
18
|
+
if: FunctionBuilder::Entry.new(
|
19
|
+
fn: ->(condition, true_value, false_value = nil) { condition ? true_value : false_value },
|
20
|
+
arity: -1, # Variable arity (2 or 3)
|
21
|
+
param_types: %i[boolean any any],
|
22
|
+
return_type: :any,
|
23
|
+
description: "If-then-else conditional"
|
24
|
+
),
|
25
|
+
|
26
|
+
coalesce: FunctionBuilder::Entry.new(
|
27
|
+
fn: ->(*values) { values.find { |v| !v.nil? } },
|
28
|
+
arity: -1, # Variable arity
|
29
|
+
param_types: [:any],
|
30
|
+
return_type: :any,
|
31
|
+
description: "Return first non-nil value"
|
32
|
+
)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# Utility class to reduce repetition in function definitions
|
7
|
+
class FunctionBuilder
|
8
|
+
Entry = Struct.new(:fn, :arity, :param_types, :return_type, :description, :inverse, :reducer, keyword_init: true)
|
9
|
+
|
10
|
+
def self.comparison(_name, description, operation)
|
11
|
+
Entry.new(
|
12
|
+
fn: ->(a, b) { a.public_send(operation, b) },
|
13
|
+
arity: 2,
|
14
|
+
param_types: %i[float float],
|
15
|
+
return_type: :boolean,
|
16
|
+
description: description
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.equality(_name, description, operation)
|
21
|
+
Entry.new(
|
22
|
+
fn: ->(a, b) { a.public_send(operation, b) },
|
23
|
+
arity: 2,
|
24
|
+
param_types: %i[any any],
|
25
|
+
return_type: :boolean,
|
26
|
+
description: description
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.math_binary(_name, description, operation, return_type: :float)
|
31
|
+
Entry.new(
|
32
|
+
fn: lambda { |a, b|
|
33
|
+
a.public_send(operation, b)
|
34
|
+
},
|
35
|
+
arity: 2,
|
36
|
+
param_types: %i[float float],
|
37
|
+
return_type: return_type,
|
38
|
+
description: description
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.math_unary(_name, description, operation, return_type: :float)
|
43
|
+
Entry.new(
|
44
|
+
fn: proc(&operation),
|
45
|
+
arity: 1,
|
46
|
+
param_types: [:float],
|
47
|
+
return_type: return_type,
|
48
|
+
description: description
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.string_unary(_name, description, operation)
|
53
|
+
Entry.new(
|
54
|
+
fn: ->(str) { str.to_s.public_send(operation) },
|
55
|
+
arity: 1,
|
56
|
+
param_types: [:string],
|
57
|
+
return_type: :string,
|
58
|
+
description: description
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.string_binary(_name, description, operation, return_type: :string)
|
63
|
+
Entry.new(
|
64
|
+
fn: ->(str, arg) { str.to_s.public_send(operation, arg.to_s) },
|
65
|
+
arity: 2,
|
66
|
+
param_types: %i[string string],
|
67
|
+
return_type: return_type,
|
68
|
+
description: description
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.logical_variadic(_name, description, operation)
|
73
|
+
Entry.new(
|
74
|
+
fn: ->(conditions) { conditions.public_send(operation) },
|
75
|
+
arity: -1,
|
76
|
+
param_types: [:boolean],
|
77
|
+
return_type: :boolean,
|
78
|
+
description: description
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.collection_unary(_name, description, operation, return_type: :boolean, reducer: false)
|
83
|
+
Entry.new(
|
84
|
+
fn: proc(&operation),
|
85
|
+
arity: 1,
|
86
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
87
|
+
return_type: return_type,
|
88
|
+
description: description,
|
89
|
+
reducer: reducer
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# Logical operations and boolean functions
|
7
|
+
module LogicalFunctions
|
8
|
+
def self.definitions
|
9
|
+
{
|
10
|
+
# Basic logical operations
|
11
|
+
and: FunctionBuilder::Entry.new(
|
12
|
+
fn: ->(*conditions) { conditions.all? },
|
13
|
+
arity: -1,
|
14
|
+
param_types: [:boolean],
|
15
|
+
return_type: :boolean,
|
16
|
+
description: "Logical AND of multiple conditions"
|
17
|
+
),
|
18
|
+
|
19
|
+
or: FunctionBuilder::Entry.new(
|
20
|
+
fn: ->(*conditions) { conditions.any? },
|
21
|
+
arity: -1,
|
22
|
+
param_types: [:boolean],
|
23
|
+
return_type: :boolean,
|
24
|
+
description: "Logical OR of multiple conditions"
|
25
|
+
),
|
26
|
+
|
27
|
+
not: FunctionBuilder::Entry.new(
|
28
|
+
fn: lambda(&:!),
|
29
|
+
arity: 1,
|
30
|
+
param_types: [:boolean],
|
31
|
+
return_type: :boolean,
|
32
|
+
description: "Logical NOT"
|
33
|
+
),
|
34
|
+
|
35
|
+
# Collection logical operations
|
36
|
+
all?: FunctionBuilder.collection_unary(:all?, "Check if all elements in collection are truthy", :all?),
|
37
|
+
any?: FunctionBuilder.collection_unary(:any?, "Check if any element in collection is truthy", :any?),
|
38
|
+
none?: FunctionBuilder.collection_unary(:none?, "Check if no elements in collection are truthy", :none?)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|