kumi-parser 0.0.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb3ed4f3c5d534b41a856cb05ecacdafa233c66f97039af6c7f695d7b11e9638
4
- data.tar.gz: dae5536d6035f67546136f12ffe1fc52d602bdffc8dd5eeba120fec2313c4cd7
3
+ metadata.gz: 0067c520152fae1c2285b2628f3b8b5d25eff145ae61780cf3d283310816c336
4
+ data.tar.gz: 4c604f36cd250a8d4672fef7c2acc667c7de498fe0ee088980bcbd8a744b8d5e
5
5
  SHA512:
6
- metadata.gz: 1bc09e791d8557b52f2b4b6475ee368f6ac000c4cae9de0efb9e877e86eb7929f1933e6bb247aa6cfa4cdd5412d22217645ab4a873f9881935613cc02bc95dc2
7
- data.tar.gz: 8cf8d948c812075b068479e25737a0d43999831f2dea87a018d6d62fe8c1049d96bb27076f88173ed91d4bd4ae1939669d768402ee9e8ce8e5c215c14014d909
6
+ metadata.gz: c3d3342f43d5bb41b140f40d02d3eaac08abeb88509d4c4f4ce127e38ec3583e7566c68d8a96956d2877a2819b046663e2bf95aa246763bcf7556cf4df380934
7
+ data.tar.gz: f7891f6e388e224ebfd0da8d027d331c6794325e0d27715f8800f4aa9124dd3421fc36ca3afb1ac10e6f4e8c5db61f610a18e9db0a33d21c9292efbbd5fa45a7
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
data/CLAUDE.md ADDED
@@ -0,0 +1,120 @@
1
+ # Kumi Parser - Technical Context
2
+
3
+ ## Current Architecture (January 2025)
4
+
5
+ ## Key Files
6
+
7
+ - `lib/kumi/parser/smart_tokenizer.rb` - Tokenizer with context tracking
8
+ - `lib/kumi/parser/direct_parser.rb` - Parser implementation (renamed from direct_ast_parser.rb)
9
+ - `lib/kumi/parser/token_metadata.rb` - Token types and metadata
10
+ - `lib/kumi/parser/text_parser.rb` - Public API maintaining compatibility
11
+ - `lib/kumi/parser/base.rb` - Core parsing interface
12
+ - `lib/kumi/parser/syntax_validator.rb` - Validation with proper diagnostics
13
+ - `lib/kumi/parser/errors.rb` - Custom error types
14
+
15
+ ## Important Syntax Rules
16
+
17
+ - **Functions**: `fn(:symbol, args...)` only (no dot notation like `fn.max()`)
18
+ - **Operators**: Standard precedence (*/% > +- > comparisons > & > |)
19
+ - **Array access**: Uses `array[index]` syntax (converted to `:at` function internally)
20
+ - **Equality**: `==` and `!=` operators (converted from `:eq`/`:ne` tokens)
21
+ - **Multi-line expressions**: Parser skips newlines within expressions
22
+ - **Cascade**: `value :name do ... on condition, result ... base result ... end`
23
+ - **Constants**: Text parser cannot resolve Ruby constants - use inline values
24
+
25
+ ## AST Structure & Compatibility
26
+
27
+ All nodes from `Kumi::Syntax::*` (defined in main kumi gem):
28
+ - `Root(inputs, attributes, traits)`
29
+ - `InputDeclaration(name, domain, type, children)`
30
+ - `ValueDeclaration(name, expression)`
31
+ - `TraitDeclaration(name, expression)`
32
+ - `CallExpression(fn_name, args)`
33
+ - `InputReference(name)` / `InputElementReference(path)`
34
+ - `DeclarationReference(name)`
35
+ - `Literal(value)`
36
+ - `CascadeExpression(cases)` / `CaseExpression(condition, result)`
37
+ - `ArrayExpression(elements)`
38
+
39
+ **Ruby DSL Compatibility**:
40
+ - Cascade conditions: Simple trait references wrapped in `all?([trait])` function calls
41
+ - Array access: `[index]` becomes `CallExpression(:at, [array, index])`
42
+ - Operators: `:eq` → `:==`, `:ne` → `:!=` for consistency
43
+ - Constants: Ruby constants resolved to values in DSL, remain as `DeclarationReference` in text parser
44
+
45
+ ## Debugging & Testing
46
+
47
+ **View AST structure**:
48
+ ```ruby
49
+ ast = Kumi::Parser::TextParser.parse(schema)
50
+ puts Kumi::Support::SExpressionPrinter.print(ast)
51
+ # => (Root
52
+ # inputs: [(InputDeclaration :income :float)]
53
+ # attributes: [(ValueDeclaration :tax (CallExpression :+ ...))]
54
+ # traits: [(TraitDeclaration :adult (CallExpression :>= ...))])
55
+ ```
56
+
57
+ **Quick validation test**:
58
+ ```ruby
59
+ ruby -r./lib/kumi/parser/text_parser -e "p Kumi::Parser::TextParser.valid?('schema do input do float :x end end')"
60
+ ```
61
+
62
+ **Compare with Ruby DSL**:
63
+ ```ruby
64
+ # Define schema in Ruby
65
+ module TestSchema
66
+ extend Kumi::Schema
67
+ schema do
68
+ input do
69
+ float :income
70
+ end
71
+ value :tax, fn(:calc, input.income)
72
+ end
73
+ end
74
+
75
+ # Parse equivalent text
76
+ text_ast = Kumi::Parser::TextParser.parse(<<~KUMI)
77
+ schema do
78
+ input do
79
+ float :income
80
+ end
81
+ value :tax, fn(:calc, input.income)
82
+ end
83
+ KUMI
84
+
85
+ # Compare ASTs
86
+ ruby_ast = TestSchema.__syntax_tree__
87
+ text_ast == ruby_ast # Should be true
88
+ ```
89
+
90
+ - Tax schema in `spec/kumi/parser/text_parser_example tax_schema_spec.rb` is canonical test
91
+ - Run all tests: `rspec spec/kumi/parser/`
92
+ - Integration tests: `rspec spec/kumi/parser/text_parser_integration_spec.rb`
93
+
94
+ ## Error Handling & Validation
95
+
96
+ - **Parse errors**: `Kumi::Parser::Errors::ParseError` (internal) → `Kumi::Errors::SyntaxError` (public API)
97
+ - **Tokenizer errors**: `Kumi::Parser::Errors::TokenizerError` with location info
98
+ - **Diagnostics**: Use `SyntaxValidator` for detailed error reporting with line/column info
99
+ - **Location tracking**: All tokens and AST nodes include `Kumi::Syntax::Location(file, line, column)`
100
+
101
+ ## Test Status (January 2025)
102
+
103
+ ✅ **All specs passing**: 32 examples, 0 failures, 1 pending
104
+ - ✅ Syntax validation with proper diagnostics
105
+ - ✅ AST compatibility with Ruby DSL (when constants aren't used)
106
+ - ✅ Integration with analyzer and compiler
107
+ - ✅ End-to-end execution testing
108
+ - ✅ Error type compatibility
109
+
110
+ ## Known Limitations
111
+
112
+ - **Ruby constants**: Text parser cannot resolve Ruby constants like `CONST_NAME` - use inline values instead
113
+ - **Domain specification**: Parsing not fully implemented
114
+ - **Diagnostic APIs**: Monaco/CodeMirror/JSON format methods not implemented
115
+
116
+ ## Performance
117
+
118
+ - Tokenization: <1ms for typical schemas
119
+ - Parsing: ~4ms for complete tax schema (21 values, 4 traits)
120
+ - Direct AST construction eliminates transformation overhead
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kumi Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # Kumi::Parser
2
+
3
+ Text parser for [Kumi](https://github.com/amuta/kumi) schemas. Direct tokenizer → AST construction with ~4ms parse time.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ gem 'kumi-parser'
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ruby
14
+ require 'kumi/parser'
15
+
16
+ schema = <<~KUMI
17
+ schema do
18
+ input do
19
+ float :income
20
+ string :status
21
+ end
22
+
23
+ trait :adult, input.age >= 18
24
+ value :tax, fn(:calculate_tax, input.income)
25
+ end
26
+ KUMI
27
+
28
+ # Parse to AST
29
+ ast = Kumi::Parser::TextParser.parse(schema)
30
+
31
+ # Validate
32
+ Kumi::Parser::TextParser.valid?(schema) # => true
33
+ ```
34
+
35
+ ## API
36
+
37
+ - `parse(text)` → AST
38
+ - `valid?(text)` → Boolean
39
+ - `validate(text)` → Array of error hashes
40
+
41
+ ## Syntax
42
+
43
+ ```
44
+ schema do
45
+ input do
46
+ <type> :<name>[, domain: <spec>]
47
+ end
48
+
49
+ trait :<name>, <expression>
50
+
51
+ value :<name>, <expression>
52
+ value :<name> do
53
+ on <condition>, <result>
54
+ base <result>
55
+ end
56
+ end
57
+ ```
58
+
59
+ **Function calls**: `fn(:name, arg1, arg2, ...)`
60
+ **Operators**: `+` `-` `*` `/` `%` `>` `<` `>=` `<=` `==` `!=` `&` `|`
61
+ **References**: `input.field`, `value_name`, `array[index]`
62
+
63
+ ## Architecture
64
+
65
+ - `smart_tokenizer.rb` - Context-aware tokenization with embedded metadata
66
+ - `direct_ast_parser.rb` - Recursive descent parser, direct AST construction
67
+ - `token_metadata.rb` - Token types, precedence, and semantic hints
68
+
69
+ See `docs/` for technical details.
70
+
71
+ ## License
72
+
73
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
@@ -0,0 +1,41 @@
1
+ # Debug the text parser transform
2
+
3
+ require_relative 'lib/kumi/text_parser'
4
+
5
+ schema_text = <<~SCHEMA
6
+ schema do
7
+ input do
8
+ integer :age
9
+ end
10
+ #{' '}
11
+ trait :adult, input.age >= 18
12
+ value :bonus, 100
13
+ end
14
+ SCHEMA
15
+
16
+ puts 'Debugging text parser...'
17
+
18
+ begin
19
+ # Test just the grammar parsing first
20
+ grammar = Kumi::TextParser::Grammar.new
21
+ parse_tree = grammar.parse(schema_text)
22
+
23
+ puts 'Raw parse tree:'
24
+ puts parse_tree.inspect
25
+ puts
26
+
27
+ # Now test the transform
28
+ transform = Kumi::TextParser::Transform.new
29
+ ast = transform.apply(parse_tree)
30
+
31
+ puts 'Transformed AST:'
32
+ puts ast.inspect
33
+ puts
34
+
35
+ puts 'AST structure:'
36
+ puts "- Values: #{ast.attributes.count} - #{ast.attributes.map(&:name)}"
37
+ puts "- Traits: #{ast.traits.count} - #{ast.traits.map(&:name)}"
38
+ rescue StandardError => e
39
+ puts "Error: #{e.message}"
40
+ puts e.backtrace.first(5)
41
+ end
@@ -0,0 +1,26 @@
1
+ # Debug specific transform rule
2
+
3
+ require_relative 'lib/kumi/text_parser'
4
+
5
+ # Test just the trait parsing
6
+ trait_text = 'trait :adult, input.age >= 18'
7
+
8
+ grammar = Kumi::TextParser::Grammar.new
9
+ transform = Kumi::TextParser::Transform.new
10
+
11
+ begin
12
+ # Parse just the trait declaration
13
+ parse_result = grammar.trait_declaration.parse(trait_text)
14
+ puts 'Trait parse result:'
15
+ puts parse_result.inspect
16
+ puts
17
+
18
+ # Try to transform it
19
+ transformed = transform.apply(parse_result)
20
+ puts 'Transformed result:'
21
+ puts transformed.inspect
22
+ puts "Class: #{transformed.class}"
23
+ rescue StandardError => e
24
+ puts "Error: #{e.message}"
25
+ puts e.backtrace.first(5)
26
+ end
@@ -0,0 +1,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/kumi'
4
+
5
+ # Comprehensive test of all text parser supported features
6
+ # This example tests what the text parser ACTUALLY supports
7
+ # (not what the Ruby DSL supports)
8
+
9
+ module TextParserComprehensiveTest
10
+ extend Kumi::Schema
11
+
12
+ schema do
13
+ input do
14
+ # Basic type declarations
15
+ integer :age, domain: 18..65
16
+ float :score, domain: 0.0..100.0
17
+ string :status, domain: %w[active inactive suspended]
18
+ boolean :verified
19
+ any :metadata
20
+
21
+ # Nested array declarations
22
+ array :items do
23
+ string :name
24
+ float :price
25
+ integer :quantity
26
+ end
27
+ end
28
+
29
+ # ============================================================
30
+ # ARITHMETIC OPERATIONS - All supported operators
31
+ # ============================================================
32
+
33
+ # Addition and subtraction
34
+ value :total_price, input.items.price + input.items.quantity
35
+ value :price_diff, input.items.price - 10.0
36
+
37
+ # Multiplication and division
38
+ value :scaled_score, input.score * 1.5
39
+ value :average_score, input.score / 2
40
+
41
+ # Modulo
42
+ value :remainder, input.age % 10
43
+
44
+ # Complex arithmetic with parentheses
45
+ value :complex_calc, (input.score * 2.5) + (input.age / 2) - 10
46
+
47
+ # ============================================================
48
+ # COMPARISON OPERATIONS - All comparison operators
49
+ # ============================================================
50
+
51
+ trait :adult, input.age >= 18
52
+ trait :senior, input.age > 60
53
+ trait :young_adult, input.age <= 25
54
+ trait :is_teen, input.age < 20
55
+ trait :perfect_score, input.score == 100.0
56
+ trait :not_perfect, input.score != 100.0
57
+
58
+ # String comparisons
59
+ trait :is_active, input.status == 'active'
60
+ trait :not_suspended, input.status != 'suspended'
61
+
62
+ # Boolean comparisons
63
+ trait :is_verified, input.verified == true
64
+ trait :not_verified, input.verified == false
65
+
66
+ # ============================================================
67
+ # LOGICAL OPERATIONS - AND and OR
68
+ # ============================================================
69
+
70
+ # AND operations using &
71
+ trait :eligible, (input.age >= 18) & (input.verified == true) & (input.status == 'active')
72
+ trait :premium_user, is_verified & is_active & (input.score > 80.0)
73
+
74
+ # OR operations using |
75
+ trait :needs_attention, (input.status == 'suspended') | (input.verified == false)
76
+ trait :special_case, senior | (input.score >= 95.0)
77
+
78
+ # Complex logical expressions
79
+ trait :complex_logic, (adult & is_verified) | (senior & is_active)
80
+
81
+ # ============================================================
82
+ # FUNCTION CALLS - fn(:name, args) syntax
83
+ # ============================================================
84
+
85
+ # Math functions
86
+ value :absolute_diff, fn(:abs, fn(:subtract, input.score, 50.0))
87
+ value :rounded_score, fn(:round, input.score)
88
+ value :clamped_score, fn(:clamp, input.score, 20.0, 80.0)
89
+
90
+ # String functions (note: no method syntax like .length)
91
+ value :name_length, fn(:string_length, input.status)
92
+ value :uppercase_status, fn(:upcase, input.status)
93
+ value :lowercase_status, fn(:downcase, input.status)
94
+
95
+ # Collection/aggregation functions on arrays
96
+ value :total_items, fn(:sum, input.items.quantity)
97
+ value :item_count, fn(:size, input.items)
98
+ value :max_price, fn(:max, input.items.price)
99
+ value :min_price, fn(:min, input.items.price)
100
+
101
+ # ============================================================
102
+ # REFERENCES - Both bare identifiers and ref() NOT supported in text parser
103
+ # ============================================================
104
+
105
+ # Using previously defined values/traits (bare identifiers)
106
+ trait :super_eligible, eligible & premium_user
107
+ value :bonus_points, fn(:multiply, total_price, 0.1)
108
+
109
+ # ============================================================
110
+ # CASCADE EXPRESSIONS - on/base syntax
111
+ # ============================================================
112
+
113
+ value :user_tier do
114
+ on premium_user, 'premium'
115
+ on eligible, 'standard'
116
+ on is_verified, 'basic'
117
+ base 'guest'
118
+ end
119
+
120
+ value :discount_rate do
121
+ on senior, 0.25
122
+ on premium_user, 0.15
123
+ on is_active, 0.05
124
+ base 0.0
125
+ end
126
+
127
+ # Cascades using complex conditions
128
+ value :risk_level do
129
+ on complex_logic, 'low'
130
+ on needs_attention, 'high'
131
+ on is_active, 'medium'
132
+ base 'unknown'
133
+ end
134
+
135
+ # ============================================================
136
+ # NESTED INPUT REFERENCES - Deep field access
137
+ # ============================================================
138
+
139
+ # Direct nested field access (parsed as multi-part input reference)
140
+ value :first_item_name, input.items.name
141
+ value :all_prices, input.items.price
142
+
143
+ # Operations on nested fields (broadcasting)
144
+ value :discounted_prices, input.items.price * 0.9
145
+
146
+ # ============================================================
147
+ # LITERALS - All supported literal types
148
+ # ============================================================
149
+
150
+ # Number literals (integers and floats)
151
+ value :constant_int, 42
152
+ value :constant_float, 3.14159
153
+
154
+ # String literals (double quotes only)
155
+ value :greeting, 'Hello, World!'
156
+
157
+ # Boolean literals
158
+ value :always_true, true
159
+ value :always_false, false
160
+
161
+ # ============================================================
162
+ # EDGE CASES AND LIMITATIONS
163
+ # ============================================================
164
+
165
+ # Complex parenthesized expressions
166
+ value :nested_parens, ((input.age + 10) * 2) / (input.score - 20)
167
+
168
+ # Multiple operators in sequence
169
+ value :operator_chain, input.age + 10 - 5 + 20 - 3
170
+
171
+ # Nested function calls
172
+ value :nested_functions, fn(:round, fn(:multiply, fn(:add, input.score, 10), 1.5))
173
+
174
+ # ============================================================
175
+ # NOT SUPPORTED (would fail in text parser)
176
+ # ============================================================
177
+
178
+ # These are commented out as they would cause parse errors:
179
+
180
+ # Array literals in expressions
181
+ # value :array_literal, [1, 2, 3, 4, 5]
182
+ # value :max_of_array, fn(:max, [input.age, 30, 50])
183
+
184
+ # Method-style function calls
185
+ # value :method_style, fn.add(input.age, 10)
186
+
187
+ # Sugar method calls on fields
188
+ # trait :long_status, input.status.length > 5
189
+ # value :name_chars, input.status.size
190
+
191
+ # Ternary/conditional expressions
192
+ # value :conditional, eligible ? 100 : 0
193
+
194
+ # Hash literals
195
+ # value :options, { min: 0, max: 100 }
196
+
197
+ # ref() syntax
198
+ # trait :ref_example, ref(:eligible) & ref(:premium_user)
199
+
200
+ # Symbol literals (except in fn() calls)
201
+ # value :symbol_value, :active
202
+
203
+ # Power operator
204
+ # value :squared, input.age ** 2
205
+
206
+ # Comments within the DSL
207
+ # value :test, 42 # this would fail
208
+ end
209
+ end
210
+
211
+ # Test the schema with the text parser
212
+ if __FILE__ == $0
213
+ require_relative '../lib/kumi/text_parser'
214
+
215
+ # Create a clean schema without comments for text parser testing
216
+ schema_text = <<~SCHEMA
217
+ schema do
218
+ input do
219
+ integer :age, domain: 18..65
220
+ float :score, domain: 0.0..100.0
221
+ string :status, domain: %w[active inactive suspended]
222
+ boolean :verified
223
+ any :metadata
224
+ #{' '}
225
+ array :items do
226
+ string :name
227
+ float :price
228
+ integer :quantity
229
+ end
230
+ end
231
+
232
+ value :total_price, input.items.price + input.items.quantity
233
+ value :price_diff, input.items.price - 10.0
234
+ value :scaled_score, input.score * 1.5
235
+ value :average_score, input.score / 2
236
+ value :remainder, input.age % 10
237
+ value :complex_calc, (input.score * 2.5) + (input.age / 2) - 10
238
+ #{' '}
239
+ trait :adult, input.age >= 18
240
+ trait :senior, input.age > 60
241
+ trait :young_adult, input.age <= 25
242
+ trait :is_teen, input.age < 20
243
+ trait :perfect_score, input.score == 100.0
244
+ trait :not_perfect, input.score != 100.0
245
+ trait :is_active, input.status == "active"
246
+ trait :not_suspended, input.status != "suspended"
247
+ trait :is_verified, input.verified == true
248
+ trait :not_verified, input.verified == false
249
+ #{' '}
250
+ trait :eligible, (input.age >= 18) & (input.verified == true) & (input.status == "active")
251
+ trait :premium_user, is_verified & is_active & (input.score > 80.0)
252
+ trait :needs_attention, (input.status == "suspended") | (input.verified == false)
253
+ trait :special_case, senior | (input.score >= 95.0)
254
+ trait :complex_logic, (adult & is_verified) | (senior & is_active)
255
+ #{' '}
256
+ value :absolute_diff, fn(:abs, fn(:subtract, input.score, 50.0))
257
+ value :rounded_score, fn(:round, input.score)
258
+ value :clamped_score, fn(:clamp, input.score, 20.0, 80.0)
259
+ value :name_length, fn(:string_length, input.status)
260
+ value :uppercase_status, fn(:upcase, input.status)
261
+ value :lowercase_status, fn(:downcase, input.status)
262
+ value :total_items, fn(:sum, input.items.quantity)
263
+ value :item_count, fn(:size, input.items)
264
+ value :max_price, fn(:max, input.items.price)
265
+ value :min_price, fn(:min, input.items.price)
266
+ #{' '}
267
+ trait :super_eligible, eligible & premium_user
268
+ value :bonus_points, fn(:multiply, total_price, 0.1)
269
+ #{' '}
270
+ value :user_tier do
271
+ on premium_user, "premium"
272
+ on eligible, "standard"
273
+ on is_verified, "basic"
274
+ base "guest"
275
+ end
276
+ #{' '}
277
+ value :discount_rate do
278
+ on senior, 0.25
279
+ on premium_user, 0.15
280
+ on is_active, 0.05
281
+ base 0.0
282
+ end
283
+ #{' '}
284
+ value :risk_level do
285
+ on complex_logic, "low"
286
+ on needs_attention, "high"
287
+ on is_active, "medium"
288
+ base "unknown"
289
+ end
290
+ #{' '}
291
+ value :first_item_name, input.items.name
292
+ value :all_prices, input.items.price
293
+ value :discounted_prices, input.items.price * 0.9
294
+ #{' '}
295
+ value :constant_int, 42
296
+ value :constant_float, 3.14159
297
+ value :greeting, "Hello, World!"
298
+ value :always_true, true
299
+ value :always_false, false
300
+ #{' '}
301
+ value :nested_parens, ((input.age + 10) * 2) / (input.score - 20)
302
+ value :operator_chain, input.age + 10 - 5 + 20 - 3
303
+ value :nested_functions, fn(:round, fn(:multiply, fn(:add, input.score, 10), 1.5))
304
+ end
305
+ SCHEMA
306
+
307
+ puts 'Testing text parser with comprehensive DSL...'
308
+ puts '=' * 60
309
+
310
+ begin
311
+ # Validate the schema
312
+ diagnostics = Kumi::TextParser.validate(schema_text)
313
+
314
+ if diagnostics.empty?
315
+ puts '✅ Schema is valid!'
316
+
317
+ # Parse and show some info
318
+ ast = Kumi::TextParser.parse(schema_text)
319
+ puts "\nParsed successfully!"
320
+ puts "- Input fields: #{ast.inputs.map(&:name).join(', ')}"
321
+ puts "- Values: #{ast.attributes.count}"
322
+ puts "- Traits: #{ast.traits.count}"
323
+ else
324
+ puts '❌ Schema has errors:'
325
+ diagnostics.to_a.each do |diagnostic|
326
+ puts " Line #{diagnostic.line}, Column #{diagnostic.column}: #{diagnostic.message}"
327
+ end
328
+ end
329
+ rescue StandardError => e
330
+ puts "❌ Parser error: #{e.message}"
331
+ puts e.backtrace.first(5)
332
+ end
333
+ end