parsanol 1.3.28 → 1.3.30

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90a12c42d3cdf5eb70a49842ae813f2f09eb1795a14eafbff6883c995b231b42
4
- data.tar.gz: b28b6cc887ef90d4adfa7f196e50392d9f427bdbce5b948cb1bf68ddc654db1b
3
+ metadata.gz: 21f56a5196775ab90fe91a82e7b71240bc1f48b64ed90886b947285bf760c0b7
4
+ data.tar.gz: 1463e0eb3af171d61f8e09f30deec7d15bc679341d129197cdb5845efd3357b3
5
5
  SHA512:
6
- metadata.gz: 7219f81d7d291bb894e6cb4e7518a5daeea0e68a4411b21bd0e219167bb43c2b257bbce1193d1d4ea283ed31cd0a6aef3ba5d350c8d7abddddee9f7ec1b2430d
7
- data.tar.gz: 69526091581aa03a9953af5ed69d2bda2ac118d730d2516534f68de9fbf1ca9de7f6d806acff2e0a2db5b8964984223b89c9f10345fbb2367fdc948a6863599c
6
+ metadata.gz: e552a716aa3cf2facea589d2310db16e14664ceefd6d1412d97fb945314dadb0ef31dbe2d835414d6d05c8dd1420386a244ce7ff7c1fba42e97ac26180d2db14
7
+ data.tar.gz: 16cf21e24b996b1f1a314e36c29b81c7ba3f16a303d9538886b823c94db1759b662e732fdb68a019e5adfb264e2c381de4aa7be3cc7907538173ef260c173a69
data/Cargo.lock CHANGED
@@ -253,7 +253,7 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
253
253
  [[package]]
254
254
  name = "parsanol"
255
255
  version = "0.5.1"
256
- source = "git+https://github.com/parsanol/parsanol-rs?branch=main#3d61560a1eebb203d2373f56bdf209105e82e37b"
256
+ source = "git+https://github.com/parsanol/parsanol-rs?branch=main#32cdbb1d3c04c9280f6fb4d2cf13f1c5befbd00f"
257
257
  dependencies = [
258
258
  "ahash",
259
259
  "getrandom 0.3.4",
@@ -270,7 +270,7 @@ dependencies = [
270
270
  [[package]]
271
271
  name = "parsanol-derive"
272
272
  version = "0.5.1"
273
- source = "git+https://github.com/parsanol/parsanol-rs?branch=main#3d61560a1eebb203d2373f56bdf209105e82e37b"
273
+ source = "git+https://github.com/parsanol/parsanol-rs?branch=main#32cdbb1d3c04c9280f6fb4d2cf13f1c5befbd00f"
274
274
  dependencies = [
275
275
  "proc-macro2",
276
276
  "quote",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.28"
4
+ VERSION = "1.3.30"
5
5
  end
data/lib/parsanol/vm.rb CHANGED
@@ -43,6 +43,7 @@ module Parsanol
43
43
  RUN_RE = 24
44
44
  RUN_STR = 25
45
45
  SEQ_SIMPLE = 26
46
+ BYTE_DISPATCH = 27
46
47
 
47
48
  K_ALT = 0
48
49
  K_REP = 1
@@ -247,6 +248,147 @@ module Parsanol
247
248
  @ops.flatten!(1)
248
249
  end
249
250
 
251
+ # Conservative lead-byte table for a branch: table[b] true iff the
252
+ # atom can start with byte b. nil = cannot prove a constraint (no
253
+ # guard emitted). Conservative in the safe direction only: a guard
254
+ # may allow a branch that then fails normally, never reject one
255
+ # that could match.
256
+ def pairwise_disjoint?(tables)
257
+ seen = Array.new(256, false)
258
+ tables.each do |t|
259
+ i = 0
260
+ while i < 256
261
+ return false if t[i] && seen[i]
262
+
263
+ seen[i] = true if t[i]
264
+ i += 1
265
+ end
266
+ end
267
+ true
268
+ end
269
+
270
+ def first_byte_table(atom, depth = 0)
271
+ return nil if depth > 10
272
+
273
+ case atom
274
+ when Parsanol::Atoms::Str
275
+ b = atom.str.getbyte(0)
276
+ return nil unless b
277
+
278
+ t = Array.new(256, false)
279
+ t[b] = true
280
+ t
281
+ when Parsanol::Atoms::Re
282
+ byte_table(atom.re)
283
+ when Parsanol::Atoms::Sequence
284
+ t = nil
285
+ nullable_all = true
286
+ atom.parslets.each do |child|
287
+ ct = first_byte_table(child, depth + 1)
288
+ return nil if ct.nil?
289
+
290
+ if t
291
+ i = 0
292
+ while i < 256
293
+ t[i] ||= ct[i]
294
+ i += 1
295
+ end
296
+ else
297
+ t = ct
298
+ end
299
+ unless nullable?(child, depth + 1)
300
+ nullable_all = false
301
+ break
302
+ end
303
+ end
304
+ nullable_all ? nil : t
305
+ when Parsanol::Atoms::Alternative
306
+ t = nil
307
+ atom.alternatives.each do |branch|
308
+ bt = first_byte_table(branch, depth + 1)
309
+ return nil if bt.nil?
310
+
311
+ if t
312
+ i = 0
313
+ while i < 256
314
+ t[i] ||= bt[i]
315
+ i += 1
316
+ end
317
+ else
318
+ t = bt
319
+ end
320
+ end
321
+ t
322
+ when Parsanol::Atoms::Named
323
+ first_byte_table(atom.parslet, depth + 1)
324
+ when Parsanol::Atoms::Ignored
325
+ first_byte_table(atom.wrapped_atom, depth + 1)
326
+ when Parsanol::Atoms::Lookahead
327
+ # Positive: body's lead bytes. Negative: body's lead bytes are
328
+ # a safe superset (a failing !-check just fails the branch
329
+ # normally).
330
+ first_byte_table(atom.bound_parslet, depth + 1)
331
+ when Parsanol::Atoms::Repetition
332
+ # min 0 can match empty — no byte constraint exists.
333
+ return nil if atom.min.zero?
334
+
335
+ first_byte_table(atom.parslet, depth + 1)
336
+ when Parsanol::Atoms::Entity
337
+ inner = begin
338
+ atom.parslet
339
+ rescue StandardError
340
+ nil
341
+ end
342
+ return nil if inner.nil?
343
+
344
+ first_byte_table(inner, depth + 1)
345
+ when Parsanol::Atoms::Scope
346
+ inner = begin
347
+ atom.block.call
348
+ rescue StandardError
349
+ nil
350
+ end
351
+ return nil if inner.nil?
352
+
353
+ first_byte_table(inner, depth + 1)
354
+ end
355
+ end
356
+
357
+ # Wrapper branches (Entity/Scope) share body shapes by design.
358
+ # rubocop:disable-next Lint/DuplicateBranch
359
+ def nullable?(atom, depth = 0)
360
+ return false if depth > 10
361
+
362
+ case atom
363
+ when Parsanol::Atoms::Str then atom.str.bytesize.zero?
364
+ when Parsanol::Atoms::Re then false
365
+ when Parsanol::Atoms::Sequence
366
+ atom.parslets.all? { |c| nullable?(c, depth + 1) }
367
+ when Parsanol::Atoms::Alternative
368
+ atom.alternatives.any? { |c| nullable?(c, depth + 1) }
369
+ when Parsanol::Atoms::Repetition then atom.min.zero?
370
+ when Parsanol::Atoms::Named then nullable?(atom.parslet, depth + 1)
371
+ when Parsanol::Atoms::Ignored then nullable?(atom.wrapped_atom, depth + 1)
372
+ when Parsanol::Atoms::Lookahead then true
373
+ when Parsanol::Atoms::Entity, Parsanol::Atoms::Scope
374
+ nullable_wrapped?( # rubocop:disable Lint/DuplicateBranch -- Entity/Scope differ only in accessor
375
+ -> { atom.is_a?(Parsanol::Atoms::Entity) ? atom.parslet : atom.block.call }, depth
376
+ )
377
+ else false
378
+ end
379
+ end
380
+
381
+ # Entity and Scope wrap lazily-resolvable inners through different
382
+ # accessors; the nullability logic is shared.
383
+ def nullable_wrapped?(resolver, depth)
384
+ inner = begin
385
+ resolver.call
386
+ rescue StandardError
387
+ nil
388
+ end
389
+ !inner.nil? && nullable?(inner, depth + 1)
390
+ end
391
+
250
392
  def compile_atom(atom, consume_all)
251
393
  case atom
252
394
  when Parsanol::Atoms::Str
@@ -282,6 +424,37 @@ module Parsanol
282
424
  count = alts.size
283
425
  return nil if count.zero?
284
426
 
427
+ # First-set discrimination: when every branch has a provable
428
+ # lead-byte table and the tables are pairwise disjoint, at most
429
+ # one branch can match any input — dispatch directly to the
430
+ # viable branch (or fail) in one lookup. Shared lead bytes
431
+ # fall back to the plain CHOICE machinery untouched.
432
+ tables = alts.map { |a| first_byte_table(a) }
433
+ if tables.all? && pairwise_disjoint?(tables)
434
+ dispatch_idx = emit(BYTE_DISPATCH, nil, nil, nil)
435
+ starts = []
436
+ idx = 0
437
+ while idx < count
438
+ starts << flat(@ops.size)
439
+ return nil unless compile_atom(alts[idx], consume_all)
440
+
441
+ idx += 1
442
+ end
443
+ table = Array.new(256, -1)
444
+ tables.each_with_index do |t, bi|
445
+ pc_i = starts[bi]
446
+ bi2 = bi
447
+ j = 0
448
+ while j < 256
449
+ table[j] = pc_i if t[j]
450
+ j += 1
451
+ end
452
+ bi2
453
+ end
454
+ patch(dispatch_idx, 1, table)
455
+ return self
456
+ end
457
+
285
458
  jumps = []
286
459
  idx = 0
287
460
  while idx < count
@@ -793,6 +966,11 @@ module Parsanol
793
966
  frames.slice!(cbase..)
794
967
  pc += 4
795
968
  end
969
+ when BYTE_DISPATCH
970
+ table = ops[pc + 1]
971
+ b = pos < n ? bytes[pos] : -1
972
+ target = b == -1 ? -1 : table[b]
973
+ pc = target >= 0 ? target : FAIL
796
974
  when SEQ_SIMPLE
797
975
  kids = ops[pc + 1]
798
976
  kn = kids.size
data/parsanol.gemspec CHANGED
@@ -46,22 +46,4 @@ Gem::Specification.new do |spec|
46
46
 
47
47
  # Required for Rust extension
48
48
  spec.add_dependency "rb_sys", "~> 0.9.39"
49
-
50
- spec.add_development_dependency "rake", "~> 13.0"
51
- spec.add_development_dependency "rake-compiler", "~> 1.2.0"
52
- spec.add_development_dependency "rdoc", "~> 6.0"
53
- spec.add_development_dependency "rspec", "~> 3.0"
54
-
55
- # For code style checking
56
- spec.add_development_dependency "rubocop", "~> 1.0"
57
-
58
- # For Parslet compatibility verification
59
- spec.add_development_dependency "parslet", "~> 2.0.0"
60
-
61
- # For benchmarking
62
- spec.add_development_dependency "benchmark-ips", "~> 2.0"
63
-
64
- # For type checking
65
- spec.add_development_dependency "rbs", "~> 3.0"
66
- spec.add_development_dependency "steep", "~> 1.0"
67
49
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parsanol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.28
4
+ version: 1.3.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -23,132 +23,6 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.9.39
26
- - !ruby/object:Gem::Dependency
27
- name: rake
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '13.0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '13.0'
40
- - !ruby/object:Gem::Dependency
41
- name: rake-compiler
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: 1.2.0
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: 1.2.0
54
- - !ruby/object:Gem::Dependency
55
- name: rdoc
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '6.0'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '6.0'
68
- - !ruby/object:Gem::Dependency
69
- name: rspec
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '3.0'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '3.0'
82
- - !ruby/object:Gem::Dependency
83
- name: rubocop
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '1.0'
89
- type: :development
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '1.0'
96
- - !ruby/object:Gem::Dependency
97
- name: parslet
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: 2.0.0
103
- type: :development
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: 2.0.0
110
- - !ruby/object:Gem::Dependency
111
- name: benchmark-ips
112
- requirement: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - "~>"
115
- - !ruby/object:Gem::Version
116
- version: '2.0'
117
- type: :development
118
- prerelease: false
119
- version_requirements: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - "~>"
122
- - !ruby/object:Gem::Version
123
- version: '2.0'
124
- - !ruby/object:Gem::Dependency
125
- name: rbs
126
- requirement: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '3.0'
131
- type: :development
132
- prerelease: false
133
- version_requirements: !ruby/object:Gem::Requirement
134
- requirements:
135
- - - "~>"
136
- - !ruby/object:Gem::Version
137
- version: '3.0'
138
- - !ruby/object:Gem::Dependency
139
- name: steep
140
- requirement: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - "~>"
143
- - !ruby/object:Gem::Version
144
- version: '1.0'
145
- type: :development
146
- prerelease: false
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - "~>"
150
- - !ruby/object:Gem::Version
151
- version: '1.0'
152
26
  description: A small Ruby library for constructing parsers in the PEG (Parsing Expression
153
27
  Grammar) fashion. Parsanol provides Parslet-compatible API with additional features
154
28
  including static frozen parsers and dynamic parsers, with optional Rust native extension