kumi 0.0.0 → 0.0.3

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +113 -3
  3. data/CHANGELOG.md +21 -1
  4. data/CLAUDE.md +387 -0
  5. data/README.md +257 -20
  6. data/docs/development/README.md +120 -0
  7. data/docs/development/error-reporting.md +361 -0
  8. data/documents/AST.md +126 -0
  9. data/documents/DSL.md +154 -0
  10. data/documents/FUNCTIONS.md +132 -0
  11. data/documents/SYNTAX.md +367 -0
  12. data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +106 -0
  13. data/examples/federal_tax_calculator_2024.rb +112 -0
  14. data/examples/wide_schema_compilation_and_evaluation_benchmark.rb +80 -0
  15. data/lib/generators/trait_engine/templates/schema_spec.rb.erb +27 -0
  16. data/lib/kumi/analyzer/constant_evaluator.rb +51 -0
  17. data/lib/kumi/analyzer/passes/definition_validator.rb +42 -0
  18. data/lib/kumi/analyzer/passes/dependency_resolver.rb +71 -0
  19. data/lib/kumi/analyzer/passes/input_collector.rb +55 -0
  20. data/lib/kumi/analyzer/passes/name_indexer.rb +24 -0
  21. data/lib/kumi/analyzer/passes/pass_base.rb +67 -0
  22. data/lib/kumi/analyzer/passes/toposorter.rb +72 -0
  23. data/lib/kumi/analyzer/passes/type_checker.rb +139 -0
  24. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +45 -0
  25. data/lib/kumi/analyzer/passes/type_inferencer.rb +125 -0
  26. data/lib/kumi/analyzer/passes/unsat_detector.rb +107 -0
  27. data/lib/kumi/analyzer/passes/visitor_pass.rb +41 -0
  28. data/lib/kumi/analyzer.rb +54 -0
  29. data/lib/kumi/atom_unsat_solver.rb +349 -0
  30. data/lib/kumi/compiled_schema.rb +41 -0
  31. data/lib/kumi/compiler.rb +127 -0
  32. data/lib/kumi/domain/enum_analyzer.rb +53 -0
  33. data/lib/kumi/domain/range_analyzer.rb +83 -0
  34. data/lib/kumi/domain/validator.rb +84 -0
  35. data/lib/kumi/domain/violation_formatter.rb +40 -0
  36. data/lib/kumi/domain.rb +8 -0
  37. data/lib/kumi/error_reporter.rb +164 -0
  38. data/lib/kumi/error_reporting.rb +95 -0
  39. data/lib/kumi/errors.rb +116 -0
  40. data/lib/kumi/evaluation_wrapper.rb +20 -0
  41. data/lib/kumi/explain.rb +282 -0
  42. data/lib/kumi/export/deserializer.rb +39 -0
  43. data/lib/kumi/export/errors.rb +12 -0
  44. data/lib/kumi/export/node_builders.rb +140 -0
  45. data/lib/kumi/export/node_registry.rb +38 -0
  46. data/lib/kumi/export/node_serializers.rb +156 -0
  47. data/lib/kumi/export/serializer.rb +23 -0
  48. data/lib/kumi/export.rb +33 -0
  49. data/lib/kumi/function_registry/collection_functions.rb +92 -0
  50. data/lib/kumi/function_registry/comparison_functions.rb +31 -0
  51. data/lib/kumi/function_registry/conditional_functions.rb +36 -0
  52. data/lib/kumi/function_registry/function_builder.rb +92 -0
  53. data/lib/kumi/function_registry/logical_functions.rb +42 -0
  54. data/lib/kumi/function_registry/math_functions.rb +72 -0
  55. data/lib/kumi/function_registry/string_functions.rb +54 -0
  56. data/lib/kumi/function_registry/type_functions.rb +51 -0
  57. data/lib/kumi/function_registry.rb +138 -0
  58. data/lib/kumi/input/type_matcher.rb +92 -0
  59. data/lib/kumi/input/validator.rb +52 -0
  60. data/lib/kumi/input/violation_creator.rb +50 -0
  61. data/lib/kumi/input.rb +8 -0
  62. data/lib/kumi/parser/build_context.rb +25 -0
  63. data/lib/kumi/parser/dsl.rb +12 -0
  64. data/lib/kumi/parser/dsl_cascade_builder.rb +125 -0
  65. data/lib/kumi/parser/expression_converter.rb +58 -0
  66. data/lib/kumi/parser/guard_rails.rb +43 -0
  67. data/lib/kumi/parser/input_builder.rb +94 -0
  68. data/lib/kumi/parser/input_proxy.rb +29 -0
  69. data/lib/kumi/parser/parser.rb +66 -0
  70. data/lib/kumi/parser/schema_builder.rb +172 -0
  71. data/lib/kumi/parser/sugar.rb +108 -0
  72. data/lib/kumi/schema.rb +49 -0
  73. data/lib/kumi/schema_instance.rb +43 -0
  74. data/lib/kumi/syntax/declarations.rb +23 -0
  75. data/lib/kumi/syntax/expressions.rb +30 -0
  76. data/lib/kumi/syntax/node.rb +46 -0
  77. data/lib/kumi/syntax/root.rb +12 -0
  78. data/lib/kumi/syntax/terminal_expressions.rb +27 -0
  79. data/lib/kumi/syntax.rb +9 -0
  80. data/lib/kumi/types/builder.rb +21 -0
  81. data/lib/kumi/types/compatibility.rb +86 -0
  82. data/lib/kumi/types/formatter.rb +24 -0
  83. data/lib/kumi/types/inference.rb +40 -0
  84. data/lib/kumi/types/normalizer.rb +70 -0
  85. data/lib/kumi/types/validator.rb +35 -0
  86. data/lib/kumi/types.rb +64 -0
  87. data/lib/kumi/version.rb +1 -1
  88. data/lib/kumi.rb +7 -3
  89. data/scripts/generate_function_docs.rb +59 -0
  90. data/test_impossible_cascade.rb +51 -0
  91. metadata +93 -10
  92. data/sig/kumi.rbs +0 -4
data/README.md CHANGED
@@ -1,39 +1,276 @@
1
- # Kumi
1
+ # Kumi
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ Kumi is a declarative rule‑and‑calculation DSL for Ruby that turns scattered business logic into a statically‑checked dependency graph.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/kumi`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ Every input, trait, and formula is compiled into a typed AST node, so the entire graph is explicit and introspectable.
6
6
 
7
- ## Installation
7
+ ## Example
8
8
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
9
+ **Instead of scattered logic:**
10
+ ```ruby
11
+ def calculate_loan_approval(credit_score, income, debt_ratio)
12
+ good_credit = credit_score >= 700
13
+ sufficient_income = income >= 50_000
14
+ low_debt = debt_ratio <= 0.3
15
+
16
+ if good_credit && sufficient_income && low_debt
17
+ { approved: true, rate: 3.5 }
18
+ else
19
+ { approved: false, rate: nil }
20
+ end
21
+ end
22
+ ```
10
23
 
11
- Install the gem and add to the application's Gemfile by executing:
24
+ **You can write:**
25
+ ```ruby
26
+ module LoanApproval
27
+ extend Kumi::Schema
12
28
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
29
+ schema do
30
+ input do
31
+ integer :credit_score
32
+ float :income
33
+ float :debt_to_income_ratio
34
+ end
35
+
36
+ trait :good_credit, input.credit_score >= 700
37
+ trait :sufficient_income, input.income >= 50_000
38
+ trait :low_debt, input.debt_to_income_ratio <= 0.3
39
+ trait :approved, good_credit & sufficient_income & low_debt
40
+
41
+ value :interest_rate do
42
+ on :approved, 3.5
43
+ base 0.0
44
+ end
45
+ end
46
+ end
47
+
48
+ runner = LoanApproval.from(credit_score: 750, income: 60_000, debt_to_income_ratio: 0.25)
49
+ puts runner[:approved] # => true
50
+ puts runner[:interest_rate] # => 3.5
15
51
  ```
16
52
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
53
+ This gets you:
54
+ - Static analysis catches impossible logic combinations at compile time
55
+ - Automatic dependency tracking prevents circular references
56
+ - Type safety with domain constraints (age: 0..150, status: %w[active inactive])
57
+ - Microsecond performance not much different than optimized pure ruby
58
+ - Introspectable - see exactly how any value was computed
18
59
 
19
- ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
60
+ ## Static Analysis & Safety
61
+
62
+ Kumi analyzes your rules to catch logical impossibilities:
63
+
64
+ ```ruby
65
+ module ImpossibleLogic
66
+ extend Kumi::Schema
67
+
68
+ schema do
69
+ input {} # No inputs needed
70
+
71
+ value :x, 100
72
+ trait :x_less_than_100, x < 100 # false: 100 < 100
73
+
74
+ value :y, x * 10 # 1000
75
+ trait :y_greater_than_1000, y > 1000 # false: 1000 > 1000
76
+
77
+ value :result do
78
+ # This is impossible
79
+ on :x_less_than_100 & :y_greater_than_1000, "Impossible!"
80
+ base "Default"
81
+ end
82
+ end
83
+ end
84
+
85
+ # Kumi::Errors::SemanticError: conjunction `x_less_than_100 AND y_greater_than_1000` is impossible
86
+ ```
87
+
88
+ Cycle detection:
89
+ ```ruby
90
+ module CircularDependency
91
+ extend Kumi::Schema
92
+
93
+ schema do
94
+ input { float :base }
95
+
96
+ # These create a circular dependency
97
+ value :monthly_rate, yearly_rate / 12
98
+ value :yearly_rate, monthly_rate * 12
99
+ end
100
+ end
101
+
102
+ # Kumi::Errors::SemanticError: cycle detected involving: monthly_rate → yearly_rate → monthly_rate
103
+ ```
104
+
105
+ ## Performance
106
+
107
+ Kumi has microsecond evaluation times through automatic memoization:
108
+
109
+ ### Deep Dependency Chains
110
+ ```
111
+ === Evaluation Performance (with Memoization) ===
112
+ eval 50-deep: 817,497 i/s (1.22 μs/i)
113
+ eval 100-deep: 509,567 i/s (1.96 μs/i)
114
+ eval 150-deep: 376,429 i/s (2.66 μs/i)
115
+ eval 200-deep: 282,243 i/s (3.54 μs/i)
116
+ ```
117
+
118
+ ### Wide Complex Schemas
119
+ ```
120
+ === Evaluation Performance (with Memoization) ===
121
+ eval 1,000-wide: 127,652 i/s (7.83 μs/i)
122
+ eval 5,000-wide: 26,604 i/s (37.59 μs/i)
123
+ eval 10,000-wide: 13,670 i/s (73.15 μs/i)
124
+ ```
125
+
126
+ Here's how the memoization works:
127
+ ```ruby
128
+ module ProductPricing
129
+ extend Kumi::Schema
130
+
131
+ schema do
132
+ input do
133
+ float :base_price
134
+ float :tax_rate
135
+ integer :quantity
136
+ end
137
+
138
+ value :unit_price_with_tax, input.base_price * (1 + input.tax_rate)
139
+ value :total_before_discount, unit_price_with_tax * input.quantity
140
+ value :bulk_discount, input.quantity >= 10 ? 0.1 : 0.0
141
+ value :final_total, total_before_discount * (1 - bulk_discount)
142
+ end
143
+ end
144
+
145
+ runner = ProductPricing.from(base_price: 100.0, tax_rate: 0.08, quantity: 15)
146
+
147
+ # First access: computes and caches all intermediate values
148
+ puts runner[:final_total] # => 1458.0 (computed + cached)
149
+
150
+ # Subsequent accesses: pure cache lookups (microsecond performance)
151
+ puts runner[:unit_price_with_tax] # => 108.0 (from cache)
152
+ puts runner[:bulk_discount] # => 0.1 (from cache)
153
+ puts runner[:final_total] # => 1458.0 (from cache)
154
+ ```
155
+
156
+ Architecture notes:
157
+ - Compile-once, evaluate-many: Schema compilation happens once, evaluations are pure computation
158
+ - `EvaluationWrapper` caches computed values automatically for subsequent access
159
+ - Stack-safe algorithms: Iterative cycle detection and dependency resolution prevent stack overflow
160
+ - Type-safe execution: No runtime type checking overhead after compilation
161
+
162
+ ## DSL Features
163
+
164
+ ### Domain Constraints
165
+ ```ruby
166
+ module UserProfile
167
+ extend Kumi::Schema
168
+
169
+ schema do
170
+ input do
171
+ integer :age, domain: 0..150
172
+ string :status, domain: %w[active inactive suspended]
173
+ float :score, domain: 0.0..100.0
174
+ end
175
+
176
+ trait :adult, input.age >= 18
177
+ trait :active_user, input.status == "active"
178
+ end
179
+ end
180
+
181
+ # Valid data works fine
182
+ UserProfile.from(age: 25, status: "active", score: 85.5)
183
+
184
+ # Invalid data raises detailed errors
185
+ UserProfile.from(age: 200, status: "invalid", score: -10)
186
+ # => Kumi::Errors::DomainViolationError: Domain constraint violations...
21
187
  ```
22
188
 
23
- ## Usage
189
+ ### Cascade Logic
190
+ ```ruby
191
+ module ShippingCost
192
+ extend Kumi::Schema
24
193
 
25
- TODO: Write usage instructions here
194
+ schema do
195
+ input do
196
+ float :order_total
197
+ string :membership_level
198
+ end
26
199
 
27
- ## Development
200
+ trait :premium_member, input.membership_level == "premium"
201
+ trait :large_order, input.order_total >= 100
28
202
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
203
+ value :shipping_cost do
204
+ on :premium_member, 0.0
205
+ on :large_order, 5.0
206
+ base 15.0
207
+ end
208
+ end
209
+ end
30
210
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
211
+ runner = ShippingCost.from(order_total: 75, membership_level: "standard")
212
+ puts runner[:shipping_cost] # => 15.0
213
+ ```
32
214
 
33
- ## Contributing
215
+ ### Functions
216
+ ```ruby
217
+ module Statistics
218
+ extend Kumi::Schema
34
219
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/kumi.
220
+ schema do
221
+ input do
222
+ array :scores, elem: { type: :float }
223
+ end
224
+
225
+ value :total, fn(:sum, input.scores)
226
+ value :count, fn(:size, input.scores)
227
+ value :average, total / count
228
+ value :max_score, fn(:max, input.scores)
229
+ end
230
+ end
231
+
232
+ runner = Statistics.from(scores: [85.5, 92.0, 78.5, 96.0])
233
+ puts runner[:average] # => 88.0
234
+ puts runner[:max_score] # => 96.0
235
+ ```
236
+
237
+ ## Introspection
238
+
239
+ You can see exactly how any value was computed:
240
+
241
+ ```ruby
242
+ module TaxCalculator
243
+ extend Kumi::Schema
244
+
245
+ schema do
246
+ input do
247
+ float :income
248
+ float :tax_rate
249
+ float :deductions
250
+ end
251
+
252
+ value :taxable_income, input.income - input.deductions
253
+ value :tax_amount, taxable_income * input.tax_rate
254
+ end
255
+ end
256
+
257
+ inputs = { income: 100_000, tax_rate: 0.25, deductions: 12_000 }
258
+
259
+ puts Kumi::Explain.call(TaxCalculator, :taxable_income, inputs: inputs)
260
+ # => taxable_income = input.income - deductions = (input.income = 100 000) - (deductions = 12 000) => 88 000
261
+
262
+ puts Kumi::Explain.call(TaxCalculator, :tax_amount, inputs: inputs)
263
+ # => tax_amount = taxable_income × input.tax_rate = (taxable_income = 88 000) × (input.tax_rate = 0.25) => 22 000
264
+ ```
265
+
266
+ ## Try It Yourself
267
+
268
+ Run the performance benchmarks:
269
+ ```bash
270
+ bundle exec ruby examples/wide_schema_compilation_and_evaluation_benchmark.rb
271
+ bundle exec ruby examples/deep_schema_compilation_and_evaluation_benchmark.rb
272
+ ```
36
273
 
37
- ## License
274
+ ## DSL Syntax Reference
38
275
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
276
+ See [`documents/SYNTAX.md`](documents/SYNTAX.md) for complete syntax documentation with sugar vs sugar-free examples.
@@ -0,0 +1,120 @@
1
+ # Development Guides
2
+
3
+ This directory contains detailed guides for developing and maintaining Kumi. These guides complement the high-level information in the main `CLAUDE.md` file.
4
+
5
+ ## Guide Index
6
+
7
+ ### Architecture & Design
8
+ - [Error Reporting Standards](error-reporting.md) - Comprehensive guide to unified error reporting
9
+ - [Analyzer Pass Development](analyzer-passes.md) - How to create new analyzer passes
10
+ - [Type System Integration](type-system.md) - Working with Kumi's type inference and checking
11
+
12
+ ### Code Quality & Standards
13
+ - [Testing Standards](testing-standards.md) - Testing patterns and requirements
14
+ - [Code Organization](code-organization.md) - File structure and class design patterns
15
+ - [RuboCop Guidelines](rubocop-guidelines.md) - Code style and quality requirements
16
+
17
+ ### Common Tasks
18
+ - [Adding New Functions](adding-functions.md) - Extending the FunctionRegistry
19
+ - [DSL Extension Patterns](dsl-extensions.md) - Adding new DSL constructs
20
+ - [Performance Considerations](performance.md) - Guidelines for maintaining performance
21
+
22
+ ### Integration & Compatibility
23
+ - [Backward Compatibility](backward-compatibility.md) - Maintaining compatibility during changes
24
+ - [Migration Patterns](migration-patterns.md) - Safe patterns for evolving APIs
25
+ - [Zeitwerk Integration](zeitwerk.md) - Autoloading patterns and requirements
26
+
27
+ ## Quick Reference
28
+
29
+ ### Key Principles
30
+ 1. **Unified Error Reporting**: All errors must provide clear location information
31
+ 2. **Multi-pass Analysis**: Each analyzer pass has single responsibility
32
+ 3. **Backward Compatibility**: Changes maintain existing API compatibility
33
+ 4. **Type Safety**: Optional but comprehensive type checking
34
+ 5. **Ruby Integration**: Leverage Ruby idioms while maintaining structure
35
+
36
+ ### Common Commands
37
+ ```bash
38
+ # Run all tests
39
+ bundle exec rspec
40
+
41
+ # Run specific test categories
42
+ bundle exec rspec spec/integration/
43
+ bundle exec rspec spec/kumi/analyzer/
44
+
45
+ # Check code quality
46
+ bundle exec rubocop
47
+ bundle exec rubocop -a
48
+
49
+ # Validate error reporting
50
+ bundle exec ruby test_location_improvements.rb
51
+ ```
52
+
53
+ ### File Templates
54
+
55
+ **New Analyzer Pass**:
56
+ ```ruby
57
+ # frozen_string_literal: true
58
+
59
+ module Kumi
60
+ module Analyzer
61
+ module Passes
62
+ class MyNewPass < PassBase
63
+ def run(errors)
64
+ # Implementation with proper error reporting
65
+ report_error(errors, "message", location: node.loc, type: :semantic)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ ```
72
+
73
+ **New Integration Test**:
74
+ ```ruby
75
+ # frozen_string_literal: true
76
+
77
+ RSpec.describe "My Feature Integration" do
78
+ it "validates the feature works correctly" do
79
+ schema = build_schema do
80
+ input { integer :field }
81
+ value :result, input.field * 2
82
+ end
83
+
84
+ expect(schema.from(field: 5).fetch(:result)).to eq(10)
85
+ end
86
+ end
87
+ ```
88
+
89
+ ## Contributing Guidelines
90
+
91
+ ### Before Making Changes
92
+ 1. Check relevant development guide in this directory
93
+ 2. Review `CLAUDE.md` for high-level architecture understanding
94
+ 3. Run existing tests to ensure baseline functionality
95
+ 4. Consider backward compatibility implications
96
+
97
+ ### After Making Changes
98
+ 1. Update relevant development guides if patterns change
99
+ 2. Add or update tests for new functionality
100
+ 3. Run full test suite: `bundle exec rspec`
101
+ 4. Check code quality: `bundle exec rubocop`
102
+ 5. Verify error reporting quality with integration tests
103
+
104
+ ### Adding New Guides
105
+ When adding new development guides:
106
+ 1. Create focused, actionable guides for specific development tasks
107
+ 2. Include code examples and common patterns
108
+ 3. Reference related files and tests
109
+ 4. Update this README index
110
+ 5. Cross-reference from main `CLAUDE.md` if needed
111
+
112
+ ## Guide Maintenance
113
+
114
+ These guides should be kept up-to-date as the codebase evolves:
115
+ - **Review quarterly** for accuracy and completeness
116
+ - **Update immediately** when patterns or APIs change significantly
117
+ - **Expand based on common questions** during development
118
+ - **Consolidate** overlapping or redundant information
119
+
120
+ The goal is to make Kumi development efficient and consistent while maintaining high code quality.