parsanol 1.3.13-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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.txt +98 -0
  3. data/LICENSE +23 -0
  4. data/README.adoc +905 -0
  5. data/Rakefile +141 -0
  6. data/lib/parsanol/3.2/parsanol_native.so +0 -0
  7. data/lib/parsanol/3.3/parsanol_native.so +0 -0
  8. data/lib/parsanol/3.4/parsanol_native.so +0 -0
  9. data/lib/parsanol/4.0/parsanol_native.so +0 -0
  10. data/lib/parsanol/ast_visitor.rb +122 -0
  11. data/lib/parsanol/atoms/alternative.rb +123 -0
  12. data/lib/parsanol/atoms/base.rb +208 -0
  13. data/lib/parsanol/atoms/can_flatten.rb +194 -0
  14. data/lib/parsanol/atoms/capture.rb +38 -0
  15. data/lib/parsanol/atoms/context.rb +357 -0
  16. data/lib/parsanol/atoms/context_optimized.rb +38 -0
  17. data/lib/parsanol/atoms/custom.rb +110 -0
  18. data/lib/parsanol/atoms/cut.rb +66 -0
  19. data/lib/parsanol/atoms/dsl.rb +96 -0
  20. data/lib/parsanol/atoms/dynamic.rb +39 -0
  21. data/lib/parsanol/atoms/entity.rb +75 -0
  22. data/lib/parsanol/atoms/ignored.rb +37 -0
  23. data/lib/parsanol/atoms/infix.rb +167 -0
  24. data/lib/parsanol/atoms/lookahead.rb +85 -0
  25. data/lib/parsanol/atoms/named.rb +74 -0
  26. data/lib/parsanol/atoms/re.rb +83 -0
  27. data/lib/parsanol/atoms/repetition.rb +277 -0
  28. data/lib/parsanol/atoms/scope.rb +35 -0
  29. data/lib/parsanol/atoms/sequence.rb +195 -0
  30. data/lib/parsanol/atoms/str.rb +109 -0
  31. data/lib/parsanol/atoms/visitor.rb +91 -0
  32. data/lib/parsanol/atoms.rb +46 -0
  33. data/lib/parsanol/buffer.rb +133 -0
  34. data/lib/parsanol/builder_callbacks.rb +353 -0
  35. data/lib/parsanol/cause.rb +122 -0
  36. data/lib/parsanol/context.rb +39 -0
  37. data/lib/parsanol/convenience.rb +36 -0
  38. data/lib/parsanol/edit_tracker.rb +111 -0
  39. data/lib/parsanol/error_reporter/contextual.rb +99 -0
  40. data/lib/parsanol/error_reporter/deepest.rb +120 -0
  41. data/lib/parsanol/error_reporter/tree.rb +63 -0
  42. data/lib/parsanol/error_reporter.rb +100 -0
  43. data/lib/parsanol/expression/treetop.rb +154 -0
  44. data/lib/parsanol/expression.rb +106 -0
  45. data/lib/parsanol/fast_mode.rb +187 -0
  46. data/lib/parsanol/first_set.rb +79 -0
  47. data/lib/parsanol/grammar_builder.rb +179 -0
  48. data/lib/parsanol/incremental_parser.rb +182 -0
  49. data/lib/parsanol/interval_tree.rb +226 -0
  50. data/lib/parsanol/lazy_result.rb +179 -0
  51. data/lib/parsanol/mermaid.rb +142 -0
  52. data/lib/parsanol/native/batch_decoder.rb +255 -0
  53. data/lib/parsanol/native/dynamic.rb +238 -0
  54. data/lib/parsanol/native/parser.rb +102 -0
  55. data/lib/parsanol/native/serializer.rb +252 -0
  56. data/lib/parsanol/native/transformer.rb +604 -0
  57. data/lib/parsanol/native/types.rb +29 -0
  58. data/lib/parsanol/native.rb +223 -0
  59. data/lib/parsanol/optimizer.rb +85 -0
  60. data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
  61. data/lib/parsanol/optimizers/cut_inserter.rb +182 -0
  62. data/lib/parsanol/optimizers/lookahead_optimizer.rb +56 -0
  63. data/lib/parsanol/optimizers/quantifier_optimizer.rb +60 -0
  64. data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
  65. data/lib/parsanol/options/zero_copy.rb +127 -0
  66. data/lib/parsanol/options.rb +21 -0
  67. data/lib/parsanol/parallel.rb +128 -0
  68. data/lib/parsanol/parser.rb +242 -0
  69. data/lib/parsanol/parslet.rb +151 -0
  70. data/lib/parsanol/pattern/binding.rb +91 -0
  71. data/lib/parsanol/pattern.rb +162 -0
  72. data/lib/parsanol/pool.rb +219 -0
  73. data/lib/parsanol/pools/array_pool.rb +75 -0
  74. data/lib/parsanol/pools/buffer_pool.rb +182 -0
  75. data/lib/parsanol/pools/position_pool.rb +92 -0
  76. data/lib/parsanol/pools/slice_pool.rb +64 -0
  77. data/lib/parsanol/position.rb +94 -0
  78. data/lib/parsanol/resettable.rb +29 -0
  79. data/lib/parsanol/result.rb +46 -0
  80. data/lib/parsanol/result_builder.rb +208 -0
  81. data/lib/parsanol/result_stream.rb +266 -0
  82. data/lib/parsanol/rig/rspec.rb +71 -0
  83. data/lib/parsanol/rope.rb +81 -0
  84. data/lib/parsanol/scope.rb +104 -0
  85. data/lib/parsanol/slice.rb +160 -0
  86. data/lib/parsanol/source/line_cache.rb +102 -0
  87. data/lib/parsanol/source.rb +185 -0
  88. data/lib/parsanol/source_location.rb +167 -0
  89. data/lib/parsanol/streaming_parser.rb +124 -0
  90. data/lib/parsanol/string_view.rb +198 -0
  91. data/lib/parsanol/transform.rb +226 -0
  92. data/lib/parsanol/version.rb +5 -0
  93. data/lib/parsanol/wasm/README.md +80 -0
  94. data/lib/parsanol/wasm/package.json +51 -0
  95. data/lib/parsanol/wasm/parsanol.js +252 -0
  96. data/lib/parsanol/wasm/parslet.d.ts +129 -0
  97. data/lib/parsanol/wasm_parser.rb +240 -0
  98. data/lib/parsanol.rb +278 -0
  99. data/parsanol.gemspec +67 -0
  100. metadata +279 -0
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Parsanol::Parslet - Nested compatibility layer for original Parslet API
4
+ #
5
+ # This provides backwards compatibility for code that uses the original Parslet API.
6
+ # Instead of root-level Parslet constant, we use Parsanol::Parslet as a nested module.
7
+ #
8
+ # == Supported Features
9
+ #
10
+ # - All parser atoms (str, match, any, sequence, alternative, repetition, etc.)
11
+ # - Parser class with rule definitions
12
+ # - Transform for AST construction
13
+ # - Error reporting with Cause
14
+ # - Treetop-style expression parsing via exp()
15
+ #
16
+ # == Limitations
17
+ #
18
+ # - Some advanced features may require direct Parsanol usage
19
+ #
20
+ # Usage:
21
+ # require 'parsanol/parslet'
22
+ #
23
+ # class MyParser < Parsanol::Parslet::Parser
24
+ # include Parsanol::Parslet
25
+ # rule(:foo) { str('foo') }
26
+ # root(:foo)
27
+ # end
28
+ #
29
+ # Migration from original Parslet:
30
+ # Before: require 'parslet'
31
+ # class MyParser < Parslet::Parser
32
+ # include Parslet
33
+ #
34
+ # After: require 'parsanol/parslet'
35
+ # class MyParser < Parsanol::Parslet::Parser
36
+ # include Parsanol::Parslet
37
+
38
+ require "parsanol"
39
+
40
+ module Parsanol
41
+ module Parslet
42
+ # Include Parsanol to get all DSL methods (str, match, any, etc.)
43
+ include Parsanol
44
+
45
+ # Error class alias for compatibility
46
+ ParseFailed = Parsanol::ParseFailed
47
+
48
+ # Atoms namespace - aliases to Parsanol atoms
49
+ # These are the atoms explicitly loaded by lib/parsanol/atoms.rb
50
+ module Atoms
51
+ Base = ::Parsanol::Atoms::Base
52
+ Str = ::Parsanol::Atoms::Str
53
+ Re = ::Parsanol::Atoms::Re
54
+ Sequence = ::Parsanol::Atoms::Sequence
55
+ Alternative = ::Parsanol::Atoms::Alternative
56
+ Repetition = ::Parsanol::Atoms::Repetition
57
+ Named = ::Parsanol::Atoms::Named
58
+ Entity = ::Parsanol::Atoms::Entity
59
+ Lookahead = ::Parsanol::Atoms::Lookahead
60
+ Cut = ::Parsanol::Atoms::Cut
61
+ Capture = ::Parsanol::Atoms::Capture
62
+ Scope = ::Parsanol::Atoms::Scope
63
+ Dynamic = ::Parsanol::Atoms::Dynamic
64
+ Infix = ::Parsanol::Atoms::Infix
65
+ Ignored = ::Parsanol::Atoms::Ignored
66
+ ParseFailed = ::Parsanol::ParseFailed
67
+ end
68
+
69
+ # Class aliases
70
+ Parser = ::Parsanol::Parser
71
+ Transform = ::Parsanol::Transform
72
+ Cause = ::Parsanol::Cause
73
+ Slice = ::Parsanol::Slice
74
+ Source = ::Parsanol::Source
75
+ Pattern = ::Parsanol::Pattern
76
+ Context = ::Parsanol::Context
77
+
78
+ # Module functions for DSL (delegate to Parsanol)
79
+ module_function
80
+
81
+ def match(str = nil)
82
+ Parsanol.match(str)
83
+ end
84
+
85
+ def str(str)
86
+ Parsanol.str(str)
87
+ end
88
+
89
+ def any
90
+ Parsanol.any
91
+ end
92
+
93
+ def scope(&)
94
+ Parsanol.scope(&)
95
+ end
96
+
97
+ def dynamic(&)
98
+ Parsanol.dynamic(&)
99
+ end
100
+
101
+ def infix_expression(element, *operations, &)
102
+ Parsanol.infix_expression(element, *operations, &)
103
+ end
104
+
105
+ # Parses a treetop-style expression string and returns the corresponding atom.
106
+ # Delegates to Parsanol.exp.
107
+ #
108
+ # @example
109
+ # # the same as str('a') >> str('b').maybe
110
+ # exp(%q("a" "b"?))
111
+ #
112
+ # @param str [String] a treetop expression
113
+ # @return [Parsanol::Atoms::Base] the corresponding parser atom
114
+ def exp(str)
115
+ Parsanol.exp(str)
116
+ end
117
+
118
+ def sequence(symbol)
119
+ Parsanol.sequence(symbol)
120
+ end
121
+
122
+ def simple(symbol)
123
+ Parsanol.simple(symbol)
124
+ end
125
+
126
+ def subtree(symbol)
127
+ Parsanol.subtree(symbol)
128
+ end
129
+
130
+ # Class method extensions for Parser
131
+ module ClassMethods
132
+ # Enable automatic rule optimization for all rules in this parser.
133
+ # @param enable [Boolean] whether to enable optimization
134
+ def optimize_rules!(enable = true)
135
+ @optimize_rules = enable
136
+ end
137
+
138
+ # Check if rule optimization is enabled.
139
+ # @return [Boolean]
140
+ def optimize_rules?
141
+ @optimize_rules = false if @optimize_rules.nil?
142
+ @optimize_rules
143
+ end
144
+ end
145
+
146
+ # Extend with class methods when included
147
+ def self.included(base)
148
+ base.extend(ClassMethods)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Pattern binding classes for transform pattern matching.
4
+ # These classes represent placeholders in transform patterns that capture
5
+ # values during pattern matching.
6
+ #
7
+ # Inspired by Parslet (MIT License).
8
+
9
+ # Base class for all pattern bindings. Matches any subtree regardless of type.
10
+ # Used internally by Parsanol::Transform for pattern-based tree transformation.
11
+ module Parsanol
12
+ class Pattern
13
+ SubtreeBind = Struct.new(:symbol) do
14
+ # Returns the symbol that will be bound during matching.
15
+ #
16
+ # @return [Symbol] the binding variable name
17
+ def variable_name
18
+ symbol
19
+ end
20
+
21
+ # Human-readable representation of this binding.
22
+ #
23
+ # @return [String] description of the binding
24
+ def inspect
25
+ "#{binding_category}(#{symbol.inspect})"
26
+ end
27
+
28
+ # Determines if this binding can match the given subtree.
29
+ # SubtreeBind is the most permissive - matches anything.
30
+ #
31
+ # @param subtree [Object] the value to test
32
+ # @return [true] always returns true
33
+ def can_bind?(_subtree)
34
+ true
35
+ end
36
+
37
+ private
38
+
39
+ # Extracts the binding category name from the class name.
40
+ #
41
+ # @return [String] lowercase category name
42
+ def binding_category
43
+ class_match = self.class.name.match(/::(\w+)Bind\z/)
44
+ return class_match[1].downcase if class_match
45
+
46
+ # Fallback for unexpected class names
47
+ "subtree"
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ # Binding that matches only simple (leaf) values.
54
+ # Simple values are those that are neither Hash nor Array.
55
+ #
56
+ # @example
57
+ # simple(:x) # matches strings, numbers, slices - but not hashes or arrays
58
+ module Parsanol
59
+ class Pattern
60
+ class SimpleBind < Parsanol::Pattern::SubtreeBind
61
+ # Tests if the subtree is a simple leaf value.
62
+ #
63
+ # @param subtree [Object] the value to test
64
+ # @return [Boolean] true if subtree is not a Hash or Array
65
+ def can_bind?(subtree)
66
+ !subtree.is_a?(Hash) && !subtree.is_a?(Array)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # Binding that matches sequences of simple leaf values.
73
+ # A sequence is an Array where no element is a Hash or Array.
74
+ #
75
+ # @example
76
+ # sequence(:items) # matches ['a', 'b', 'c'] but not ['a', {x: 1}]
77
+ module Parsanol
78
+ class Pattern
79
+ class SequenceBind < Parsanol::Pattern::SubtreeBind
80
+ # Tests if the subtree is a flat sequence of simple values.
81
+ #
82
+ # @param subtree [Object] the value to test
83
+ # @return [Boolean] true if subtree is an Array of simple values
84
+ def can_bind?(subtree)
85
+ return false unless subtree.is_a?(Array)
86
+
87
+ subtree.none? { |element| element.is_a?(Hash) || element.is_a?(Array) }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Pattern matching for parse tree structures.
4
+ #
5
+ # This class provides tree pattern matching functionality where patterns
6
+ # are expressed using hashes for key-value structures and arrays for
7
+ # sequences. Leaf nodes can be matched using binding expressions.
8
+ #
9
+ # @example Matching a function call tree
10
+ # tree = {
11
+ # function_call: {
12
+ # name: 'foobar',
13
+ # args: [1, 2, 3]
14
+ # }
15
+ # }
16
+ #
17
+ # pattern = Parsanol::Pattern.new(
18
+ # function_call: { name: simple(:name), args: sequence(:args) }
19
+ # )
20
+ # bindings = pattern.match(tree)
21
+ # # => { name: 'foobar', args: [1, 2, 3] }
22
+ #
23
+ # Note: Pattern matching is performed at a single subtree level only.
24
+ # For recursive matching throughout a tree, use Parsanol::Transform.
25
+ #
26
+ # Inspired by pattern matching concepts in functional programming.
27
+ #
28
+ module Parsanol
29
+ class Pattern
30
+ # Creates a new pattern matcher with the given pattern structure.
31
+ #
32
+ # @param pattern [Hash, Array, Object] the pattern to match against
33
+ def initialize(pattern)
34
+ @pattern_def = pattern
35
+ end
36
+
37
+ # Attempts to match the given subtree against this pattern.
38
+ #
39
+ # Returns a hash of variable bindings if matching succeeds, or nil if
40
+ # the pattern does not match. Existing bindings can be provided to
41
+ # verify consistency with previous matches.
42
+ #
43
+ # @param subtree [Object] the tree or value to match
44
+ # @param bindings [Hash, nil] existing variable bindings to verify
45
+ # @return [Hash, nil] bindings hash on success, nil on failure
46
+ #
47
+ # @example Matching with existing bindings
48
+ # pattern = Parsanol::Pattern.new('a')
49
+ # pattern.match('a', { foo: 'bar' })
50
+ # # => { foo: 'bar' }
51
+ #
52
+ def match(subtree, bindings = nil)
53
+ current_bindings = bindings ? bindings.dup : {}
54
+ if check_match(subtree, @pattern_def,
55
+ current_bindings)
56
+ current_bindings
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Core matching dispatcher based on types.
63
+ # Routes to appropriate matching strategy based on tree and pattern types.
64
+ #
65
+ # @param target [Object] the value being matched
66
+ # @param pattern_val [Object] the pattern to match against
67
+ # @param captured [Hash] accumulated bindings (modified in place)
68
+ # @return [Boolean] true if match succeeds
69
+ #
70
+ def check_match(target, pattern_val, captured)
71
+ if target.is_a?(Hash) && pattern_val.is_a?(Hash)
72
+ match_hash_structure(target, pattern_val, captured)
73
+ elsif target.is_a?(Array) && pattern_val.is_a?(Array)
74
+ match_array_elements(target, pattern_val, captured)
75
+ else
76
+ match_leaf_value(target, pattern_val, captured)
77
+ end
78
+ end
79
+
80
+ # Matches leaf values (non-containers).
81
+ # Handles direct equality, case equality, and binding capture.
82
+ #
83
+ # @param target [Object] the value being matched
84
+ # @param pattern_val [Object] the pattern element
85
+ # @param captured [Hash] bindings hash
86
+ # @return [Boolean] true if match succeeds
87
+ #
88
+ def match_leaf_value(target, pattern_val, captured)
89
+ # Case equality covers exact matches and class matches
90
+ return true if pattern_val === target
91
+
92
+ # Check if pattern is a binding expression (like simple(:x))
93
+ if pattern_val.respond_to?(:can_bind?) && pattern_val.can_bind?(target)
94
+ return capture_binding(target, pattern_val, captured)
95
+ end
96
+
97
+ # No match possible
98
+ false
99
+ end
100
+
101
+ # Handles binding capture for expressions like simple(:name).
102
+ # If the variable is already bound, verifies consistency.
103
+ # Otherwise, creates a new binding.
104
+ #
105
+ # @param value [Object] the value to bind
106
+ # @param binder [Object] the binding expression object
107
+ # @param captured [Hash] bindings hash (modified in place)
108
+ # @return [Boolean] true if binding succeeds
109
+ #
110
+ def capture_binding(value, binder, captured)
111
+ var_key = binder.variable_name
112
+
113
+ # Verify existing binding consistency if present
114
+ return captured[var_key] == value if var_key && captured.key?(var_key)
115
+
116
+ # Store new binding
117
+ captured[var_key] = value if var_key
118
+ true
119
+ end
120
+
121
+ # Matches array structures element-by-element.
122
+ # Arrays must have identical length and each element must match.
123
+ #
124
+ # @param target_ary [Array] the array being matched
125
+ # @param pattern_ary [Array] the pattern array
126
+ # @param captured [Hash] bindings hash
127
+ # @return [Boolean] true if all elements match
128
+ #
129
+ def match_array_elements(target_ary, pattern_ary, captured)
130
+ # Length mismatch means no match
131
+ return false unless target_ary.length == pattern_ary.length
132
+
133
+ # Each position must match
134
+ target_ary.zip(pattern_ary).all? do |elem, pat|
135
+ check_match(elem, pat, captured)
136
+ end
137
+ end
138
+
139
+ # Matches hash structures key-by-key.
140
+ # All keys in pattern must exist in target with matching values.
141
+ #
142
+ # @param target_hash [Hash] the hash being matched
143
+ # @param pattern_hash [Hash] the pattern hash
144
+ # @param captured [Hash] bindings hash
145
+ # @return [Boolean] true if all key-value pairs match
146
+ #
147
+ def match_hash_structure(target_hash, pattern_hash, captured)
148
+ # Size mismatch means no match
149
+ return false unless target_hash.size == pattern_hash.size
150
+
151
+ # Verify each expected key exists with matching value
152
+ pattern_hash.each do |key, expected|
153
+ return false unless target_hash.key?(key)
154
+
155
+ actual = target_hash[key]
156
+ return false unless check_match(actual, expected, captured)
157
+ end
158
+
159
+ true
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ # Generic object pool for reducing garbage collection pressure.
5
+ #
6
+ # The ObjectPool class implements a simple object pooling strategy:
7
+ # - Objects are pre-allocated on initialization
8
+ # - Objects are reused instead of created new
9
+ # - Objects are reset before being returned to the pool
10
+ # - Pool size is bounded to prevent unbounded growth
11
+ #
12
+ # This reduces GC pressure by reusing objects instead of constantly
13
+ # creating and destroying them, which is particularly beneficial for
14
+ # frequently allocated objects like Slice instances.
15
+ #
16
+ # == Thread Safety
17
+ #
18
+ # This implementation is NOT thread-safe. If thread safety is required,
19
+ # wrap pool operations in a mutex or use thread-local pools.
20
+ #
21
+ # == Usage Example
22
+ #
23
+ # # Create a pool for Slice objects
24
+ # pool = Parsanol::ObjectPool.new(Parsanol::Slice, size: 1000)
25
+ #
26
+ # # Acquire an object from the pool
27
+ # slice = pool.acquire
28
+ # slice.instance_variable_set(:@bytepos, 0)
29
+ # slice.instance_variable_set(:@str, "hello")
30
+ #
31
+ # # Use the slice...
32
+ #
33
+ # # Return it to the pool for reuse
34
+ # pool.release(slice)
35
+ #
36
+ # == Object Reset Protocol
37
+ #
38
+ # Objects returned to the pool will have their reset! method called
39
+ # if they respond to it. This allows objects to clean up their state
40
+ # before being reused. If reset! is not defined, the object is still
41
+ # pooled but without automatic cleanup.
42
+ #
43
+ class ObjectPool
44
+ # @return [Integer] Maximum number of objects to keep in the pool
45
+ attr_reader :size
46
+
47
+ # @return [Hash] Statistics about pool usage
48
+ attr_reader :stats
49
+
50
+ # Initialize a new object pool.
51
+ #
52
+ # @param klass [Class] The class of objects to pool
53
+ # @param size [Integer] Maximum number of objects to keep in pool (default: 1000)
54
+ # @param preallocate [Boolean] Whether to pre-allocate objects on initialization (default: true)
55
+ #
56
+ # @example Create a pool with default settings
57
+ # pool = ObjectPool.new(Array, size: 1000)
58
+ #
59
+ # @example Create a pool without pre-allocation
60
+ # pool = ObjectPool.new(Array, size: 1000, preallocate: false)
61
+ #
62
+ def initialize(klass, size: 1000, preallocate: true)
63
+ @klass = klass
64
+ @size = size
65
+ @available = []
66
+ @stats = {
67
+ created: 0,
68
+ reused: 0,
69
+ released: 0,
70
+ discarded: 0,
71
+ }
72
+
73
+ # Pre-allocate objects for efficiency if requested
74
+ # This reduces allocation overhead during initial parsing
75
+ preallocate(size) if preallocate && can_preallocate?
76
+ end
77
+
78
+ # Acquire an object from the pool.
79
+ #
80
+ # If the pool has available objects, one is returned (and considered "reused").
81
+ # If the pool is empty, a new object is created (and considered "created").
82
+ #
83
+ # @return [Object] An object instance from the pool or newly created
84
+ #
85
+ # @example Acquire from pool
86
+ # obj = pool.acquire
87
+ #
88
+ def acquire
89
+ if @available.empty?
90
+ @stats[:created] += 1
91
+ @klass.new
92
+ else
93
+ @stats[:reused] += 1
94
+ @available.pop
95
+ end
96
+ end
97
+
98
+ # Return an object to the pool for reuse.
99
+ #
100
+ # Before returning to the pool:
101
+ # 1. If object responds to reset!, that method is called to clean up state
102
+ # 2. If pool is at capacity, the object is discarded instead of pooled
103
+ #
104
+ # This ensures:
105
+ # - Objects are cleaned before reuse (no stale state)
106
+ # - Pool doesn't grow unbounded (respects size limit)
107
+ #
108
+ # @param obj [Object] The object to return to the pool
109
+ # @return [Boolean] true if object was returned to pool, false if discarded
110
+ #
111
+ # @example Return object to pool
112
+ # pool.release(obj)
113
+ #
114
+ def release(obj)
115
+ # Don't pool if we're at capacity - discard instead
116
+ if @available.size >= @size
117
+ @stats[:discarded] += 1
118
+ return false
119
+ end
120
+
121
+ # Reset object state if it supports the protocol
122
+ obj.reset! if obj.respond_to?(:reset!)
123
+
124
+ @stats[:released] += 1
125
+ @available.push(obj)
126
+ true
127
+ end
128
+
129
+ # Get current pool statistics.
130
+ #
131
+ # Statistics include:
132
+ # - size: Maximum pool capacity
133
+ # - available: Number of objects currently available in pool
134
+ # - created: Total number of new objects created
135
+ # - reused: Total number of times objects were reused from pool
136
+ # - released: Total number of objects returned to pool
137
+ # - discarded: Total number of objects discarded (pool was full)
138
+ # - utilization: Percentage of acquires that were reused (0-100)
139
+ #
140
+ # @return [Hash] Hash containing pool statistics
141
+ #
142
+ # @example Get statistics
143
+ # stats = pool.stats
144
+ # puts "Pool utilization: #{stats[:utilization]}%"
145
+ #
146
+ def statistics
147
+ total_acquires = @stats[:created] + @stats[:reused]
148
+ utilization = total_acquires.zero? ? 0.0 : (@stats[:reused].to_f / total_acquires * 100)
149
+
150
+ {
151
+ size: @size,
152
+ available: @available.size,
153
+ created: @stats[:created],
154
+ reused: @stats[:reused],
155
+ released: @stats[:released],
156
+ discarded: @stats[:discarded],
157
+ utilization: utilization.round(2),
158
+ }
159
+ end
160
+
161
+ # Clear all objects from the pool.
162
+ #
163
+ # This removes all pooled objects and resets statistics.
164
+ # Useful for testing or when you want to force fresh allocations.
165
+ #
166
+ # @return [void]
167
+ #
168
+ # @example Clear the pool
169
+ # pool.clear!
170
+ #
171
+ def clear!
172
+ @available.clear
173
+ @stats = {
174
+ created: 0,
175
+ reused: 0,
176
+ released: 0,
177
+ discarded: 0,
178
+ }
179
+ end
180
+
181
+ private
182
+
183
+ # Check if the pooled class can be pre-allocated.
184
+ #
185
+ # Some classes require arguments to initialize and cannot be
186
+ # pre-allocated without those arguments. This method checks if
187
+ # the class has a zero-arity initialize method.
188
+ #
189
+ # @return [Boolean] true if class can be instantiated without arguments
190
+ #
191
+ def can_preallocate?
192
+ # Check if the class can be instantiated without arguments
193
+ # This is a heuristic - we try to create one instance to test
194
+
195
+ @klass.new
196
+ true
197
+ rescue ArgumentError
198
+ # Class requires arguments, cannot pre-allocate
199
+ false
200
+ end
201
+
202
+ # Pre-allocate objects to fill the pool.
203
+ #
204
+ # This is called during initialization if preallocate: true is set.
205
+ # Pre-allocation reduces allocation overhead during initial parsing.
206
+ #
207
+ # @param count [Integer] Number of objects to pre-allocate
208
+ # @return [void]
209
+ #
210
+ def preallocate(count)
211
+ count.times do
212
+ @available.push(@klass.new)
213
+ end
214
+ # Adjust stats to reflect pre-allocation as "released" not "created"
215
+ # since these objects haven't been acquired yet
216
+ @stats[:released] = count
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ module Pools
5
+ # Specialized object pool for Array instances.
6
+ #
7
+ # ArrayPool extends ObjectPool to provide array-specific behavior,
8
+ # particularly ensuring arrays are cleared before being returned to
9
+ # the pool for reuse.
10
+ #
11
+ # == Usage
12
+ #
13
+ # pool = Parsanol::Pools::ArrayPool.new(size: 1000)
14
+ #
15
+ # # Acquire an array
16
+ # array = pool.acquire
17
+ # array << 'item1'
18
+ # array << 'item2'
19
+ #
20
+ # # Return to pool (automatically cleared)
21
+ # pool.release(array)
22
+ #
23
+ # # Next acquire gets a clean, empty array
24
+ # array2 = pool.acquire
25
+ # array2.empty? # => true
26
+ #
27
+ # == Why Pool Arrays?
28
+ #
29
+ # Profiling (Session 19) showed that array allocations account for
30
+ # 74% of memory usage during parsing. Temporary arrays used for:
31
+ # - Collecting repetition results
32
+ # - Building sequence results
33
+ # - Accumulating alternative matches
34
+ #
35
+ # By pooling arrays, we can:
36
+ # - Reduce array allocations by 60-70%
37
+ # - Decrease memory pressure
38
+ # - Improve overall parsing performance
39
+ #
40
+ class ArrayPool < Parsanol::ObjectPool
41
+ # Initialize a new ArrayPool.
42
+ #
43
+ # @param size [Integer] Maximum number of Arrays to pool (default: 1000)
44
+ # @param preallocate [Boolean] Whether to pre-allocate arrays (default: true)
45
+ #
46
+ # @example Create an ArrayPool
47
+ # pool = ArrayPool.new(size: 2000)
48
+ #
49
+ def initialize(size: 1000, preallocate: true)
50
+ super(Array, size: size, preallocate: preallocate)
51
+ end
52
+
53
+ # Return an array to the pool after clearing its contents.
54
+ #
55
+ # This override ensures arrays are always empty when returned to
56
+ # the pool, preventing stale data from polluting future uses.
57
+ #
58
+ # @param array [Array] The array to return to the pool
59
+ # @return [Boolean] true if returned to pool, false if discarded
60
+ #
61
+ # @example Release with automatic clearing
62
+ # array = pool.acquire
63
+ # array << 1 << 2 << 3
64
+ # pool.release(array)
65
+ # # Array is now cleared and back in pool
66
+ #
67
+ def release(array)
68
+ # Clear array before pooling to prevent stale data
69
+ # Note: Array#clear is more efficient than array = []
70
+ array.clear
71
+ super
72
+ end
73
+ end
74
+ end
75
+ end