parsanol 3.0.0
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.
Potentially problematic release.
This version of parsanol might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/HISTORY.txt +25 -0
- data/LICENSE +23 -0
- data/README.adoc +643 -0
- data/Rakefile +189 -0
- data/example/balanced-parens/basic.rb +42 -0
- data/example/balanced-parens/basic.rb.md +86 -0
- data/example/balanced-parens/parens.rb +42 -0
- data/example/balanced-parens/ruby_transform.rb +162 -0
- data/example/big.erb +73 -0
- data/example/boolean-algebra/basic.rb +70 -0
- data/example/boolean-algebra/basic.rb.md +108 -0
- data/example/boolean-algebra/ruby_transform.rb +263 -0
- data/example/calculator/basic.rb +153 -0
- data/example/calculator/basic.rb.md +120 -0
- data/example/calculator/pattern.rb +153 -0
- data/example/calculator/ruby_transform.rb +156 -0
- data/example/calculator/ruby_transform.rb.md +32 -0
- data/example/calculator/serialized.rb +257 -0
- data/example/calculator/serialized.rb.md +32 -0
- data/example/calculator/transform.rb +153 -0
- data/example/calculator/zero_copy.rb +269 -0
- data/example/calculator/zero_copy.rb.md +36 -0
- data/example/capture/basic.rb +49 -0
- data/example/capture/basic.rb.md +106 -0
- data/example/capture/example.json +39 -0
- data/example/comments/basic.rb +35 -0
- data/example/comments/basic.rb.md +110 -0
- data/example/csv/ruby_transform.rb +148 -0
- data/example/csv/ruby_transform.rb.md +131 -0
- data/example/csv/serialized.rb +201 -0
- data/example/csv/serialized.rb.md +31 -0
- data/example/csv/zero_copy.rb +276 -0
- data/example/csv/zero_copy.rb.md +36 -0
- data/example/custom_atoms/indent_atom.rb +79 -0
- data/example/deepest-errors/basic.rb +131 -0
- data/example/deepest-errors/basic.rb.md +152 -0
- data/example/documentation/basic.rb +18 -0
- data/example/documentation/basic.rb.md +97 -0
- data/example/email/basic.rb +55 -0
- data/example/email/basic.rb.md +102 -0
- data/example/email/ruby_transform.rb +106 -0
- data/example/empty/basic.rb +13 -0
- data/example/empty/basic.rb.md +73 -0
- data/example/empty/example.json +38 -0
- data/example/erb/basic.rb +47 -0
- data/example/erb/basic.rb.md +103 -0
- data/example/erb/optimized.rb +42 -0
- data/example/error-reporting/basic.rb +132 -0
- data/example/error-reporting/basic.rb.md +122 -0
- data/example/expression-evaluator/basic.rb +284 -0
- data/example/expression-evaluator/basic.rb.md +138 -0
- data/example/ini/basic.rb +154 -0
- data/example/ini/basic.rb.md +129 -0
- data/example/ini/ruby_transform.rb +154 -0
- data/example/ip-address/basic.rb +125 -0
- data/example/ip-address/basic.rb.md +139 -0
- data/example/iso-6709/basic.rb +231 -0
- data/example/iso-6709/basic.rb.md +143 -0
- data/example/iso-8601/basic.rb +275 -0
- data/example/iso-8601/basic.rb.md +149 -0
- data/example/json/basic.rb +128 -0
- data/example/json/basic.rb.md +121 -0
- data/example/json/pattern.rb +128 -0
- data/example/json/ruby_transform.rb +200 -0
- data/example/json/ruby_transform.rb.md +32 -0
- data/example/json/serialized.rb +233 -0
- data/example/json/serialized.rb.md +31 -0
- data/example/json/transform.rb +128 -0
- data/example/json/zero_copy.rb +316 -0
- data/example/json/zero_copy.rb.md +36 -0
- data/example/local/basic.rb +34 -0
- data/example/local/basic.rb.md +91 -0
- data/example/local/example.json +38 -0
- data/example/markdown/basic.rb +287 -0
- data/example/markdown/basic.rb.md +160 -0
- data/example/markup/basic.rb +173 -0
- data/example/markup/basic.rb.md +118 -0
- data/example/mathn/basic.rb +47 -0
- data/example/mathn/basic.rb.md +96 -0
- data/example/mathn/example.json +39 -0
- data/example/minilisp/basic.rb +94 -0
- data/example/minilisp/basic.rb.md +133 -0
- data/example/modularity/basic.rb +47 -0
- data/example/modularity/basic.rb.md +152 -0
- data/example/nested-errors/basic.rb +132 -0
- data/example/nested-errors/basic.rb.md +157 -0
- data/example/output/boolean_algebra.out +4 -0
- data/example/output/calc.out +1 -0
- data/example/output/capture.out +3 -0
- data/example/output/comments.out +8 -0
- data/example/output/deepest_errors.out +54 -0
- data/example/output/documentation.err +4 -0
- data/example/output/documentation.out +1 -0
- data/example/output/email_parser.out +2 -0
- data/example/output/empty.err +1 -0
- data/example/output/erb.out +7 -0
- data/example/output/ignore.out +1 -0
- data/example/output/ignore_whitespace.out +1 -0
- data/example/output/ip_address.out +9 -0
- data/example/output/json.out +5 -0
- data/example/output/local.out +3 -0
- data/example/output/mathn.out +4 -0
- data/example/output/minilisp.out +5 -0
- data/example/output/modularity.out +0 -0
- data/example/output/nested_errors.out +54 -0
- data/example/output/optimized_erb.out +1 -0
- data/example/output/parens.out +8 -0
- data/example/output/prec_calc.out +5 -0
- data/example/output/readme.out +1 -0
- data/example/output/scopes.out +1 -0
- data/example/output/seasons.out +28 -0
- data/example/output/sentence.out +1 -0
- data/example/output/simple_xml.out +2 -0
- data/example/output/string_parser.out +3 -0
- data/example/prec-calc/basic.rb +71 -0
- data/example/prec-calc/basic.rb.md +114 -0
- data/example/readme/basic.rb +30 -0
- data/example/readme/basic.rb.md +80 -0
- data/example/scopes/basic.rb +15 -0
- data/example/scopes/basic.rb.md +73 -0
- data/example/scopes/example.json +38 -0
- data/example/seasons/basic.rb +46 -0
- data/example/seasons/basic.rb.md +117 -0
- data/example/seasons/example.json +40 -0
- data/example/sentence/basic.rb +36 -0
- data/example/sentence/basic.rb.md +81 -0
- data/example/sexp/ruby_transform.rb +180 -0
- data/example/sexp/ruby_transform.rb.md +143 -0
- data/example/simple-xml/basic.rb +54 -0
- data/example/simple-xml/basic.rb.md +125 -0
- data/example/simple.lit +3 -0
- data/example/string-literal/basic.rb +77 -0
- data/example/string-literal/basic.rb.md +128 -0
- data/example/test.lit +4 -0
- data/example/toml/basic.rb +226 -0
- data/example/toml/basic.rb.md +173 -0
- data/example/url/basic.rb +219 -0
- data/example/url/basic.rb.md +142 -0
- data/example/url/ruby_transform.rb +219 -0
- data/example/yaml/basic.rb +216 -0
- data/example/yaml/basic.rb.md +148 -0
- data/ext/parsanol_native/extconf.rb +4 -0
- data/lib/parsanol/accelerator/application.rb +62 -0
- data/lib/parsanol/accelerator/engine.rb +112 -0
- data/lib/parsanol/accelerator.rb +162 -0
- data/lib/parsanol/ast_visitor.rb +122 -0
- data/lib/parsanol/atoms/alternative.rb +97 -0
- data/lib/parsanol/atoms/base.rb +214 -0
- data/lib/parsanol/atoms/can_flatten.rb +192 -0
- data/lib/parsanol/atoms/capture.rb +41 -0
- data/lib/parsanol/atoms/context.rb +351 -0
- data/lib/parsanol/atoms/context_optimized.rb +42 -0
- data/lib/parsanol/atoms/custom.rb +110 -0
- data/lib/parsanol/atoms/cut.rb +62 -0
- data/lib/parsanol/atoms/dsl.rb +130 -0
- data/lib/parsanol/atoms/dynamic.rb +33 -0
- data/lib/parsanol/atoms/entity.rb +55 -0
- data/lib/parsanol/atoms/ignored.rb +28 -0
- data/lib/parsanol/atoms/infix.rb +121 -0
- data/lib/parsanol/atoms/lookahead.rb +64 -0
- data/lib/parsanol/atoms/named.rb +50 -0
- data/lib/parsanol/atoms/re.rb +61 -0
- data/lib/parsanol/atoms/repetition.rb +241 -0
- data/lib/parsanol/atoms/scope.rb +28 -0
- data/lib/parsanol/atoms/sequence.rb +157 -0
- data/lib/parsanol/atoms/str.rb +90 -0
- data/lib/parsanol/atoms/visitor.rb +91 -0
- data/lib/parsanol/atoms.rb +36 -0
- data/lib/parsanol/buffer.rb +130 -0
- data/lib/parsanol/builder_callbacks.rb +353 -0
- data/lib/parsanol/cause.rb +101 -0
- data/lib/parsanol/context.rb +23 -0
- data/lib/parsanol/convenience.rb +35 -0
- data/lib/parsanol/edit_tracker.rb +107 -0
- data/lib/parsanol/error_reporter/contextual.rb +122 -0
- data/lib/parsanol/error_reporter/deepest.rb +106 -0
- data/lib/parsanol/error_reporter/tree.rb +68 -0
- data/lib/parsanol/error_reporter.rb +98 -0
- data/lib/parsanol/export.rb +163 -0
- data/lib/parsanol/expression/treetop.rb +94 -0
- data/lib/parsanol/expression.rb +51 -0
- data/lib/parsanol/fast_mode.rb +145 -0
- data/lib/parsanol/first_set.rb +75 -0
- data/lib/parsanol/grammar_builder.rb +177 -0
- data/lib/parsanol/graphviz.rb +97 -0
- data/lib/parsanol/incremental_parser.rb +179 -0
- data/lib/parsanol/interval_tree.rb +215 -0
- data/lib/parsanol/lazy_result.rb +178 -0
- data/lib/parsanol/lexer.rb +146 -0
- data/lib/parsanol/native/parser.rb +630 -0
- data/lib/parsanol/native/serializer.rb +245 -0
- data/lib/parsanol/native/transformer.rb +438 -0
- data/lib/parsanol/native/types.rb +41 -0
- data/lib/parsanol/native.rb +217 -0
- data/lib/parsanol/optimizer.rb +86 -0
- data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
- data/lib/parsanol/optimizers/cut_inserter.rb +175 -0
- data/lib/parsanol/optimizers/lookahead_optimizer.rb +58 -0
- data/lib/parsanol/optimizers/quantifier_optimizer.rb +62 -0
- data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
- data/lib/parsanol/options/ruby_transform.rb +109 -0
- data/lib/parsanol/options/serialized.rb +94 -0
- data/lib/parsanol/options/zero_copy.rb +130 -0
- data/lib/parsanol/options.rb +20 -0
- data/lib/parsanol/parallel.rb +133 -0
- data/lib/parsanol/parsanol_native.bundle +0 -0
- data/lib/parsanol/parser.rb +151 -0
- data/lib/parsanol/parslet.rb +148 -0
- data/lib/parsanol/parslet_native.bundle +0 -0
- data/lib/parsanol/pattern/binding.rb +49 -0
- data/lib/parsanol/pattern.rb +115 -0
- data/lib/parsanol/pool.rb +220 -0
- data/lib/parsanol/pools/array_pool.rb +75 -0
- data/lib/parsanol/pools/buffer_pool.rb +173 -0
- data/lib/parsanol/pools/position_pool.rb +92 -0
- data/lib/parsanol/pools/slice_pool.rb +64 -0
- data/lib/parsanol/position.rb +89 -0
- data/lib/parsanol/result.rb +44 -0
- data/lib/parsanol/result_builder.rb +208 -0
- data/lib/parsanol/result_stream.rb +262 -0
- data/lib/parsanol/rig/rspec.rb +52 -0
- data/lib/parsanol/rope.rb +78 -0
- data/lib/parsanol/scope.rb +42 -0
- data/lib/parsanol/slice.rb +172 -0
- data/lib/parsanol/source/line_cache.rb +99 -0
- data/lib/parsanol/source.rb +171 -0
- data/lib/parsanol/source_location.rb +164 -0
- data/lib/parsanol/streaming_parser.rb +124 -0
- data/lib/parsanol/string_view.rb +192 -0
- data/lib/parsanol/transform.rb +267 -0
- data/lib/parsanol/version.rb +5 -0
- data/lib/parsanol/wasm/README.md +80 -0
- data/lib/parsanol/wasm/package.json +51 -0
- data/lib/parsanol/wasm/parsanol.js +252 -0
- data/lib/parsanol/wasm/parslet.d.ts +129 -0
- data/lib/parsanol/wasm_parser.rb +239 -0
- data/lib/parsanol.rb +408 -0
- data/parsanol-ruby.gemspec +56 -0
- data/spec/acceptance/examples_spec.rb +96 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/benchmark/comparative/runner_spec.rb +105 -0
- data/spec/integration/array_pooling_spec.rb +193 -0
- data/spec/integration/buffer_allocation_spec.rb +324 -0
- data/spec/integration/position_pooling_spec.rb +184 -0
- data/spec/integration/result_builder_spec.rb +282 -0
- data/spec/integration/rope_stringview_integration_spec.rb +188 -0
- data/spec/integration/slice_pooling_spec.rb +63 -0
- data/spec/integration/string_view_integration_spec.rb +125 -0
- data/spec/lexer_spec.rb +231 -0
- data/spec/parsanol/atom_results_spec.rb +39 -0
- data/spec/parsanol/atoms/alternative_spec.rb +26 -0
- data/spec/parsanol/atoms/base_spec.rb +127 -0
- data/spec/parsanol/atoms/capture_spec.rb +21 -0
- data/spec/parsanol/atoms/combinations_spec.rb +5 -0
- data/spec/parsanol/atoms/custom_spec.rb +79 -0
- data/spec/parsanol/atoms/dsl_spec.rb +7 -0
- data/spec/parsanol/atoms/entity_spec.rb +77 -0
- data/spec/parsanol/atoms/ignored_spec.rb +15 -0
- data/spec/parsanol/atoms/infix_spec.rb +5 -0
- data/spec/parsanol/atoms/lookahead_spec.rb +22 -0
- data/spec/parsanol/atoms/named_spec.rb +4 -0
- data/spec/parsanol/atoms/re_spec.rb +14 -0
- data/spec/parsanol/atoms/repetition_spec.rb +24 -0
- data/spec/parsanol/atoms/scope_spec.rb +26 -0
- data/spec/parsanol/atoms/sequence_spec.rb +28 -0
- data/spec/parsanol/atoms/str_spec.rb +15 -0
- data/spec/parsanol/atoms/visitor_spec.rb +101 -0
- data/spec/parsanol/atoms_spec.rb +488 -0
- data/spec/parsanol/auto_optimize_spec.rb +334 -0
- data/spec/parsanol/buffer_spec.rb +219 -0
- data/spec/parsanol/builder_callbacks_spec.rb +377 -0
- data/spec/parsanol/choice_optimizer_spec.rb +231 -0
- data/spec/parsanol/convenience_spec.rb +54 -0
- data/spec/parsanol/cut_inserter_spec.rb +248 -0
- data/spec/parsanol/cut_spec.rb +66 -0
- data/spec/parsanol/edit_tracker_spec.rb +218 -0
- data/spec/parsanol/error_reporter/contextual_spec.rb +122 -0
- data/spec/parsanol/error_reporter/deepest_spec.rb +82 -0
- data/spec/parsanol/error_reporter/tree_spec.rb +7 -0
- data/spec/parsanol/export_spec.rb +67 -0
- data/spec/parsanol/expression/treetop_spec.rb +75 -0
- data/spec/parsanol/first_set_spec.rb +298 -0
- data/spec/parsanol/interval_tree_spec.rb +205 -0
- data/spec/parsanol/lazy_result_spec.rb +288 -0
- data/spec/parsanol/lookahead_optimizer_spec.rb +252 -0
- data/spec/parsanol/minilisp.citrus +29 -0
- data/spec/parsanol/minilisp.tt +29 -0
- data/spec/parsanol/optimizer_spec.rb +459 -0
- data/spec/parsanol/options/parslet_compat_spec.rb +166 -0
- data/spec/parsanol/options/ruby_transform_spec.rb +70 -0
- data/spec/parsanol/options/serialized_spec.rb +69 -0
- data/spec/parsanol/options/zero_copy_spec.rb +230 -0
- data/spec/parsanol/parser_spec.rb +36 -0
- data/spec/parsanol/parslet_spec.rb +38 -0
- data/spec/parsanol/pattern_spec.rb +272 -0
- data/spec/parsanol/pool_spec.rb +392 -0
- data/spec/parsanol/pools/array_pool_spec.rb +356 -0
- data/spec/parsanol/pools/buffer_pool_spec.rb +365 -0
- data/spec/parsanol/pools/position_pool_spec.rb +118 -0
- data/spec/parsanol/pools/slice_pool_spec.rb +262 -0
- data/spec/parsanol/position_spec.rb +14 -0
- data/spec/parsanol/result_builder_spec.rb +391 -0
- data/spec/parsanol/rig/rspec_spec.rb +54 -0
- data/spec/parsanol/rope_spec.rb +207 -0
- data/spec/parsanol/scope_spec.rb +45 -0
- data/spec/parsanol/slice_spec.rb +249 -0
- data/spec/parsanol/source/line_cache_spec.rb +74 -0
- data/spec/parsanol/source_spec.rb +207 -0
- data/spec/parsanol/string_view_spec.rb +345 -0
- data/spec/parsanol/transform/context_spec.rb +56 -0
- data/spec/parsanol/transform_spec.rb +183 -0
- data/spec/parsanol/tree_memoization_spec.rb +149 -0
- data/spec/parslet_compatibility/expressir_edge_cases_spec.rb +153 -0
- data/spec/parslet_compatibility/minimal_reproduction.rb +199 -0
- data/spec/parslet_compatibility_spec.rb +399 -0
- data/spec/parslet_imported/atom_spec.rb +93 -0
- data/spec/parslet_imported/combinator_spec.rb +161 -0
- data/spec/parslet_imported/spec_helper.rb +73 -0
- data/spec/performance/batch_parsing_benchmark.rb +129 -0
- data/spec/performance/complete_optimization_summary.rb +143 -0
- data/spec/performance/grammar_caching_analysis.rb +121 -0
- data/spec/performance/grammar_caching_benchmark.rb +80 -0
- data/spec/performance/native_benchmark_spec.rb +230 -0
- data/spec/performance/phase5_benchmark.rb +144 -0
- data/spec/performance/profiling_benchmark.rb +131 -0
- data/spec/performance/ruby_improvements_benchmark.rb +171 -0
- data/spec/performance_spec.rb +374 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- metadata +485 -0
|
@@ -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(array)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../buffer'
|
|
4
|
+
|
|
5
|
+
module Parsanol
|
|
6
|
+
module Pools
|
|
7
|
+
# Manages fixed-size buffers organized by size class.
|
|
8
|
+
#
|
|
9
|
+
# BufferPool provides efficient buffer allocation by maintaining
|
|
10
|
+
# separate pools for common buffer sizes. This reduces allocation
|
|
11
|
+
# overhead and enables buffer reuse across parses.
|
|
12
|
+
#
|
|
13
|
+
# == Usage
|
|
14
|
+
#
|
|
15
|
+
# pool = BufferPool.new
|
|
16
|
+
# buffer = pool.acquire(size: 8) # Get buffer with capacity >= 8
|
|
17
|
+
# buffer.push("a")
|
|
18
|
+
# pool.release(buffer)
|
|
19
|
+
#
|
|
20
|
+
# == Size Classes
|
|
21
|
+
#
|
|
22
|
+
# Buffers are organized into size classes:
|
|
23
|
+
# - Small: 2, 4, 8 (most common)
|
|
24
|
+
# - Medium: 16, 32 (common)
|
|
25
|
+
# - Large: 64+ (rare, allocated on demand)
|
|
26
|
+
#
|
|
27
|
+
# This matches typical parsing patterns where most arrays are small.
|
|
28
|
+
#
|
|
29
|
+
class BufferPool
|
|
30
|
+
# Standard size classes (power of 2 for efficiency)
|
|
31
|
+
SIZE_CLASSES = [2, 4, 8, 16, 32, 64].freeze
|
|
32
|
+
|
|
33
|
+
# Default pool size per class
|
|
34
|
+
DEFAULT_POOL_SIZE = 100
|
|
35
|
+
|
|
36
|
+
# @return [Hash] Pools by size class
|
|
37
|
+
attr_reader :pools
|
|
38
|
+
|
|
39
|
+
# @return [Hash] Statistics per size class
|
|
40
|
+
attr_reader :stats
|
|
41
|
+
|
|
42
|
+
# Initialize a new BufferPool.
|
|
43
|
+
#
|
|
44
|
+
# @param pool_size [Integer] Number of buffers per size class
|
|
45
|
+
#
|
|
46
|
+
def initialize(pool_size: DEFAULT_POOL_SIZE)
|
|
47
|
+
@pool_size = pool_size
|
|
48
|
+
@pools = {}
|
|
49
|
+
@stats = {}
|
|
50
|
+
|
|
51
|
+
# Create pool for each size class
|
|
52
|
+
SIZE_CLASSES.each do |size|
|
|
53
|
+
@pools[size] = []
|
|
54
|
+
@stats[size] = { created: 0, reused: 0, released: 0, discarded: 0 }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Acquire a buffer with at least the requested capacity.
|
|
59
|
+
#
|
|
60
|
+
# Returns a buffer from the appropriate size class pool.
|
|
61
|
+
# If no buffer available, creates a new one.
|
|
62
|
+
#
|
|
63
|
+
# @param size [Integer] Minimum required capacity
|
|
64
|
+
# @return [Buffer] Buffer with capacity >= size
|
|
65
|
+
#
|
|
66
|
+
def acquire(size:)
|
|
67
|
+
size_class = select_size_class(size)
|
|
68
|
+
|
|
69
|
+
# For non-standard size classes, create buffer on demand
|
|
70
|
+
unless @pools.key?(size_class)
|
|
71
|
+
return Buffer.new(capacity: size_class)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
pool = @pools[size_class]
|
|
75
|
+
|
|
76
|
+
if pool.empty?
|
|
77
|
+
@stats[size_class][:created] += 1
|
|
78
|
+
Buffer.new(capacity: size_class)
|
|
79
|
+
else
|
|
80
|
+
@stats[size_class][:reused] += 1
|
|
81
|
+
pool.pop
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Release a buffer back to the pool.
|
|
86
|
+
#
|
|
87
|
+
# Clears the buffer and returns it to the appropriate size class pool.
|
|
88
|
+
#
|
|
89
|
+
# @param buffer [Buffer] Buffer to release
|
|
90
|
+
# @return [Boolean] true if returned to pool, false if discarded
|
|
91
|
+
#
|
|
92
|
+
def release(buffer)
|
|
93
|
+
size_class = buffer.capacity
|
|
94
|
+
pool = @pools[size_class]
|
|
95
|
+
|
|
96
|
+
# Discard if pool is full or size not in standard classes
|
|
97
|
+
if !pool || pool.size >= @pool_size
|
|
98
|
+
@stats[size_class][:discarded] += 1 if @stats[size_class]
|
|
99
|
+
return false
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
buffer.clear!
|
|
103
|
+
@stats[size_class][:released] += 1
|
|
104
|
+
pool.push(buffer)
|
|
105
|
+
true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Get statistics for all size classes.
|
|
109
|
+
#
|
|
110
|
+
# @return [Hash] Statistics by size class
|
|
111
|
+
#
|
|
112
|
+
def statistics
|
|
113
|
+
result = {}
|
|
114
|
+
SIZE_CLASSES.each do |size|
|
|
115
|
+
stats = @stats[size]
|
|
116
|
+
total_acquires = stats[:created] + stats[:reused]
|
|
117
|
+
utilization = total_acquires.zero? ? 0.0 :
|
|
118
|
+
(stats[:reused].to_f / total_acquires * 100)
|
|
119
|
+
|
|
120
|
+
result[size] = {
|
|
121
|
+
available: @pools[size].size,
|
|
122
|
+
created: stats[:created],
|
|
123
|
+
reused: stats[:reused],
|
|
124
|
+
released: stats[:released],
|
|
125
|
+
discarded: stats[:discarded],
|
|
126
|
+
utilization: utilization.round(2)
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
result
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Clear all pools.
|
|
133
|
+
#
|
|
134
|
+
# @return [void]
|
|
135
|
+
#
|
|
136
|
+
def clear!
|
|
137
|
+
@pools.each_value(&:clear)
|
|
138
|
+
@stats.each_value do |s|
|
|
139
|
+
s[:created] = s[:reused] = s[:released] = s[:discarded] = 0
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
# Select appropriate size class for requested size.
|
|
146
|
+
#
|
|
147
|
+
# Returns smallest size class >= requested size.
|
|
148
|
+
#
|
|
149
|
+
# @param size [Integer] Requested size
|
|
150
|
+
# @return [Integer] Size class
|
|
151
|
+
#
|
|
152
|
+
def select_size_class(size)
|
|
153
|
+
SIZE_CLASSES.find { |sc| sc >= size } || next_power_of_2(size)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Find next power of 2 greater than or equal to n.
|
|
157
|
+
#
|
|
158
|
+
# @param n [Integer] Input value
|
|
159
|
+
# @return [Integer] Next power of 2
|
|
160
|
+
#
|
|
161
|
+
def next_power_of_2(n)
|
|
162
|
+
return 1 if n <= 0
|
|
163
|
+
n = n - 1
|
|
164
|
+
n |= n >> 1
|
|
165
|
+
n |= n >> 2
|
|
166
|
+
n |= n >> 4
|
|
167
|
+
n |= n >> 8
|
|
168
|
+
n |= n >> 16
|
|
169
|
+
n + 1
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Parsanol
|
|
4
|
+
module Pools
|
|
5
|
+
# Specialized object pool for Position instances.
|
|
6
|
+
#
|
|
7
|
+
# PositionPool extends ObjectPool to provide position-specific behavior,
|
|
8
|
+
# particularly managing the line and column state for reuse.
|
|
9
|
+
#
|
|
10
|
+
# == Usage
|
|
11
|
+
#
|
|
12
|
+
# pool = Parsanol::Pools::PositionPool.new(size: 1000)
|
|
13
|
+
#
|
|
14
|
+
# # Acquire a position with line/column
|
|
15
|
+
# pos = pool.acquire_with(string: "source", bytepos: 42, charpos: 42)
|
|
16
|
+
#
|
|
17
|
+
# # Return to pool (automatically reset)
|
|
18
|
+
# pool.release(pos)
|
|
19
|
+
#
|
|
20
|
+
# == Architecture
|
|
21
|
+
#
|
|
22
|
+
# v3.0.0 uses integer positions during parsing for efficiency.
|
|
23
|
+
# Position objects are only created when:
|
|
24
|
+
# - Generating error messages (need line/column)
|
|
25
|
+
# - Materializing error context
|
|
26
|
+
#
|
|
27
|
+
# By pooling Position objects, we reduce GC pressure at the
|
|
28
|
+
# materialization point without changing the fast integer-based
|
|
29
|
+
# parsing path.
|
|
30
|
+
#
|
|
31
|
+
class PositionPool < Parsanol::ObjectPool
|
|
32
|
+
# Initialize a new PositionPool.
|
|
33
|
+
#
|
|
34
|
+
# @param size [Integer] Maximum number of Position objects to pool
|
|
35
|
+
# @param preallocate [Boolean] Whether to pre-allocate positions
|
|
36
|
+
#
|
|
37
|
+
def initialize(size: 1000, preallocate: false)
|
|
38
|
+
# Note: Position requires arguments, so we cannot pre-allocate
|
|
39
|
+
super(Parsanol::Position, size: size, preallocate: false)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Acquire a Position from the pool.
|
|
43
|
+
# Overrides ObjectPool#acquire to handle Position's required arguments.
|
|
44
|
+
#
|
|
45
|
+
# @return [Parsanol::Position] A position instance from pool or newly created
|
|
46
|
+
#
|
|
47
|
+
def acquire
|
|
48
|
+
if @available.empty?
|
|
49
|
+
@stats[:created] += 1
|
|
50
|
+
# Create Position with default values since it requires arguments
|
|
51
|
+
Parsanol::Position.new("", 0, 0)
|
|
52
|
+
else
|
|
53
|
+
@stats[:reused] += 1
|
|
54
|
+
@available.pop
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Acquire a Position from the pool and initialize it with values.
|
|
59
|
+
#
|
|
60
|
+
# @param string [String] Source string for position tracking
|
|
61
|
+
# @param bytepos [Integer] Byte position in source
|
|
62
|
+
# @param charpos [Integer, nil] Character position (optional)
|
|
63
|
+
# @return [Parsanol::Position] Initialized position from pool
|
|
64
|
+
#
|
|
65
|
+
def acquire_with(string:, bytepos:, charpos: nil)
|
|
66
|
+
pos = acquire
|
|
67
|
+
pos.reset!(string, bytepos, charpos)
|
|
68
|
+
pos
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Return a position to the pool after resetting it.
|
|
72
|
+
#
|
|
73
|
+
# @param pos [Parsanol::Position] The position to return
|
|
74
|
+
# @return [Boolean] true if returned to pool, false if discarded
|
|
75
|
+
#
|
|
76
|
+
def release(pos)
|
|
77
|
+
# Don't pool if we're at capacity - discard instead
|
|
78
|
+
if @available.size >= @size
|
|
79
|
+
@stats[:discarded] += 1
|
|
80
|
+
return false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Reset position state with default values before returning to pool
|
|
84
|
+
pos.reset!("", 0, 0)
|
|
85
|
+
|
|
86
|
+
@stats[:released] += 1
|
|
87
|
+
@available.push(pos)
|
|
88
|
+
true
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Parsanol
|
|
4
|
+
module Pools
|
|
5
|
+
# Specialized object pool for Parsanol::Slice instances.
|
|
6
|
+
#
|
|
7
|
+
# SlicePool extends ObjectPool to provide convenient methods for
|
|
8
|
+
# acquiring and configuring Slice objects. Since Slices are frequently
|
|
9
|
+
# created during parsing, pooling them significantly reduces GC pressure.
|
|
10
|
+
#
|
|
11
|
+
# == Usage
|
|
12
|
+
#
|
|
13
|
+
# pool = Parsanol::Pools::SlicePool.new(size: 1000)
|
|
14
|
+
#
|
|
15
|
+
# # Acquire and initialize in one step
|
|
16
|
+
# slice = pool.acquire_with(0, "hello", line_cache)
|
|
17
|
+
#
|
|
18
|
+
# # Use the slice...
|
|
19
|
+
#
|
|
20
|
+
# # Return to pool
|
|
21
|
+
# pool.release(slice)
|
|
22
|
+
#
|
|
23
|
+
# == Why Pool Slices?
|
|
24
|
+
#
|
|
25
|
+
# Profiling (Session 19) showed that Slice allocation contributes
|
|
26
|
+
# significantly to GC overhead. By reusing Slice objects, we can:
|
|
27
|
+
# - Reduce object allocations by 70-80%
|
|
28
|
+
# - Decrease GC time from 67% to ~20%
|
|
29
|
+
# - Improve overall parsing throughput by 2-3x
|
|
30
|
+
#
|
|
31
|
+
class SlicePool < Parsanol::ObjectPool
|
|
32
|
+
# Initialize a new SlicePool.
|
|
33
|
+
#
|
|
34
|
+
# @param size [Integer] Maximum number of Slice objects to pool (default: 1000)
|
|
35
|
+
# @param preallocate [Boolean] Whether to pre-allocate slices (default: true)
|
|
36
|
+
#
|
|
37
|
+
# @example Create a SlicePool
|
|
38
|
+
# pool = SlicePool.new(size: 2000)
|
|
39
|
+
#
|
|
40
|
+
def initialize(size: 1000, preallocate: true)
|
|
41
|
+
super(Parsanol::Slice, size: size, preallocate: preallocate)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Acquire a Slice from the pool and initialize it with given values.
|
|
45
|
+
#
|
|
46
|
+
# This is a convenience method that combines acquire + reset! into
|
|
47
|
+
# a single operation, making it easier to work with pooled slices.
|
|
48
|
+
#
|
|
49
|
+
# @param bytepos [Integer] Byte position in the original input
|
|
50
|
+
# @param str [String] The slice content
|
|
51
|
+
# @param line_cache [Object] Optional line cache for line/column info
|
|
52
|
+
# @return [Parsanol::Slice] An initialized slice ready for use
|
|
53
|
+
#
|
|
54
|
+
# @example Acquire and initialize
|
|
55
|
+
# slice = pool.acquire_with(0, "hello", line_cache)
|
|
56
|
+
#
|
|
57
|
+
def acquire_with(bytepos, str, line_cache = nil)
|
|
58
|
+
slice = acquire
|
|
59
|
+
slice.reset!(bytepos, str, line_cache)
|
|
60
|
+
slice
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
|
|
2
|
+
# Encapsules the concept of a position inside a string.
|
|
3
|
+
#
|
|
4
|
+
class Parsanol::Position
|
|
5
|
+
# Changed to accessor to support pooling
|
|
6
|
+
attr_accessor :bytepos
|
|
7
|
+
attr_accessor :string, :charpos
|
|
8
|
+
|
|
9
|
+
include Comparable
|
|
10
|
+
|
|
11
|
+
def initialize(string, bytepos, charpos = nil)
|
|
12
|
+
@string = string
|
|
13
|
+
@bytepos = bytepos
|
|
14
|
+
@charpos = charpos
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Reset the position for reuse in object pooling.
|
|
18
|
+
# This allows the position to be reinitialized with new values for efficient reuse.
|
|
19
|
+
#
|
|
20
|
+
# @param string [String] Source string for position tracking
|
|
21
|
+
# @param bytepos [Integer] New byte position
|
|
22
|
+
# @param charpos [Integer, nil] Optional character position
|
|
23
|
+
# @return [self] Returns self for method chaining
|
|
24
|
+
#
|
|
25
|
+
def reset!(string, bytepos, charpos = nil)
|
|
26
|
+
@string = string
|
|
27
|
+
@bytepos = bytepos
|
|
28
|
+
@charpos = charpos
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def charpos
|
|
33
|
+
# If charpos was provided during initialization, use it
|
|
34
|
+
return @charpos if @charpos
|
|
35
|
+
|
|
36
|
+
# Cache the calculated charpos to avoid repeated calculations
|
|
37
|
+
@charpos ||= calculate_charpos
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def calculate_charpos
|
|
43
|
+
# Calculate it based on platform
|
|
44
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'opal'
|
|
45
|
+
# In Opal, convert byte position to character position.
|
|
46
|
+
# We need to calculate how many characters occupy the first @bytepos bytes.
|
|
47
|
+
%x{
|
|
48
|
+
var str = #{@string};
|
|
49
|
+
var bytePos = #{@bytepos};
|
|
50
|
+
var chars = Array.from(str);
|
|
51
|
+
var byteCount = 0;
|
|
52
|
+
var charCount = 0;
|
|
53
|
+
|
|
54
|
+
for (var i = 0; i < chars.length; i++) {
|
|
55
|
+
if (byteCount >= bytePos) break;
|
|
56
|
+
|
|
57
|
+
var char = chars[i];
|
|
58
|
+
var codePoint = char.codePointAt(0);
|
|
59
|
+
|
|
60
|
+
// Calculate UTF-8 byte length for this character
|
|
61
|
+
if (codePoint < 0x80) {
|
|
62
|
+
byteCount += 1;
|
|
63
|
+
} else if (codePoint < 0x800) {
|
|
64
|
+
byteCount += 2;
|
|
65
|
+
} else if (codePoint < 0x10000) {
|
|
66
|
+
byteCount += 3;
|
|
67
|
+
} else {
|
|
68
|
+
byteCount += 4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (byteCount <= bytePos) {
|
|
72
|
+
charCount++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return charCount;
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
# Ruby: Use standard byteslice which handles Unicode correctly
|
|
80
|
+
@string.byteslice(0, @bytepos).size
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
public
|
|
85
|
+
|
|
86
|
+
def <=>(b)
|
|
87
|
+
bytepos <=> b.bytepos
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Phase 58: Result wrapper to replace [success, value] arrays
|
|
4
|
+
#
|
|
5
|
+
# This class wraps parse results to eliminate array allocations.
|
|
6
|
+
# Instead of [true, value] or [false, cause], we use Result objects.
|
|
7
|
+
#
|
|
8
|
+
# Benefits:
|
|
9
|
+
# - Eliminates array allocations (40% reduction)
|
|
10
|
+
# - Cleaner API with success? method
|
|
11
|
+
# - Can be optimized further (object pooling, etc.)
|
|
12
|
+
#
|
|
13
|
+
class Parsanol::Result
|
|
14
|
+
attr_reader :value
|
|
15
|
+
|
|
16
|
+
def initialize(success, value)
|
|
17
|
+
@success = success
|
|
18
|
+
@value = value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def success?
|
|
22
|
+
@success
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def error?
|
|
26
|
+
!@success
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Compatibility: Allow destructuring like arrays
|
|
30
|
+
# This enables gradual migration: result.success?, result.value
|
|
31
|
+
# or: success, value = result (array-like)
|
|
32
|
+
def to_ary
|
|
33
|
+
[@success, @value]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Factory methods for common cases
|
|
37
|
+
def self.success(value)
|
|
38
|
+
new(true, value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.error(cause)
|
|
42
|
+
new(false, cause)
|
|
43
|
+
end
|
|
44
|
+
end
|