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 +4 -4
- data/.rspec +3 -0
- data/CLAUDE.md +120 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/Rakefile +10 -0
- data/examples/debug_text_parser.rb +41 -0
- data/examples/debug_transform_rule.rb +26 -0
- data/examples/text_parser_comprehensive_test.rb +333 -0
- data/examples/text_parser_test_with_comments.rb +146 -0
- data/kumi-parser.gemspec +45 -0
- data/lib/kumi/parser/base.rb +51 -0
- data/lib/kumi/parser/direct_parser.rb +502 -0
- data/lib/kumi/parser/error_extractor.rb +89 -0
- data/lib/kumi/parser/errors.rb +40 -0
- data/lib/kumi/parser/smart_tokenizer.rb +287 -0
- data/lib/kumi/parser/syntax_validator.rb +21 -0
- data/lib/kumi/parser/text_parser/api.rb +60 -0
- data/lib/kumi/parser/text_parser.rb +38 -0
- data/lib/kumi/parser/token.rb +84 -0
- data/lib/kumi/parser/token_metadata.rb +370 -0
- data/lib/kumi/parser/version.rb +7 -0
- data/lib/kumi/text_parser.rb +40 -0
- data/lib/kumi/text_schema.rb +31 -0
- data/lib/kumi-parser.rb +19 -0
- metadata +26 -2
@@ -0,0 +1,146 @@
|
|
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.attributes.count}"
|
74
|
+
ast.attributes.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
|
data/kumi-parser.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/kumi/parser/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'kumi-parser'
|
7
|
+
spec.version = Kumi::Parser::VERSION
|
8
|
+
spec.authors = ['Kumi Team']
|
9
|
+
spec.email = ['dev@kumi.ai']
|
10
|
+
|
11
|
+
spec.summary = 'Text parser for Kumi'
|
12
|
+
spec.description = 'Allows Kumi schemas to be written as plain text with syntax validation and editor integration.'
|
13
|
+
spec.homepage = 'https://github.com/amuta/kumi-parser'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = '>= 3.0.0'
|
16
|
+
|
17
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/amuta/kumi-parser'
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/amuta/kumi-parser/blob/main/CHANGELOG.md'
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
(File.expand_path(f) == __FILE__) ||
|
26
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) ||
|
27
|
+
f.end_with?('.gem')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
34
|
+
# Dependencies
|
35
|
+
spec.add_dependency 'kumi', '~> 0.0.7'
|
36
|
+
spec.add_dependency 'parslet', '~> 2.0'
|
37
|
+
spec.add_dependency 'zeitwerk', '~> 2.6'
|
38
|
+
|
39
|
+
# Development dependencies
|
40
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
41
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
42
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 1.21'
|
44
|
+
spec.add_development_dependency 'simplecov', '~> 0.22'
|
45
|
+
end
|
@@ -0,0 +1,51 @@
|
|
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
|