parsanol 1.3.133 → 1.3.134

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: aaa8f6b6e151403cdf4bc4e972efed9bb50e8110319efc691b6f17641622022d
4
- data.tar.gz: fd5e0547ab14476606878ec4574d9843c21c5bd69fb5a9229b73c6addc44042e
3
+ metadata.gz: f08814380448c3eda0ac68afa1e30d4abdbfe7a1eb7dd40d0f1da7865df7b26a
4
+ data.tar.gz: 9c22bd914b428f88bbe351931a126dceb05fcc0a213ce6bbbbef3f710fa69693
5
5
  SHA512:
6
- metadata.gz: 2c538d4fccf9e5ca53925c95cd750e6d3630d5c14bc8b3f0e9a2a03c5ae1b835d44d2b459f05c3e7f4d5df88335b61a07bc31046390400ea0ce2a7d36587b198
7
- data.tar.gz: ea52f3331b65a1d561acc73e886cbf65f417c8474e1fde1bec33a30a298f9720831b919695f3158b4738839c3b914b77cd631336e64ab28a402ef14d17a9493b
6
+ metadata.gz: 1041019c537d1ad263b1752ac2943c55573147a403eea07a0fa4a58ef2ea07cb8d36194a23845d35143b13aeaf25e757baf3ec22a2520c09a72e192a0dc1f4dc
7
+ data.tar.gz: 6054b35ed91e9f0fab5a04b4219ff3f94ebd3d981efe2a38d6f1a9650c2a7308e3a279c7aa94cee7b4503fe435e73f388e9578c520807771ccc7cbbdf7ada8af
@@ -99,7 +99,7 @@ module Parsanol
99
99
  return handle
100
100
  end
101
101
 
102
- json = legacy ? GrammarSerializer.serialize(root_atom) : grammar_json(root_atom)
102
+ json = legacy ? GrammarSerializer.serialize(root_atom, preserve_identity: true) : grammar_json(root_atom)
103
103
  handle = Native._register_grammar(json)
104
104
  HANDLE_CACHE[cache_key] = handle
105
105
  trim_caches
@@ -8,14 +8,22 @@ module Parsanol
8
8
  # Serialize a Parslet grammar (root atom) to JSON string
9
9
  #
10
10
  # @param root [Parsanol::Atoms::Base] The root atom of the grammar
11
+ # @param preserve_identity [Boolean] emit exactly one wire atom per
12
+ # Ruby atom, even when an atom is reached again through a recursive
13
+ # rule while its own children are still being serialized. Parslet
14
+ # compatibility needs this: its memo is keyed by atom identity, so a
15
+ # duplicated wire atom splits one memo entry in two and the native
16
+ # engine can pick a different alternative than the Ruby engine. The
17
+ # default keeps the established wire layout of strict grammars.
11
18
  # @return [String] JSON representation of the grammar
12
- def self.serialize(root)
19
+ def self.serialize(root, preserve_identity: false)
13
20
  # Create fresh instance for each serialization
14
21
  # (state is specific to each grammar)
15
- new.serialize(root)
22
+ new(preserve_identity: preserve_identity).serialize(root)
16
23
  end
17
24
 
18
- def initialize
25
+ def initialize(preserve_identity: false)
26
+ @preserve_identity = preserve_identity
19
27
  @atoms = []
20
28
  @atom_cache = {} # object_id => atom_id for deduplication
21
29
  # Strong retention: cache keys are object_ids; without holding the
@@ -52,74 +60,19 @@ module Parsanol
52
60
  # Don't create a new atom, just resolve and return the referenced atom_id
53
61
  return serialize_entity(atom) if atom.is_a?(Parsanol::Atoms::Entity)
54
62
 
55
- # Serialize based on atom type first (recursively)
56
- serialized = case atom
57
- when Parsanol::Atoms::Str
58
- serialize_str(atom)
59
- when Parsanol::Atoms::Re
60
- serialize_re(atom)
61
- when Parsanol::Atoms::Sequence
62
- serialize_sequence(atom)
63
- when Parsanol::Atoms::Alternative
64
- serialize_alternative(atom)
65
- when Parsanol::Atoms::Repetition
66
- serialize_repetition(atom)
67
- when Parsanol::Atoms::Named
68
- serialize_named(atom)
69
- when Parsanol::Atoms::Lookahead
70
- serialize_lookahead(atom)
71
- when Parsanol::Atoms::Capture
72
- serialize_capture(atom)
73
- when Parsanol::Atoms::Scope
74
- serialize_scope(atom)
75
- when Parsanol::Atoms::StateSet
76
- serialize_state_set(atom)
77
- when Parsanol::Atoms::StateMatch
78
- serialize_state_match(atom)
79
- when Parsanol::Atoms::StateSwitch
80
- serialize_state_switch(atom)
81
- when Parsanol::Atoms::CustomRef
82
- serialize_custom_ref(atom)
83
- when Parsanol::Atoms::Dynamic
84
- serialize_dynamic(atom)
85
- when Parsanol::Atoms::TriviaCapture
86
- # parsanol-ruby#152: the capture wrapper carries
87
- # its marker table so the native engine records
88
- # comment-shaped units into the pending channel
89
- # and attaches them to the next Named capture
90
- # under `comments:`. A distinct variant keeps the
91
- # declared gate: engines predating it reject
92
- # capture-carrying artifacts loudly.
93
- serialize_trivia_capture(atom)
94
- when Parsanol::Atoms::Trivia
95
- # Wire-distinct from Ignore (rs#172): the Rust
96
- # engine suppresses trivia-position diagnostics by
97
- # atom kind. Engines predating the Trivia tag
98
- # reject skip-carrying artifacts loudly (unknown
99
- # variant), which is the declared gate.
100
- serialize_trivia(atom)
101
- when Parsanol::Atoms::Constant
102
- # rs#137 follow-up: coradoc-markdown's Output
103
- # atoms — empty match yielding a wire constant.
104
- { "Constant" => { "value" => serialize_constant_value(atom.value) } }
105
- when Parsanol::Atoms::Lookbehind
106
- # rs#137/#163: the behind-window test — a fixed
107
- # literal window or an end-anchored regex (the
108
- # CommonMark flanking form).
109
- look = if atom.regex?
110
- { "Regex" => { "source" => atom.regex_source } }
111
- else
112
- { "Literal" => { "count" => atom.count,
113
- "pattern" => atom.pattern } }
114
- end
115
- { "Lookbehind" => { "look" => look,
116
- "positive" => atom.positive } }
117
- when Parsanol::Atoms::Ignored
118
- serialize_ignored(atom)
119
- else
120
- # Fallback for unknown atom types
121
- serialize_unknown(atom)
122
- end
63
+ # Reserve the id before the children, as serialize_entity does, so a
64
+ # recursive path back to this atom reuses it instead of emitting a
65
+ # second copy.
66
+ if @preserve_identity
67
+ atom_id = @atoms.size
68
+ @atom_cache[cache_key] = atom_id
69
+ @retained_atoms << atom
70
+ @atoms << nil
71
+ @atoms[atom_id] = serialize_body(atom)
72
+ return atom_id
73
+ end
74
+
75
+ serialized = serialize_body(atom)
123
76
 
124
77
  # Now reserve an atom_id and cache
125
78
  atom_id = @atoms.size
@@ -130,6 +83,77 @@ module Parsanol
130
83
  atom_id
131
84
  end
132
85
 
86
+ def serialize_body(atom)
87
+ # Serialize based on atom type first (recursively)
88
+ case atom
89
+ when Parsanol::Atoms::Str
90
+ serialize_str(atom)
91
+ when Parsanol::Atoms::Re
92
+ serialize_re(atom)
93
+ when Parsanol::Atoms::Sequence
94
+ serialize_sequence(atom)
95
+ when Parsanol::Atoms::Alternative
96
+ serialize_alternative(atom)
97
+ when Parsanol::Atoms::Repetition
98
+ serialize_repetition(atom)
99
+ when Parsanol::Atoms::Named
100
+ serialize_named(atom)
101
+ when Parsanol::Atoms::Lookahead
102
+ serialize_lookahead(atom)
103
+ when Parsanol::Atoms::Capture
104
+ serialize_capture(atom)
105
+ when Parsanol::Atoms::Scope
106
+ serialize_scope(atom)
107
+ when Parsanol::Atoms::StateSet
108
+ serialize_state_set(atom)
109
+ when Parsanol::Atoms::StateMatch
110
+ serialize_state_match(atom)
111
+ when Parsanol::Atoms::StateSwitch
112
+ serialize_state_switch(atom)
113
+ when Parsanol::Atoms::CustomRef
114
+ serialize_custom_ref(atom)
115
+ when Parsanol::Atoms::Dynamic
116
+ serialize_dynamic(atom)
117
+ when Parsanol::Atoms::TriviaCapture
118
+ # parsanol-ruby#152: the capture wrapper carries
119
+ # its marker table so the native engine records
120
+ # comment-shaped units into the pending channel
121
+ # and attaches them to the next Named capture
122
+ # under `comments:`. A distinct variant keeps the
123
+ # declared gate: engines predating it reject
124
+ # capture-carrying artifacts loudly.
125
+ serialize_trivia_capture(atom)
126
+ when Parsanol::Atoms::Trivia
127
+ # Wire-distinct from Ignore (rs#172): the Rust
128
+ # engine suppresses trivia-position diagnostics by
129
+ # atom kind. Engines predating the Trivia tag
130
+ # reject skip-carrying artifacts loudly (unknown
131
+ # variant), which is the declared gate.
132
+ serialize_trivia(atom)
133
+ when Parsanol::Atoms::Constant
134
+ # rs#137 follow-up: coradoc-markdown's Output
135
+ # atoms — empty match yielding a wire constant.
136
+ { "Constant" => { "value" => serialize_constant_value(atom.value) } }
137
+ when Parsanol::Atoms::Lookbehind
138
+ # rs#137/#163: the behind-window test — a fixed
139
+ # literal window or an end-anchored regex (the
140
+ # CommonMark flanking form).
141
+ look = if atom.regex?
142
+ { "Regex" => { "source" => atom.regex_source } }
143
+ else
144
+ { "Literal" => { "count" => atom.count,
145
+ "pattern" => atom.pattern } }
146
+ end
147
+ { "Lookbehind" => { "look" => look,
148
+ "positive" => atom.positive } }
149
+ when Parsanol::Atoms::Ignored
150
+ serialize_ignored(atom)
151
+ else
152
+ # Fallback for unknown atom types
153
+ serialize_unknown(atom)
154
+ end
155
+ end
156
+
133
157
  def serialize_str(atom)
134
158
  {
135
159
  "Str" => {
@@ -402,12 +402,22 @@ module Parsanol
402
402
  BatchDecoder.decode_and_flatten(raw, input, Parsanol::Slice)
403
403
  end
404
404
 
405
+ # The native engine's recursion-limit abort (ParseError::
406
+ # RecursionLimitExceeded in parsanol-rs). It carries no position
407
+ # marker: it is a resource limit, not a refusal of the input.
408
+ NATIVE_RECURSION_LIMIT = /\A(?:Recursion limit exceeded\b|Maximum recursion depth \(\d+\) exceeded)/
409
+
405
410
  def raise_compatible_parse_error(error, input)
406
411
  message = error.message
407
412
  position = message[NATIVE_POS_MARKER, 1].to_i
408
413
  cause = Parsanol::Cause.new(message.sub(NATIVE_POS_MARKER, ""),
409
414
  Parsanol::Source.new(input), position)
410
- raise Parsanol::ParseFailed.new(cause.to_s, cause), cause: nil
415
+ failure = if !message.match?(NATIVE_POS_MARKER) && message.match?(NATIVE_RECURSION_LIMIT)
416
+ Parsanol::RecursionLimitExceeded
417
+ else
418
+ Parsanol::ParseFailed
419
+ end
420
+ raise failure.new(cause.to_s, cause), cause: nil
411
421
  end
412
422
 
413
423
  # Reference-walker path (TODO.sota/8 B1): the plain recursive
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.133"
4
+ VERSION = "1.3.134"
5
5
  end
data/lib/parsanol.rb CHANGED
@@ -66,6 +66,13 @@ module Parsanol
66
66
  attr_reader :parse_failure_cause
67
67
  end
68
68
 
69
+ # Raised when the native engine stops because the input nests deeper
70
+ # than its recursion limit. A subclass of ParseFailed with the same
71
+ # message and cause, so existing rescues behave as before; callers that
72
+ # want to retry on another engine can rescue this class instead of
73
+ # matching the message.
74
+ class RecursionLimitExceeded < ParseFailed; end
75
+
69
76
  # Class methods added to classes that include Parsanol.
70
77
  module ClassMethods
71
78
  # Enable automatic rule optimization for all rules in this parser.
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.133
4
+ version: 1.3.134
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.