kpeg 1.3.1 → 1.3.2
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/Rakefile +5 -13
- data/kpeg.gemspec +16 -22
- data/lib/kpeg/compiled_parser.rb +12 -21
- data/lib/kpeg/format_parser.kpeg +1 -1
- data/lib/kpeg/format_parser.rb +66 -47
- data/lib/kpeg/position.rb +52 -22
- data/lib/kpeg/string_escape.rb +64 -43
- data/test/test_kpeg.rb +9 -9
- data/test/test_kpeg_code_generator.rb +1 -1
- data/test/test_kpeg_compiled_parser.rb +36 -7
- metadata +18 -13
- data/.hoeignore +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abed16806bd433f58dd9911eb8c68f2d50e4a56af15b4e2a7f4eba333727a969
|
|
4
|
+
data.tar.gz: 28e547321489a71fa5fc8058d9b236123d1e4da143fe6e7d4f437a8f8bd2c27f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96523e0c2694a0f5e5af682e22ecbebf268d40add7dd97e25db90725c686982d220fb77c22962bf21025cbddfe570763c01b3720304a8b57d99508e9eab0d00a
|
|
7
|
+
data.tar.gz: 94716fa06ffb6e5a9322753ae60de879ad4aab8bdeb854590e9b5a201cb73b35aeec7378a5907b38e8571433586f28c323251a66c274368ce687f4994614b041
|
data/Rakefile
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
# -*- ruby -*-
|
|
2
2
|
|
|
3
3
|
require 'rubygems'
|
|
4
|
-
require 'hoe'
|
|
5
|
-
|
|
6
|
-
Hoe.plugin :gemspec
|
|
7
|
-
Hoe.plugin :git
|
|
8
|
-
Hoe.plugin :minitest
|
|
9
|
-
Hoe.plugin :travis
|
|
10
|
-
|
|
11
|
-
Hoe.spec 'kpeg' do
|
|
12
|
-
self.readme_file = "README.rdoc"
|
|
13
|
-
developer 'Evan Phoenix', 'evan@fallingsnow.net'
|
|
14
|
-
|
|
15
|
-
dependency 'minitest', '~> 5.0', :dev
|
|
16
|
-
end
|
|
17
4
|
|
|
18
5
|
task :test => :parser
|
|
19
6
|
|
|
@@ -39,9 +26,14 @@ PARSER_FILES.map do |parser_file|
|
|
|
39
26
|
file parser_file => 'lib/kpeg/compiled_parser.rb'
|
|
40
27
|
file parser_file => 'lib/kpeg/code_generator.rb'
|
|
41
28
|
file parser_file => 'lib/kpeg/position.rb'
|
|
29
|
+
file parser_file => parser_file.sub(/\.rb$/, '.kpeg')
|
|
42
30
|
end
|
|
43
31
|
|
|
44
32
|
desc "build the parser"
|
|
45
33
|
task :parser => PARSER_FILES
|
|
46
34
|
|
|
35
|
+
task :gem do
|
|
36
|
+
sh "gem build"
|
|
37
|
+
end
|
|
38
|
+
|
|
47
39
|
# vim: syntax=ruby
|
data/kpeg.gemspec
CHANGED
|
@@ -1,42 +1,36 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: kpeg 1.0.0.20140103162640 ruby lib
|
|
3
2
|
|
|
4
3
|
Gem::Specification.new do |s|
|
|
5
4
|
s.name = "kpeg"
|
|
6
|
-
s.version = "1.
|
|
5
|
+
s.version = "1.3.2"
|
|
7
6
|
|
|
8
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
8
|
+
s.metadata = { "bug_tracker_uri" => "https://github.com/evanphx/kpeg/issues", "homepage_uri" => "https://github.com/evanphx/kpeg" } if s.respond_to? :metadata=
|
|
9
|
+
s.require_paths = ["lib"]
|
|
9
10
|
s.authors = ["Evan Phoenix"]
|
|
10
|
-
s.date = "
|
|
11
|
+
s.date = "2022-11-02"
|
|
11
12
|
s.description = "KPeg is a simple PEG library for Ruby. It provides an API as well as native\ngrammar to build the grammar.\n\nKPeg strives to provide a simple, powerful API without being too exotic.\n\nKPeg supports direct left recursion of rules via the\n{OMeta memoization}[http://www.vpri.org/pdf/tr2008003_experimenting.pdf] trick."
|
|
12
13
|
s.email = ["evan@fallingsnow.net"]
|
|
13
14
|
s.executables = ["kpeg"]
|
|
14
|
-
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc", "examples/phone_number/README.md", "examples/upper/README.md"]
|
|
15
|
-
s.files = [".autotest", "
|
|
15
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc", "examples/phone_number/README.md", "examples/tiny_markdown/sample.md", "examples/upper/README.md"]
|
|
16
|
+
s.files = [".autotest", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.rdoc", "Rakefile", "bin/kpeg", "examples/calculator/calculator.kpeg", "examples/calculator/calculator.rb", "examples/foreign_reference/literals.kpeg", "examples/foreign_reference/matcher.kpeg", "examples/foreign_reference/matcher.rb", "examples/lua_string/driver.rb", "examples/lua_string/lua_string.kpeg", "examples/lua_string/lua_string.kpeg.rb", "examples/phone_number/README.md", "examples/phone_number/phone_number.kpeg", "examples/phone_number/phone_number.rb", "examples/tiny_markdown/Rakefile", "examples/tiny_markdown/driver.rb", "examples/tiny_markdown/node.rb", "examples/tiny_markdown/sample.md", "examples/tiny_markdown/tiny_markdown.kpeg", "examples/tiny_markdown/tiny_markdown.kpeg.rb", "examples/upper/README.md", "examples/upper/upper.kpeg", "examples/upper/upper.rb", "kpeg.gemspec", "lib/hoe/kpeg.rb", "lib/kpeg.rb", "lib/kpeg/code_generator.rb", "lib/kpeg/compiled_parser.rb", "lib/kpeg/format_parser.kpeg", "lib/kpeg/format_parser.rb", "lib/kpeg/grammar.rb", "lib/kpeg/grammar_renderer.rb", "lib/kpeg/match.rb", "lib/kpeg/parser.rb", "lib/kpeg/position.rb", "lib/kpeg/string_escape.kpeg", "lib/kpeg/string_escape.rb", "test/inputs/comments.kpeg", "test/test_kpeg.rb", "test/test_kpeg_code_generator.rb", "test/test_kpeg_compiled_parser.rb", "test/test_kpeg_format.rb", "test/test_kpeg_format_parser_round_trip.rb", "test/test_kpeg_grammar.rb", "test/test_kpeg_grammar_renderer.rb", "test/test_kpeg_string_escape.rb", "vim/syntax_kpeg/ftdetect/kpeg.vim", "vim/syntax_kpeg/syntax/kpeg.vim"]
|
|
16
17
|
s.homepage = "https://github.com/evanphx/kpeg"
|
|
17
|
-
s.licenses = ["
|
|
18
|
+
s.licenses = ["BSD-3-Clause"]
|
|
18
19
|
s.rdoc_options = ["--main", "README.rdoc"]
|
|
19
|
-
s.
|
|
20
|
-
s.rubyforge_project = "kpeg"
|
|
21
|
-
s.rubygems_version = "2.1.10"
|
|
20
|
+
s.rubygems_version = "3.3.7"
|
|
22
21
|
s.summary = "KPeg is a simple PEG library for Ruby"
|
|
23
|
-
s.test_files = ["test/test_kpeg.rb", "test/test_kpeg_code_generator.rb", "test/test_kpeg_compiled_parser.rb", "test/test_kpeg_format.rb", "test/test_kpeg_format_parser_round_trip.rb", "test/test_kpeg_grammar.rb", "test/test_kpeg_grammar_renderer.rb", "test/test_kpeg_string_escape.rb"]
|
|
24
22
|
|
|
25
23
|
if s.respond_to? :specification_version then
|
|
26
24
|
s.specification_version = 4
|
|
25
|
+
end
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
else
|
|
33
|
-
s.add_dependency(%q<minitest>, ["~> 5.2"])
|
|
34
|
-
s.add_dependency(%q<rdoc>, ["~> 4.0"])
|
|
35
|
-
s.add_dependency(%q<hoe>, ["~> 3.7"])
|
|
36
|
-
end
|
|
27
|
+
if s.respond_to? :add_runtime_dependency then
|
|
28
|
+
s.add_development_dependency(%q<minitest>, ["~> 5.16"])
|
|
29
|
+
s.add_development_dependency(%q<rdoc>, [">= 4.0", "< 7"])
|
|
30
|
+
s.add_development_dependency(%q<rake>, [">= 0.8", "< 15.0"])
|
|
37
31
|
else
|
|
38
|
-
s.add_dependency(%q<minitest>, ["~> 5.
|
|
39
|
-
s.add_dependency(%q<rdoc>, ["
|
|
40
|
-
s.add_dependency(%q<
|
|
32
|
+
s.add_dependency(%q<minitest>, ["~> 5.16"])
|
|
33
|
+
s.add_dependency(%q<rdoc>, [">= 4.0", "< 7"])
|
|
34
|
+
s.add_dependency(%q<rake>, [">= 0.8", "< 15.0"])
|
|
41
35
|
end
|
|
42
36
|
end
|
data/lib/kpeg/compiled_parser.rb
CHANGED
|
@@ -52,6 +52,7 @@ module KPeg
|
|
|
52
52
|
@string = string
|
|
53
53
|
@string_size = string ? string.size : 0
|
|
54
54
|
@pos = pos
|
|
55
|
+
@position_line_offsets = nil
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def show_pos
|
|
@@ -76,30 +77,22 @@ module KPeg
|
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
def failure_caret
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
line = lines[l-1]
|
|
83
|
-
"#{line}\n#{' ' * (c - 1)}^"
|
|
80
|
+
p = current_pos_info @failing_rule_offset
|
|
81
|
+
"#{p.line.chomp}\n#{' ' * (p.col - 1)}^"
|
|
84
82
|
end
|
|
85
83
|
|
|
86
84
|
def failure_character
|
|
87
|
-
|
|
88
|
-
c = current_column @failing_rule_offset
|
|
89
|
-
lines[l-1][c-1, 1]
|
|
85
|
+
current_character @failing_rule_offset
|
|
90
86
|
end
|
|
91
87
|
|
|
92
88
|
def failure_oneline
|
|
93
|
-
|
|
94
|
-
c = current_column @failing_rule_offset
|
|
95
|
-
|
|
96
|
-
char = lines[l-1][c-1, 1]
|
|
89
|
+
p = current_pos_info @failing_rule_offset
|
|
97
90
|
|
|
98
91
|
if @failed_rule.kind_of? Symbol
|
|
99
92
|
info = self.class::Rules[@failed_rule]
|
|
100
|
-
"@#{
|
|
93
|
+
"@#{p.lno}:#{p.col} failed rule '#{info.name}', got '#{p.char}'"
|
|
101
94
|
else
|
|
102
|
-
"@#{
|
|
95
|
+
"@#{p.lno}:#{p.col} failed rule '#{@failed_rule}', got '#{p.char}'"
|
|
103
96
|
end
|
|
104
97
|
end
|
|
105
98
|
|
|
@@ -112,10 +105,9 @@ module KPeg
|
|
|
112
105
|
|
|
113
106
|
def show_error(io=STDOUT)
|
|
114
107
|
error_pos = @failing_rule_offset
|
|
115
|
-
|
|
116
|
-
col_no = current_column(error_pos)
|
|
108
|
+
p = current_pos_info(error_pos)
|
|
117
109
|
|
|
118
|
-
io.puts "On line #{
|
|
110
|
+
io.puts "On line #{p.lno}, column #{p.col}:"
|
|
119
111
|
|
|
120
112
|
if @failed_rule.kind_of? Symbol
|
|
121
113
|
info = self.class::Rules[@failed_rule]
|
|
@@ -124,10 +116,9 @@ module KPeg
|
|
|
124
116
|
io.puts "Failed to match rule '#{@failed_rule}'"
|
|
125
117
|
end
|
|
126
118
|
|
|
127
|
-
io.puts "Got: #{
|
|
128
|
-
|
|
129
|
-
io.
|
|
130
|
-
io.print(" " * (col_no + 3))
|
|
119
|
+
io.puts "Got: #{p.char.inspect}"
|
|
120
|
+
io.puts "=> #{p.line}"
|
|
121
|
+
io.print(" " * (p.col + 2))
|
|
131
122
|
io.puts "^"
|
|
132
123
|
end
|
|
133
124
|
|
data/lib/kpeg/format_parser.kpeg
CHANGED
|
@@ -53,7 +53,7 @@ require 'kpeg/grammar'
|
|
|
53
53
|
| "x" < /[a-f\d]{2}/i > { [text.to_i(16)].pack("U") }
|
|
54
54
|
# TODO use /\h{2}/ after 1.8 support is dropped
|
|
55
55
|
dbl_seq = < /[^\\"]+/ > { text }
|
|
56
|
-
dbl_not_quote = ("\\" dbl_escapes
|
|
56
|
+
dbl_not_quote = ("\\" dbl_escapes | dbl_seq)*:ary { Array(ary) }
|
|
57
57
|
dbl_string = "\"" dbl_not_quote:s "\"" { @g.str(s.join) }
|
|
58
58
|
sgl_escape_quote = "\\'" { "'" }
|
|
59
59
|
sgl_seq = < /[^']/ > { text }
|
data/lib/kpeg/format_parser.rb
CHANGED
|
@@ -20,45 +20,75 @@ class KPeg::FormatParser
|
|
|
20
20
|
attr_accessor :result, :pos
|
|
21
21
|
|
|
22
22
|
def current_column(target=pos)
|
|
23
|
-
if c = string.rindex("\n", target-1)
|
|
24
|
-
return target - c
|
|
23
|
+
if string[target] == "\n" && (c = string.rindex("\n", target-1) || -1)
|
|
24
|
+
return target - c
|
|
25
|
+
elsif c = string.rindex("\n", target)
|
|
26
|
+
return target - c
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
target + 1
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
def position_line_offsets
|
|
33
|
+
unless @position_line_offsets
|
|
34
|
+
@position_line_offsets = []
|
|
35
|
+
total = 0
|
|
36
|
+
string.each_line do |line|
|
|
37
|
+
total += line.size
|
|
38
|
+
@position_line_offsets << total
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
@position_line_offsets
|
|
42
|
+
end
|
|
43
|
+
|
|
30
44
|
if [].respond_to? :bsearch_index
|
|
31
45
|
def current_line(target=pos)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
total = 0
|
|
35
|
-
string.each_line do |line|
|
|
36
|
-
total += line.size
|
|
37
|
-
@line_offsets << total
|
|
38
|
-
end
|
|
46
|
+
if line = position_line_offsets.bsearch_index {|x| x > target }
|
|
47
|
+
return line + 1
|
|
39
48
|
end
|
|
40
|
-
|
|
41
|
-
@line_offsets.bsearch_index {|x| x >= target } + 1 || -1
|
|
49
|
+
raise "Target position #{target} is outside of string"
|
|
42
50
|
end
|
|
43
51
|
else
|
|
44
52
|
def current_line(target=pos)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
string.each_line do |line|
|
|
49
|
-
cur_line += 1
|
|
50
|
-
cur_offset += line.size
|
|
51
|
-
return cur_line if cur_offset >= target
|
|
53
|
+
if line = position_line_offsets.index {|x| x > target }
|
|
54
|
+
return line + 1
|
|
52
55
|
end
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
raise "Target position #{target} is outside of string"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def current_character(target=pos)
|
|
62
|
+
if target < 0 || target >= string.size
|
|
63
|
+
raise "Target position #{target} is outside of string"
|
|
55
64
|
end
|
|
65
|
+
string[target, 1]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
KpegPosInfo = Struct.new(:pos, :lno, :col, :line, :char)
|
|
69
|
+
|
|
70
|
+
def current_pos_info(target=pos)
|
|
71
|
+
l = current_line target
|
|
72
|
+
c = current_column target
|
|
73
|
+
ln = get_line(l-1)
|
|
74
|
+
chr = string[target,1]
|
|
75
|
+
KpegPosInfo.new(target, l, c, ln, chr)
|
|
56
76
|
end
|
|
57
77
|
|
|
58
78
|
def lines
|
|
59
|
-
lines
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
string.lines
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def get_line(no)
|
|
83
|
+
loff = position_line_offsets
|
|
84
|
+
if no < 0
|
|
85
|
+
raise "Line No is out of range: #{no} < 0"
|
|
86
|
+
elsif no >= loff.size
|
|
87
|
+
raise "Line No is out of range: #{no} >= #{loff.size}"
|
|
88
|
+
end
|
|
89
|
+
lend = loff[no]-1
|
|
90
|
+
lstart = no > 0 ? loff[no-1] : 0
|
|
91
|
+
string[lstart..lend]
|
|
62
92
|
end
|
|
63
93
|
|
|
64
94
|
|
|
@@ -72,6 +102,7 @@ class KPeg::FormatParser
|
|
|
72
102
|
@string = string
|
|
73
103
|
@string_size = string ? string.size : 0
|
|
74
104
|
@pos = pos
|
|
105
|
+
@position_line_offsets = nil
|
|
75
106
|
end
|
|
76
107
|
|
|
77
108
|
def show_pos
|
|
@@ -96,30 +127,22 @@ class KPeg::FormatParser
|
|
|
96
127
|
end
|
|
97
128
|
|
|
98
129
|
def failure_caret
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
line = lines[l-1]
|
|
103
|
-
"#{line}\n#{' ' * (c - 1)}^"
|
|
130
|
+
p = current_pos_info @failing_rule_offset
|
|
131
|
+
"#{p.line.chomp}\n#{' ' * (p.col - 1)}^"
|
|
104
132
|
end
|
|
105
133
|
|
|
106
134
|
def failure_character
|
|
107
|
-
|
|
108
|
-
c = current_column @failing_rule_offset
|
|
109
|
-
lines[l-1][c-1, 1]
|
|
135
|
+
current_character @failing_rule_offset
|
|
110
136
|
end
|
|
111
137
|
|
|
112
138
|
def failure_oneline
|
|
113
|
-
|
|
114
|
-
c = current_column @failing_rule_offset
|
|
115
|
-
|
|
116
|
-
char = lines[l-1][c-1, 1]
|
|
139
|
+
p = current_pos_info @failing_rule_offset
|
|
117
140
|
|
|
118
141
|
if @failed_rule.kind_of? Symbol
|
|
119
142
|
info = self.class::Rules[@failed_rule]
|
|
120
|
-
"@#{
|
|
143
|
+
"@#{p.lno}:#{p.col} failed rule '#{info.name}', got '#{p.char}'"
|
|
121
144
|
else
|
|
122
|
-
"@#{
|
|
145
|
+
"@#{p.lno}:#{p.col} failed rule '#{@failed_rule}', got '#{p.char}'"
|
|
123
146
|
end
|
|
124
147
|
end
|
|
125
148
|
|
|
@@ -132,10 +155,9 @@ class KPeg::FormatParser
|
|
|
132
155
|
|
|
133
156
|
def show_error(io=STDOUT)
|
|
134
157
|
error_pos = @failing_rule_offset
|
|
135
|
-
|
|
136
|
-
col_no = current_column(error_pos)
|
|
158
|
+
p = current_pos_info(error_pos)
|
|
137
159
|
|
|
138
|
-
io.puts "On line #{
|
|
160
|
+
io.puts "On line #{p.lno}, column #{p.col}:"
|
|
139
161
|
|
|
140
162
|
if @failed_rule.kind_of? Symbol
|
|
141
163
|
info = self.class::Rules[@failed_rule]
|
|
@@ -144,10 +166,9 @@ class KPeg::FormatParser
|
|
|
144
166
|
io.puts "Failed to match rule '#{@failed_rule}'"
|
|
145
167
|
end
|
|
146
168
|
|
|
147
|
-
io.puts "Got: #{
|
|
148
|
-
|
|
149
|
-
io.
|
|
150
|
-
io.print(" " * (col_no + 3))
|
|
169
|
+
io.puts "Got: #{p.char.inspect}"
|
|
170
|
+
io.puts "=> #{p.line}"
|
|
171
|
+
io.print(" " * (p.col + 2))
|
|
151
172
|
io.puts "^"
|
|
152
173
|
end
|
|
153
174
|
|
|
@@ -910,7 +931,7 @@ class KPeg::FormatParser
|
|
|
910
931
|
return _tmp
|
|
911
932
|
end
|
|
912
933
|
|
|
913
|
-
# dbl_not_quote = ("\\" dbl_escapes
|
|
934
|
+
# dbl_not_quote = ("\\" dbl_escapes | dbl_seq)*:ary { Array(ary) }
|
|
914
935
|
def _dbl_not_quote
|
|
915
936
|
|
|
916
937
|
_save = self.pos
|
|
@@ -929,7 +950,6 @@ class KPeg::FormatParser
|
|
|
929
950
|
break
|
|
930
951
|
end
|
|
931
952
|
_tmp = apply(:_dbl_escapes)
|
|
932
|
-
s = @result
|
|
933
953
|
unless _tmp
|
|
934
954
|
self.pos = _save3
|
|
935
955
|
end
|
|
@@ -939,7 +959,6 @@ class KPeg::FormatParser
|
|
|
939
959
|
break if _tmp
|
|
940
960
|
self.pos = _save2
|
|
941
961
|
_tmp = apply(:_dbl_seq)
|
|
942
|
-
s = @result
|
|
943
962
|
break if _tmp
|
|
944
963
|
self.pos = _save2
|
|
945
964
|
break
|
|
@@ -3158,7 +3177,7 @@ class KPeg::FormatParser
|
|
|
3158
3177
|
Rules[:_dbl_escapes] = rule_info("dbl_escapes", "(\"n\" { \"\\n\" } | \"s\" { \" \" } | \"r\" { \"\\r\" } | \"t\" { \"\\t\" } | \"v\" { \"\\v\" } | \"f\" { \"\\f\" } | \"b\" { \"\\b\" } | \"a\" { \"\\a\" } | \"e\" { \"\\e\" } | \"\\\\\" { \"\\\\\" } | \"\\\"\" { \"\\\"\" } | num_escapes | < . > { text })")
|
|
3159
3178
|
Rules[:_num_escapes] = rule_info("num_escapes", "(< /[0-7]{1,3}/ > { [text.to_i(8)].pack(\"U\") } | \"x\" < /[a-f\\d]{2}/i > { [text.to_i(16)].pack(\"U\") })")
|
|
3160
3179
|
Rules[:_dbl_seq] = rule_info("dbl_seq", "< /[^\\\\\"]+/ > { text }")
|
|
3161
|
-
Rules[:_dbl_not_quote] = rule_info("dbl_not_quote", "(\"\\\\\" dbl_escapes
|
|
3180
|
+
Rules[:_dbl_not_quote] = rule_info("dbl_not_quote", "(\"\\\\\" dbl_escapes | dbl_seq)*:ary { Array(ary) }")
|
|
3162
3181
|
Rules[:_dbl_string] = rule_info("dbl_string", "\"\\\"\" dbl_not_quote:s \"\\\"\" { @g.str(s.join) }")
|
|
3163
3182
|
Rules[:_sgl_escape_quote] = rule_info("sgl_escape_quote", "\"\\\\'\" { \"'\" }")
|
|
3164
3183
|
Rules[:_sgl_seq] = rule_info("sgl_seq", "< /[^']/ > { text }")
|
data/lib/kpeg/position.rb
CHANGED
|
@@ -3,45 +3,75 @@ module KPeg
|
|
|
3
3
|
# STANDALONE START
|
|
4
4
|
|
|
5
5
|
def current_column(target=pos)
|
|
6
|
-
if c = string.rindex("\n", target-1)
|
|
7
|
-
return target - c
|
|
6
|
+
if string[target] == "\n" && (c = string.rindex("\n", target-1) || -1)
|
|
7
|
+
return target - c
|
|
8
|
+
elsif c = string.rindex("\n", target)
|
|
9
|
+
return target - c
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
target + 1
|
|
11
13
|
end
|
|
12
14
|
|
|
15
|
+
def position_line_offsets
|
|
16
|
+
unless @position_line_offsets
|
|
17
|
+
@position_line_offsets = []
|
|
18
|
+
total = 0
|
|
19
|
+
string.each_line do |line|
|
|
20
|
+
total += line.size
|
|
21
|
+
@position_line_offsets << total
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
@position_line_offsets
|
|
25
|
+
end
|
|
26
|
+
|
|
13
27
|
if [].respond_to? :bsearch_index
|
|
14
28
|
def current_line(target=pos)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
total = 0
|
|
18
|
-
string.each_line do |line|
|
|
19
|
-
total += line.size
|
|
20
|
-
@line_offsets << total
|
|
21
|
-
end
|
|
29
|
+
if line = position_line_offsets.bsearch_index {|x| x > target }
|
|
30
|
+
return line + 1
|
|
22
31
|
end
|
|
23
|
-
|
|
24
|
-
@line_offsets.bsearch_index {|x| x >= target } + 1 || -1
|
|
32
|
+
raise "Target position #{target} is outside of string"
|
|
25
33
|
end
|
|
26
34
|
else
|
|
27
35
|
def current_line(target=pos)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
string.each_line do |line|
|
|
32
|
-
cur_line += 1
|
|
33
|
-
cur_offset += line.size
|
|
34
|
-
return cur_line if cur_offset >= target
|
|
36
|
+
if line = position_line_offsets.index {|x| x > target }
|
|
37
|
+
return line + 1
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
raise "Target position #{target} is outside of string"
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
43
|
|
|
44
|
+
def current_character(target=pos)
|
|
45
|
+
if target < 0 || target >= string.size
|
|
46
|
+
raise "Target position #{target} is outside of string"
|
|
47
|
+
end
|
|
48
|
+
string[target, 1]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
KpegPosInfo = Struct.new(:pos, :lno, :col, :line, :char)
|
|
52
|
+
|
|
53
|
+
def current_pos_info(target=pos)
|
|
54
|
+
l = current_line target
|
|
55
|
+
c = current_column target
|
|
56
|
+
ln = get_line(l-1)
|
|
57
|
+
chr = string[target,1]
|
|
58
|
+
KpegPosInfo.new(target, l, c, ln, chr)
|
|
59
|
+
end
|
|
60
|
+
|
|
41
61
|
def lines
|
|
42
|
-
lines
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
string.lines
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_line(no)
|
|
66
|
+
loff = position_line_offsets
|
|
67
|
+
if no < 0
|
|
68
|
+
raise "Line No is out of range: #{no} < 0"
|
|
69
|
+
elsif no >= loff.size
|
|
70
|
+
raise "Line No is out of range: #{no} >= #{loff.size}"
|
|
71
|
+
end
|
|
72
|
+
lend = loff[no]-1
|
|
73
|
+
lstart = no > 0 ? loff[no-1] : 0
|
|
74
|
+
string[lstart..lend]
|
|
45
75
|
end
|
|
46
76
|
|
|
47
77
|
# STANDALONE END
|
data/lib/kpeg/string_escape.rb
CHANGED
|
@@ -28,45 +28,75 @@ class KPeg::StringEscape
|
|
|
28
28
|
attr_accessor :result, :pos
|
|
29
29
|
|
|
30
30
|
def current_column(target=pos)
|
|
31
|
-
if c = string.rindex("\n", target-1)
|
|
32
|
-
return target - c
|
|
31
|
+
if string[target] == "\n" && (c = string.rindex("\n", target-1) || -1)
|
|
32
|
+
return target - c
|
|
33
|
+
elsif c = string.rindex("\n", target)
|
|
34
|
+
return target - c
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
target + 1
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
def position_line_offsets
|
|
41
|
+
unless @position_line_offsets
|
|
42
|
+
@position_line_offsets = []
|
|
43
|
+
total = 0
|
|
44
|
+
string.each_line do |line|
|
|
45
|
+
total += line.size
|
|
46
|
+
@position_line_offsets << total
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
@position_line_offsets
|
|
50
|
+
end
|
|
51
|
+
|
|
38
52
|
if [].respond_to? :bsearch_index
|
|
39
53
|
def current_line(target=pos)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
total = 0
|
|
43
|
-
string.each_line do |line|
|
|
44
|
-
total += line.size
|
|
45
|
-
@line_offsets << total
|
|
46
|
-
end
|
|
54
|
+
if line = position_line_offsets.bsearch_index {|x| x > target }
|
|
55
|
+
return line + 1
|
|
47
56
|
end
|
|
48
|
-
|
|
49
|
-
@line_offsets.bsearch_index {|x| x >= target } + 1 || -1
|
|
57
|
+
raise "Target position #{target} is outside of string"
|
|
50
58
|
end
|
|
51
59
|
else
|
|
52
60
|
def current_line(target=pos)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
string.each_line do |line|
|
|
57
|
-
cur_line += 1
|
|
58
|
-
cur_offset += line.size
|
|
59
|
-
return cur_line if cur_offset >= target
|
|
61
|
+
if line = position_line_offsets.index {|x| x > target }
|
|
62
|
+
return line + 1
|
|
60
63
|
end
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
raise "Target position #{target} is outside of string"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def current_character(target=pos)
|
|
70
|
+
if target < 0 || target >= string.size
|
|
71
|
+
raise "Target position #{target} is outside of string"
|
|
63
72
|
end
|
|
73
|
+
string[target, 1]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
KpegPosInfo = Struct.new(:pos, :lno, :col, :line, :char)
|
|
77
|
+
|
|
78
|
+
def current_pos_info(target=pos)
|
|
79
|
+
l = current_line target
|
|
80
|
+
c = current_column target
|
|
81
|
+
ln = get_line(l-1)
|
|
82
|
+
chr = string[target,1]
|
|
83
|
+
KpegPosInfo.new(target, l, c, ln, chr)
|
|
64
84
|
end
|
|
65
85
|
|
|
66
86
|
def lines
|
|
67
|
-
lines
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
string.lines
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def get_line(no)
|
|
91
|
+
loff = position_line_offsets
|
|
92
|
+
if no < 0
|
|
93
|
+
raise "Line No is out of range: #{no} < 0"
|
|
94
|
+
elsif no >= loff.size
|
|
95
|
+
raise "Line No is out of range: #{no} >= #{loff.size}"
|
|
96
|
+
end
|
|
97
|
+
lend = loff[no]-1
|
|
98
|
+
lstart = no > 0 ? loff[no-1] : 0
|
|
99
|
+
string[lstart..lend]
|
|
70
100
|
end
|
|
71
101
|
|
|
72
102
|
|
|
@@ -80,6 +110,7 @@ class KPeg::StringEscape
|
|
|
80
110
|
@string = string
|
|
81
111
|
@string_size = string ? string.size : 0
|
|
82
112
|
@pos = pos
|
|
113
|
+
@position_line_offsets = nil
|
|
83
114
|
end
|
|
84
115
|
|
|
85
116
|
def show_pos
|
|
@@ -104,30 +135,22 @@ class KPeg::StringEscape
|
|
|
104
135
|
end
|
|
105
136
|
|
|
106
137
|
def failure_caret
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
line = lines[l-1]
|
|
111
|
-
"#{line}\n#{' ' * (c - 1)}^"
|
|
138
|
+
p = current_pos_info @failing_rule_offset
|
|
139
|
+
"#{p.line.chomp}\n#{' ' * (p.col - 1)}^"
|
|
112
140
|
end
|
|
113
141
|
|
|
114
142
|
def failure_character
|
|
115
|
-
|
|
116
|
-
c = current_column @failing_rule_offset
|
|
117
|
-
lines[l-1][c-1, 1]
|
|
143
|
+
current_character @failing_rule_offset
|
|
118
144
|
end
|
|
119
145
|
|
|
120
146
|
def failure_oneline
|
|
121
|
-
|
|
122
|
-
c = current_column @failing_rule_offset
|
|
123
|
-
|
|
124
|
-
char = lines[l-1][c-1, 1]
|
|
147
|
+
p = current_pos_info @failing_rule_offset
|
|
125
148
|
|
|
126
149
|
if @failed_rule.kind_of? Symbol
|
|
127
150
|
info = self.class::Rules[@failed_rule]
|
|
128
|
-
"@#{
|
|
151
|
+
"@#{p.lno}:#{p.col} failed rule '#{info.name}', got '#{p.char}'"
|
|
129
152
|
else
|
|
130
|
-
"@#{
|
|
153
|
+
"@#{p.lno}:#{p.col} failed rule '#{@failed_rule}', got '#{p.char}'"
|
|
131
154
|
end
|
|
132
155
|
end
|
|
133
156
|
|
|
@@ -140,10 +163,9 @@ class KPeg::StringEscape
|
|
|
140
163
|
|
|
141
164
|
def show_error(io=STDOUT)
|
|
142
165
|
error_pos = @failing_rule_offset
|
|
143
|
-
|
|
144
|
-
col_no = current_column(error_pos)
|
|
166
|
+
p = current_pos_info(error_pos)
|
|
145
167
|
|
|
146
|
-
io.puts "On line #{
|
|
168
|
+
io.puts "On line #{p.lno}, column #{p.col}:"
|
|
147
169
|
|
|
148
170
|
if @failed_rule.kind_of? Symbol
|
|
149
171
|
info = self.class::Rules[@failed_rule]
|
|
@@ -152,10 +174,9 @@ class KPeg::StringEscape
|
|
|
152
174
|
io.puts "Failed to match rule '#{@failed_rule}'"
|
|
153
175
|
end
|
|
154
176
|
|
|
155
|
-
io.puts "Got: #{
|
|
156
|
-
|
|
157
|
-
io.
|
|
158
|
-
io.print(" " * (col_no + 3))
|
|
177
|
+
io.puts "Got: #{p.char.inspect}"
|
|
178
|
+
io.puts "=> #{p.line}"
|
|
179
|
+
io.print(" " * (p.col + 2))
|
|
159
180
|
io.puts "^"
|
|
160
181
|
end
|
|
161
182
|
|
data/test/test_kpeg.rb
CHANGED
|
@@ -22,7 +22,7 @@ class TestKPeg < Minitest::Test
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
assert_match KPeg.match("hello", gram), "hello"
|
|
25
|
-
|
|
25
|
+
assert_nil KPeg.match("vador", gram)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def test_reg
|
|
@@ -48,7 +48,7 @@ class TestKPeg < Minitest::Test
|
|
|
48
48
|
|
|
49
49
|
assert_match KPeg.match("hello", gram), "hello"
|
|
50
50
|
assert_match KPeg.match("chicken", gram), "chicken"
|
|
51
|
-
|
|
51
|
+
assert_nil KPeg.match("vador", gram)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def test_maybe
|
|
@@ -78,7 +78,7 @@ class TestKPeg < Minitest::Test
|
|
|
78
78
|
assert_match sm, "run"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
assert_nil KPeg.match("vador", gram)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def test_kleene
|
|
@@ -124,9 +124,9 @@ class TestKPeg < Minitest::Test
|
|
|
124
124
|
assert_match sm, "run"
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
assert_nil KPeg.match("run", gram)
|
|
128
|
+
assert_nil KPeg.match("runrunrunrunrun", gram)
|
|
129
|
+
assert_nil KPeg.match("vador", gram)
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
def test_seq
|
|
@@ -141,8 +141,8 @@ class TestKPeg < Minitest::Test
|
|
|
141
141
|
|
|
142
142
|
assert_equal m.value, ["hello", ", world"]
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
assert_nil KPeg.match("vador", gram)
|
|
145
|
+
assert_nil KPeg.match("hello, vador", gram)
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
def test_andp
|
|
@@ -346,7 +346,7 @@ class TestKPeg < Minitest::Test
|
|
|
346
346
|
parser = KPeg::Parser.new "hello", gram
|
|
347
347
|
m = parser.parse
|
|
348
348
|
|
|
349
|
-
|
|
349
|
+
assert_nil m
|
|
350
350
|
end
|
|
351
351
|
|
|
352
352
|
def test_math_grammar
|
|
@@ -21,11 +21,40 @@ class TestKPegCompiledParser < Minitest::Test
|
|
|
21
21
|
KPeg.compile gram, "CompTestParser", self
|
|
22
22
|
|
|
23
23
|
def test_current_column
|
|
24
|
-
r = TestParser.new "hello\nsir"
|
|
24
|
+
r = TestParser.new "hello\nsir\nand goodbye"
|
|
25
|
+
assert_equal 1, r.current_column(0)
|
|
25
26
|
assert_equal 2, r.current_column(1)
|
|
26
27
|
assert_equal 6, r.current_column(5)
|
|
27
|
-
assert_equal
|
|
28
|
-
assert_equal 4, r.current_column(
|
|
28
|
+
assert_equal 2, r.current_column(7)
|
|
29
|
+
assert_equal 4, r.current_column(9)
|
|
30
|
+
assert_equal 1, r.current_column(10)
|
|
31
|
+
assert_equal 11, r.current_column(20)
|
|
32
|
+
assert_equal 13, r.current_column(22)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_current_line
|
|
36
|
+
r = TestParser.new "hello\nsir\nand goodbye"
|
|
37
|
+
assert_equal 1, r.current_line(0)
|
|
38
|
+
assert_equal 1, r.current_line(1)
|
|
39
|
+
assert_equal 1, r.current_line(5)
|
|
40
|
+
assert_equal 2, r.current_line(7)
|
|
41
|
+
assert_equal 2, r.current_line(9)
|
|
42
|
+
assert_equal 3, r.current_line(10)
|
|
43
|
+
assert_equal 3, r.current_line(20)
|
|
44
|
+
assert_raises { r.current_line(22) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_current_character
|
|
49
|
+
r = TestParser.new "hello\nsir\nand goodbye"
|
|
50
|
+
assert_equal ?h, r.current_character(0)
|
|
51
|
+
assert_equal ?e, r.current_character(1)
|
|
52
|
+
assert_equal ?\n, r.current_character(5)
|
|
53
|
+
assert_equal ?i, r.current_character(7)
|
|
54
|
+
assert_equal ?\n, r.current_character(9)
|
|
55
|
+
assert_equal ?a, r.current_character(10)
|
|
56
|
+
assert_equal ?e, r.current_character(20)
|
|
57
|
+
assert_raises { r.current_character(22) }
|
|
29
58
|
end
|
|
30
59
|
|
|
31
60
|
def test_failed_rule
|
|
@@ -36,7 +65,7 @@ class TestKPegCompiledParser < Minitest::Test
|
|
|
36
65
|
end
|
|
37
66
|
|
|
38
67
|
def test_failure_info
|
|
39
|
-
r = TestParser.new "9"
|
|
68
|
+
r = TestParser.new "9\n1"
|
|
40
69
|
assert !r.parse, "shouldn't parse"
|
|
41
70
|
|
|
42
71
|
expected = "line 1, column 1: failed rule 'letter' = '[a-z]'"
|
|
@@ -45,21 +74,21 @@ class TestKPegCompiledParser < Minitest::Test
|
|
|
45
74
|
end
|
|
46
75
|
|
|
47
76
|
def test_failure_caret
|
|
48
|
-
r = TestParser.new "9"
|
|
77
|
+
r = TestParser.new "9\n1"
|
|
49
78
|
assert !r.parse, "shouldn't parse"
|
|
50
79
|
|
|
51
80
|
assert_equal "9\n^", r.failure_caret
|
|
52
81
|
end
|
|
53
82
|
|
|
54
83
|
def test_failure_character
|
|
55
|
-
r = TestParser.new "9"
|
|
84
|
+
r = TestParser.new "9\n1"
|
|
56
85
|
assert !r.parse, "shouldn't parse"
|
|
57
86
|
|
|
58
87
|
assert_equal "9", r.failure_character
|
|
59
88
|
end
|
|
60
89
|
|
|
61
90
|
def test_failure_oneline
|
|
62
|
-
r = TestParser.new "9"
|
|
91
|
+
r = TestParser.new "9\n1"
|
|
63
92
|
assert !r.parse, "shouldn't parse"
|
|
64
93
|
|
|
65
94
|
expected = "@1:1 failed rule 'letter', got '9'"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kpeg
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evan Phoenix
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-11-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '5.
|
|
19
|
+
version: '5.16'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '5.
|
|
26
|
+
version: '5.16'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rdoc
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -45,19 +45,25 @@ dependencies:
|
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
46
|
version: '7'
|
|
47
47
|
- !ruby/object:Gem::Dependency
|
|
48
|
-
name:
|
|
48
|
+
name: rake
|
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
53
|
+
version: '0.8'
|
|
54
|
+
- - "<"
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '15.0'
|
|
54
57
|
type: :development
|
|
55
58
|
prerelease: false
|
|
56
59
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
60
|
requirements:
|
|
58
|
-
- - "
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '0.8'
|
|
64
|
+
- - "<"
|
|
59
65
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '
|
|
66
|
+
version: '15.0'
|
|
61
67
|
description: |-
|
|
62
68
|
KPeg is a simple PEG library for Ruby. It provides an API as well as native
|
|
63
69
|
grammar to build the grammar.
|
|
@@ -80,7 +86,6 @@ extra_rdoc_files:
|
|
|
80
86
|
- examples/upper/README.md
|
|
81
87
|
files:
|
|
82
88
|
- ".autotest"
|
|
83
|
-
- ".hoeignore"
|
|
84
89
|
- Gemfile
|
|
85
90
|
- History.txt
|
|
86
91
|
- LICENSE
|
|
@@ -135,10 +140,10 @@ files:
|
|
|
135
140
|
- vim/syntax_kpeg/syntax/kpeg.vim
|
|
136
141
|
homepage: https://github.com/evanphx/kpeg
|
|
137
142
|
licenses:
|
|
138
|
-
-
|
|
143
|
+
- BSD-3-Clause
|
|
139
144
|
metadata:
|
|
140
|
-
homepage_uri: https://github.com/evanphx/kpeg
|
|
141
145
|
bug_tracker_uri: https://github.com/evanphx/kpeg/issues
|
|
146
|
+
homepage_uri: https://github.com/evanphx/kpeg
|
|
142
147
|
post_install_message:
|
|
143
148
|
rdoc_options:
|
|
144
149
|
- "--main"
|
|
@@ -156,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
161
|
- !ruby/object:Gem::Version
|
|
157
162
|
version: '0'
|
|
158
163
|
requirements: []
|
|
159
|
-
rubygems_version: 3.
|
|
164
|
+
rubygems_version: 3.3.7
|
|
160
165
|
signing_key:
|
|
161
166
|
specification_version: 4
|
|
162
167
|
summary: KPeg is a simple PEG library for Ruby
|