parsanol 1.3.107-arm-linux → 1.3.109-arm-linux

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.
@@ -0,0 +1,407 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ module PARG
5
+ module Import
6
+ # ANTLR 4 importer — scoped v1 (ruby#309).
7
+ #
8
+ # Imported: parser rules, lexer rules (as PARG rules), fragments
9
+ # (inlined name rules), literals, character sets and ranges,
10
+ # `?`/`*`/`+` postfixes, groups, `|` alternatives, `//` and `/* */`
11
+ # comments.
12
+ #
13
+ # Reported per occurrence with position, never silently dropped:
14
+ # semantic predicates and actions `{...}`, non-greedy quantifiers
15
+ # (`*?`), alternative labels (`# name`), `options {}`, `imports`,
16
+ # `tokenVocab`, lexer commands (`-> mode/skip/channel`).
17
+ #
18
+ # The deep semantic difference, stated like the ABNF importer's:
19
+ # ANTLR's adaptive LL(*) tolerates ambiguity; PARG's ordered choice
20
+ # is decisive. The compile-time order-dependence lint surfaces
21
+ # every branch pair whose order matters, so a mechanical
22
+ # transliteration is reviewable instead of silently different.
23
+ class Antlr
24
+ class Error < StandardError
25
+ end
26
+
27
+ TOKEN = /
28
+ (?<block_comment>\/\*(?:[^*]|\*+[^*\/])*\*+\/)
29
+ | (?<line_comment>\/\/[^\n]*)
30
+ | (?<literal>'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")
31
+ | (?<charset>\[(?:[^\]\\]|\\.)*\])
32
+ | (?<action>\{(?:(?>[^{}])|\{(?:(?>[^{}])|\{[^{}]*\}))*\}*)
33
+ | (?<punct>=\/|->|\.\.|[;:|()+*?.#\[\]=~^])
34
+ | (?<name>[A-Za-z_][A-Za-z0-9_]*)
35
+ | (?<nl>\n)
36
+ | (?<ws>[ \t\r]+)
37
+ /x
38
+
39
+ SKIP = %i[block_comment line_comment ws].freeze
40
+
41
+ class << self
42
+ def call(text)
43
+ new(text).call
44
+ end
45
+ end
46
+
47
+ def initialize(text)
48
+ @text = text
49
+ @tokens = []
50
+ @pos = 0
51
+ @rules = {}
52
+ @order = []
53
+ scan
54
+ end
55
+
56
+ def call
57
+ parse_grammar
58
+ emit
59
+ end
60
+
61
+ private
62
+
63
+ def scan
64
+ pos = 0
65
+ line = 1
66
+ until pos >= @text.length
67
+ if @text[pos] == "\n"
68
+ @tokens << [:nl, "\n", line]
69
+ line += 1
70
+ pos += 1
71
+ next
72
+ end
73
+ match = TOKEN.match(@text, pos)
74
+ if match.nil? || match.begin(0) != pos
75
+ raise Error, "ANTLR: unexpected #{@text[pos].inspect} at offset #{pos} (line #{line})"
76
+ end
77
+
78
+ name = TOKEN.names.find { |n| match[n] }
79
+ @tokens << [name.to_sym, match[0], line]
80
+ pos = match.end(0)
81
+ end
82
+ end
83
+
84
+ def peek(offset = 0)
85
+ @tokens[@pos + offset]
86
+ end
87
+
88
+ def advance
89
+ token = @tokens[@pos]
90
+ @pos += 1
91
+ token
92
+ end
93
+
94
+ def skip_fillers
95
+ advance while (peek && SKIP.include?(peek[0])) || peek&.[](0) == :nl
96
+ end
97
+
98
+ def parse_grammar
99
+ loop do
100
+ skip_fillers
101
+ break if peek.nil?
102
+
103
+ # `grammar Name;` — the declaration carries no rules.
104
+ if peek&.[](0) == :name && peek[1] == "grammar"
105
+ advance
106
+ skip_fillers
107
+ advance while peek&.[](0) == :name
108
+ skip_fillers
109
+ advance if peek&.[](0) == :punct && peek[1] == ";"
110
+ next
111
+ end
112
+
113
+ parse_rule
114
+ end
115
+ end
116
+
117
+ def parse_rule
118
+ skip_fillers
119
+ name_token = advance
120
+ raise Error, "ANTLR: expected rule name, got #{name_token&.[](1).inspect}" unless name_token&.[](0) == :name
121
+
122
+ skip_fillers
123
+
124
+ colon = advance
125
+ unless colon && colon[0] == :punct && colon[1] == ":"
126
+ what = colon&.[](1) == "=" ? "'=' (ANTLR has no rule assignment)" : colon&.[](1).inspect
127
+ raise Error,
128
+ "ANTLR: rule #{name_token[1].inspect} (line #{name_token[2]}): expected ':', got #{what} — " \
129
+ "options/imports/tokenVocab sections are out of scope for v1"
130
+ end
131
+
132
+ name = normalize_name(name_token[1])
133
+ body = parse_alternation
134
+ finish_rule(name, body)
135
+ end
136
+
137
+ def finish_rule(name, body)
138
+ raise Error, "ANTLR: expected ';' after rule #{name.inspect}" unless semicolon?
139
+
140
+ if @rules.key?(name)
141
+ raise Error, "ANTLR: duplicate rule #{name.inspect}"
142
+ end
143
+
144
+ @rules[name] = body
145
+ @order << name
146
+ end
147
+
148
+ def semicolon?
149
+ skip_fillers
150
+ return false unless peek&.[](0) == :punct && peek[1] == ";"
151
+
152
+ advance
153
+ true
154
+ end
155
+
156
+ def parse_alternation
157
+ parts = [parse_element_sequence]
158
+ while peek&.[](0) == :punct && peek[1] == "|"
159
+ advance
160
+ parts << parse_element_sequence
161
+ end
162
+ reject_label!
163
+ parts.join(" / ")
164
+ end
165
+
166
+ def reject_label!
167
+ return unless peek&.[](0) == :punct && peek[1] == "#"
168
+
169
+ token = advance
170
+ label = advance
171
+ raise Error,
172
+ "ANTLR: alternative labels (##{label&.[](1)}) are out of scope for v1 " \
173
+ "(line #{token[2]}) — label the rule instead"
174
+ end
175
+
176
+ def element_sequence_terminator?(token)
177
+ token&.[](0) == :punct && %w[; | )].include?(token[1])
178
+ end
179
+
180
+ def reject_lexer_command!
181
+ return unless peek&.[](0) == :punct && peek[1] == "->"
182
+
183
+ token = advance
184
+ skip_fillers
185
+ command = advance
186
+ guidance =
187
+ if command&.[](1) == "skip"
188
+ "PARG's equivalent is a `skip = <rule>` declaration at the grammar level, " \
189
+ "not a per-token command"
190
+ else
191
+ "lexer modes/channels have no PARG equivalent"
192
+ end
193
+ raise Error,
194
+ "ANTLR: lexer command -> #{command&.[](1).inspect} (line #{token[2]}) is out of " \
195
+ "scope for v1 — #{guidance}"
196
+ end
197
+
198
+ def parse_element_sequence
199
+ parts = []
200
+ loop do
201
+ skip_fillers
202
+ break if peek.nil?
203
+ break if element_sequence_terminator?(peek)
204
+
205
+ reject_lexer_command!
206
+
207
+ part = parse_postfixed
208
+ next if part == :eof # consume-all is implicit in PARG
209
+
210
+ parts << part
211
+ end
212
+ raise Error, "ANTLR: empty rule body" if parts.empty?
213
+
214
+ parts.length == 1 ? parts[0] : "(#{parts.join(' ')})"
215
+ end
216
+
217
+ def parse_postfixed
218
+ element = parse_element
219
+ loop do
220
+ skip_fillers
221
+ token = peek
222
+ break unless token&.[](0) == :punct
223
+
224
+ case token[1]
225
+ when "?" then advance and element = "[#{element}]"
226
+ when "*", "+"
227
+ advance
228
+ skip_fillers
229
+ if peek&.[](0) == :punct && peek[1] == "?"
230
+ raise Error,
231
+ "ANTLR: non-greedy quantifier #{token[1]}? (line #{token[2]}) is out of " \
232
+ "scope for v1 — ordered choice has no lazy repetition; " \
233
+ "restructure the rule"
234
+ end
235
+ element = token[1] == "*" ? "*#{element}" : "1*#{element}"
236
+ else break
237
+ end
238
+ end
239
+ element
240
+ end
241
+
242
+ def parse_element
243
+ token = advance
244
+ raise Error, "ANTLR: unexpected end of rule body" if token.nil?
245
+
246
+ case token[0]
247
+ when :literal then emit_literal(token[1], token[2])
248
+ when :charset then emit_charset(token[1], token[2])
249
+ when :name
250
+ return :eof if token[1] == "EOF" # consume-all is implicit
251
+
252
+ normalize_name(token[1])
253
+ when :punct
254
+ case token[1]
255
+ when "#"
256
+ raise Error,
257
+ "ANTLR: alternative labels are out of scope for v1 " \
258
+ "(line #{token[2]}) — label the rule instead"
259
+ when "("
260
+ body = parse_alternation
261
+ skip_fillers
262
+ expect ")"
263
+ "(#{body})"
264
+ when "."
265
+ "ANY"
266
+ when "~"
267
+ # The negation idiom: ~[set] = any codepoint outside the
268
+ # set, as a lookahead-guarded ANY. Quantifiers land outside
269
+ # via parse_postfixed.
270
+ set_token = advance
271
+ raise Error, "ANTLR: ~ must apply to a character set (line #{token[2]})" unless set_token&.[](0) == :charset
272
+
273
+ ranges = charset_ranges(set_token[1][1..-2], token[2])
274
+ set_text = ranges.map { |first, last| range_text(first, last) }.join(" / ")
275
+ "( !( (#{set_text}) ) ANY )"
276
+ else
277
+ raise Error, "ANTLR: unexpected #{token[1].inspect} (line #{token[2]})"
278
+ end
279
+ when :action
280
+ raise Error,
281
+ "ANTLR: action/predicate #{token[1][0, 24].inspect} (line #{token[2]}) is out of " \
282
+ "scope for v1 — target-language code has no PARG equivalent"
283
+ else
284
+ raise Error, "ANTLR: unexpected token #{token[0]} (line #{token[2]})"
285
+ end
286
+ end
287
+
288
+ def expect(char)
289
+ token = advance
290
+ return if token&.[](0) == :punct && token[1] == char
291
+
292
+ raise Error, "ANTLR: expected #{char.inspect}, got #{token&.[](1).inspect} (line #{token&.[](2)})"
293
+ end
294
+
295
+ def emit_literal(token, line)
296
+ body = token[1..-2]
297
+ raise Error, "ANTLR: empty literal (line #{line})" if body.empty?
298
+
299
+ escaped = body.gsub("\\", "\\\\").gsub('"', '\\"')
300
+ "\"#{escaped}\""
301
+ end
302
+
303
+ # [a-z], [abc], [^abc], [a-zA-Z0-9_] -- ANTLR char sets are
304
+ # case-SENSITIVE single characters. PARG expresses classes as
305
+ # %x-range alternations.
306
+ def emit_charset(token, line)
307
+ body = token[1..-2]
308
+ negated = body.start_with?("^")
309
+ body = body[1..] if negated
310
+ ranges = charset_ranges(body, line)
311
+ ranges = complement_ranges(ranges) if negated
312
+ "(#{ranges.map { |lo, hi| range_text(lo, hi) }.join(' / ')})"
313
+ end
314
+
315
+ def charset_ranges(body, line)
316
+ ranges = []
317
+ i = 0
318
+ while i < body.length
319
+ if body[i] == "\\"
320
+ byte = escape_byte(body[i + 1], line)
321
+ ranges << [byte, byte]
322
+ i += 2
323
+ elsif body[i + 1] == "-" && body[i + 2] && body[i + 2] != "]"
324
+ lo = literal_byte(body[i], line)
325
+ hi = literal_byte(body[i + 2], line)
326
+ raise Error, "ANTLR: reversed range (line #{line})" if lo > hi
327
+
328
+ ranges << [lo, hi]
329
+ i += 3
330
+ else
331
+ ranges << [literal_byte(body[i], line), literal_byte(body[i], line)]
332
+ i += 1
333
+ end
334
+ end
335
+ ranges
336
+ end
337
+
338
+ # A plain (unescaped) charset character is its own codepoint --
339
+ # running it through the escape table mapped lowercase r/t/n to
340
+ # CR/TAB/LF (the [Rr][Ee][Cc][Tt]-style keyword idiom broke).
341
+ def literal_byte(char, line)
342
+ byte = char.ord
343
+ return byte if byte <= 0x7F
344
+
345
+ raise Error, "ANTLR: non-ASCII #{char.inspect} in a character set (line #{line}) -- " \
346
+ "write a %x range or a literal instead"
347
+ end
348
+
349
+ def escape_byte(char, line)
350
+ case char
351
+ when "n" then 0x0A
352
+ when "r" then 0x0D
353
+ when "t" then 0x09
354
+ when "\\" then 0x5C
355
+ else
356
+ byte = char.ord
357
+ if byte > 0x7F
358
+ raise Error, "ANTLR: non-ASCII #{char.inspect} in a character set (line #{line}) -- " \
359
+ "write a %x range or a literal instead"
360
+ end
361
+
362
+ byte
363
+ end
364
+ end
365
+
366
+ def complement_ranges(ranges)
367
+ result = []
368
+ cursor = 0x00
369
+ ranges.sort.each do |lo, hi|
370
+ result << [cursor, lo - 1] if lo > cursor
371
+ cursor = hi + 1
372
+ end
373
+ result << [cursor, 0x7F] if cursor <= 0x7F
374
+ raise Error, "ANTLR: negated set covers everything" if result.empty?
375
+
376
+ result
377
+ end
378
+
379
+ def range_text(first, last)
380
+ first == last ? "%x#{format('%02X', first)}" : "%x#{format('%02X', first)}-#{format('%02X', last)}"
381
+ end
382
+
383
+ def normalize_name(name)
384
+ # Lexer rules are UPPER_CASE; PARG rule names are lowercase.
385
+ name.downcase
386
+ end
387
+
388
+ def emit
389
+ header = <<~PARG
390
+ # Imported from ANTLR 4 (scoped v1).
391
+ # Notes:
392
+ # - ANTLR's adaptive LL(*) tolerates ambiguity; PARG's ordered
393
+ # choice is decisive — the compiler's order-dependence lint
394
+ # surfaces every branch pair whose order matters.
395
+ # - Lexer rules imported as ordinary rules; case-sensitivity of
396
+ # ANTLR literals is preserved (plain "..." literals).
397
+ # - Actions, predicates, labels, options, lexer commands:
398
+ # rejected with position, never silently dropped.
399
+ PARG
400
+ body = @order.filter_map { |name| " #{"#{name} = #{@rules[name]}" if @rules[name]}" }
401
+ .join("\n")
402
+ "#{header}grammar Imported version \"0.0.0\" {\n#{body}\n}\n"
403
+ end
404
+ end
405
+ end
406
+ end
407
+ end
@@ -17,6 +17,7 @@ module Parsanol
17
17
  | (?<doc>\#\#[^\n]*)
18
18
  | (?<comment>\#[^\n]*)
19
19
  | (?<hex>%x[0-9A-Fa-f]{2,6}(?:-[0-9A-Fa-f]{2,6}|(?:\.[0-9A-Fa-f]{2,6})+)?)
20
+ | (?<category>%p\{[A-Za-z]{1,2}\})
20
21
  | (?<istr>%i"(?:[^"\\]|\\.)*")
21
22
  | (?<sstr>%s"(?:[^"\\]|\\.)*")
22
23
  | (?<str>"(?:[^"\\]|\\.)*")