ebnf 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc55292610eb978d5751361069f3b993d35db3a597442e2027cf0fd2ff886ba5
4
- data.tar.gz: cc74cd0257a36fa3591f54becfdb51dfffbf44662598f2d67c6a36bf4e969e61
3
+ metadata.gz: '08b2411d5c4d34425d00259126e0d6f55c086b2c60c74e8d3ddc6a099a60ec5e'
4
+ data.tar.gz: d8185780e437d3db9c2644d62f51d497b25be130d20b79d63e3101e222180408
5
5
  SHA512:
6
- metadata.gz: bf7c7df32e027a0739b4830651dcff1f4b5186ff2177e1f57b4e952db33619660c4025a29ffc8dba5c7d0f5f5b95f2cbd432a379bc2ffb02d22f7ec6913a48e2
7
- data.tar.gz: 909a8ff438172431054a33fb067198e32d98d5b88fa630978831af4f1dd728f0197512ee1f341667eb3039662db618122d7da5d0cc4cc55838625f685f7d7a9b
6
+ metadata.gz: b972788258b8261d6e59a093268f31be74d3db13535b0db63199aae9ed36b93602d6b8034439152ce62361797eb3990b747d33a55551baa01bd2d1a9aed6bf6f
7
+ data.tar.gz: cc3b0bb1ecd8c0f0e7135989e96bb826d35f1406ecc3482e88a00f77aaf1df0c8ab5c6f98cb11b37c2c83f77b2d9d762f227d20a0bec44a27cbe48616c31f4a4
data/README.md CHANGED
@@ -3,8 +3,9 @@
3
3
  [EBNF][] parser and generic parser generator.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/ebnf.png)](https://badge.fury.io/rb/ebnf)
6
- [![Build Status](https://secure.travis-ci.org/dryruby/ebnf.png?branch=master)](https://travis-ci.org/dryruby/ebnf)
7
- [![Coverage Status](https://coveralls.io/repos/dryruby/ebnf/badge.svg)](https://coveralls.io/r/dryruby/ebnf)
6
+ [![Build Status](https://github.com/dryruby/ebnf/workflows/CI/badge.svg?branch=develop)](https://github.com/dryruby/ebnf/actions?query=workflow%3ACI)
7
+ [![Coverage Status](https://coveralls.io/repos/dryruby/ebnf/badge.svg?branch=develop)](https://coveralls.io/r/dryruby/ebnf?branch=develop)
8
+ [![Gitter chat](https://badges.gitter.im/ruby-rdf/rdf.png)](https://gitter.im/ruby-rdf/rdf)
8
9
 
9
10
  ## Description
10
11
  This is a [Ruby][] implementation of an [EBNF][] and [BNF][] parser and parser generator.
@@ -101,6 +102,8 @@ On a parsing failure, and exception is raised with information that may be usefu
101
102
  The [EBNF][] variant used here is based on [W3C](https://w3.org/) [EBNF][] (see {file:etc/ebnf.ebnf EBNF grammar}) as defined in the
102
103
  [XML 1.0 recommendation](https://www.w3.org/TR/REC-xml/), with minor extensions:
103
104
 
105
+ Note that the grammar includes an optional `[identifer]` in front of rule names, which can be in conflict with the `RANGE` terminal. It is typically not a problem, but if it comes up, try parsing with the `native` parser, add comments or sequences to disambiguate. EBNF does not have beginning of line checks as all whitespace is treated the same, so the common practice of identifying each rule inherently leads to such ambiguity.
106
+
104
107
  The character set for EBNF is UTF-8.
105
108
 
106
109
  The general form of a rule is:
@@ -259,7 +262,8 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo
259
262
  list in the the `README`. Alphabetical order applies.
260
263
  * Do note that in order for us to merge any non-trivial changes (as a rule
261
264
  of thumb, additions larger than about 15 lines of code), we need an
262
- explicit [public domain dedication][PDD] on record from you.
265
+ explicit [public domain dedication][PDD] on record from you,
266
+ which you will be asked to agree to on the first commit to a repo within the organization.
263
267
 
264
268
  ## License
265
269
  This is free and unencumbered public domain software. For more information,
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.0
1
+ 2.2.0
data/bin/ebnf CHANGED
@@ -34,7 +34,7 @@ OPT_ARGS = [
34
34
  ["--prefix", "-p", GetoptLong::REQUIRED_ARGUMENT,"Prefix to use when generating Turtle"],
35
35
  ["--progress", "-v", GetoptLong::NO_ARGUMENT, "Detail on execution"],
36
36
  ["--renumber", GetoptLong::NO_ARGUMENT, "Renumber parsed reules"],
37
- ["--validate", GetoptLong::NO_ARGUMENT, "Validate grammar"],
37
+ ["--validate", GetoptLong::NO_ARGUMENT, "Validate grammar and any generated HTML"],
38
38
  ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"]
39
39
  ]
40
40
  def usage
@@ -67,7 +67,7 @@ opts.each do |opt, arg|
67
67
  end
68
68
  options[:format] = arg.to_sym
69
69
  when '--format'
70
- unless %w(abnf abnfh ebnf html isoebnf isoebnfh rb sxp).include?(arg)
70
+ unless %w(abnf abnfh ebnf html isoebnf isoebnfh rb sxp ttl).include?(arg)
71
71
  STDERR.puts("unrecognized output format #{arg}")
72
72
  usage
73
73
  end
@@ -99,11 +99,11 @@ ebnf.renumber! if options[:renumber]
99
99
 
100
100
  res = case options[:output_format]
101
101
  when :abnf then ebnf.to_s(format: :abnf)
102
- when :abnfh then ebnf.to_html(format: :abnf)
102
+ when :abnfh then ebnf.to_html(format: :abnf, validate: options[:validate])
103
103
  when :ebnf then ebnf.to_s
104
- when :html then ebnf.to_html
104
+ when :html then ebnf.to_html(validate: options[:validate])
105
105
  when :isoebnf then ebnf.to_s(format: :isoebnf)
106
- when :isoebnfh then ebnf.to_html(format: :isoebnf)
106
+ when :isoebnfh then ebnf.to_html(format: :isoebnf, validate: options[:validate])
107
107
  when :sxp then ebnf.to_sxp
108
108
  when :ttl then ebnf.to_ttl(options[:prefix], options[:namespace])
109
109
  when :rb then ebnf.to_ruby(out, grammarFile: ARGV[0], **options)
data/etc/doap.ttl CHANGED
@@ -12,11 +12,18 @@
12
12
  doap:name "ebnf" ;
13
13
  doap:homepage <https://github.com/dryruby/ebnf> ;
14
14
  doap:license <https://unlicense.org/1.0/> ;
15
- doap:shortdesc "EBNF parser and parser generator"@en ;
16
- doap:description "EBNF is a Ruby parser for W3C EBNF and a parser generator for compliant LL(1) grammars."@en ;
15
+ doap:shortdesc "EBNF parser and parser generator in Ruby."@en ;
16
+ doap:description "EBNF is a Ruby parser for W3C EBNF and a parser generator for PEG and LL(1). Also includes parsing modes for ISO EBNF and ABNF."@en ;
17
17
  doap:created "2011-08-29"^^xsd:date ;
18
18
  doap:programming-language "Ruby" ;
19
- doap:implements <http://dbpedia.org/resource/Compiler-compiler> ;
19
+ doap:implements <http://dbpedia.org/resource/Compiler-compiler>,
20
+ <https://en.wikipedia.org/wiki/LL_parser>,
21
+ <https://en.wikipedia.org/wiki/Parsing_expression_grammar>,
22
+ <https://pdos.csail.mit.edu/~baford/packrat/thesis/>,
23
+ <https://www.w3.org/TR/REC-xml/#sec-notation>,
24
+ <https://en.wikipedia.org/wiki/Backus–Naur_form>,
25
+ <https://www.iso.org/standard/26153.html>,
26
+ <https://www.rfc-editor.org/rfc/rfc5234>;
20
27
  doap:category <http://dbpedia.org/resource/Resource_Description_Framework>,
21
28
  <http://dbpedia.org/resource/Ruby_(programming_language)> ;
22
29
  doap:download-page <> ;
@@ -27,7 +34,4 @@
27
34
  doap:maintainer <https://greggkellogg.net/foaf#me> ;
28
35
  doap:documenter <https://greggkellogg.net/foaf#me> ;
29
36
  foaf:maker <https://greggkellogg.net/foaf#me> ;
30
- dc:title "ebnf" ;
31
- dc:description "EBNF is a Ruby parser for W3C EBNF and a parser generator for compliant LL(1) grammars."@en ;
32
- dc:date "2011-08-29"^^xsd:date ;
33
37
  dc:creator <https://greggkellogg.net/foaf#me> .
data/etc/ebnf.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <td>[2]</td>
12
12
  <td><code>declaration</code></td>
13
13
  <td>::=</td>
14
- <td>"@terminals" <code>|</code> <a href="#grammar-production-pass">pass</a></td>
14
+ <td>&quot;@terminals&quot; <code>|</code> <a href="#grammar-production-pass">pass</a></td>
15
15
  </tr>
16
16
  <tr id="grammar-production-rule">
17
17
  <td>[3]</td>
@@ -53,61 +53,24 @@
53
53
  <td>[9]</td>
54
54
  <td><code>primary</code></td>
55
55
  <td>::=</td>
56
- <td><a href="#grammar-production-HEX">HEX</a></td>
57
- </tr>
58
- <tr>
59
- <td>[9]</td>
60
- <td><code></code></td>
61
- <td>|</td>
62
- <td><a href="#grammar-production-SYMBOL">SYMBOL</a></td>
63
- </tr>
64
- <tr>
65
- <td>[9]</td>
66
- <td><code></code></td>
67
- <td>|</td>
68
- <td><a href="#grammar-production-O_RANGE">O_RANGE</a></td>
69
- </tr>
70
- <tr>
71
- <td>[9]</td>
72
- <td><code></code></td>
73
- <td>|</td>
74
- <td><a href="#grammar-production-RANGE">RANGE</a></td>
75
- </tr>
76
- <tr>
77
- <td>[9]</td>
78
- <td><code></code></td>
79
- <td>|</td>
80
- <td><a href="#grammar-production-STRING1">STRING1</a></td>
81
- </tr>
82
- <tr>
83
- <td>[9]</td>
84
- <td><code></code></td>
85
- <td>|</td>
86
- <td><a href="#grammar-production-STRING2">STRING2</a></td>
87
- </tr>
88
- <tr>
89
- <td>[9]</td>
90
- <td><code></code></td>
91
- <td>|</td>
92
- <td><code>(</code> "<code class="grammar-literal">(</code>" <a href="#grammar-production-expression">expression</a> "<code class="grammar-literal">)</code>"<code>)</code> </td>
56
+ <td><a href="#grammar-production-HEX">HEX</a> <code>|</code> <a href="#grammar-production-SYMBOL">SYMBOL</a> <code>|</code> <a href="#grammar-production-O_RANGE">O_RANGE</a> <code>|</code> <a href="#grammar-production-RANGE">RANGE</a> <code>|</code> <a href="#grammar-production-STRING1">STRING1</a> <code>|</code> <a href="#grammar-production-STRING2">STRING2</a> <code>|</code> <code>(</code> "<code class="grammar-literal">(</code>" <a href="#grammar-production-expression">expression</a> "<code class="grammar-literal">)</code>"<code>)</code> </td>
93
57
  </tr>
94
58
  <tr id="grammar-production-pass">
95
59
  <td>[10]</td>
96
60
  <td><code>pass</code></td>
97
61
  <td>::=</td>
98
- <td>"@pass" <a href="#grammar-production-expression">expression</a></td>
62
+ <td>&quot;@pass&quot; <a href="#grammar-production-expression">expression</a></td>
99
63
  </tr>
100
- <tr id="grammar-production-">
101
- <td>@terminals</td>
102
- <td><code></code></td>
64
+ <tr>
65
+ <td colspan=2>@terminals</td>
103
66
  <td></td>
104
- <td><strong>Productions for terminals</strong></td>
67
+ <td><strong># Productions for terminals</strong></td>
105
68
  </tr>
106
69
  <tr id="grammar-production-LHS">
107
70
  <td>[11]</td>
108
71
  <td><code>LHS</code></td>
109
72
  <td>::=</td>
110
- <td><code>(</code> "<code class="grammar-literal">[</code>" <a href="#grammar-production-SYMBOL">SYMBOL</a> "<code class="grammar-literal">]</code>" <code class="grammar-char-escape"><abbr title="space">#x20</abbr></code><code>+</code> <code>)</code> <code>?</code> <a href="#grammar-production-SYMBOL">SYMBOL</a> <code class="grammar-char-escape"><abbr title="space">#x20</abbr></code><code>*</code> "::="</td>
73
+ <td><code>(</code> "<code class="grammar-literal">[</code>" <a href="#grammar-production-SYMBOL">SYMBOL</a> "<code class="grammar-literal">]</code>" <code class="grammar-char-escape"><abbr title="space">#x20</abbr></code><code>+</code> <code>)</code> <code>?</code> <a href="#grammar-production-SYMBOL">SYMBOL</a> <code class="grammar-char-escape"><abbr title="space">#x20</abbr></code><code>*</code> &quot;::=&quot;</td>
111
74
  </tr>
112
75
  <tr id="grammar-production-SYMBOL">
113
76
  <td>[12]</td>
@@ -119,91 +82,37 @@
119
82
  <td>[13]</td>
120
83
  <td><code>HEX</code></td>
121
84
  <td>::=</td>
122
- <td>"#x" <code>(</code> <code>[</code> <code class="grammar-literal">a-f</code><code>]</code> <code>|</code> <code>[</code> <code class="grammar-literal">A-F</code><code>]</code> <code>|</code> <code>[</code> <code class="grammar-literal">0-9</code><code>]</code> <code>)</code> <code>+</code> </td>
85
+ <td>&quot;#x&quot; <code>(</code> <code>[</code> <code class="grammar-literal">a-f</code><code>]</code> <code>|</code> <code>[</code> <code class="grammar-literal">A-F</code><code>]</code> <code>|</code> <code>[</code> <code class="grammar-literal">0-9</code><code>]</code> <code>)</code> <code>+</code> </td>
123
86
  </tr>
124
87
  <tr id="grammar-production-RANGE">
125
88
  <td>[14]</td>
126
89
  <td><code>RANGE</code></td>
127
90
  <td>::=</td>
128
- <td>"<code class="grammar-literal">[</code>"</td>
129
- </tr>
130
- <tr id="grammar-production-">
131
- <td>[14]</td>
132
- <td><code></code></td>
133
- <td></td>
134
- <td><code>(</code> <code>(</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-R_CHAR">R_CHAR</a><code>)</code><code>(</code> <a href="#grammar-production-HEX">HEX</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>|</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> <code>|</code> <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>+</code></td>
135
- </tr>
136
- <tr id="grammar-production-">
137
- <td>[14]</td>
138
- <td><code></code></td>
139
- <td></td>
140
- <td>"<code class="grammar-literal">-</code>"<code>?</code></td>
141
- </tr>
142
- <tr id="grammar-production-">
143
- <td>[14]</td>
144
- <td><code></code></td>
145
- <td></td>
146
- <td><code>(</code> "<code class="grammar-literal">]</code>" <code>-</code> <a href="#grammar-production-LHS">LHS</a><code>)</code> </td>
91
+ <td>"<code class="grammar-literal">[</code>" <code>(</code> <code>(</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-R_CHAR">R_CHAR</a><code>)</code> <code>|</code> <code>(</code> <a href="#grammar-production-HEX">HEX</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>|</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> <code>|</code> <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>+</code> "<code class="grammar-literal">-</code>"<code>?</code> <code>(</code> "<code class="grammar-literal">]</code>" <code>-</code> <a href="#grammar-production-LHS">LHS</a><code>)</code> </td>
147
92
  </tr>
148
93
  <tr id="grammar-production-O_RANGE">
149
94
  <td>[15]</td>
150
95
  <td><code>O_RANGE</code></td>
151
96
  <td>::=</td>
152
- <td>"[^"</td>
153
- </tr>
154
- <tr id="grammar-production-">
155
- <td>[15]</td>
156
- <td><code></code></td>
157
- <td></td>
158
- <td><code>(</code> <code>(</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-R_CHAR">R_CHAR</a><code>)</code><code>(</code> <a href="#grammar-production-HEX">HEX</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>|</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> <code>|</code> <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>+</code></td>
159
- </tr>
160
- <tr id="grammar-production-">
161
- <td>[15]</td>
162
- <td><code></code></td>
163
- <td></td>
164
- <td>"<code class="grammar-literal">-</code>"<code>?</code></td>
165
- </tr>
166
- <tr id="grammar-production-">
167
- <td>[15]</td>
168
- <td><code></code></td>
169
- <td></td>
170
- <td>"<code class="grammar-literal">]</code>"</td>
97
+ <td>&quot;[^&quot; <code>(</code> <code>(</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-R_CHAR">R_CHAR</a><code>)</code> <code>|</code> <code>(</code> <a href="#grammar-production-HEX">HEX</a> "<code class="grammar-literal">-</code>" <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>|</code> <a href="#grammar-production-R_CHAR">R_CHAR</a> <code>|</code> <a href="#grammar-production-HEX">HEX</a><code>)</code> <code>+</code> "<code class="grammar-literal">-</code>"<code>?</code> "<code class="grammar-literal">]</code>"</td>
171
98
  </tr>
172
99
  <tr id="grammar-production-STRING1">
173
100
  <td>[16]</td>
174
101
  <td><code>STRING1</code></td>
175
102
  <td>::=</td>
176
- <td>'<code class="grammar-literal">"</code>' <code>(</code> <a href="#grammar-production-CHAR">CHAR</a> <code>-</code> '<code class="grammar-literal">"</code>'<code>)</code> <code>*</code> '<code class="grammar-literal">"</code>'</td>
103
+ <td>'<code class="grammar-literal">&quot;</code>' <code>(</code> <a href="#grammar-production-CHAR">CHAR</a> <code>-</code> '<code class="grammar-literal">&quot;</code>'<code>)</code> <code>*</code> '<code class="grammar-literal">&quot;</code>'</td>
177
104
  </tr>
178
105
  <tr id="grammar-production-STRING2">
179
106
  <td>[17]</td>
180
107
  <td><code>STRING2</code></td>
181
108
  <td>::=</td>
182
- <td>"<code class="grammar-literal">'</code>" <code>(</code> <a href="#grammar-production-CHAR">CHAR</a> <code>-</code> "<code class="grammar-literal">'</code>"<code>)</code> <code>*</code> "<code class="grammar-literal">'</code>"</td>
109
+ <td>"<code class="grammar-literal">&apos;</code>" <code>(</code> <a href="#grammar-production-CHAR">CHAR</a> <code>-</code> "<code class="grammar-literal">&apos;</code>"<code>)</code> <code>*</code> "<code class="grammar-literal">&apos;</code>"</td>
183
110
  </tr>
184
111
  <tr id="grammar-production-CHAR">
185
112
  <td>[18]</td>
186
113
  <td><code>CHAR</code></td>
187
114
  <td>::=</td>
188
- <td><code>[</code> <code class="grammar-char-escape"><abbr title="horizontal tab">#x09</abbr></code><code class="grammar-char-escape"><abbr title="new line">#x0A</abbr></code><code class="grammar-char-escape"><abbr title="carriage return">#x0D</abbr></code><code>]</code></td>
189
- </tr>
190
- <tr>
191
- <td>[18]</td>
192
- <td><code></code></td>
193
- <td>|</td>
194
- <td><code>[</code> <code class="grammar-char-escape"><abbr title="space">#x20</abbr></code><code class="grammar-literal">-</code><code class="grammar-char-escape"><abbr title="unicode '퟿'">#xD7FF</abbr></code><code>]</code></td>
195
- </tr>
196
- <tr>
197
- <td>[18]</td>
198
- <td><code></code></td>
199
- <td>|</td>
200
- <td><code>[</code> <code class="grammar-char-escape"><abbr title="unicode ''">#xE000</abbr></code><code class="grammar-literal">-</code><code class="grammar-char-escape"><abbr title="unicode '�'">#xFFFD</abbr></code><code>]</code></td>
201
- </tr>
202
- <tr>
203
- <td>[18]</td>
204
- <td><code></code></td>
205
- <td>|</td>
206
- <td><code>[</code> <code class="grammar-char-escape"><abbr title="unicode '𐀀'">#x00010000</abbr></code><code class="grammar-literal">-</code><code class="grammar-char-escape"><abbr title="unicode '􏿿'">#x0010FFFF</abbr></code><code>]</code> </td>
115
+ <td><code>[</code> <code class="grammar-char-escape"><abbr title="horizontal tab">#x09</abbr></code><code class="grammar-char-escape"><abbr title="new line">#x0A</abbr></code><code class="grammar-char-escape"><abbr title="carriage return">#x0D</abbr></code><code>]</code> <code>|</code> <code>[</code> <code class="grammar-char-escape"><abbr title="space">#x20</abbr></code><code class="grammar-literal">-</code><code class="grammar-char-escape"><abbr title="unicode 'Reserved'">#xD7FF</abbr></code><code>]</code> <code>|</code> <code>[</code> <code class="grammar-char-escape"><abbr title="unicode 'Private-use'">#xE000</abbr></code><code class="grammar-literal">-</code><code class="grammar-char-escape"><abbr title="unicode 'Graphic'">#xFFFD</abbr></code><code>]</code> <code>|</code> <code>[</code> <code class="grammar-char-escape"><abbr title="unicode 'Graphic'">#x00010000</abbr></code><code class="grammar-literal">-</code><code class="grammar-char-escape"><abbr title="unicode 'Noncharacter'">#x0010FFFF</abbr></code><code>]</code> </td>
207
116
  </tr>
208
117
  <tr id="grammar-production-R_CHAR">
209
118
  <td>[19]</td>
@@ -224,28 +133,24 @@
224
133
  <td><code>[</code> <code class="grammar-char-escape"><abbr title="horizontal tab">#x09</abbr></code><code class="grammar-char-escape"><abbr title="new line">#x0A</abbr></code><code class="grammar-char-escape"><abbr title="carriage return">#x0D</abbr></code><code class="grammar-char-escape"><abbr title="space">#x20</abbr></code><code>]</code></td>
225
134
  </tr>
226
135
  <tr>
227
- <td>[21]</td>
228
- <td><code></code></td>
136
+ <td colspan=2></td>
229
137
  <td>|</td>
230
- <td><code>(</code> <code>(</code> <code>(</code> "<code class="grammar-literal">#</code>" <code>-</code> "#x"<code>)</code> <code>|</code> "//"<code>)</code> <code>[</code> <code class="grammar-literal">^</code><code class="grammar-char-escape"><abbr title="new line">#x0A</abbr></code><code class="grammar-char-escape"><abbr title="carriage return">#x0D</abbr></code><code>]</code> <code>*</code> <code>)</code></td>
138
+ <td><code>(</code> <code>(</code> <code>(</code> "<code class="grammar-literal">#</code>" <code>-</code> &quot;#x&quot;<code>)</code> <code>|</code> &quot;//&quot;<code>)</code> <code>[</code> <code class="grammar-literal">^</code><code class="grammar-char-escape"><abbr title="new line">#x0A</abbr></code><code class="grammar-char-escape"><abbr title="carriage return">#x0D</abbr></code><code>]</code> <code>*</code> <code>)</code></td>
231
139
  </tr>
232
140
  <tr>
233
- <td>[21]</td>
234
- <td><code></code></td>
141
+ <td colspan=2></td>
235
142
  <td>|</td>
236
- <td><code>(</code> "/*" <code>(</code> <code>(</code> "<code class="grammar-literal">*</code>" <code>[</code> <code class="grammar-literal">^/</code><code>]</code> <code>)</code> <code>?</code> <code>|</code> <code>[</code> <code class="grammar-literal">^*</code><code>]</code> <code>)</code> <code>*</code> "*/"<code>)</code></td>
143
+ <td><code>(</code> &quot;/*&quot; <code>(</code> <code>(</code> "<code class="grammar-literal">*</code>" <code>[</code> <code class="grammar-literal">^/</code><code>]</code> <code>)</code> <code>?</code> <code>|</code> <code>[</code> <code class="grammar-literal">^*</code><code>]</code> <code>)</code> <code>*</code> &quot;*/&quot;<code>)</code></td>
237
144
  </tr>
238
145
  <tr>
239
- <td>[21]</td>
240
- <td><code></code></td>
146
+ <td colspan=2></td>
241
147
  <td>|</td>
242
- <td><code>(</code> "(*" <code>(</code> <code>(</code> "<code class="grammar-literal">*</code>" <code>[</code> <code class="grammar-literal">^)</code><code>]</code> <code>)</code> <code>?</code> <code>|</code> <code>[</code> <code class="grammar-literal">^*</code><code>]</code> <code>)</code> <code>*</code> "*)"<code>)</code> </td>
148
+ <td><code>(</code> &quot;(*&quot; <code>(</code> <code>(</code> "<code class="grammar-literal">*</code>" <code>[</code> <code class="grammar-literal">^)</code><code>]</code> <code>)</code> <code>?</code> <code>|</code> <code>[</code> <code class="grammar-literal">^*</code><code>]</code> <code>)</code> <code>*</code> &quot;*)&quot;<code>)</code> </td>
243
149
  </tr>
244
- <tr id="grammar-production-">
245
- <td>@pass</td>
246
- <td><code></code></td>
247
- <td></td>
150
+ <tr>
151
+ <td colspan=2>@pass</td>
248
152
  <td></td>
153
+ <td><a href="#grammar-production-PASS">PASS</a></td>
249
154
  </tr>
250
155
  </tbody>
251
156
  </table>
data/etc/ebnf.ll1.rb CHANGED
@@ -1,4 +1,4 @@
1
- # This file is automatically generated by ebnf version 2.0.0
1
+ # This file is automatically generated by ebnf version 2.1.2
2
2
  # Derived from etc/ebnf.ebnf
3
3
  module Meta
4
4
  START = :ebnf
data/etc/ebnf.peg.rb CHANGED
@@ -1,4 +1,4 @@
1
- # This file is automatically generated by ebnf version 2.0.0
1
+ # This file is automatically generated by ebnf version 2.1.2
2
2
  # Derived from etc/ebnf.ebnf
3
3
  module EBNFMeta
4
4
  RULES = [
data/lib/ebnf/base.rb CHANGED
@@ -220,9 +220,10 @@ module EBNF
220
220
  # Output formatted EBNF as HTML
221
221
  #
222
222
  # @param [:abnf, :ebnf, :isoebnf] format (:ebnf)
223
+ # @param [Boolean] validate (false) validate generated HTML.
223
224
  # @return [String]
224
- def to_html(format: :ebnf)
225
- Writer.html(*ast, format: format)
225
+ def to_html(format: :ebnf, validate: false)
226
+ Writer.html(*ast, format: format, validate: validate)
226
227
  end
227
228
 
228
229
  ##
@@ -32,60 +32,12 @@ module EBNF::LL1
32
32
  # @see https://en.wikipedia.org/wiki/Lexical_analysis
33
33
  class Lexer
34
34
  include Enumerable
35
-
36
- ESCAPE_CHARS = {
37
- '\\t' => "\t", # \u0009 (tab)
38
- '\\n' => "\n", # \u000A (line feed)
39
- '\\r' => "\r", # \u000D (carriage return)
40
- '\\b' => "\b", # \u0008 (backspace)
41
- '\\f' => "\f", # \u000C (form feed)
42
- '\\"' => '"', # \u0022 (quotation mark, double quote mark)
43
- "\\'" => '\'', # \u0027 (apostrophe-quote, single quote mark)
44
- '\\\\' => '\\' # \u005C (backslash)
45
- }.freeze
46
- ESCAPE_CHAR4 = /\\u(?:[0-9A-Fa-f]{4,4})/u.freeze # \uXXXX
47
- ESCAPE_CHAR8 = /\\U(?:[0-9A-Fa-f]{8,8})/u.freeze # \UXXXXXXXX
48
- ECHAR = /\\./u.freeze # More liberal unescaping
49
- UCHAR = /#{ESCAPE_CHAR4}|#{ESCAPE_CHAR8}/n.freeze
35
+ include ::EBNF::Unescape
50
36
 
51
37
  ##
52
38
  # @return [Regexp] defines whitespace, including comments, otherwise whitespace must be explicit in terminals
53
39
  attr_reader :whitespace
54
40
 
55
- ##
56
- # Returns a copy of the given `input` string with all `\uXXXX` and
57
- # `\UXXXXXXXX` Unicode codepoint escape sequences replaced with their
58
- # unescaped UTF-8 character counterparts.
59
- #
60
- # @param [String] string
61
- # @return [String]
62
- # @see https://www.w3.org/TR/rdf-sparql-query/#codepointEscape
63
- def self.unescape_codepoints(string)
64
- string = string.dup
65
- string.force_encoding(Encoding::ASCII_8BIT) if string.respond_to?(:force_encoding)
66
-
67
- # Decode \uXXXX and \UXXXXXXXX code points:
68
- string = string.gsub(UCHAR) do |c|
69
- s = [(c[2..-1]).hex].pack('U*')
70
- s.respond_to?(:force_encoding) ? s.force_encoding(Encoding::ASCII_8BIT) : s
71
- end
72
-
73
- string.force_encoding(Encoding::UTF_8) if string.respond_to?(:force_encoding)
74
- string
75
- end
76
-
77
- ##
78
- # Returns a copy of the given `input` string with all string escape
79
- # sequences (e.g. `\n` and `\t`) replaced with their unescaped UTF-8
80
- # character counterparts.
81
- #
82
- # @param [String] input
83
- # @return [String]
84
- # @see https://www.w3.org/TR/rdf-sparql-query/#grammarEscapes
85
- def self.unescape_string(input)
86
- input.gsub(ECHAR) { |escaped| ESCAPE_CHARS[escaped] || escaped[1..-1]}
87
- end
88
-
89
41
  ##
90
42
  # Tokenizes the given `input` string or stream.
91
43
  #
@@ -338,7 +290,7 @@ module EBNF::LL1
338
290
  # @return [String]
339
291
  def unescape(string)
340
292
  if @options[:unescape]
341
- Lexer.unescape_string(Lexer.unescape_codepoints(string))
293
+ EBNF::Unescape.unescape(string)
342
294
  else
343
295
  string
344
296
  end
@@ -69,7 +69,6 @@ module EBNF::LL1
69
69
  # @return [String]
70
70
  def rest
71
71
  feed_me
72
- @lineno += 1 if eos?
73
72
  encode_utf8 super
74
73
  end
75
74
 
data/lib/ebnf/native.rb CHANGED
@@ -287,10 +287,10 @@ module EBNF
287
287
  case m = s[0,1]
288
288
  when '"', "'" # STRING1 or STRING2
289
289
  l, s = s[1..-1].split(m.rstrip, 2)
290
- [LL1::Lexer.unescape_string(l), s]
290
+ [Unescape.unescape_string(l), s]
291
291
  when '[' # RANGE, O_RANGE
292
292
  l, s = s[1..-1].split(/(?<=[^\\])\]/, 2)
293
- [[:range, LL1::Lexer.unescape_string(l)], s]
293
+ [[:range, Unescape.unescape_string(l)], s]
294
294
  when '#' # HEX
295
295
  s.match(/(#x\h+)(.*)$/)
296
296
  l, s = $1, $2
@@ -55,6 +55,7 @@ module EBNF::PEG
55
55
  def production_handlers; (@production_handlers ||= {}); end
56
56
  def terminal_handlers; (@terminal_handlers ||= {}); end
57
57
  def terminal_regexps; (@terminal_regexps ||= {}); end
58
+ def terminal_options; (@terminal_options ||= {}); end
58
59
 
59
60
  ##
60
61
  # Defines the pattern for a terminal node and a block to be invoked
@@ -72,9 +73,6 @@ module EBNF::PEG
72
73
  # defaults to the expression defined in the associated rule.
73
74
  # If unset, the terminal rule is used for matching.
74
75
  # @param [Hash] options
75
- # @option options [Hash{String => String}] :map ({})
76
- # A mapping from terminals, in lower-case form, to
77
- # their canonical value
78
76
  # @option options [Boolean] :unescape
79
77
  # Cause strings and codepoints to be unescaped.
80
78
  # @yield [value, prod]
@@ -88,6 +86,7 @@ module EBNF::PEG
88
86
  def terminal(term, regexp = nil, **options, &block)
89
87
  terminal_regexps[term] = regexp if regexp
90
88
  terminal_handlers[term] = block if block_given?
89
+ terminal_options[term] = options.freeze
91
90
  end
92
91
 
93
92
  ##
@@ -102,6 +101,8 @@ module EBNF::PEG
102
101
  # Options which are returned from {Parser#onStart}.
103
102
  # @option options [Boolean] :as_hash (false)
104
103
  # If the production is a `seq`, causes the value to be represented as a single hash, rather than an array of individual hashes for each sub-production. Note that this is not always advisable due to the possibility of repeated productions within the sequence.
104
+ # @option options[:upper, :lower] :insensitive_strings
105
+ # Perform case-insensitive match of strings not defined as terminals, and map to either upper or lower case.
105
106
  # @yield [data, block]
106
107
  # @yieldparam [Hash] data
107
108
  # A Hash defined for the current production, during :start
@@ -184,6 +185,8 @@ module EBNF::PEG
184
185
  # @option options[Integer] :high_water passed to lexer
185
186
  # @option options [Logger] :logger for errors/progress/debug.
186
187
  # @option options[Integer] :low_water passed to lexer
188
+ # @option options[Boolean] :seq_hash (false)
189
+ # If `true`, sets the default for the value sent to a production handler that is for a `seq` to a hash composed of the flattened consitutent hashes that are otherwise provided.
187
190
  # @option options [Symbol, Regexp] :whitespace
188
191
  # Symbol of whitespace rule (defaults to `@pass`), or a regular expression
189
192
  # for eating whitespace between non-terminal rules (strongly encouraged).
@@ -197,6 +200,7 @@ module EBNF::PEG
197
200
  # @raise [Exception] Raises exceptions for parsing errors
198
201
  # or errors raised during processing callbacks. Internal
199
202
  # errors are raised using {Error}.
203
+ # @todo FIXME implement seq_hash
200
204
  def parse(input = nil, start = nil, rules = nil, **options, &block)
201
205
  start ||= options[:start]
202
206
  rules ||= options[:rules] || []
@@ -269,7 +273,8 @@ module EBNF::PEG
269
273
  # @param [String] message Error string
270
274
  # @param [Hash{Symbol => Object}] options
271
275
  # @option options [URI, #to_s] :production
272
- # @option options [Token] :token
276
+ # @option options [Boolean] :raise abort furhter processing
277
+ # @option options [Array] :backtrace state where error occured
273
278
  # @see #debug
274
279
  def error(node, message, **options)
275
280
  lineno = options[:lineno] || (scanner.lineno if scanner)
@@ -282,7 +287,11 @@ module EBNF::PEG
282
287
  @recovering = true
283
288
  debug(node, m, level: 3, **options)
284
289
  if options[:raise] || @options[:validate]
285
- raise Error.new(m, lineno: lineno, rest: options[:rest], production: options[:production])
290
+ raise Error.new(m,
291
+ lineno: lineno,
292
+ rest: options[:rest],
293
+ production: options[:production],
294
+ backtrace: options[:backtrace])
286
295
  end
287
296
  end
288
297
 
@@ -365,25 +374,27 @@ module EBNF::PEG
365
374
  @productions << prod
366
375
  debug("#{prod}(:start)", "",
367
376
  lineno: (scanner.lineno if scanner),
368
- pos: (scanner.pos if scanner),
369
- depth: (depth + 1)) {"#{prod}, pos: #{scanner ? scanner.pos : '?'}, rest: #{scanner ? scanner.rest[0..20].inspect : '?'}"}
377
+ pos: (scanner.pos if scanner)
378
+ ) do
379
+ "#{prod}, pos: #{scanner ? scanner.pos : '?'}, rest: #{scanner ? scanner.rest[0..20].inspect : '?'}"
380
+ end
370
381
  if handler
371
382
  # Create a new production data element, potentially allowing handler
372
383
  # to customize before pushing on the @prod_data stack
373
- data = {}
384
+ data = {_production: prod}
374
385
  begin
375
386
  self.class.eval_with_binding(self) {
376
387
  handler.call(data, @parse_callback)
377
388
  }
378
389
  rescue ArgumentError, Error => e
379
- error("start", "#{e.class}: #{e.message}", production: prod)
390
+ error("start", "#{e.class}: #{e.message}", production: prod, backtrace: e.backtrace)
380
391
  @recovering = false
381
392
  end
382
393
  @prod_data << data
383
394
  elsif self.class.production_handlers[prod]
384
395
  # Make sure we push as many was we pop, even if there is no
385
396
  # explicit start handler
386
- @prod_data << {}
397
+ @prod_data << {_production: prod}
387
398
  end
388
399
  return self.class.start_options.fetch(prod, {}) # any options on this production
389
400
  end
@@ -397,6 +408,9 @@ module EBNF::PEG
397
408
  prod = @productions.last
398
409
  handler, clear_packrat = self.class.production_handlers[prod]
399
410
  data = @prod_data.pop if handler || self.class.start_handlers[prod]
411
+ error("finish",
412
+ "prod_data production mismatch: expected #{prod.inspect}, got #{data[:_production].inspect}",
413
+ production: prod, prod_data: @prod_data) if data && prod != data[:_production]
400
414
  if handler && !@recovering && result != :unmatched
401
415
  # Pop production data element from stack, potentially allowing handler to use it
402
416
  result = begin
@@ -404,14 +418,13 @@ module EBNF::PEG
404
418
  handler.call(result, data, @parse_callback)
405
419
  }
406
420
  rescue ArgumentError, Error => e
407
- error("finish", "#{e.class}: #{e.message}", production: prod)
421
+ error("finish", "#{e.class}: #{e.message}", production: prod, backtrace: e.backtrace)
408
422
  @recovering = false
409
423
  end
410
424
  end
411
- progress("#{prod}(:finish)", "",
412
- depth: (depth + 1),
413
- lineno: (scanner.lineno if scanner),
414
- level: result == :unmatched ? 0 : 1) do
425
+ debug("#{prod}(:finish)", "",
426
+ lineno: (scanner.lineno if scanner),
427
+ level: result == :unmatched ? 0 : 1) do
415
428
  "#{result.inspect}@(#{scanner ? scanner.pos : '?'}), rest: #{scanner ? scanner.rest[0..20].inspect : '?'}"
416
429
  end
417
430
  self.clear_packrat if clear_packrat
@@ -433,12 +446,12 @@ module EBNF::PEG
433
446
  handler.call(value, parentProd, @parse_callback)
434
447
  }
435
448
  rescue ArgumentError, Error => e
436
- error("terminal", "#{e.class}: #{e.message}", value: value, production: prod)
449
+ error("terminal", "#{e.class}: #{e.message}", value: value, production: prod, backtrace: e.backtrace)
437
450
  @recovering = false
438
451
  end
439
452
  end
440
453
  progress("#{prod}(:terminal)", "",
441
- depth: (depth + 2),
454
+ depth: (depth + 1),
442
455
  lineno: (scanner.lineno if scanner),
443
456
  level: value == :unmatched ? 0 : 1) do
444
457
  "#{value.inspect}@(#{scanner ? scanner.pos : '?'})"
@@ -460,10 +473,19 @@ module EBNF::PEG
460
473
  #
461
474
  # @param [Symbol] sym
462
475
  # @return [Regexp]
463
- def find_terminal_regexp(sym)
476
+ def terminal_regexp(sym)
464
477
  self.class.terminal_regexps[sym]
465
478
  end
466
479
 
480
+ ##
481
+ # Find a regular expression defined for a terminal
482
+ #
483
+ # @param [Symbol] sym
484
+ # @return [Regexp]
485
+ def terminal_options(sym)
486
+ self.class.terminal_options[sym]
487
+ end
488
+
467
489
  ##
468
490
  # Record furthest failure.
469
491
  #
data/lib/ebnf/peg/rule.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  module EBNF::PEG
2
2
  # Behaviior for parsing a PEG rule
3
3
  module Rule
4
+ include ::EBNF::Unescape
5
+
4
6
  ##
5
7
  # Initialized by parser when loading rules.
6
8
  # Used for finding rules and invoking elements of the parse process.
@@ -24,6 +26,7 @@ module EBNF::PEG
24
26
  # * `opt`: returns the value matched, or `nil` if unmatched.
25
27
  # * `plus`: returns an array of the values matched for the specified production, or `:unmatched`, if none are matched. For Terminals, these are concatenated into a single string.
26
28
  # * `range`: returns a string composed of the values matched, or `:unmatched`, if less than `min` are matched.
29
+ # * `rept`: returns an array of the values matched for the speficied production, or `:unmatched`, if none are matched. For Terminals, these are concatenated into a single string.
27
30
  # * `seq`: returns an array composed of single-entry hashes for each matched production indexed by the production name, or `:unmatched` if any production fails to match. For Terminals, returns a string created by concatenating these values. Via option in a `production` or definition, the result can be a single hash with values for each matched production; note that this is not always possible due to the possibility of repeated productions within the sequence.
28
31
  # * `star`: returns an array of the values matched for the specified production. For Terminals, these are concatenated into a single string.
29
32
  #
@@ -44,9 +47,18 @@ module EBNF::PEG
44
47
  # If the terminal is defined with a regular expression,
45
48
  # use that to match the input,
46
49
  # otherwise,
47
- if regexp = parser.find_terminal_regexp(sym)
48
- matched = input.scan(regexp)
50
+ if regexp = parser.terminal_regexp(sym)
51
+ term_opts = parser.terminal_options(sym)
52
+ if matched = input.scan(regexp)
53
+ # Optionally map matched
54
+ matched = term_opts.fetch(:map, {}).fetch(matched.downcase, matched)
55
+
56
+ # Optionally unescape matched
57
+ matched = unescape(matched) if term_opts[:unescape]
58
+ end
59
+
49
60
  result = parser.onTerminal(sym, (matched ? matched : :unmatched))
61
+
50
62
  # Update furthest failure for strings and terminals
51
63
  parser.update_furthest_failure(input.pos, input.lineno, sym) if result == :unmatched
52
64
  parser.packrat[sym][pos] = {
@@ -60,6 +72,7 @@ module EBNF::PEG
60
72
  eat_whitespace(input)
61
73
  end
62
74
  start_options = parser.onStart(sym)
75
+ string_regexp_opts = start_options[:insensitive_strings] ? Regexp::IGNORECASE : 0
63
76
 
64
77
  result = case expr.first
65
78
  when :alt
@@ -73,7 +86,12 @@ module EBNF::PEG
73
86
  raise "No rule found for #{prod}" unless rule
74
87
  rule.parse(input)
75
88
  when String
76
- input.scan(Regexp.new(Regexp.quote(prod))) || :unmatched
89
+ s = input.scan(Regexp.new(Regexp.quote(prod), string_regexp_opts))
90
+ case start_options[:insensitive_strings]
91
+ when :lower then s && s.downcase
92
+ when :upper then s && s.upcase
93
+ else s
94
+ end || :unmatched
77
95
  end
78
96
  if alt == :unmatched
79
97
  # Update furthest failure for strings and terminals
@@ -111,7 +129,7 @@ module EBNF::PEG
111
129
  raise "No rule found for #{prod}" unless rule
112
130
  rule.parse(input)
113
131
  when String
114
- input.scan(Regexp.new(Regexp.quote(prod))) || :unmatched
132
+ input.scan(Regexp.new(Regexp.quote(prod), string_regexp_opts)) || :unmatched
115
133
  end
116
134
  if res != :unmatched
117
135
  # Update furthest failure for terminals
@@ -122,7 +140,7 @@ module EBNF::PEG
122
140
  end
123
141
  when :opt
124
142
  # Result is the matched value or nil
125
- opt = rept(input, 0, 1, expr[1])
143
+ opt = rept(input, 0, 1, expr[1], string_regexp_opts, **start_options)
126
144
 
127
145
  # Update furthest failure for strings and terminals
128
146
  parser.update_furthest_failure(input.pos, input.lineno, expr[1]) if terminal?
@@ -130,7 +148,7 @@ module EBNF::PEG
130
148
  when :plus
131
149
  # Result is an array of all expressions while they match,
132
150
  # at least one must match
133
- plus = rept(input, 1, '*', expr[1])
151
+ plus = rept(input, 1, '*', expr[1], string_regexp_opts)
134
152
 
135
153
  # Update furthest failure for strings and terminals
136
154
  parser.update_furthest_failure(input.pos, input.lineno, expr[1]) if terminal?
@@ -142,6 +160,14 @@ module EBNF::PEG
142
160
  parser.update_furthest_failure(input.pos, input.lineno, expr[1])
143
161
  :unmatched
144
162
  end
163
+ when :rept
164
+ # Result is an array of all expressions while they match,
165
+ # an empty array of none match
166
+ rept = rept(input, expr[1], expr[2], expr[3], string_regexp_opts)
167
+
168
+ # # Update furthest failure for strings and terminals
169
+ parser.update_furthest_failure(input.pos, input.lineno, expr[3]) if terminal?
170
+ rept.is_a?(Array) && terminal? ? rept.join("") : rept
145
171
  when :seq
146
172
  # Evaluate each expression into an array of hashes where each hash contains a key from the associated production and the value is the parsed value of that production. Returns :unmatched if the input does not match the production. Value ordering is ensured by native Hash ordering.
147
173
  seq = expr[1..-1].each_with_object([]) do |prod, accumulator|
@@ -152,7 +178,12 @@ module EBNF::PEG
152
178
  raise "No rule found for #{prod}" unless rule
153
179
  rule.parse(input)
154
180
  when String
155
- input.scan(Regexp.new(Regexp.quote(prod))) || :unmatched
181
+ s = input.scan(Regexp.new(Regexp.quote(prod), string_regexp_opts))
182
+ case start_options[:insensitive_strings]
183
+ when :lower then s && s.downcase
184
+ when :upper then s && s.upcase
185
+ else s
186
+ end || :unmatched
156
187
  end
157
188
  if res == :unmatched
158
189
  # Update furthest failure for strings and terminals
@@ -173,7 +204,7 @@ module EBNF::PEG
173
204
  when :star
174
205
  # Result is an array of all expressions while they match,
175
206
  # an empty array of none match
176
- star = rept(input, 0, '*', expr[1])
207
+ star = rept(input, 0, '*', expr[1], string_regexp_opts)
177
208
 
178
209
  # Update furthest failure for strings and terminals
179
210
  parser.update_furthest_failure(input.pos, input.lineno, expr[1]) if terminal?
@@ -205,8 +236,9 @@ module EBNF::PEG
205
236
  # @param [Integer] max
206
237
  # If it is an integer, it stops matching after max entries.
207
238
  # @param [Symbol, String] prod
239
+ # @param [Integer] string_regexp_opts
208
240
  # @return [:unmatched, Array]
209
- def rept(input, min, max, prod)
241
+ def rept(input, min, max, prod, string_regexp_opts, **options)
210
242
  result = []
211
243
 
212
244
  case prod
@@ -218,9 +250,13 @@ module EBNF::PEG
218
250
  result << res
219
251
  end
220
252
  when String
221
- while (res = input.scan(Regexp.new(Regexp.quote(prod)))) && (max == '*' || result.length < max)
253
+ while (res = input.scan(Regexp.new(Regexp.quote(prod), string_regexp_opts))) && (max == '*' || result.length < max)
222
254
  eat_whitespace(input) unless terminal?
223
- result << res
255
+ result << case options[:insensitive_strings]
256
+ when :lower then res.downcase
257
+ when :upper then res.upcase
258
+ else res
259
+ end
224
260
  end
225
261
  end
226
262
 
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+ # Unsecape strings
3
+ module EBNF::Unescape
4
+ ESCAPE_CHARS = {
5
+ '\\t' => "\t", # \u0009 (tab)
6
+ '\\n' => "\n", # \u000A (line feed)
7
+ '\\r' => "\r", # \u000D (carriage return)
8
+ '\\b' => "\b", # \u0008 (backspace)
9
+ '\\f' => "\f", # \u000C (form feed)
10
+ '\\"' => '"', # \u0022 (quotation mark, double quote mark)
11
+ "\\'" => '\'', # \u0027 (apostrophe-quote, single quote mark)
12
+ '\\\\' => '\\' # \u005C (backslash)
13
+ }.freeze
14
+ ESCAPE_CHAR4 = /\\u(?:[0-9A-Fa-f]{4,4})/u.freeze # \uXXXX
15
+ ESCAPE_CHAR8 = /\\U(?:[0-9A-Fa-f]{8,8})/u.freeze # \UXXXXXXXX
16
+ ECHAR = /\\./u.freeze # More liberal unescaping
17
+ UCHAR = /#{ESCAPE_CHAR4}|#{ESCAPE_CHAR8}/n.freeze
18
+
19
+ ##
20
+ # Returns a copy of the given `input` string with all `\uXXXX` and
21
+ # `\UXXXXXXXX` Unicode codepoint escape sequences replaced with their
22
+ # unescaped UTF-8 character counterparts.
23
+ #
24
+ # @param [String] string
25
+ # @return [String]
26
+ # @see https://www.w3.org/TR/rdf-sparql-query/#codepointEscape
27
+ def unescape_codepoints(string)
28
+ string = string.dup
29
+ string.force_encoding(Encoding::ASCII_8BIT) if string.respond_to?(:force_encoding)
30
+
31
+ # Decode \uXXXX and \UXXXXXXXX code points:
32
+ string = string.gsub(UCHAR) do |c|
33
+ s = [(c[2..-1]).hex].pack('U*')
34
+ s.respond_to?(:force_encoding) ? s.force_encoding(Encoding::ASCII_8BIT) : s
35
+ end
36
+
37
+ string.force_encoding(Encoding::UTF_8) if string.respond_to?(:force_encoding)
38
+ string
39
+ end
40
+ module_function :unescape_codepoints
41
+
42
+ ##
43
+ # Returns a copy of the given `input` string with all string escape
44
+ # sequences (e.g. `\n` and `\t`) replaced with their unescaped UTF-8
45
+ # character counterparts.
46
+ #
47
+ # @param [String] input
48
+ # @return [String]
49
+ # @see https://www.w3.org/TR/rdf-sparql-query/#grammarEscapes
50
+ def unescape_string(input)
51
+ input.gsub(ECHAR) { |escaped| ESCAPE_CHARS[escaped] || escaped[1..-1]}
52
+ end
53
+ module_function :unescape_string
54
+
55
+ # Perform string and codepoint unescaping if defined for this terminal
56
+ # @param [String] string
57
+ # @return [String]
58
+ def unescape(string)
59
+ unescape_string(unescape_codepoints(string))
60
+ end
61
+ module_function :unescape
62
+ end
data/lib/ebnf/writer.rb CHANGED
@@ -2,12 +2,14 @@
2
2
  require 'rdf'
3
3
  require 'strscan' unless defined?(StringScanner)
4
4
  require "ostruct"
5
+ require 'unicode/types'
5
6
 
6
7
  ##
7
8
  # Serialize ruleset back to EBNF
8
9
  module EBNF
9
10
  class Writer
10
11
  LINE_LENGTH = 80
12
+ LINE_LENGTH_HTML = 200
11
13
 
12
14
  # ASCII escape names
13
15
  ASCII_ESCAPE_NAMES = [
@@ -85,22 +87,23 @@ module EBNF
85
87
  #
86
88
  # @param [Array<Rule>] rules
87
89
  # @param [:abnf, :ebnf, :isoebnf] format (:ebnf)
90
+ # @param [Boolean] validate (false) validate generated HTML.
88
91
  # @return [Object]
89
- def self.html(*rules, format: :ebnf)
92
+ def self.html(*rules, format: :ebnf, validate: false)
90
93
  require 'stringio' unless defined?(StringIO)
91
94
  buf = StringIO.new
92
- Writer.new(rules, out: buf, html: true, format: format)
95
+ Writer.new(rules, out: buf, html: true, format: format, validate: validate)
93
96
  buf.string
94
97
  end
95
98
 
96
99
  ##
97
100
  # @param [Array<Rule>] rules
101
+ # @param [:abnf, :ebnf, :isoebnf] format (:ebnf)
102
+ # @param [Boolean] html (false) generate HTML output
103
+ # @param [Boolean] validate (false) validate generated HTML.
98
104
  # @param [Hash{Symbol => Object}] options
99
105
  # @param [#write] out ($stdout)
100
- # @param [:abnf, :ebnf, :isoebnf] format (:ebnf)
101
- # @option options [Symbol] format
102
- # @option options [Boolean] html (false)
103
- def initialize(rules, out: $stdout, html: false, format: :ebnf, **options)
106
+ def initialize(rules, out: $stdout, html: false, format: :ebnf, validate: false, **options)
104
107
  @options = options.merge(html: html)
105
108
  return if rules.empty?
106
109
 
@@ -118,19 +121,24 @@ module EBNF
118
121
  lhs_fmt = "%<id>-#{max_id+2}s " + lhs_fmt
119
122
  lhs_length += max_id + 3
120
123
  end
121
- rhs_length = LINE_LENGTH - lhs_length
124
+ rhs_length = (html ? LINE_LENGTH_HTML : LINE_LENGTH) - lhs_length
122
125
 
123
126
  if html
124
127
  # Output as formatted HTML
125
128
  begin
126
129
  require 'erubis'
130
+ require 'htmlentities'
131
+ @coder = HTMLEntities.new
127
132
  eruby = Erubis::Eruby.new(ERB_DESC)
128
133
  formatted_rules = rules.map do |rule|
129
134
  if rule.kind == :terminals || rule.kind == :pass
130
135
  OpenStruct.new(id: ("@#{rule.kind}"),
131
136
  sym: nil,
132
137
  assign: nil,
133
- formatted: ("<strong>Productions for terminals</strong>" if rule.kind == :terminals))
138
+ formatted: (
139
+ rule.kind == :terminals ?
140
+ "<strong># Productions for terminals</strong>" :
141
+ self.send(format_meth, rule.expr)))
134
142
  else
135
143
  formatted_expr = self.send(format_meth, rule.expr)
136
144
  # Measure text without markup
@@ -151,7 +159,7 @@ module EBNF
151
159
  formatted.sub!(%r{\s*<code>\|</code>\s*}, '')
152
160
  (ndx > 0 ? (rule.alt? ? '|' : '') : '=')
153
161
  end
154
- lines << OpenStruct.new(id: ("[#{rule.id}]" if rule.id),
162
+ lines << OpenStruct.new(id: ((ndx == 0 ? "[#{rule.id}]" : "") if rule.id),
155
163
  sym: (rule.sym if ndx == 0 || format == :abnf),
156
164
  assign: assign,
157
165
  formatted: formatted)
@@ -168,10 +176,24 @@ module EBNF
168
176
  end
169
177
  end
170
178
  end.flatten
171
- out.write eruby.evaluate(format: format, rules: formatted_rules)
179
+
180
+ html_result = eruby.evaluate(format: format, rules: formatted_rules)
181
+
182
+ if validate
183
+ begin
184
+ # Validate the output HTML
185
+ doc = Nokogiri::HTML5("<!DOCTYPE html>" + html_result, max_errors: 10)
186
+ raise EncodingError, "Errors found in generated HTML:\n " +
187
+ doc.errors.map(&:to_s).join("\n ") unless doc.errors.empty?
188
+ rescue LoadError, NoMethodError
189
+ # Skip
190
+ end
191
+ end
192
+
193
+ out.write html_result
172
194
  return
173
195
  rescue LoadError
174
- $stderr.puts "Generating HTML requires erubis gem to be loaded"
196
+ $stderr.puts "Generating HTML requires erubis and htmlentities gems to be loaded"
175
197
  end
176
198
  end
177
199
 
@@ -216,7 +238,7 @@ module EBNF
216
238
 
217
239
  # Format the expression part of a rule
218
240
  def format_ebnf(expr, sep: nil, embedded: false)
219
- return (@options[:html] ? %(<a href="#grammar-production-#{expr}">#{expr}</a>) : expr.to_s) if expr.is_a?(Symbol)
241
+ return (@options[:html] ? %(<a href="#grammar-production-#{@coder.encode expr}">#{@coder.encode expr}</a>) : expr.to_s) if expr.is_a?(Symbol)
220
242
  if expr.is_a?(String)
221
243
  return expr.length == 1 ?
222
244
  format_ebnf_char(expr) :
@@ -290,10 +312,10 @@ module EBNF
290
312
  # Format a single-character string, prefering hex for non-main ASCII
291
313
  def format_ebnf_char(c)
292
314
  case c.ord
293
- when (0x21) then (@options[:html] ? %("<code class="grammar-literal">#{c}</code>") : %{"#{c}"})
294
- when 0x22 then (@options[:html] ? %('<code class="grammar-literal">"</code>') : %{'"'})
295
- when (0x23..0x7e) then (@options[:html] ? %("<code class="grammar-literal">#{c}</code>") : %{"#{c}"})
296
- when (0x80..0xFFFD) then (@options[:html] ? %("<code class="grammar-literal">#{c}</code>") : %{"#{c}"})
315
+ when (0x21) then (@options[:html] ? %("<code class="grammar-literal">#{@coder.encode c}</code>") : %{"#{c}"})
316
+ when 0x22 then (@options[:html] ? %('<code class="grammar-literal">&quot;</code>') : %{'"'})
317
+ when (0x23..0x7e) then (@options[:html] ? %("<code class="grammar-literal">#{@coder.encode c}</code>") : %{"#{c}"})
318
+ when (0x80..0xFFFD) then (@options[:html] ? %("<code class="grammar-literal">#{@coder.encode c}</code>") : %{"#{c}"})
297
319
  else escape_ebnf_hex(c)
298
320
  end
299
321
  end
@@ -308,7 +330,7 @@ module EBNF
308
330
  while !s.eos?
309
331
  case
310
332
  when s.scan(/\A[!"\u0024-\u007e]+/)
311
- buffer << (@options[:html] ? %(<code class="grammar-literal">#{s.matched}</code>) : s.matched)
333
+ buffer << (@options[:html] ? %(<code class="grammar-literal">#{@coder.encode s.matched}</code>) : s.matched)
312
334
  when s.scan(/\A#x\h+/)
313
335
  buffer << escape_ebnf_hex(s.matched[2..-1].hex.chr(Encoding::UTF_8))
314
336
  else
@@ -328,7 +350,8 @@ module EBNF
328
350
  end
329
351
  end
330
352
 
331
- "#{quote}#{string}#{quote}"
353
+ res = "#{quote}#{string}#{quote}"
354
+ @options[:html] ? @coder.encode(res) : res
332
355
  end
333
356
 
334
357
  def escape_ebnf_hex(u)
@@ -340,16 +363,20 @@ module EBNF
340
363
  end
341
364
  char = fmt % u.ord
342
365
  if @options[:html]
343
- if u.ord <= 0x20
344
- char = %(<abbr title="#{ASCII_ESCAPE_NAMES[u.ord]}">#{char}</abbr>)
366
+ char = if u.ord <= 0x20
367
+ %(<abbr title="#{ASCII_ESCAPE_NAMES[u.ord]}">#{@coder.encode char}</abbr>)
368
+ elsif u.ord == 0x22
369
+ %(<abbr title="quot">>&quot;</abbr>)
345
370
  elsif u.ord < 0x7F
346
- char = %(<abbr title="ascii '#{u}'">#{char}</abbr>)
371
+ %(<abbr title="ascii '#{@coder.encode u}'">#{@coder.encode char}</abbr>)
347
372
  elsif u.ord == 0x7F
348
- char = %(<abbr title="delete">#{char}</abbr>)
373
+ %(<abbr title="delete">#{@coder.encode char}</abbr>)
349
374
  elsif u.ord <= 0xFF
350
- char = %(<abbr title="extended ascii '#{u}'">#{char}</abbr>)
375
+ %(<abbr title="extended ascii '#{@coder.encode char}'">#{char}</abbr>)
376
+ elsif (%w(Control Private-use Surrogate Noncharacter Reserved) - ::Unicode::Types.of(u)).empty?
377
+ %(<abbr title="unicode '#{u}'">#{char}</abbr>)
351
378
  else
352
- char = %(<abbr title="unicode '#{u}'">#{char}</abbr>)
379
+ %(<abbr title="unicode '#{::Unicode::Types.of(u).first}'">#{char}</abbr>)
353
380
  end
354
381
  %(<code class="grammar-char-escape">#{char}</code>)
355
382
  else
@@ -363,7 +390,7 @@ module EBNF
363
390
 
364
391
  # Format the expression part of a rule
365
392
  def format_abnf(expr, sep: nil, embedded: false, sensitive: true)
366
- return (@options[:html] ? %(<a href="#grammar-production-#{expr}">#{expr}</a>) : expr.to_s) if expr.is_a?(Symbol)
393
+ return (@options[:html] ? %(<a href="#grammar-production-#{@coder.encode expr}">#{@coder.encode expr}</a>) : expr.to_s) if expr.is_a?(Symbol)
367
394
  if expr.is_a?(String)
368
395
  if expr.length == 1
369
396
  return format_abnf_char(expr)
@@ -380,7 +407,7 @@ module EBNF
380
407
  seq.unshift(:seq)
381
408
  return format_abnf(seq, sep: nil, embedded: false)
382
409
  else
383
- return (@options[:html] ? %("<code class="grammar-literal">#{'%s' if sensitive}#{expr}</code>") : %(#{'%s' if sensitive}"#{expr}"))
410
+ return (@options[:html] ? %("<code class="grammar-literal">#{'%s' if sensitive}#{@coder.encode expr}</code>") : %(#{'%s' if sensitive}"#{expr}"))
384
411
  end
385
412
  end
386
413
  parts = {
@@ -448,7 +475,7 @@ module EBNF
448
475
  # Format a single-character string, prefering hex for non-main ASCII
449
476
  def format_abnf_char(c)
450
477
  if /[\x20-\x21\x23-\x7E]/.match?(c)
451
- c.inspect
478
+ @options[:html] ? %("<code class="grammar-literal">#{@coder.encode c}</code>") : c.inspect
452
479
  else
453
480
  escape_abnf_hex(c)
454
481
  end
@@ -528,15 +555,17 @@ module EBNF
528
555
  char = "%x" + (fmt % u.ord)
529
556
  if @options[:html]
530
557
  if u.ord <= 0x20
531
- char = %(<abbr title="#{ASCII_ESCAPE_NAMES[u.ord]}">#{char}</abbr>)
532
- elsif u.ord <= 0x7F
533
- char = %(<abbr title="ascii '#{u}'">#{char}</abbr>)
558
+ char = %(<abbr title="#{ASCII_ESCAPE_NAMES[u.ord]}">#{@coder.encode char}</abbr>)
559
+ elsif u.ord == 0x22
560
+ %(<abbr title="quot">>&quot;</abbr>)
561
+ elsif u.ord < 0x7F
562
+ char = %(<abbr title="ascii '#{u}'">#{@coder.encode char}</abbr>)
534
563
  elsif u.ord == 0x7F
535
- char = %(<abbr title="delete">#{char}</abbr>)
564
+ char = %(<abbr title="delete">#{@coder.encode char}</abbr>)
536
565
  elsif u.ord <= 0xFF
537
566
  char = %(<abbr title="extended ascii '#{u}'">#{char}</abbr>)
538
567
  else
539
- char = %(<abbr title="unicode '#{u}'">#{char}</abbr>)
568
+ char = %(<abbr title="unicode '#{u.unicode_normaliz}'">#{char}</abbr>)
540
569
  end
541
570
  %(<code class="grammar-char-escape">#{char}</code>)
542
571
  else
@@ -550,7 +579,7 @@ module EBNF
550
579
 
551
580
  # Format the expression part of a rule
552
581
  def format_isoebnf(expr, sep: nil, embedded: false)
553
- return (@options[:html] ? %(<a href="#grammar-production-#{expr}">#{expr}</a>) : expr.to_s) if expr.is_a?(Symbol)
582
+ return (@options[:html] ? %(<a href="#grammar-production-#{@coder.encode expr}">#{@coder.encode expr}</a>) : expr.to_s) if expr.is_a?(Symbol)
554
583
  if expr.is_a?(String)
555
584
  expr = expr[2..-1].hex.chr if expr =~ /\A#x\h+/
556
585
  expr.chars.each do |c|
@@ -558,9 +587,9 @@ module EBNF
558
587
  ISOEBNF::TERMINAL_CHARACTER.match?(c)
559
588
  end
560
589
  if expr =~ /"/
561
- return (@options[:html] ? %('<code class="grammar-literal">#{expr}</code>') : %('#{expr}'))
590
+ return (@options[:html] ? %('<code class="grammar-literal">#{@coder.encode expr}</code>') : %('#{expr}'))
562
591
  else
563
- return (@options[:html] ? %("<code class="grammar-literal">#{expr}</code>") : %("#{expr}"))
592
+ return (@options[:html] ? %("<code class="grammar-literal">#{@coder.encode expr}</code>") : %("#{expr}"))
564
593
  end
565
594
  end
566
595
  parts = {
@@ -679,11 +708,13 @@ module EBNF
679
708
  <table class="grammar">
680
709
  <tbody id="grammar-productions" class="<%= @format %>">
681
710
  <% for rule in @rules %>
682
- <tr<%= %{ id="grammar-production-#{rule.sym}"} unless %w(=/ |).include?(rule.assign)%>>
711
+ <tr<%= %{ id="grammar-production-#{rule.sym}"} unless %w(=/ |).include?(rule.assign) || rule.sym.nil?%>>
683
712
  <% if rule.id %>
684
- <td><%= rule.id %></td>
713
+ <td<%= " colspan=2" unless rule.sym %>><%= rule.id %></td>
685
714
  <% end %>
715
+ <% if rule.sym %>
686
716
  <td><code><%== rule.sym %></code></td>
717
+ <% end %>
687
718
  <td><%= rule.assign %></td>
688
719
  <td><%= rule.formatted %></td>
689
720
  </tr>
data/lib/ebnf.rb CHANGED
@@ -9,6 +9,7 @@ module EBNF
9
9
  autoload :PEG, "ebnf/peg"
10
10
  autoload :Rule, "ebnf/rule"
11
11
  autoload :Terminals,"ebnf/terminals"
12
+ autoload :Unescape, "ebnf/unescape"
12
13
  autoload :Writer, "ebnf/writer"
13
14
  autoload :VERSION, "ebnf/version"
14
15
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ebnf
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregg Kellogg
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-13 00:00:00.000000000 Z
11
+ date: 2021-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sxp
@@ -52,6 +52,48 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: htmlentities
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: unicode-types
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.6'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: amazing_print
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.2'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: rdf-spec
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -81,47 +123,47 @@ dependencies:
81
123
  - !ruby/object:Gem::Version
82
124
  version: '3.1'
83
125
  - !ruby/object:Gem::Dependency
84
- name: erubis
126
+ name: nokogiri
85
127
  requirement: !ruby/object:Gem::Requirement
86
128
  requirements:
87
129
  - - "~>"
88
130
  - !ruby/object:Gem::Version
89
- version: '2.7'
131
+ version: '1.10'
90
132
  type: :development
91
133
  prerelease: false
92
134
  version_requirements: !ruby/object:Gem::Requirement
93
135
  requirements:
94
136
  - - "~>"
95
137
  - !ruby/object:Gem::Version
96
- version: '2.7'
138
+ version: '1.10'
97
139
  - !ruby/object:Gem::Dependency
98
- name: nokogiri
140
+ name: erubis
99
141
  requirement: !ruby/object:Gem::Requirement
100
142
  requirements:
101
143
  - - "~>"
102
144
  - !ruby/object:Gem::Version
103
- version: '1.10'
145
+ version: '2.7'
104
146
  type: :development
105
147
  prerelease: false
106
148
  version_requirements: !ruby/object:Gem::Requirement
107
149
  requirements:
108
150
  - - "~>"
109
151
  - !ruby/object:Gem::Version
110
- version: '1.10'
152
+ version: '2.7'
111
153
  - !ruby/object:Gem::Dependency
112
154
  name: rspec
113
155
  requirement: !ruby/object:Gem::Requirement
114
156
  requirements:
115
157
  - - "~>"
116
158
  - !ruby/object:Gem::Version
117
- version: '3.9'
159
+ version: '3.10'
118
160
  type: :development
119
161
  prerelease: false
120
162
  version_requirements: !ruby/object:Gem::Requirement
121
163
  requirements:
122
164
  - - "~>"
123
165
  - !ruby/object:Gem::Version
124
- version: '3.9'
166
+ version: '3.10'
125
167
  - !ruby/object:Gem::Dependency
126
168
  name: rspec-its
127
169
  requirement: !ruby/object:Gem::Requirement
@@ -164,8 +206,8 @@ dependencies:
164
206
  - - "~>"
165
207
  - !ruby/object:Gem::Version
166
208
  version: '13.0'
167
- description: EBNF is a Ruby parser for W3C EBNF and a parser generator for compliant
168
- LL(1) grammars.
209
+ description: EBNF is a Ruby parser for W3C EBNF and a parser generator for PEG and
210
+ LL(1). Also includes parsing modes for ISO EBNF and ABNF.
169
211
  email: public-rdf-ruby@w3.org
170
212
  executables:
171
213
  - ebnf
@@ -226,13 +268,14 @@ files:
226
268
  - lib/ebnf/peg/rule.rb
227
269
  - lib/ebnf/rule.rb
228
270
  - lib/ebnf/terminals.rb
271
+ - lib/ebnf/unescape.rb
229
272
  - lib/ebnf/version.rb
230
273
  - lib/ebnf/writer.rb
231
274
  homepage: https://github.com/dryruby/ebnf
232
275
  licenses:
233
276
  - Unlicense
234
277
  metadata: {}
235
- post_install_message:
278
+ post_install_message:
236
279
  rdoc_options: []
237
280
  require_paths:
238
281
  - lib
@@ -247,8 +290,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
247
290
  - !ruby/object:Gem::Version
248
291
  version: '0'
249
292
  requirements: []
250
- rubygems_version: 3.1.3
251
- signing_key:
293
+ rubygems_version: 3.2.15
294
+ signing_key:
252
295
  specification_version: 4
253
- summary: EBNF parser and parser generator.
296
+ summary: EBNF parser and parser generator in Ruby.
254
297
  test_files: []