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,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Compatibility Test Helper
|
|
4
|
+
#
|
|
5
|
+
# This helper allows running tests against both Parslet and Parsanol::Parslet
|
|
6
|
+
# to verify behavioral compatibility.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# PARSANOL_BACKEND=parslet bundle exec rspec spec/parslet_imported/
|
|
10
|
+
# PARSANOL_BACKEND=parsanol bundle exec rspec spec/parslet_imported/
|
|
11
|
+
#
|
|
12
|
+
# Default is to use Parsanol::Parslet
|
|
13
|
+
|
|
14
|
+
module ParsletCompatibilityHelper
|
|
15
|
+
def use_original_parslet?
|
|
16
|
+
ENV['PARSANOL_BACKEND'] == 'parslet'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parslet_module
|
|
20
|
+
@parslet_module ||= begin
|
|
21
|
+
if use_original_parslet?
|
|
22
|
+
require 'parslet'
|
|
23
|
+
Parslet
|
|
24
|
+
else
|
|
25
|
+
require 'parsanol/parslet'
|
|
26
|
+
Parsanol::Parslet
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Normalize results for comparison
|
|
32
|
+
# Parslet returns Slice objects, Parsanol returns strings/hashes
|
|
33
|
+
def normalize_result(obj)
|
|
34
|
+
case obj
|
|
35
|
+
when defined?(Parslet::Slice) && Parslet::Slice
|
|
36
|
+
obj.to_s
|
|
37
|
+
when Hash
|
|
38
|
+
obj.transform_values { |v| normalize_result(v) }
|
|
39
|
+
when Array
|
|
40
|
+
obj.map { |v| normalize_result(v) }
|
|
41
|
+
else
|
|
42
|
+
obj
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Parse and normalize result for comparison
|
|
47
|
+
def parslet_parse(parser, input)
|
|
48
|
+
result = parser.parse(input)
|
|
49
|
+
normalize_result(result)
|
|
50
|
+
rescue => e
|
|
51
|
+
e
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if two results are equivalent
|
|
55
|
+
def results_equivalent?(result1, result2)
|
|
56
|
+
normalize_result(result1) == normalize_result(result2)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
RSpec.configure do |config|
|
|
61
|
+
config.include ParsletCompatibilityHelper
|
|
62
|
+
|
|
63
|
+
config.before(:suite) do
|
|
64
|
+
# Pre-load the appropriate module
|
|
65
|
+
if ENV['PARSANOL_BACKEND'] == 'parslet'
|
|
66
|
+
puts "Running tests with original Parslet"
|
|
67
|
+
require 'parslet'
|
|
68
|
+
else
|
|
69
|
+
puts "Running tests with Parsanol::Parslet compatibility layer"
|
|
70
|
+
require 'parsanol/parslet'
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require "parslet"
|
|
5
|
+
require "parslet/native"
|
|
6
|
+
|
|
7
|
+
puts "=" * 60
|
|
8
|
+
puts "Parsanol Batch Parsing Benchmark"
|
|
9
|
+
puts "=" * 60
|
|
10
|
+
|
|
11
|
+
# First ensure native extension is loaded
|
|
12
|
+
unless Parsanol::Native.available?
|
|
13
|
+
puts "ERROR: Native extension not available. Run 'rake compile' first."
|
|
14
|
+
exit 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class SimpleParser < Parsanol::Parser
|
|
18
|
+
rule(:comma) { str(",") >> str(" ").maybe }
|
|
19
|
+
rule(:word) { match(/[a-z]/).repeat(1) }
|
|
20
|
+
rule(:alnum) { match(/[a-z0-9]/).repeat(1) }
|
|
21
|
+
|
|
22
|
+
rule(:value) { (word | alnum).as(:v) }
|
|
23
|
+
rule(:list) { value >> (comma >> value).repeat }
|
|
24
|
+
|
|
25
|
+
root(:list)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
parser = SimpleParser.new
|
|
29
|
+
|
|
30
|
+
# Create test inputs
|
|
31
|
+
inputs = (1..50).map { |i| "item#{i}, item#{i+1}, item#{i+2}" }
|
|
32
|
+
|
|
33
|
+
puts "\nTest: 50 inputs, 10 items each"
|
|
34
|
+
puts "Input count: #{inputs.length}"
|
|
35
|
+
puts "Total chars: #{inputs.sum(&:length)}"
|
|
36
|
+
|
|
37
|
+
# ============================================================================
|
|
38
|
+
# Test 1: Individual parsing (current approach)
|
|
39
|
+
# ============================================================================
|
|
40
|
+
puts "\n" + "-" * 60
|
|
41
|
+
puts "Test 1: Individual parsing (parse_parslet_compatible)"
|
|
42
|
+
puts "-" * 60
|
|
43
|
+
|
|
44
|
+
Parsanol::Native.clear_cache
|
|
45
|
+
|
|
46
|
+
individual_times = []
|
|
47
|
+
inputs.each do |input|
|
|
48
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
49
|
+
result = Parsanol::Native.parse_parslet_compatible(parser, input)
|
|
50
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
|
|
51
|
+
individual_times << elapsed
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts "Total time: #{individual_times.sum.round(0)} μs"
|
|
55
|
+
puts "Average per parse: #{(individual_times.sum / individual_times.length).round(2)} μs"
|
|
56
|
+
puts "Cache: #{Parsanol::Native.cache_stats}"
|
|
57
|
+
|
|
58
|
+
# ============================================================================
|
|
59
|
+
# Test 2: Batch parsing with transform
|
|
60
|
+
# ============================================================================
|
|
61
|
+
puts "\n" + "-" * 60
|
|
62
|
+
puts "Test 2: Batch parsing with transform (parse_batch_with_transform)"
|
|
63
|
+
puts "-" * 60
|
|
64
|
+
|
|
65
|
+
Parsanol::Native.clear_cache
|
|
66
|
+
|
|
67
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
68
|
+
results = Parsanol::Native.parse_batch_with_transform(parser, inputs)
|
|
69
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
|
|
70
|
+
|
|
71
|
+
puts "Total time: #{elapsed.round(0)} μs"
|
|
72
|
+
puts "Average per parse: #{(elapsed / inputs.length).round(2)} μs"
|
|
73
|
+
puts "Results count: #{results.length}"
|
|
74
|
+
puts "Cache: #{Parsanol::Native.cache_stats}"
|
|
75
|
+
|
|
76
|
+
# ============================================================================
|
|
77
|
+
# Test 3: Raw parsing (no transform)
|
|
78
|
+
# ============================================================================
|
|
79
|
+
puts "\n" + "-" * 60
|
|
80
|
+
puts "Test 3: Raw parsing (parse_raw - no transformation)"
|
|
81
|
+
puts "-" * 60
|
|
82
|
+
|
|
83
|
+
Parsanol::Native.clear_cache
|
|
84
|
+
|
|
85
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
86
|
+
results = Parsanol::Native.parse_batch(parser, inputs)
|
|
87
|
+
elapsed_raw = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
|
|
88
|
+
|
|
89
|
+
puts "Total time: #{elapsed_raw.round(0)} μs"
|
|
90
|
+
puts "Average per parse: #{(elapsed_raw / inputs.length).round(2)} μs"
|
|
91
|
+
puts "Results count: #{results.length}"
|
|
92
|
+
|
|
93
|
+
# ============================================================================
|
|
94
|
+
# Comparison
|
|
95
|
+
# ============================================================================
|
|
96
|
+
puts "\n" + "=" * 60
|
|
97
|
+
puts "Comparison"
|
|
98
|
+
puts "=" * 60
|
|
99
|
+
|
|
100
|
+
total_individual = individual_times.sum
|
|
101
|
+
speedup_with_transform = (total_individual / elapsed).round(2)
|
|
102
|
+
speedup_raw = (total_individual / elapsed_raw).round(2)
|
|
103
|
+
|
|
104
|
+
puts "\nIndividual: #{total_individual.round(0)} μs"
|
|
105
|
+
puts "Batch + transform: #{elapsed.round(0)} μs"
|
|
106
|
+
puts "Batch raw: #{elapsed_raw.round(0)} μs"
|
|
107
|
+
|
|
108
|
+
puts "\nSpeedup (batch vs individual):"
|
|
109
|
+
puts " With transform: #{speedup_with_transform}x faster"
|
|
110
|
+
puts " Raw (no transform): #{speedup_raw}x faster"
|
|
111
|
+
|
|
112
|
+
puts "\n" + "=" * 60
|
|
113
|
+
puts "Analysis"
|
|
114
|
+
puts "=" * 60
|
|
115
|
+
|
|
116
|
+
if speedup_with_transform > 1.5
|
|
117
|
+
puts "✓ Batch parsing is #{speedup_with_transform}x faster"
|
|
118
|
+
else
|
|
119
|
+
puts "⚠ Batch parsing improvement: #{speedup_with_transform}x"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if speedup_raw > speedup_with_transform
|
|
123
|
+
transform_overhead = ((elapsed - elapsed_raw) / elapsed * 100).round(1)
|
|
124
|
+
puts " Transformation adds #{transform_overhead}% overhead"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
puts "\n" + "=" * 60
|
|
128
|
+
puts "Benchmark complete"
|
|
129
|
+
puts "=" * 60
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require "parslet"
|
|
5
|
+
require "parslet/native"
|
|
6
|
+
|
|
7
|
+
puts "=" * 70
|
|
8
|
+
puts "Parsanol Complete Optimization Summary"
|
|
9
|
+
puts "=" * 70
|
|
10
|
+
|
|
11
|
+
# First ensure native extension is loaded
|
|
12
|
+
unless Parsanol::Native.available?
|
|
13
|
+
puts "ERROR: Native extension not available. Run 'rake compile' first."
|
|
14
|
+
exit 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class SimpleParser < Parsanol::Parser
|
|
18
|
+
rule(:comma) { str(",") >> str(" ") }
|
|
19
|
+
rule(:word) { match(/[a-z]/).repeat(1) }
|
|
20
|
+
|
|
21
|
+
rule(:value) { word.as(:v) }
|
|
22
|
+
rule(:list) { value >> (comma >> value).repeat }
|
|
23
|
+
|
|
24
|
+
root(:list)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
parser = SimpleParser.new
|
|
28
|
+
test_input = "one, two, three, four, five"
|
|
29
|
+
|
|
30
|
+
# Clear all caches
|
|
31
|
+
Parsanol::Native.clear_cache
|
|
32
|
+
|
|
33
|
+
puts "\n" + "-" * 70
|
|
34
|
+
puts "1. Cold Cache (first parse)"
|
|
35
|
+
puts "-" * 70
|
|
36
|
+
|
|
37
|
+
# Profile if available (native extension method)
|
|
38
|
+
has_profiling = Parsanol::Native.respond_to?(:profile_reset)
|
|
39
|
+
Parsanol::Native.profile_reset if has_profiling
|
|
40
|
+
|
|
41
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
42
|
+
result = Parsanol::Native.parse_parslet_compatible(parser, test_input)
|
|
43
|
+
cold_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
|
|
44
|
+
|
|
45
|
+
if has_profiling
|
|
46
|
+
profile = Parsanol::Native.profile_stats
|
|
47
|
+
puts "Time: #{profile["total_parse_us"]} μs"
|
|
48
|
+
puts "Grammar JSON: #{profile["grammar_parse_us"]} μs"
|
|
49
|
+
else
|
|
50
|
+
puts "Time: #{cold_time} μs"
|
|
51
|
+
end
|
|
52
|
+
puts "Cache: #{Parsanol::Native.cache_stats}"
|
|
53
|
+
|
|
54
|
+
puts "\n" + "-" * 70
|
|
55
|
+
puts "2. Warm Cache (repeated parsing - grammar already cached)"
|
|
56
|
+
puts "-" * 70
|
|
57
|
+
|
|
58
|
+
times = []
|
|
59
|
+
20.times do
|
|
60
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
61
|
+
Parsanol::Native.parse_parslet_compatible(parser, test_input)
|
|
62
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
|
|
63
|
+
times << elapsed
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
avg_warm = times.sum / times.length
|
|
67
|
+
puts "Average time: #{avg_warm.round(2)} μs"
|
|
68
|
+
puts "Min: #{times.min.round(2)} μs, Max: #{times.max.round(2)} μs"
|
|
69
|
+
speedup_cold_warm = cold_time > 0 && avg_warm > 0 ? (cold_time.to_f / avg_warm).round(0) : 0
|
|
70
|
+
puts "Speedup (cold vs warm): #{speedup_cold_warm}x"
|
|
71
|
+
|
|
72
|
+
puts "\n" + "-" * 70
|
|
73
|
+
puts "3. Batch Parsing (50 inputs)"
|
|
74
|
+
puts "-" * 70
|
|
75
|
+
|
|
76
|
+
# Use simple alphabetic inputs (Rust parser has issues with compound character classes)
|
|
77
|
+
words = %w[one two three four five six seven eight nine ten]
|
|
78
|
+
inputs = (0...50).map { |i| "#{words[i % 10]}, #{words[(i+1) % 10]}, #{words[(i+2) % 10]}" }
|
|
79
|
+
|
|
80
|
+
# Individual
|
|
81
|
+
Parsanol::Native.clear_cache
|
|
82
|
+
individual_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
83
|
+
inputs.each { |i| Parsanol::Native.parse_parslet_compatible(parser, i) }
|
|
84
|
+
individual_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - individual_start
|
|
85
|
+
|
|
86
|
+
# Batch with transform
|
|
87
|
+
batch_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
88
|
+
results = Parsanol::Native.parse_batch_with_transform(parser, inputs)
|
|
89
|
+
batch_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - batch_start
|
|
90
|
+
|
|
91
|
+
# Batch raw (no transform)
|
|
92
|
+
Parsanol::Native.clear_cache
|
|
93
|
+
batch_raw_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
94
|
+
raw_results = Parsanol::Native.parse_batch_inputs(parser, inputs)
|
|
95
|
+
batch_raw_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - batch_raw_start
|
|
96
|
+
|
|
97
|
+
puts "Individual: #{(individual_time / 1000.0).round(2)} ms"
|
|
98
|
+
puts "Batch + transform: #{(batch_time / 1000.0).round(2)} ms"
|
|
99
|
+
puts "Batch raw: #{(batch_raw_time / 1000.0).round(2)} ms"
|
|
100
|
+
puts ""
|
|
101
|
+
puts "Speedup (individual vs batch + transform): #{(individual_time / batch_time).round(1)}x"
|
|
102
|
+
puts "Speedup (individual vs batch raw): #{(individual_time / batch_raw_time).round(1)}x"
|
|
103
|
+
|
|
104
|
+
puts "\n" + "=" * 70
|
|
105
|
+
puts "SUMMARY"
|
|
106
|
+
puts "=" * 70
|
|
107
|
+
|
|
108
|
+
cold_time_ms = cold_time / 1000.0 # Convert to ms
|
|
109
|
+
warm_time_ms = avg_warm / 1000.0 # Convert to ms
|
|
110
|
+
speedup_warm = cold_time_ms > 0 && warm_time_ms > 0 ? (cold_time_ms / warm_time_ms).round(0) : 0
|
|
111
|
+
|
|
112
|
+
puts <<~SUMMARY
|
|
113
|
+
|
|
114
|
+
COLD CACHE (first parse):
|
|
115
|
+
Time: #{cold_time_ms.round(2)} ms
|
|
116
|
+
|
|
117
|
+
WARM CACHE (repeated parsing):
|
|
118
|
+
Time: #{warm_time_ms.round(4)} ms
|
|
119
|
+
Speedup: #{speedup_warm}x faster
|
|
120
|
+
|
|
121
|
+
BATCH (50 inputs, with transform):
|
|
122
|
+
Time: #{(batch_time / 1000.0).round(2)} ms
|
|
123
|
+
Speedup: #{(individual_time / batch_time).round(1)}x faster than individual
|
|
124
|
+
|
|
125
|
+
OPTIMIZATIONS APPLIED:
|
|
126
|
+
✓ Two-level grammar caching
|
|
127
|
+
✓ Single-key hash optimization
|
|
128
|
+
✓ Array slicing optimization
|
|
129
|
+
✓ Batch transformation API
|
|
130
|
+
✓ Symbol key caching
|
|
131
|
+
✓ Frozen string constants
|
|
132
|
+
✓ Optimized flatten_sequence/flatten_repetition
|
|
133
|
+
|
|
134
|
+
EXPRESSIR BENEFITS:
|
|
135
|
+
✓ NO native code changes needed
|
|
136
|
+
✓ Automatic performance improvement
|
|
137
|
+
✓ Reduced memory allocations
|
|
138
|
+
|
|
139
|
+
SUMMARY
|
|
140
|
+
|
|
141
|
+
puts "=" * 70
|
|
142
|
+
puts "Benchmark complete"
|
|
143
|
+
puts "=" * 70
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require "parslet"
|
|
5
|
+
require "parslet/native"
|
|
6
|
+
|
|
7
|
+
puts "=" * 60
|
|
8
|
+
puts "Parsanol Grammar Caching Analysis"
|
|
9
|
+
puts "=" * 60
|
|
10
|
+
|
|
11
|
+
# First ensure native extension is loaded
|
|
12
|
+
unless Parsanol::Native.available?
|
|
13
|
+
puts "ERROR: Native extension not available. Run 'rake compile' first."
|
|
14
|
+
exit 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class SimpleParser < Parsanol::Parser
|
|
18
|
+
rule(:comma) { str(",") >> str(" ").maybe }
|
|
19
|
+
rule(:word) { match(/[a-z]/).repeat(1) }
|
|
20
|
+
rule(:alnum) { match(/[a-z0-9]/).repeat(1) }
|
|
21
|
+
|
|
22
|
+
rule(:value) { (word | alnum).as(:v) }
|
|
23
|
+
rule(:list) { value >> (comma >> value).repeat }
|
|
24
|
+
|
|
25
|
+
root(:list)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
parser = SimpleParser.new
|
|
29
|
+
|
|
30
|
+
# Test input
|
|
31
|
+
test_input = "one, two, three, four, five, six, seven, eight, nine, ten"
|
|
32
|
+
large_input = (1..100).map { |i| "word" }.join(", ")
|
|
33
|
+
|
|
34
|
+
# ============================================================================
|
|
35
|
+
# Test 1: Without grammar caching (parse_parslet_compatible)
|
|
36
|
+
# ============================================================================
|
|
37
|
+
puts "\n" + "-" * 60
|
|
38
|
+
puts "Test 1: parse_parslet_compatible (NO caching)"
|
|
39
|
+
puts "-" * 60
|
|
40
|
+
|
|
41
|
+
Parsanol::Native.profile_reset
|
|
42
|
+
100.times { Parsanol::Native.parse_parslet_compatible(parser, test_input) }
|
|
43
|
+
profile_no_cache = Parsanol::Native.profile_stats
|
|
44
|
+
|
|
45
|
+
puts "\nTiming (microseconds):"
|
|
46
|
+
puts " Total: #{profile_no_cache["total_parse_us"]} us"
|
|
47
|
+
puts " Grammar JSON: #{profile_no_cache["grammar_parse_us"]} us"
|
|
48
|
+
puts " PEG match: #{profile_no_cache["peg_match_us"]} us"
|
|
49
|
+
|
|
50
|
+
# ============================================================================
|
|
51
|
+
# Test 2: With grammar caching (parse_with_grammar)
|
|
52
|
+
# ============================================================================
|
|
53
|
+
puts "\n" + "-" * 60
|
|
54
|
+
puts "Test 2: parse_with_grammar (cached grammar)"
|
|
55
|
+
puts "-" * 60
|
|
56
|
+
|
|
57
|
+
# Pre-serialize grammar ONCE
|
|
58
|
+
grammar_json = Parsanol::Native.send(:serialize_grammar, parser)
|
|
59
|
+
puts "Grammar JSON size: #{grammar_json.length} chars"
|
|
60
|
+
|
|
61
|
+
Parsanol::Native.profile_reset
|
|
62
|
+
100.times { Parsanol::Native.parse(grammar_json, test_input) }
|
|
63
|
+
profile_cached = Parsanol::Native.profile_stats
|
|
64
|
+
|
|
65
|
+
puts "\nTiming (microseconds):"
|
|
66
|
+
puts " Total: #{profile_cached["total_parse_us"]} us"
|
|
67
|
+
puts " Grammar JSON: #{profile_cached["grammar_parse_us"]} us"
|
|
68
|
+
puts " PEG match: #{profile_cached["peg_match_us"]} us"
|
|
69
|
+
|
|
70
|
+
# ============================================================================
|
|
71
|
+
# Comparison
|
|
72
|
+
# ============================================================================
|
|
73
|
+
puts "\n" + "=" * 60
|
|
74
|
+
puts "Comparison"
|
|
75
|
+
puts "=" * 60
|
|
76
|
+
|
|
77
|
+
total_no_cache = profile_no_cache["total_parse_us"].to_i
|
|
78
|
+
total_cached = profile_cached["total_parse_us"].to_i
|
|
79
|
+
|
|
80
|
+
if total_no_cache > 0 && total_cached > 0
|
|
81
|
+
improvement = ((total_no_cache - total_cached).to_f / total_no_cache * 100).round(1)
|
|
82
|
+
speedup = (total_no_cache.to_f / total_cached).round(2)
|
|
83
|
+
|
|
84
|
+
puts "\nTotal time (100 parses):"
|
|
85
|
+
puts " Without caching: #{total_no_cache} us"
|
|
86
|
+
puts " With caching: #{total_cached} us"
|
|
87
|
+
puts " Improvement: #{improvement}%"
|
|
88
|
+
puts " Speedup: #{speedup}x"
|
|
89
|
+
|
|
90
|
+
puts "\nGrammar JSON parsing:"
|
|
91
|
+
puts " Without caching: #{profile_no_cache["grammar_parse_us"]} us"
|
|
92
|
+
puts " With caching: #{profile_cached["grammar_parse_us"]} us"
|
|
93
|
+
|
|
94
|
+
puts "\nCache performance:"
|
|
95
|
+
puts " Hits: #{profile_cached["cache_hits"]}"
|
|
96
|
+
puts " Misses: #{profile_cached["cache_misses"]}"
|
|
97
|
+
puts " Hit rate: #{profile_cached["cache_hit_rate"]}%"
|
|
98
|
+
else
|
|
99
|
+
puts "\nWARNING: No timing data available"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# ============================================================================
|
|
103
|
+
# Large input test with caching
|
|
104
|
+
# ============================================================================
|
|
105
|
+
puts "\n" + "-" * 60
|
|
106
|
+
puts "Large Input Test (cached grammar)"
|
|
107
|
+
puts "-" * 60
|
|
108
|
+
|
|
109
|
+
Parsanol::Native.profile_reset
|
|
110
|
+
20.times { Parsanol::Native.parse(grammar_json, large_input) }
|
|
111
|
+
profile_large = Parsanol::Native.profile_stats
|
|
112
|
+
|
|
113
|
+
puts "\nTiming (#{large_input.length} chars, 20 parses):"
|
|
114
|
+
puts " Total: #{profile_large["total_parse_us"]} us"
|
|
115
|
+
puts " Grammar JSON: #{profile_large["grammar_parse_us"]} us"
|
|
116
|
+
puts " PEG match: #{profile_large["peg_match_us"]} us"
|
|
117
|
+
puts " Cache hit rate: #{profile_large["cache_hit_rate"]}%"
|
|
118
|
+
|
|
119
|
+
puts "\n" + "=" * 60
|
|
120
|
+
puts "Analysis complete"
|
|
121
|
+
puts "=" * 60
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require "parslet"
|
|
5
|
+
|
|
6
|
+
# Test the grammar caching performance using the native parser
|
|
7
|
+
# This benchmark compares parse times for repeated parsing with the same grammar
|
|
8
|
+
|
|
9
|
+
class SimpleParser < Parsanol::Parser
|
|
10
|
+
rule(:comma) { str(",") >> str(" ").maybe }
|
|
11
|
+
rule(:word) { match(/[a-z]/).repeat(1) }
|
|
12
|
+
rule(:alnum) { match(/[a-z0-9]/).repeat(1) }
|
|
13
|
+
|
|
14
|
+
rule(:value) { (word | alnum).as(:v) }
|
|
15
|
+
rule(:list) { value >> (comma >> value).repeat }
|
|
16
|
+
|
|
17
|
+
root(:list)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
puts "=" * 60
|
|
21
|
+
puts "Parsanol Grammar Caching Benchmark"
|
|
22
|
+
puts "=" * 60
|
|
23
|
+
|
|
24
|
+
parser = SimpleParser.new
|
|
25
|
+
|
|
26
|
+
# Test input - simple list of values
|
|
27
|
+
test_input = "one, two, three, four, five, six, seven, eight, nine, ten"
|
|
28
|
+
|
|
29
|
+
# Warm-up run
|
|
30
|
+
puts "\nWarming up..."
|
|
31
|
+
10.times { parser.parse(test_input) }
|
|
32
|
+
|
|
33
|
+
# Benchmark: First parse (no cache)
|
|
34
|
+
puts "\n--- First Parse (no cache) ---"
|
|
35
|
+
first_time = Benchmark.realtime do
|
|
36
|
+
parser.parse(test_input)
|
|
37
|
+
end
|
|
38
|
+
puts "First parse: #{(first_time * 1000).round(2)} ms"
|
|
39
|
+
|
|
40
|
+
# Benchmark: Cached parses
|
|
41
|
+
puts "\n--- Cached Parses (100 iterations) ---"
|
|
42
|
+
cached_times = []
|
|
43
|
+
100.times do
|
|
44
|
+
time = Benchmark.realtime do
|
|
45
|
+
parser.parse(test_input)
|
|
46
|
+
end
|
|
47
|
+
cached_times << time
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
avg_cached = cached_times.sum / cached_times.length
|
|
51
|
+
min_cached = cached_times.min
|
|
52
|
+
max_cached = cached_times.max
|
|
53
|
+
|
|
54
|
+
puts "Average cached parse: #{(avg_cached * 1000).round(2)} ms"
|
|
55
|
+
puts "Min cached parse: #{(min_cached * 1000).round(2)} ms"
|
|
56
|
+
puts "Max cached parse: #{(max_cached * 1000).round(2)} ms"
|
|
57
|
+
|
|
58
|
+
# Calculate improvement
|
|
59
|
+
improvement = ((first_time - avg_cached) / first_time * 100).round(1)
|
|
60
|
+
puts "\nFirst-to-cached improvement: #{improvement}% faster"
|
|
61
|
+
|
|
62
|
+
# Larger input test
|
|
63
|
+
puts "\n--- Larger Input Test ---"
|
|
64
|
+
# Use pure word input to avoid number/word parsing issues
|
|
65
|
+
large_input = (1..100).map { |i| "word" }.join(", ")
|
|
66
|
+
|
|
67
|
+
# Warm up
|
|
68
|
+
3.times { parser.parse(large_input) }
|
|
69
|
+
|
|
70
|
+
large_times = 20.times.map do
|
|
71
|
+
Benchmark.realtime { parser.parse(large_input) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
avg_large = large_times.sum / large_times.length
|
|
75
|
+
puts "Input size: #{large_input.length} chars"
|
|
76
|
+
puts "Average parse: #{(avg_large * 1000).round(2)} ms"
|
|
77
|
+
|
|
78
|
+
puts "\n" + "=" * 60
|
|
79
|
+
puts "Benchmark complete"
|
|
80
|
+
puts "=" * 60
|