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,151 @@
|
|
|
1
|
+
|
|
2
|
+
# The base class for all your parsers. Use as follows:
|
|
3
|
+
#
|
|
4
|
+
# require 'parslet'
|
|
5
|
+
#
|
|
6
|
+
# class MyParser < Parsanol::Parser
|
|
7
|
+
# rule(:a) { str('a').repeat }
|
|
8
|
+
# root(:a)
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# pp MyParser.new.parse('aaaa') # => 'aaaa'
|
|
12
|
+
# pp MyParser.new.parse('bbbb') # => Parsanol::Atoms::ParseFailed:
|
|
13
|
+
# # Don't know what to do with bbbb at line 1 char 1.
|
|
14
|
+
#
|
|
15
|
+
# Parsanol::Parser is also a grammar atom. This means that you can mix full
|
|
16
|
+
# fledged parsers freely with small parts of a different parser.
|
|
17
|
+
#
|
|
18
|
+
# Example:
|
|
19
|
+
# class ParserA < Parsanol::Parser
|
|
20
|
+
# root :aaa
|
|
21
|
+
# rule(:aaa) { str('a').repeat(3,3) }
|
|
22
|
+
# end
|
|
23
|
+
# class ParserB < Parsanol::Parser
|
|
24
|
+
# root :expression
|
|
25
|
+
# rule(:expression) { str('b') >> ParserA.new >> str('b') }
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# In the above example, ParserB would parse something like 'baaab'.
|
|
29
|
+
#
|
|
30
|
+
class Parsanol::Parser < Parsanol::Atoms::Base
|
|
31
|
+
include Parsanol
|
|
32
|
+
|
|
33
|
+
class << self # class methods
|
|
34
|
+
# Define the parsers #root function. This is the place where you start
|
|
35
|
+
# parsing; if you have a rule for 'file' that describes what should be
|
|
36
|
+
# in a file, this would be your root declaration:
|
|
37
|
+
#
|
|
38
|
+
# class Parser
|
|
39
|
+
# root :file
|
|
40
|
+
# rule(:file) { ... }
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# #root declares a 'parse' function that works just like the parse
|
|
44
|
+
# function that you can call on a simple parslet, taking a string as input
|
|
45
|
+
# and producing parse output.
|
|
46
|
+
#
|
|
47
|
+
# In a way, #root is a shorthand for:
|
|
48
|
+
#
|
|
49
|
+
# def parse(str)
|
|
50
|
+
# your_parser_root.parse(str)
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
def root(name)
|
|
54
|
+
undef_method :root if method_defined? :root
|
|
55
|
+
define_method(:root) do
|
|
56
|
+
self.send(name)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def try(source, context, consume_all)
|
|
62
|
+
root.try(source, context, consume_all)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_s_inner(prec)
|
|
66
|
+
root.to_s(prec)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Unified parsing method with mode selection
|
|
70
|
+
#
|
|
71
|
+
# @param input [String] Input string to parse
|
|
72
|
+
# @param mode_or_options [Symbol, Hash] Parsing mode (:ruby, :native, :json) or options hash
|
|
73
|
+
# @param options [Hash] Additional options (if mode provided):
|
|
74
|
+
# - :reporter - Error reporter to use
|
|
75
|
+
# - :prefix - Allow prefix matching (default: false)
|
|
76
|
+
#
|
|
77
|
+
# @return [Hash, Array, String, Parsanol::Slice] Parsed result
|
|
78
|
+
# @raise [Parsanol::ParseFailed] If parsing fails
|
|
79
|
+
#
|
|
80
|
+
# @example Parse in Ruby mode (default)
|
|
81
|
+
# parser.parse("1+2")
|
|
82
|
+
# # => {:left=>"1", :op=>"+", :right=>"2"}
|
|
83
|
+
#
|
|
84
|
+
# @example Parse in native mode (faster, if extension available)
|
|
85
|
+
# parser.parse("1+2", mode: :native)
|
|
86
|
+
# # => {:left=>"1", :op=>"+", :right=>"2"}
|
|
87
|
+
#
|
|
88
|
+
# @example Parse in JSON mode (for cross-language use)
|
|
89
|
+
# parser.parse("1+2", mode: :json)
|
|
90
|
+
# # => '{"left":"1","op":"+","right":"2"}'
|
|
91
|
+
#
|
|
92
|
+
def parse(input, mode_or_options = {}, **options)
|
|
93
|
+
# Support both old API (parse(input, options)) and new API (parse(input, mode: :ruby))
|
|
94
|
+
if mode_or_options.is_a?(Hash)
|
|
95
|
+
# Old API: parse(input, options={})
|
|
96
|
+
# Merge keyword options into the options hash (handles parse(str, prefix: true))
|
|
97
|
+
merged_options = mode_or_options.merge(options)
|
|
98
|
+
super(input, merged_options)
|
|
99
|
+
else
|
|
100
|
+
# New API: parse(input, mode: :ruby, **options)
|
|
101
|
+
mode = mode_or_options
|
|
102
|
+
case mode
|
|
103
|
+
when :ruby
|
|
104
|
+
parse_ruby(input, options)
|
|
105
|
+
when :native
|
|
106
|
+
parse_native(input, options)
|
|
107
|
+
when :json
|
|
108
|
+
parse_json(input, options)
|
|
109
|
+
else
|
|
110
|
+
raise ArgumentError, "Unknown mode: #{mode}. Use :ruby, :native, or :json"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Batch parsing with mode selection
|
|
116
|
+
#
|
|
117
|
+
# @param inputs [Array<String>] Array of input strings
|
|
118
|
+
# @param mode [Symbol] Parsing mode (:ruby, :native, or :json)
|
|
119
|
+
# @param options [Hash] Additional options
|
|
120
|
+
# @return [Array] Array of parsed results
|
|
121
|
+
#
|
|
122
|
+
def parse_batch(inputs, mode: :ruby, **options)
|
|
123
|
+
inputs.map { |input| parse(input, mode: mode, **options) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def parse_ruby(input, options = {})
|
|
129
|
+
# Use the original Ruby parser from Base
|
|
130
|
+
super(input, options)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_native(input, options = {})
|
|
134
|
+
# Use native if available, fallback to pure Ruby
|
|
135
|
+
if Parsanol::Native.available?
|
|
136
|
+
Parsanol::Native.parse_parslet_compatible(root, input)
|
|
137
|
+
else
|
|
138
|
+
parse_ruby(input, options)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def parse_json(input, options = {})
|
|
143
|
+
if Parsanol::Native.available?
|
|
144
|
+
grammar_json = Parsanol::Native.serialize_grammar(root)
|
|
145
|
+
result = Parsanol::Native.parse(grammar_json, input)
|
|
146
|
+
result.to_json
|
|
147
|
+
else
|
|
148
|
+
parse_ruby(input, options).to_json
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
# Usage:
|
|
9
|
+
# require 'parsanol/parslet'
|
|
10
|
+
#
|
|
11
|
+
# class MyParser < Parsanol::Parslet::Parser
|
|
12
|
+
# include Parsanol::Parslet
|
|
13
|
+
# rule(:foo) { str('foo') }
|
|
14
|
+
# root(:foo)
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# Migration from original Parslet:
|
|
18
|
+
# Before: require 'parslet'
|
|
19
|
+
# class MyParser < Parslet::Parser
|
|
20
|
+
# include Parslet
|
|
21
|
+
#
|
|
22
|
+
# After: require 'parsanol/parslet'
|
|
23
|
+
# class MyParser < Parsanol::Parslet::Parser
|
|
24
|
+
# include Parsanol::Parslet
|
|
25
|
+
|
|
26
|
+
require 'parsanol'
|
|
27
|
+
|
|
28
|
+
module Parsanol
|
|
29
|
+
module Parslet
|
|
30
|
+
# Include Parsanol to get all DSL methods (str, match, any, etc.)
|
|
31
|
+
include Parsanol
|
|
32
|
+
|
|
33
|
+
# Error class alias for compatibility
|
|
34
|
+
ParseFailed = Parsanol::ParseFailed
|
|
35
|
+
|
|
36
|
+
# Atoms namespace - aliases to Parsanol atoms
|
|
37
|
+
# These are the atoms explicitly loaded by lib/parsanol/atoms.rb
|
|
38
|
+
module Atoms
|
|
39
|
+
Base = ::Parsanol::Atoms::Base
|
|
40
|
+
Str = ::Parsanol::Atoms::Str
|
|
41
|
+
Re = ::Parsanol::Atoms::Re
|
|
42
|
+
Sequence = ::Parsanol::Atoms::Sequence
|
|
43
|
+
Alternative = ::Parsanol::Atoms::Alternative
|
|
44
|
+
Repetition = ::Parsanol::Atoms::Repetition
|
|
45
|
+
Named = ::Parsanol::Atoms::Named
|
|
46
|
+
Entity = ::Parsanol::Atoms::Entity
|
|
47
|
+
Lookahead = ::Parsanol::Atoms::Lookahead
|
|
48
|
+
Cut = ::Parsanol::Atoms::Cut
|
|
49
|
+
Capture = ::Parsanol::Atoms::Capture
|
|
50
|
+
Scope = ::Parsanol::Atoms::Scope
|
|
51
|
+
Dynamic = ::Parsanol::Atoms::Dynamic
|
|
52
|
+
Infix = ::Parsanol::Atoms::Infix
|
|
53
|
+
Ignored = ::Parsanol::Atoms::Ignored
|
|
54
|
+
ParseFailed = ::Parsanol::ParseFailed
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Class aliases
|
|
58
|
+
Parser = ::Parsanol::Parser
|
|
59
|
+
Transform = ::Parsanol::Transform
|
|
60
|
+
Cause = ::Parsanol::Cause
|
|
61
|
+
Slice = ::Parsanol::Slice
|
|
62
|
+
Source = ::Parsanol::Source
|
|
63
|
+
Pattern = ::Parsanol::Pattern
|
|
64
|
+
Context = ::Parsanol::Context
|
|
65
|
+
|
|
66
|
+
# Module functions for DSL (delegate to Parsanol)
|
|
67
|
+
extend self
|
|
68
|
+
|
|
69
|
+
def match(str = nil)
|
|
70
|
+
Parsanol.match(str)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def str(str)
|
|
74
|
+
Parsanol.str(str)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def any
|
|
78
|
+
Parsanol.any
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def scope(&block)
|
|
82
|
+
Parsanol.scope(&block)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def dynamic(&block)
|
|
86
|
+
Parsanol.dynamic(&block)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def infix_expression(element, *operations, &reducer)
|
|
90
|
+
Parsanol.infix_expression(element, *operations, &reducer)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def exp(str)
|
|
94
|
+
Parsanol.exp(str)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def sequence(symbol)
|
|
98
|
+
Parsanol.sequence(symbol)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def simple(symbol)
|
|
102
|
+
Parsanol.simple(symbol)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def subtree(symbol)
|
|
106
|
+
Parsanol.subtree(symbol)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Class method extensions for Parser
|
|
110
|
+
module ClassMethods
|
|
111
|
+
# Delegate rule definition to Parsanol's implementation
|
|
112
|
+
# This works for both classes and modules that include Parsanol::Parslet
|
|
113
|
+
def rule(name, opts = {}, &definition)
|
|
114
|
+
# Remove existing method if present
|
|
115
|
+
undef_method name if method_defined? name
|
|
116
|
+
|
|
117
|
+
# Define the rule method that memoizes the entity
|
|
118
|
+
define_method(name) do
|
|
119
|
+
@rules ||= {} # <name, rule> memoization
|
|
120
|
+
return @rules[name] if @rules.has_key?(name)
|
|
121
|
+
|
|
122
|
+
# Capture the self of the parser class along with the definition.
|
|
123
|
+
definition_closure = proc {
|
|
124
|
+
result = instance_eval(&definition)
|
|
125
|
+
|
|
126
|
+
# Apply optimizations if enabled (only for classes that support it)
|
|
127
|
+
if self.class.respond_to?(:optimize_rules?) && self.class.optimize_rules?
|
|
128
|
+
# Apply all optimizers: quantifiers, sequences, choices, and lookaheads
|
|
129
|
+
result = Parsanol::Optimizer.simplify_quantifiers(result)
|
|
130
|
+
result = Parsanol::Optimizer.simplify_sequences(result)
|
|
131
|
+
result = Parsanol::Optimizer.simplify_choices(result)
|
|
132
|
+
result = Parsanol::Optimizer.simplify_lookaheads(result)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
result
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@rules[name] = Parsanol::Atoms::Entity.new(name, opts[:label], &definition_closure)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Extend with class methods when included
|
|
144
|
+
def self.included(base)
|
|
145
|
+
base.extend(ClassMethods)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
# Used internally for representing a bind placeholder in a Parsanol::Transform
|
|
3
|
+
# pattern. This is the superclass for all bindings.
|
|
4
|
+
#
|
|
5
|
+
# It defines the most permissive kind of bind, the one that matches any subtree
|
|
6
|
+
# whatever it looks like.
|
|
7
|
+
#
|
|
8
|
+
class Parsanol::Pattern::SubtreeBind < Struct.new(:symbol)
|
|
9
|
+
def variable_name
|
|
10
|
+
symbol
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def inspect
|
|
14
|
+
"#{bind_type_name}(#{symbol.inspect})"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def can_bind?(subtree)
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def bind_type_name
|
|
23
|
+
if md=self.class.name.match(/(\w+)Bind/)
|
|
24
|
+
md.captures.first.downcase
|
|
25
|
+
else
|
|
26
|
+
# This path should never be used, but since this is for inspection only,
|
|
27
|
+
# let's not raise.
|
|
28
|
+
'unknown_bind'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Binds a symbol to a simple subtree, one that is not either a sequence of
|
|
34
|
+
# elements or a collection of attributes.
|
|
35
|
+
#
|
|
36
|
+
class Parsanol::Pattern::SimpleBind < Parsanol::Pattern::SubtreeBind
|
|
37
|
+
def can_bind?(subtree)
|
|
38
|
+
not [Hash, Array].include?(subtree.class)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Binds a symbol to a sequence of simple leafs ([element1, element2, ...])
|
|
43
|
+
#
|
|
44
|
+
class Parsanol::Pattern::SequenceBind < Parsanol::Pattern::SubtreeBind
|
|
45
|
+
def can_bind?(subtree)
|
|
46
|
+
subtree.kind_of?(Array) &&
|
|
47
|
+
(not subtree.any? { |el| [Hash, Array].include?(el.class) })
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matches trees against expressions. Trees are formed by arrays and hashes
|
|
4
|
+
# for expressing membership and sequence. The leafs of the tree are other
|
|
5
|
+
# classes.
|
|
6
|
+
#
|
|
7
|
+
# A tree issued by the parslet library might look like this:
|
|
8
|
+
#
|
|
9
|
+
# {
|
|
10
|
+
# :function_call => {
|
|
11
|
+
# :name => 'foobar',
|
|
12
|
+
# :args => [1, 2, 3]
|
|
13
|
+
# }
|
|
14
|
+
# }
|
|
15
|
+
#
|
|
16
|
+
# A pattern that would match against this tree would be:
|
|
17
|
+
#
|
|
18
|
+
# { :function_call => { :name => simple(:name), :args => sequence(:args) }}
|
|
19
|
+
#
|
|
20
|
+
# Note that Parsanol::Pattern only matches at a given subtree; it wont try
|
|
21
|
+
# to match recursively. To do that, please use Parsanol::Transform.
|
|
22
|
+
#
|
|
23
|
+
class Parsanol::Pattern
|
|
24
|
+
def initialize(pattern)
|
|
25
|
+
@pattern = pattern
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Decides if the given subtree matches this pattern. Returns the bindings
|
|
29
|
+
# made on a successful match or nil if the match fails. If you specify
|
|
30
|
+
# bindings to be a hash, the mappings in it will be treated like bindings
|
|
31
|
+
# made during an attempted match.
|
|
32
|
+
#
|
|
33
|
+
# Pattern.new('a').match('a', :foo => 'bar') # => { :foo => 'bar' }
|
|
34
|
+
#
|
|
35
|
+
# @param subtree [String, Hash, Array] poro subtree returned by a parse
|
|
36
|
+
# @param bindings [Hash] variable bindings to be verified
|
|
37
|
+
# @return [Hash, nil] On success: variable bindings that allow a match. On
|
|
38
|
+
# failure: nil
|
|
39
|
+
#
|
|
40
|
+
def match(subtree, bindings=nil)
|
|
41
|
+
bindings = bindings && bindings.dup || Hash.new
|
|
42
|
+
return bindings if element_match(subtree, @pattern, bindings)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns true if the tree element given by +tree+ matches the expression
|
|
46
|
+
# given by +exp+. This match must respect bindings already made in
|
|
47
|
+
# +bindings+. Note that bindings is carried along and modified.
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
#
|
|
51
|
+
def element_match(tree, exp, bindings)
|
|
52
|
+
# p [:elm, tree, exp]
|
|
53
|
+
if tree.is_a?(Hash) && exp.is_a?(Hash)
|
|
54
|
+
return element_match_hash(tree, exp, bindings)
|
|
55
|
+
elsif tree.is_a?(Array) && exp.is_a?(Array)
|
|
56
|
+
return element_match_ary_single(tree, exp, bindings)
|
|
57
|
+
else
|
|
58
|
+
# If elements match exactly, then that is good enough in all cases
|
|
59
|
+
return true if exp === tree
|
|
60
|
+
|
|
61
|
+
# If exp is a bind variable: Check if the binding matches
|
|
62
|
+
if exp.respond_to?(:can_bind?) && exp.can_bind?(tree)
|
|
63
|
+
return element_match_binding(tree, exp, bindings)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Otherwise: No match (we don't know anything about the element
|
|
67
|
+
# combination)
|
|
68
|
+
return false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
#
|
|
74
|
+
def element_match_binding(tree, exp, bindings)
|
|
75
|
+
var_name = exp.variable_name
|
|
76
|
+
|
|
77
|
+
# TODO test for the hidden :_ feature.
|
|
78
|
+
if var_name && bound_value = bindings[var_name]
|
|
79
|
+
return bound_value == tree
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# New binding:
|
|
83
|
+
bindings.store var_name, tree
|
|
84
|
+
|
|
85
|
+
return true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @api private
|
|
89
|
+
#
|
|
90
|
+
def element_match_ary_single(sequence, exp, bindings)
|
|
91
|
+
return false if sequence.size != exp.size
|
|
92
|
+
|
|
93
|
+
return sequence.zip(exp).all? { |elt, subexp|
|
|
94
|
+
element_match(elt, subexp, bindings) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @api private
|
|
98
|
+
#
|
|
99
|
+
def element_match_hash(tree, exp, bindings)
|
|
100
|
+
# Early failure when one hash is bigger than the other
|
|
101
|
+
return false unless exp.size == tree.size
|
|
102
|
+
|
|
103
|
+
# We iterate over expected pattern, since we demand that the keys that
|
|
104
|
+
# are there should be in tree as well.
|
|
105
|
+
exp.each do |expected_key, expected_value|
|
|
106
|
+
return false unless tree.has_key? expected_key
|
|
107
|
+
|
|
108
|
+
# Recurse into the value and stop early on failure
|
|
109
|
+
value = tree[expected_key]
|
|
110
|
+
return false unless element_match(value, expected_value, bindings)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
return true
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
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
|
+
begin
|
|
195
|
+
@klass.new
|
|
196
|
+
true
|
|
197
|
+
rescue ArgumentError
|
|
198
|
+
# Class requires arguments, cannot pre-allocate
|
|
199
|
+
false
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Pre-allocate objects to fill the pool.
|
|
204
|
+
#
|
|
205
|
+
# This is called during initialization if preallocate: true is set.
|
|
206
|
+
# Pre-allocation reduces allocation overhead during initial parsing.
|
|
207
|
+
#
|
|
208
|
+
# @param count [Integer] Number of objects to pre-allocate
|
|
209
|
+
# @return [void]
|
|
210
|
+
#
|
|
211
|
+
def preallocate(count)
|
|
212
|
+
count.times do
|
|
213
|
+
@available.push(@klass.new)
|
|
214
|
+
end
|
|
215
|
+
# Adjust stats to reflect pre-allocation as "released" not "created"
|
|
216
|
+
# since these objects haven't been acquired yet
|
|
217
|
+
@stats[:released] = count
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|