kumi-parser 0.0.33 โ†’ 0.1.0

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.
@@ -1,333 +0,0 @@
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.values.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
@@ -1,146 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../lib/kumi/text_parser'
4
-
5
- # Test schema with comments and verify AST structure
6
- schema_text = <<~SCHEMA
7
- schema do
8
- # Input section with type declarations
9
- input do
10
- integer :age, domain: 18..65 # User's age
11
- float :score, domain: 0.0..100.0 # Test score
12
- string :status, domain: %w[active inactive] # User status
13
- boolean :verified # Verification status
14
- #{' '}
15
- # Nested array example
16
- array :items do
17
- string :name # Item name
18
- float :price # Item price
19
- integer :quantity # Item quantity
20
- end
21
- end
22
-
23
- # Basic arithmetic operations
24
- value :total_price, input.items.price + input.items.quantity
25
- value :scaled_score, input.score * 1.5
26
- #{' '}
27
- # Trait definitions with comparisons
28
- trait :adult, input.age >= 18 # Is adult
29
- trait :high_scorer, input.score > 80.0 # High score
30
- trait :is_active, input.status == "active" # Active user
31
- #{' '}
32
- # Complex logical operations
33
- trait :eligible, adult & is_active & (input.verified == true)
34
- #{' '}
35
- # Function calls
36
- value :rounded_score, fn(:round, input.score)
37
- value :item_count, fn(:size, input.items)
38
- #{' '}
39
- # Cascade expressions
40
- value :user_level do
41
- on high_scorer, "premium" # Premium users
42
- on eligible, "standard" # Standard users#{' '}
43
- base "basic" # Default level
44
- end
45
- end
46
- SCHEMA
47
-
48
- puts 'Testing text parser with comments...'
49
- puts '=' * 50
50
-
51
- begin
52
- # Test parsing
53
- diagnostics = Kumi::TextParser.validate(schema_text)
54
-
55
- if diagnostics.empty?
56
- puts 'โœ… Schema parsed successfully!'
57
-
58
- # Parse and examine AST structure
59
- ast = Kumi::TextParser.parse(schema_text)
60
-
61
- puts "\n๐Ÿ“Š AST Structure:"
62
- puts "- Root type: #{ast.class.name}"
63
- puts "- Input fields: #{ast.inputs.count}"
64
- ast.inputs.each_with_index do |input, i|
65
- puts " #{i + 1}. #{input.name} (#{input.type})"
66
- next unless input.children && input.children.any?
67
-
68
- input.children.each do |child|
69
- puts " - #{child.name} (#{child.type})"
70
- end
71
- end
72
-
73
- puts "- Value declarations: #{ast.values.count}"
74
- ast.values.each_with_index do |value, i|
75
- puts " #{i + 1}. #{value.name}"
76
- end
77
-
78
- puts "- Trait declarations: #{ast.traits.count}"
79
- ast.traits.each_with_index do |trait, i|
80
- puts " #{i + 1}. #{trait.name}"
81
- end
82
-
83
- # Test with actual Ruby DSL to verify it works end-to-end
84
- puts "\n๐Ÿงช Testing with full Kumi analysis..."
85
- begin
86
- analyzer = Kumi::Analyzer.new
87
- analysis_result = analyzer.analyze(ast)
88
-
89
- if analysis_result.errors.any?
90
- puts 'โŒ Analysis errors:'
91
- analysis_result.errors.each do |error|
92
- if error.respond_to?(:message)
93
- puts " - #{error.message}"
94
- elsif error.is_a?(Array)
95
- puts " - #{error[1]}"
96
- else
97
- puts " - #{error}"
98
- end
99
- end
100
- else
101
- puts 'โœ… Schema analysis successful!'
102
-
103
- # Try to create a compiled schema
104
- compiler = Kumi::Compiler.new
105
- compiled = compiler.compile(ast, analysis_result)
106
- puts 'โœ… Schema compilation successful!'
107
-
108
- # Test execution with sample data
109
- test_data = {
110
- age: 25,
111
- score: 85.5,
112
- status: 'active',
113
- verified: true,
114
- items: [
115
- { name: 'Item 1', price: 10.0, quantity: 2 },
116
- { name: 'Item 2', price: 15.0, quantity: 1 }
117
- ]
118
- }
119
-
120
- runner = Kumi::Runner.new(compiled, test_data)
121
-
122
- # Test a few key calculations
123
- puts "\n๐ŸŽฏ Test Results:"
124
- puts "- total_price: #{runner.fetch(:total_price)}"
125
- puts "- scaled_score: #{runner.fetch(:scaled_score)}"
126
- puts "- adult: #{runner.fetch(:adult)}"
127
- puts "- eligible: #{runner.fetch(:eligible)}"
128
- puts "- user_level: #{runner.fetch(:user_level)}"
129
- puts "- item_count: #{runner.fetch(:item_count)}"
130
-
131
- end
132
- rescue StandardError => e
133
- puts "โŒ Analysis/compilation error: #{e.message}"
134
- puts e.backtrace.first(3)
135
- end
136
-
137
- else
138
- puts 'โŒ Schema has parse errors:'
139
- diagnostics.to_a.each do |diagnostic|
140
- puts " Line #{diagnostic.line}, Column #{diagnostic.column}: #{diagnostic.message}"
141
- end
142
- end
143
- rescue StandardError => e
144
- puts "โŒ Unexpected error: #{e.message}"
145
- puts e.backtrace.first(5)
146
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'smart_tokenizer'
4
- require_relative 'direct_parser'
5
- require_relative 'errors'
6
-
7
- module Kumi
8
- module Parser
9
- # Text parser using tokenizer + direct AST construction
10
- class Base
11
- def self.parse(source, source_file: '<input>')
12
- tokens = SmartTokenizer.new(source, source_file: source_file).tokenize
13
- Kumi::Parser::DirectParser.new(tokens).parse
14
- end
15
-
16
- def self.valid?(source, source_file: '<input>')
17
- parse(source, source_file: source_file)
18
- true
19
- rescue Errors::TokenizerError, Errors::ParseError
20
- false
21
- end
22
-
23
- def self.validate(source, source_file: '<input>')
24
- parse(source, source_file: source_file)
25
- []
26
- rescue Errors::TokenizerError, Errors::ParseError => e
27
- [create_diagnostic(e, source_file)]
28
- end
29
-
30
- private
31
-
32
- def self.create_diagnostic(error, source_file)
33
- location = if error.is_a?(Errors::ParseError) && error.token
34
- error.token.location
35
- elsif error.respond_to?(:location)
36
- error.location
37
- else
38
- nil
39
- end
40
-
41
- {
42
- line: location&.line || 1,
43
- column: location&.column || 1,
44
- message: error.message,
45
- severity: :error,
46
- type: :syntax
47
- }
48
- end
49
- end
50
- end
51
- end