kumi 0.0.0 → 0.0.4

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 +270 -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 +22 -0
  41. data/lib/kumi/explain.rb +281 -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,289 @@
1
- # Kumi
1
+ # Kumi
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ [![CI](https://github.com/amuta/kumi/workflows/CI/badge.svg)](https://github.com/amuta/kumi/actions)
4
+ [![Gem Version](https://badge.fury.io/rb/kumi.svg)](https://badge.fury.io/rb/kumi)
4
5
 
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.
6
+ Kumi is a declarative rule‑and‑calculation DSL for Ruby that turns scattered business logic into a statically‑checked dependency graph.
6
7
 
7
- ## Installation
8
+ Every input, trait, and formula is compiled into a typed AST node, so the entire graph is explicit and introspectable.
8
9
 
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.
10
+ Note: The examples here are small for the sake of readability. I would not recommend using this gem unless you need to keep track of 100+ conditions/variables.
10
11
 
11
- Install the gem and add to the application's Gemfile by executing:
12
12
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
13
+ ## How to get started
14
+
15
+ Install Kumi and try running the examples below or explore the `./examples` directory of this repository.
16
+ ```
17
+ gem install kumi
15
18
  ```
16
19
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
20
+ ## Example
18
21
 
19
- ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
22
+ **Instead of scattered logic:**
23
+ ```ruby
24
+ def calculate_loan_approval(credit_score, income, debt_ratio)
25
+ good_credit = credit_score >= 700
26
+ sufficient_income = income >= 50_000
27
+ low_debt = debt_ratio <= 0.3
28
+
29
+ if good_credit && sufficient_income && low_debt
30
+ { approved: true, rate: 3.5 }
31
+ else
32
+ { approved: false, rate: nil }
33
+ end
34
+ end
35
+ ```
36
+
37
+ **You can write:**
38
+ ```ruby
39
+ module LoanApproval
40
+ extend Kumi::Schema
41
+
42
+ schema do
43
+ input do
44
+ integer :credit_score
45
+ float :income
46
+ float :debt_to_income_ratio
47
+ end
48
+
49
+ trait :good_credit, input.credit_score >= 700
50
+ trait :sufficient_income, input.income >= 50_000
51
+ trait :low_debt, input.debt_to_income_ratio <= 0.3
52
+ trait :approved, good_credit & sufficient_income & low_debt
53
+
54
+ value :interest_rate do
55
+ on :approved, 3.5
56
+ base 0.0
57
+ end
58
+ end
59
+ end
60
+
61
+ runner = LoanApproval.from(credit_score: 750, income: 60_000, debt_to_income_ratio: 0.25)
62
+ puts runner[:approved] # => true
63
+ puts runner[:interest_rate] # => 3.5
21
64
  ```
22
65
 
23
- ## Usage
66
+ This gets you:
67
+ - Static analysis catches impossible logic combinations at compile time
68
+ - Automatic dependency tracking prevents circular references
69
+ - Type safety with domain constraints (age: 0..150, status: %w[active inactive])
70
+ - Microsecond performance not much different than optimized pure ruby
71
+ - Introspectable - see exactly how any value was computed
72
+
73
+ ## Static Analysis & Safety
74
+
75
+ Kumi analyzes your rules to catch logical impossibilities:
76
+
77
+ ```ruby
78
+ module ImpossibleLogic
79
+ extend Kumi::Schema
24
80
 
25
- TODO: Write usage instructions here
81
+ schema do
82
+ input {} # No inputs needed
83
+
84
+ value :x, 100
85
+ trait :x_less_than_100, x < 100 # false: 100 < 100
86
+
87
+ value :y, x * 10 # 1000
88
+ trait :y_greater_than_1000, y > 1000 # false: 1000 > 1000
89
+
90
+ value :result do
91
+ # This is impossible
92
+ on :x_less_than_100 & :y_greater_than_1000, "Impossible!"
93
+ base "Default"
94
+ end
95
+ end
96
+ end
97
+
98
+ # Kumi::Errors::SemanticError: conjunction `x_less_than_100 AND y_greater_than_1000` is impossible
99
+ ```
26
100
 
27
- ## Development
101
+ Cycle detection:
102
+ ```ruby
103
+ module CircularDependency
104
+ extend Kumi::Schema
28
105
 
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.
106
+ schema do
107
+ input { float :base }
30
108
 
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).
109
+ # These create a circular dependency
110
+ value :monthly_rate, yearly_rate / 12
111
+ value :yearly_rate, monthly_rate * 12
112
+ end
113
+ end
114
+
115
+ # Kumi::Errors::SemanticError: cycle detected involving: monthly_rate → yearly_rate → monthly_rate
116
+ ```
117
+
118
+ ## Performance
119
+
120
+ Kumi has microsecond evaluation times through automatic memoization:
121
+
122
+ ### Deep Dependency Chains
123
+ ```
124
+ === Evaluation Performance (with Memoization) ===
125
+ eval 50-deep: 817,497 i/s (1.22 μs/i)
126
+ eval 100-deep: 509,567 i/s (1.96 μs/i)
127
+ eval 150-deep: 376,429 i/s (2.66 μs/i)
128
+ eval 200-deep: 282,243 i/s (3.54 μs/i)
129
+ ```
130
+
131
+ ### Wide Complex Schemas
132
+ ```
133
+ === Evaluation Performance (with Memoization) ===
134
+ eval 1,000-wide: 127,652 i/s (7.83 μs/i)
135
+ eval 5,000-wide: 26,604 i/s (37.59 μs/i)
136
+ eval 10,000-wide: 13,670 i/s (73.15 μs/i)
137
+ ```
138
+
139
+ Here's how the memoization works:
140
+ ```ruby
141
+ module ProductPricing
142
+ extend Kumi::Schema
143
+
144
+ schema do
145
+ input do
146
+ float :base_price
147
+ float :tax_rate
148
+ integer :quantity
149
+ end
150
+
151
+ value :unit_price_with_tax, input.base_price * (1 + input.tax_rate)
152
+ value :total_before_discount, unit_price_with_tax * input.quantity
153
+ value :bulk_discount, input.quantity >= 10 ? 0.1 : 0.0
154
+ value :final_total, total_before_discount * (1 - bulk_discount)
155
+ end
156
+ end
157
+
158
+ runner = ProductPricing.from(base_price: 100.0, tax_rate: 0.08, quantity: 15)
159
+
160
+ # First access: computes and caches all intermediate values
161
+ puts runner[:final_total] # => 1458.0 (computed + cached)
162
+
163
+ # Subsequent accesses: pure cache lookups (microsecond performance)
164
+ puts runner[:unit_price_with_tax] # => 108.0 (from cache)
165
+ puts runner[:bulk_discount] # => 0.1 (from cache)
166
+ puts runner[:final_total] # => 1458.0 (from cache)
167
+ ```
32
168
 
33
- ## Contributing
169
+ Architecture notes:
170
+ - Compile-once, evaluate-many: Schema compilation happens once, evaluations are pure computation
171
+ - `EvaluationWrapper` caches computed values automatically for subsequent access
172
+ - Stack-safe algorithms: Iterative cycle detection and dependency resolution prevent stack overflow
173
+ - Type-safe execution: No runtime type checking overhead after compilation
34
174
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/kumi.
175
+ ## DSL Features
176
+
177
+ ### Domain Constraints
178
+ ```ruby
179
+ module UserProfile
180
+ extend Kumi::Schema
181
+
182
+ schema do
183
+ input do
184
+ integer :age, domain: 0..150
185
+ string :status, domain: %w[active inactive suspended]
186
+ float :score, domain: 0.0..100.0
187
+ end
188
+
189
+ trait :adult, input.age >= 18
190
+ trait :active_user, input.status == "active"
191
+ end
192
+ end
193
+
194
+ # Valid data works fine
195
+ UserProfile.from(age: 25, status: "active", score: 85.5)
196
+
197
+ # Invalid data raises detailed errors
198
+ UserProfile.from(age: 200, status: "invalid", score: -10)
199
+ # => Kumi::Errors::DomainViolationError: Domain constraint violations...
200
+ ```
201
+
202
+ ### Cascade Logic
203
+ ```ruby
204
+ module ShippingCost
205
+ extend Kumi::Schema
206
+
207
+ schema do
208
+ input do
209
+ float :order_total
210
+ string :membership_level
211
+ end
212
+
213
+ trait :premium_member, input.membership_level == "premium"
214
+ trait :large_order, input.order_total >= 100
215
+
216
+ value :shipping_cost do
217
+ on :premium_member, 0.0
218
+ on :large_order, 5.0
219
+ base 15.0
220
+ end
221
+ end
222
+ end
223
+
224
+ runner = ShippingCost.from(order_total: 75, membership_level: "standard")
225
+ puts runner[:shipping_cost] # => 15.0
226
+ ```
227
+
228
+ ### Functions
229
+ ```ruby
230
+ module Statistics
231
+ extend Kumi::Schema
232
+
233
+ schema do
234
+ input do
235
+ array :scores, elem: { type: :float }
236
+ end
237
+
238
+ value :total, fn(:sum, input.scores)
239
+ value :count, fn(:size, input.scores)
240
+ value :average, total / count
241
+ value :max_score, fn(:max, input.scores)
242
+ end
243
+ end
244
+
245
+ runner = Statistics.from(scores: [85.5, 92.0, 78.5, 96.0])
246
+ puts runner[:average] # => 88.0
247
+ puts runner[:max_score] # => 96.0
248
+ ```
249
+
250
+ ## Introspection
251
+
252
+ You can see exactly how any value was computed:
253
+
254
+ ```ruby
255
+ module TaxCalculator
256
+ extend Kumi::Schema
257
+
258
+ schema do
259
+ input do
260
+ float :income
261
+ float :tax_rate
262
+ float :deductions
263
+ end
264
+
265
+ value :taxable_income, input.income - input.deductions
266
+ value :tax_amount, taxable_income * input.tax_rate
267
+ end
268
+ end
269
+
270
+ inputs = { income: 100_000, tax_rate: 0.25, deductions: 12_000 }
271
+
272
+ puts Kumi::Explain.call(TaxCalculator, :taxable_income, inputs: inputs)
273
+ # => taxable_income = input.income - deductions = (input.income = 100 000) - (deductions = 12 000) => 88 000
274
+
275
+ puts Kumi::Explain.call(TaxCalculator, :tax_amount, inputs: inputs)
276
+ # => tax_amount = taxable_income × input.tax_rate = (taxable_income = 88 000) × (input.tax_rate = 0.25) => 22 000
277
+ ```
278
+
279
+ ## Try It Yourself
280
+
281
+ Run the performance benchmarks:
282
+ ```bash
283
+ bundle exec ruby examples/wide_schema_compilation_and_evaluation_benchmark.rb
284
+ bundle exec ruby examples/deep_schema_compilation_and_evaluation_benchmark.rb
285
+ ```
36
286
 
37
- ## License
287
+ ## DSL Syntax Reference
38
288
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
289
+ 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.