kumi 0.0.12 → 0.0.14
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/.rspec +0 -1
- data/BACKLOG.md +34 -0
- data/CHANGELOG.md +15 -0
- data/CLAUDE.md +4 -6
- data/README.md +0 -18
- data/config/functions.yaml +352 -0
- data/docs/dev/analyzer-debug.md +52 -0
- data/docs/dev/parse-command.md +64 -0
- data/docs/functions/analyzer_integration.md +199 -0
- data/docs/functions/signatures.md +171 -0
- data/examples/hash_objects_demo.rb +138 -0
- data/golden/array_operations/schema.kumi +17 -0
- data/golden/cascade_logic/schema.kumi +16 -0
- data/golden/mixed_nesting/schema.kumi +42 -0
- data/golden/simple_math/schema.kumi +10 -0
- data/lib/kumi/analyzer.rb +72 -21
- data/lib/kumi/core/analyzer/checkpoint.rb +72 -0
- data/lib/kumi/core/analyzer/debug.rb +167 -0
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +1 -3
- data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +199 -0
- data/lib/kumi/core/analyzer/passes/load_input_cse.rb +120 -0
- data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +99 -151
- data/lib/kumi/core/analyzer/passes/toposorter.rb +37 -1
- data/lib/kumi/core/analyzer/state_serde.rb +64 -0
- data/lib/kumi/core/analyzer/structs/access_plan.rb +12 -10
- data/lib/kumi/core/compiler/access_planner.rb +3 -2
- data/lib/kumi/core/function_registry/collection_functions.rb +3 -1
- data/lib/kumi/core/functions/dimension.rb +98 -0
- data/lib/kumi/core/functions/dtypes.rb +20 -0
- data/lib/kumi/core/functions/errors.rb +11 -0
- data/lib/kumi/core/functions/kernel_adapter.rb +45 -0
- data/lib/kumi/core/functions/loader.rb +119 -0
- data/lib/kumi/core/functions/registry_v2.rb +68 -0
- data/lib/kumi/core/functions/shape.rb +70 -0
- data/lib/kumi/core/functions/signature.rb +122 -0
- data/lib/kumi/core/functions/signature_parser.rb +86 -0
- data/lib/kumi/core/functions/signature_resolver.rb +272 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +98 -7
- data/lib/kumi/core/ir/execution_engine/profiler.rb +202 -0
- data/lib/kumi/core/ir/execution_engine.rb +30 -1
- data/lib/kumi/dev/ir.rb +75 -0
- data/lib/kumi/dev/parse.rb +105 -0
- data/lib/kumi/dev/runner.rb +83 -0
- data/lib/kumi/frontends/ruby.rb +28 -0
- data/lib/kumi/frontends/text.rb +46 -0
- data/lib/kumi/frontends.rb +29 -0
- data/lib/kumi/kernels/ruby/aggregate_core.rb +105 -0
- data/lib/kumi/kernels/ruby/datetime_scalar.rb +21 -0
- data/lib/kumi/kernels/ruby/mask_scalar.rb +15 -0
- data/lib/kumi/kernels/ruby/scalar_core.rb +63 -0
- data/lib/kumi/kernels/ruby/string_scalar.rb +19 -0
- data/lib/kumi/kernels/ruby/vector_struct.rb +39 -0
- data/lib/kumi/runtime/executable.rb +63 -20
- data/lib/kumi/schema.rb +4 -4
- data/lib/kumi/support/diff.rb +22 -0
- data/lib/kumi/support/ir_render.rb +61 -0
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +2 -0
- data/performance_results.txt +63 -0
- data/scripts/test_mixed_nesting_performance.rb +206 -0
- metadata +45 -5
- data/docs/features/javascript-transpiler.md +0 -148
- data/lib/kumi/js.rb +0 -23
- data/lib/kumi/support/ir_dump.rb +0 -491
@@ -0,0 +1,199 @@
|
|
1
|
+
# Function Signature Analysis Integration
|
2
|
+
|
3
|
+
This document describes how NEP-20 function signatures integrate with Kumi's multi-pass analyzer architecture.
|
4
|
+
|
5
|
+
## Architecture Overview
|
6
|
+
|
7
|
+
Function signature resolution is integrated into the analyzer pipeline through a node index system that allows passes to share metadata efficiently.
|
8
|
+
|
9
|
+
```
|
10
|
+
┌─────────────────┐ ┌──────────────────────┐ ┌────────────────────┐
|
11
|
+
│ Toposorter │───▶│ FunctionSignaturePass │───▶│ LowerToIRPass │
|
12
|
+
│ Creates node │ │ Resolves signatures │ │ Validates metadata │
|
13
|
+
│ index by ID │ │ Attaches metadata │ │ Emits IR ops │
|
14
|
+
└─────────────────┘ └──────────────────────┘ └────────────────────┘
|
15
|
+
```
|
16
|
+
|
17
|
+
## Node Index System
|
18
|
+
|
19
|
+
### Creation (Toposorter)
|
20
|
+
|
21
|
+
The `Toposorter` pass creates a comprehensive index of all nodes in the AST:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# State structure after Toposorter
|
25
|
+
state[:node_index] = {
|
26
|
+
object_id_1 => {
|
27
|
+
node: CallExpression_instance,
|
28
|
+
type: "CallExpression",
|
29
|
+
metadata: {}
|
30
|
+
},
|
31
|
+
# ... more nodes
|
32
|
+
}
|
33
|
+
```
|
34
|
+
|
35
|
+
### Population (FunctionSignaturePass)
|
36
|
+
|
37
|
+
The `FunctionSignaturePass` populates metadata for `CallExpression` nodes:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# After FunctionSignaturePass
|
41
|
+
entry[:metadata] = {
|
42
|
+
signature: Signature_object, # Full signature object
|
43
|
+
result_axes: [:i, :j], # Output dimension names
|
44
|
+
join_policy: :zip, # Cross-dimensional policy
|
45
|
+
dropped_axes: [:k], # Dimensions eliminated by reductions
|
46
|
+
effective_signature: { # Normalized for lowering
|
47
|
+
in_shapes: [[:i, :k], [:k, :j]],
|
48
|
+
out_shape: [:i, :j],
|
49
|
+
join_policy: :zip
|
50
|
+
},
|
51
|
+
dim_env: { i: :i, j: :j, k: :k }, # Dimension variable bindings
|
52
|
+
shape_contract: { # Simplified for lowering
|
53
|
+
in: [[:i, :k], [:k, :j]],
|
54
|
+
out: [:i, :j],
|
55
|
+
join: :zip
|
56
|
+
},
|
57
|
+
signature_score: 0 # Match quality (0 = exact)
|
58
|
+
}
|
59
|
+
```
|
60
|
+
|
61
|
+
### Consumption (LowerToIRPass)
|
62
|
+
|
63
|
+
The lowering pass validates and uses the signature metadata:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
def validate_signature_metadata(expr, entry)
|
67
|
+
node_index = get_state(:node_index)
|
68
|
+
metadata = node_index[expr.object_id][:metadata]
|
69
|
+
|
70
|
+
# Validate dropped axes for reductions
|
71
|
+
if entry&.reducer && metadata[:dropped_axes]
|
72
|
+
# Assert axis exists in scope
|
73
|
+
end
|
74
|
+
|
75
|
+
# Warn about join policies not yet implemented
|
76
|
+
if metadata[:join_policy]
|
77
|
+
# Log warning or feature flag check
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
## Pass Integration Points
|
83
|
+
|
84
|
+
### 1. Signature Resolution
|
85
|
+
|
86
|
+
**Location**: After `BroadcastDetector`, before `TypeChecker`
|
87
|
+
|
88
|
+
**Purpose**: Resolve NEP-20 signatures for all function calls
|
89
|
+
|
90
|
+
**Input**:
|
91
|
+
- Node index from Toposorter
|
92
|
+
- Broadcast metadata (optional)
|
93
|
+
- Current function registry
|
94
|
+
|
95
|
+
**Output**: Rich signature metadata attached to call nodes
|
96
|
+
|
97
|
+
### 2. Type Checking Enhancement
|
98
|
+
|
99
|
+
**Integration**: `TypeChecker` can now use signature metadata for enhanced validation:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
def validate_function_call(node, errors)
|
103
|
+
# Get resolved signature metadata
|
104
|
+
node_entry = node_index[node.object_id]
|
105
|
+
if node_entry && node_entry[:metadata][:signature]
|
106
|
+
# Use NEP-20 signature for validation
|
107
|
+
validate_nep20_signature(node, node_entry[:metadata], errors)
|
108
|
+
else
|
109
|
+
# Fall back to legacy registry-based validation
|
110
|
+
validate_legacy_signature(node, errors)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
### 3. Lowering Integration
|
116
|
+
|
117
|
+
**Integration**: `LowerToIRPass` validates signature consistency and emits appropriate IR:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
when Syntax::CallExpression
|
121
|
+
entry = Kumi::Registry.entry(expr.fn_name)
|
122
|
+
validate_signature_metadata(expr, entry) # Read-only validation
|
123
|
+
|
124
|
+
# Use shape contract for IR generation
|
125
|
+
if node_entry = node_index[expr.object_id]
|
126
|
+
contract = node_entry[:metadata][:shape_contract]
|
127
|
+
# Use contract to emit appropriate reduce/join ops
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
## Metadata Contract
|
132
|
+
|
133
|
+
All passes can rely on this metadata structure for `CallExpression` nodes:
|
134
|
+
|
135
|
+
| Field | Type | Description |
|
136
|
+
|-------|------|-------------|
|
137
|
+
| `:signature` | `Signature` | Full signature object with dimensions |
|
138
|
+
| `:result_axes` | `Array<Symbol>` | Output dimension names |
|
139
|
+
| `:join_policy` | `Symbol?` | `:zip`, `:product`, or `nil` |
|
140
|
+
| `:dropped_axes` | `Array<Symbol>` | Dimensions eliminated (reductions) |
|
141
|
+
| `:effective_signature` | `Hash` | Normalized signature for lowering |
|
142
|
+
| `:dim_env` | `Hash` | Dimension variable bindings |
|
143
|
+
| `:shape_contract` | `Hash` | Simplified contract for IR generation |
|
144
|
+
| `:signature_score` | `Integer` | Match quality (0 = exact) |
|
145
|
+
|
146
|
+
## Error Handling
|
147
|
+
|
148
|
+
Enhanced error messages include signature information:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
# Before
|
152
|
+
"operator `multiply` expects 2 args, got 3"
|
153
|
+
|
154
|
+
# After
|
155
|
+
"Signature mismatch for `multiply` with args (i,j), (k).
|
156
|
+
Candidates: (),()->() | (i),(i)->(i).
|
157
|
+
no matching signature for shapes (i,j), (k)"
|
158
|
+
```
|
159
|
+
|
160
|
+
## Feature Flags
|
161
|
+
|
162
|
+
Control NEP-20 behavior at runtime:
|
163
|
+
|
164
|
+
```bash
|
165
|
+
export KUMI_ENABLE_FLEX=1 # Enable flexible dimension matching (?)
|
166
|
+
export KUMI_ENABLE_BCAST1=1 # Enable broadcastable matching (|1)
|
167
|
+
```
|
168
|
+
|
169
|
+
## Future Extensions
|
170
|
+
|
171
|
+
The node index architecture supports future enhancements:
|
172
|
+
|
173
|
+
### Registry V2 Integration
|
174
|
+
```ruby
|
175
|
+
# Read signatures from YAML registry
|
176
|
+
signatures = RegistryV2.get_function_signatures(node.fn_name)
|
177
|
+
```
|
178
|
+
|
179
|
+
### Type System Integration
|
180
|
+
```ruby
|
181
|
+
# Add dtype metadata alongside shape metadata
|
182
|
+
metadata[:result_dtype] = infer_result_dtype(args, signature)
|
183
|
+
```
|
184
|
+
|
185
|
+
### Optimization Metadata
|
186
|
+
```ruby
|
187
|
+
# Add optimization hints
|
188
|
+
metadata[:can_vectorize] = true
|
189
|
+
metadata[:memory_access_pattern] = :sequential
|
190
|
+
```
|
191
|
+
|
192
|
+
## Performance Considerations
|
193
|
+
|
194
|
+
- **Node index creation**: O(n) where n = total AST nodes
|
195
|
+
- **Signature resolution**: O(k*m) where k = signatures, m = arguments
|
196
|
+
- **Memory overhead**: ~100 bytes per CallExpression node
|
197
|
+
- **Pass integration**: Zero performance impact on existing passes
|
198
|
+
|
199
|
+
The architecture is designed for extensibility while maintaining backward compatibility with the existing analyzer pipeline.
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# Function Signatures (NEP 20)
|
2
|
+
|
3
|
+
Kumi implements **NEP 20 conformant function signatures** for describing generalized universal functions. This system provides precise control over multidimensional operations with support for fixed-size, flexible, and broadcastable dimensions.
|
4
|
+
|
5
|
+
## Basic Syntax
|
6
|
+
|
7
|
+
Function signatures use the format: `<inputs> -> <outputs>[@policy]`
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
"(i),(i)->(i)" # Element-wise operation on vectors
|
11
|
+
"(m,n),(n,p)->(m,p)" # Matrix multiplication
|
12
|
+
"(i,j)->(i)" # Reduction along j axis
|
13
|
+
"(),()->()" # Scalar operation
|
14
|
+
```
|
15
|
+
|
16
|
+
## NEP 20 Extensions
|
17
|
+
|
18
|
+
### Fixed-Size Dimensions
|
19
|
+
|
20
|
+
Use integers to specify exact dimension sizes:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
"(3),(3)->(3)" # Cross product for 3-vectors
|
24
|
+
"()->(2)" # Function returning 2D vector
|
25
|
+
"(),()->(3)" # Two scalars to 3D vector
|
26
|
+
```
|
27
|
+
|
28
|
+
Fixed-size dimensions must match exactly - no broadcasting allowed.
|
29
|
+
|
30
|
+
### Flexible Dimensions (?)
|
31
|
+
|
32
|
+
The `?` modifier indicates dimensions that can be omitted if not present in all operands:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
"(m?,n),(n,p?)->(m?,p?)" # matmul signature - handles:
|
36
|
+
# - (m,n),(n,p) -> (m,p) matrix * matrix
|
37
|
+
# - (n),(n,p) -> (p) vector * matrix
|
38
|
+
# - (m,n),(n) -> (m) matrix * vector
|
39
|
+
# - (n),(n) -> () vector * vector
|
40
|
+
```
|
41
|
+
|
42
|
+
### Broadcastable Dimensions (|1)
|
43
|
+
|
44
|
+
The `|1` modifier allows dimensions to broadcast against scalar or size-1 dimensions:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
"(n|1),(n|1)->()" # all_equal - compares vectors or scalars
|
48
|
+
"(i|1),(j|1)->(i,j)" # Outer product with broadcasting
|
49
|
+
```
|
50
|
+
|
51
|
+
**Constraints:**
|
52
|
+
- Only input dimensions can be broadcastable
|
53
|
+
- Output dimensions cannot have `|1` modifier
|
54
|
+
- Cannot combine `?` and `|1` on same dimension
|
55
|
+
|
56
|
+
## Join Policies
|
57
|
+
|
58
|
+
Control how different dimension names are combined:
|
59
|
+
|
60
|
+
### Default (nil policy)
|
61
|
+
All non-scalar arguments must have compatible dimension names:
|
62
|
+
```ruby
|
63
|
+
"(i),(i)->(i)" # ✓ Compatible - same dimensions
|
64
|
+
"(i),(j)->(i,j)" # ✗ Incompatible without policy
|
65
|
+
```
|
66
|
+
|
67
|
+
### @product Policy
|
68
|
+
Allows different dimensions, creates Cartesian product:
|
69
|
+
```ruby
|
70
|
+
"(i),(j)->(i,j)@product" # Outer product
|
71
|
+
```
|
72
|
+
|
73
|
+
### @zip Policy
|
74
|
+
Allows different dimensions, pairs them up:
|
75
|
+
```ruby
|
76
|
+
"(i),(j)->(i)@zip" # Paired operation
|
77
|
+
```
|
78
|
+
|
79
|
+
## Examples from NEP 20
|
80
|
+
|
81
|
+
| Signature | Use Case | Description |
|
82
|
+
|-----------|----------|-------------|
|
83
|
+
| `(),()->()` | Addition | Scalar operations |
|
84
|
+
| `(i)->()` | Sum | Reduction over last axis |
|
85
|
+
| `(i\|1),(i\|1)->()` | all_equal | Equality test with broadcasting |
|
86
|
+
| `(i),(i)->()` | dot product | Inner vector product |
|
87
|
+
| `(m,n),(n,p)->(m,p)` | matmul | Matrix multiplication |
|
88
|
+
| `(3),(3)->(3)` | cross | Cross product for 3-vectors |
|
89
|
+
| `(m?,n),(n,p?)->(m?,p?)` | matmul | Universal matrix operations |
|
90
|
+
|
91
|
+
## Signature Validation Rules
|
92
|
+
|
93
|
+
1. **Arity matching**: Number of arguments must match signature
|
94
|
+
2. **Fixed-size consistency**: Integer dimensions must match exactly
|
95
|
+
3. **Broadcasting rules**: `|1` dimensions can broadcast to any size
|
96
|
+
4. **Flexible resolution**: `?` dimensions resolved based on presence
|
97
|
+
5. **Output constraints**: No broadcastable (`|1`) dimensions in outputs
|
98
|
+
6. **Policy requirements**: Different dimension names need explicit policy
|
99
|
+
|
100
|
+
## Matching Priority
|
101
|
+
|
102
|
+
When multiple signatures are available, resolution prefers:
|
103
|
+
|
104
|
+
1. **Exact matches** (score: 0) - All dimensions match perfectly
|
105
|
+
2. **Fixed-size matches** (score: 0-2) - Integer dimensions match
|
106
|
+
3. **Broadcast matches** (score: 1-3) - Scalar broadcasting
|
107
|
+
4. **Flexible matches** (score: 10+) - Dimension omission/addition
|
108
|
+
|
109
|
+
Lower scores indicate better matches.
|
110
|
+
|
111
|
+
## Implementation Notes
|
112
|
+
|
113
|
+
Signatures are parsed into `Dimension` objects that track:
|
114
|
+
- Name (Symbol or Integer)
|
115
|
+
- Flexible flag (`?`)
|
116
|
+
- Broadcastable flag (`|1`)
|
117
|
+
|
118
|
+
The resolver handles NEP 20 semantics including:
|
119
|
+
- Flexible dimension resolution
|
120
|
+
- Broadcastable matching
|
121
|
+
- Fixed-size validation
|
122
|
+
- Join policy enforcement
|
123
|
+
|
124
|
+
## Constraint Solver
|
125
|
+
|
126
|
+
Kumi implements a **dimension constraint solver** that validates cross-argument dimension consistency for operations like matrix multiplication:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
signature = "(m,n),(n,p)->(m,p)"
|
130
|
+
arg_shapes = [[:m, :n], [:n, :p]]
|
131
|
+
|
132
|
+
# The solver validates that 'n' dimensions are consistent across arguments
|
133
|
+
plan = resolver.choose(signatures: [sig], arg_shapes: arg_shapes)
|
134
|
+
# Returns: { env: { m: :m, n: :n, p: :p }, result_axes: [:m, :p] }
|
135
|
+
```
|
136
|
+
|
137
|
+
The constraint solver:
|
138
|
+
- Links repeated dimension names across arguments
|
139
|
+
- Validates dimension consistency (same `n` in both positions)
|
140
|
+
- Returns dimension environment bindings
|
141
|
+
- Enables complex operations without explicit join policies
|
142
|
+
|
143
|
+
## Integration with Analyzer
|
144
|
+
|
145
|
+
Function signatures integrate with Kumi's analyzer pipeline through:
|
146
|
+
|
147
|
+
### FunctionSignaturePass
|
148
|
+
Resolves NEP-20 signatures for all function calls and attaches metadata:
|
149
|
+
- `:result_axes` - Output dimension names
|
150
|
+
- `:join_policy` - Cross-dimensional operation policy
|
151
|
+
- `:dropped_axes` - Dimensions eliminated by reductions
|
152
|
+
- `:effective_signature` - Normalized signature for lowering
|
153
|
+
- `:dim_env` - Dimension variable bindings
|
154
|
+
- `:shape_contract` - Simplified contract for lowering
|
155
|
+
|
156
|
+
### Node Index Architecture
|
157
|
+
The analyzer creates a node index mapping `object_id → metadata` that passes can use to:
|
158
|
+
- Attach signature resolution results
|
159
|
+
- Share metadata across analysis passes
|
160
|
+
- Validate consistency in lowering
|
161
|
+
|
162
|
+
## Feature Flags
|
163
|
+
|
164
|
+
Control NEP-20 extensions with environment variables:
|
165
|
+
|
166
|
+
```bash
|
167
|
+
KUMI_ENABLE_FLEX=1 # Enable flexible dimension matching (?)
|
168
|
+
KUMI_ENABLE_BCAST1=1 # Enable broadcastable dimension matching (|1)
|
169
|
+
```
|
170
|
+
|
171
|
+
For more details see [NEP 20 specification](https://numpy.org/neps/nep-0020-expansion-of-generalized-ufunc-signatures.html).
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Hash Objects Demo - Demonstrates Kumi's structured input syntax
|
4
|
+
# This example shows how to use hash objects for organizing related input fields
|
5
|
+
|
6
|
+
require_relative "../lib/kumi"
|
7
|
+
|
8
|
+
module HashObjectsDemo
|
9
|
+
extend Kumi::Schema
|
10
|
+
|
11
|
+
schema do
|
12
|
+
input do
|
13
|
+
# Employee information as hash object
|
14
|
+
hash :employee do
|
15
|
+
string :name
|
16
|
+
integer :age, domain: 18..65
|
17
|
+
float :base_salary, domain: 30_000.0..200_000.0
|
18
|
+
boolean :is_manager
|
19
|
+
integer :years_experience, domain: 0..40
|
20
|
+
end
|
21
|
+
|
22
|
+
# Company configuration as hash object
|
23
|
+
hash :company_config do
|
24
|
+
string :name
|
25
|
+
float :bonus_percentage, domain: 0.0..0.50
|
26
|
+
float :manager_multiplier, domain: 1.0..2.0
|
27
|
+
integer :current_year, domain: 2020..2030
|
28
|
+
end
|
29
|
+
|
30
|
+
# Benefits configuration as hash object
|
31
|
+
hash :benefits do
|
32
|
+
boolean :health_insurance
|
33
|
+
boolean :dental_coverage
|
34
|
+
float :retirement_match, domain: 0.0..0.10
|
35
|
+
integer :vacation_days, domain: 10..30
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Traits using hash object access
|
40
|
+
trait :is_senior, input.employee.years_experience >= 5
|
41
|
+
trait :eligible_for_bonus, is_senior & input.employee.is_manager
|
42
|
+
trait :has_full_benefits, input.benefits.health_insurance & input.benefits.dental_coverage
|
43
|
+
|
44
|
+
# Salary calculations using structured data
|
45
|
+
value :base_annual_salary, input.employee.base_salary
|
46
|
+
|
47
|
+
value :bonus_amount do
|
48
|
+
on eligible_for_bonus, base_annual_salary * input.company_config.bonus_percentage
|
49
|
+
base 0.0
|
50
|
+
end
|
51
|
+
|
52
|
+
trait :is_manager_trait, input.employee.is_manager == true
|
53
|
+
|
54
|
+
value :manager_adjustment do
|
55
|
+
on is_manager_trait, input.company_config.manager_multiplier
|
56
|
+
base 1.0
|
57
|
+
end
|
58
|
+
|
59
|
+
value :total_compensation, (base_annual_salary * manager_adjustment) + bonus_amount
|
60
|
+
|
61
|
+
# Benefits calculations
|
62
|
+
value :retirement_contribution, total_compensation * input.benefits.retirement_match
|
63
|
+
|
64
|
+
value :benefits_package_value do
|
65
|
+
on has_full_benefits, 5_000.0 + input.benefits.vacation_days * 150.0
|
66
|
+
base input.benefits.vacation_days * 100.0
|
67
|
+
end
|
68
|
+
|
69
|
+
# Final totals
|
70
|
+
value :total_package_value, total_compensation + retirement_contribution + benefits_package_value
|
71
|
+
|
72
|
+
# Summary calculations
|
73
|
+
value :years_to_retirement, 65 - input.employee.age
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Example usage
|
78
|
+
if __FILE__ == $0
|
79
|
+
puts "Hash Objects Demo - Employee Compensation Calculator"
|
80
|
+
puts "=" * 55
|
81
|
+
|
82
|
+
# Sample data demonstrating hash objects structure
|
83
|
+
employee_data = {
|
84
|
+
employee: {
|
85
|
+
name: "Alice Johnson",
|
86
|
+
age: 32,
|
87
|
+
base_salary: 85_000.0,
|
88
|
+
is_manager: true,
|
89
|
+
years_experience: 8
|
90
|
+
},
|
91
|
+
company_config: {
|
92
|
+
name: "Tech Solutions Inc",
|
93
|
+
bonus_percentage: 0.15,
|
94
|
+
manager_multiplier: 1.25,
|
95
|
+
current_year: 2024
|
96
|
+
},
|
97
|
+
benefits: {
|
98
|
+
health_insurance: true,
|
99
|
+
dental_coverage: true,
|
100
|
+
retirement_match: 0.06,
|
101
|
+
vacation_days: 25
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
# Calculate compensation
|
106
|
+
result = HashObjectsDemo.from(employee_data)
|
107
|
+
|
108
|
+
def format_currency(amount)
|
109
|
+
"$#{amount.round(0).to_s.gsub(/\B(?=(\d{3})+(?!\d))/, ',')}"
|
110
|
+
end
|
111
|
+
|
112
|
+
puts "\nEmployee Information:"
|
113
|
+
puts "- Name: #{employee_data[:employee][:name]}"
|
114
|
+
puts "- Age: #{employee_data[:employee][:age]}"
|
115
|
+
puts "- Experience: #{employee_data[:employee][:years_experience]} years"
|
116
|
+
puts "- Manager: #{employee_data[:employee][:is_manager] ? 'Yes' : 'No'}"
|
117
|
+
|
118
|
+
puts "\nCompany: #{employee_data[:company_config][:name]}"
|
119
|
+
puts "- Bonus Rate: #{(employee_data[:company_config][:bonus_percentage] * 100).round(1)}%"
|
120
|
+
puts "- Manager Multiplier: #{employee_data[:company_config][:manager_multiplier]}x"
|
121
|
+
|
122
|
+
puts "\nBenefits:"
|
123
|
+
puts "- Health Insurance: #{employee_data[:benefits][:health_insurance] ? 'Yes' : 'No'}"
|
124
|
+
puts "- Dental Coverage: #{employee_data[:benefits][:dental_coverage] ? 'Yes' : 'No'}"
|
125
|
+
puts "- Retirement Match: #{(employee_data[:benefits][:retirement_match] * 100).round(1)}%"
|
126
|
+
puts "- Vacation Days: #{employee_data[:benefits][:vacation_days]}"
|
127
|
+
|
128
|
+
puts "\nCompensation Breakdown:"
|
129
|
+
puts "- Base Salary: #{format_currency(result[:base_annual_salary])}"
|
130
|
+
puts "- Manager Adjustment: #{result[:manager_adjustment]}x"
|
131
|
+
puts "- Bonus: #{format_currency(result[:bonus_amount])}"
|
132
|
+
puts "- Total Compensation: #{format_currency(result[:total_compensation])}"
|
133
|
+
puts "- Retirement Contribution: #{format_currency(result[:retirement_contribution])}"
|
134
|
+
puts "- Benefits Package Value: #{format_currency(result[:benefits_package_value])}"
|
135
|
+
|
136
|
+
puts "\nTotal Package: #{format_currency(result[:total_package_value])}"
|
137
|
+
puts "Years to Retirement: #{result[:years_to_retirement]}"
|
138
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
schema do
|
2
|
+
input do
|
3
|
+
array :items do
|
4
|
+
float :price
|
5
|
+
integer :quantity
|
6
|
+
string :category
|
7
|
+
end
|
8
|
+
float :tax_rate
|
9
|
+
end
|
10
|
+
|
11
|
+
value :subtotals, input.items.price * input.items.quantity
|
12
|
+
trait :expensive_items, input.items.price > 100.0
|
13
|
+
trait :electronics, input.items.category == "electronics"
|
14
|
+
|
15
|
+
value :discounted_price, input.items.price * 0.9
|
16
|
+
value :is_valid_quantity, input.items.quantity > 0
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
schema do
|
2
|
+
input do
|
3
|
+
integer :x
|
4
|
+
integer :y
|
5
|
+
end
|
6
|
+
|
7
|
+
trait :x_positive, input.x > 0
|
8
|
+
trait :y_positive, input.y > 0
|
9
|
+
|
10
|
+
value :status do
|
11
|
+
on x_positive, y_positive, "both positive"
|
12
|
+
on x_positive, "x positive"
|
13
|
+
on y_positive, "y positive"
|
14
|
+
base "neither positive"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
schema do
|
2
|
+
input do
|
3
|
+
hash :organization do
|
4
|
+
string :name
|
5
|
+
array :regions do
|
6
|
+
string :region_name
|
7
|
+
hash :headquarters do
|
8
|
+
string :city
|
9
|
+
array :buildings do
|
10
|
+
string :building_name
|
11
|
+
hash :facilities do
|
12
|
+
string :facility_type
|
13
|
+
integer :capacity
|
14
|
+
float :utilization_rate
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Deep access across 5 levels
|
23
|
+
value :org_name, input.organization.name
|
24
|
+
value :region_names, input.organization.regions.region_name
|
25
|
+
value :hq_cities, input.organization.regions.headquarters.city
|
26
|
+
value :building_names, input.organization.regions.headquarters.buildings.building_name
|
27
|
+
value :facility_types, input.organization.regions.headquarters.buildings.facilities.facility_type
|
28
|
+
value :capacities, input.organization.regions.headquarters.buildings.facilities.capacity
|
29
|
+
value :utilization_rates, input.organization.regions.headquarters.buildings.facilities.utilization_rate
|
30
|
+
|
31
|
+
# Traits using deep nesting - avoiding cross-scope issues
|
32
|
+
trait :large_organization, fn(:size, input.organization.regions) > 1
|
33
|
+
|
34
|
+
# Simple cascade using traits that work within same scope
|
35
|
+
value :org_classification do
|
36
|
+
on large_organization, "Enterprise"
|
37
|
+
base "Standard"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Aggregations that work properly
|
41
|
+
value :total_capacity, fn(:sum, input.organization.regions.headquarters.buildings.facilities.capacity)
|
42
|
+
end
|