From 2c4e490614528dc873f8275fe10c34ae489cfee5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 23 Mar 2015 00:22:59 +0100 Subject: [PATCH] Added CSS/XPath Parser.parse_with_cache This method parses and caches ASTs using Oga::LRU. Currently the default of 1024 keys is used. See #71 for more information. --- benchmark/css/parser/parser_bench.rb | 8 +++++++- benchmark/xpath/parser/parser_bench.rb | 8 +++++++- lib/oga/css/parser.rll | 13 +++++++++++++ lib/oga/xpath/parser.rll | 13 +++++++++++++ spec/oga/css/parser/cache_spec.rb | 27 ++++++++++++++++++++++++++ spec/oga/xpath/parser/cache_spec.rb | 27 ++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 spec/oga/css/parser/cache_spec.rb create mode 100644 spec/oga/xpath/parser/cache_spec.rb diff --git a/benchmark/css/parser/parser_bench.rb b/benchmark/css/parser/parser_bench.rb index a3e167f..18aa9f0 100644 --- a/benchmark/css/parser/parser_bench.rb +++ b/benchmark/css/parser/parser_bench.rb @@ -3,7 +3,13 @@ require_relative '../../benchmark_helper' css = 'foo bar bar.some_class element#with_id[title="Foo"]' Benchmark.ips do |bench| - bench.report 'CSS' do + bench.report 'without cache' do Oga::CSS::Parser.new(css).parse end + + bench.report 'with cache' do + Oga::CSS::Parser.parse_with_cache(css) + end + + bench.compare! end diff --git a/benchmark/xpath/parser/parser_bench.rb b/benchmark/xpath/parser/parser_bench.rb index 8fa3090..d3cead7 100644 --- a/benchmark/xpath/parser/parser_bench.rb +++ b/benchmark/xpath/parser/parser_bench.rb @@ -3,7 +3,13 @@ require_relative '../../benchmark_helper' xpath = '/wikimedia/projects/project[@name="Wikipedia"]/editions/edition/text()' Benchmark.ips do |bench| - bench.report 'Wikipedia example' do + bench.report 'without cache' do Oga::XPath::Parser.new(xpath).parse end + + bench.report 'with cache' do + Oga::XPath::Parser.parse_with_cache(xpath) + end + + bench.compare! end diff --git a/lib/oga/css/parser.rll b/lib/oga/css/parser.rll index ec03323..1b22ae4 100644 --- a/lib/oga/css/parser.rll +++ b/lib/oga/css/parser.rll @@ -310,6 +310,19 @@ even %inner { + ## + # @return [Oga::LRU] + # + CACHE = LRU.new + + ## + # @param [String] data + # @return [AST::Node] + # + def self.parse_with_cache(data) + return CACHE.get_or_set(data) { new(data).parse } + end + ## # @param [String] data The input to parse. # diff --git a/lib/oga/xpath/parser.rll b/lib/oga/xpath/parser.rll index ef6afc9..37d73c5 100644 --- a/lib/oga/xpath/parser.rll +++ b/lib/oga/xpath/parser.rll @@ -223,6 +223,19 @@ variable %inner { + ## + # @return [Oga::LRU] + # + CACHE = LRU.new + + ## + # @param [String] data + # @return [AST::Node] + # + def self.parse_with_cache(data) + return CACHE.get_or_set(data) { new(data).parse } + end + ## # @param [String] data The input to parse. # diff --git a/spec/oga/css/parser/cache_spec.rb b/spec/oga/css/parser/cache_spec.rb new file mode 100644 index 0000000..4ddbad2 --- /dev/null +++ b/spec/oga/css/parser/cache_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Oga::CSS::Parser do + describe 'parse_with_cache' do + after do + described_class::CACHE.clear + end + + it 'parses an expression' do + described_class.parse_with_cache('foo') + .should == s(:axis, 'descendant', s(:test, nil, 'foo')) + end + + it 'caches an expression after parsing it' do + described_class.any_instance + .should_receive(:parse) + .once + .and_call_original + + described_class.parse_with_cache('foo') + .should == s(:axis, 'descendant', s(:test, nil, 'foo')) + + described_class.parse_with_cache('foo') + .should == s(:axis, 'descendant', s(:test, nil, 'foo')) + end + end +end diff --git a/spec/oga/xpath/parser/cache_spec.rb b/spec/oga/xpath/parser/cache_spec.rb new file mode 100644 index 0000000..c1eecb6 --- /dev/null +++ b/spec/oga/xpath/parser/cache_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Oga::XPath::Parser do + describe 'parse_with_cache' do + after do + described_class::CACHE.clear + end + + it 'parses an expression' do + described_class.parse_with_cache('foo') + .should == s(:axis, 'child', s(:test, nil, 'foo')) + end + + it 'caches an expression after parsing it' do + described_class.any_instance + .should_receive(:parse) + .once + .and_call_original + + described_class.parse_with_cache('foo') + .should == s(:axis, 'child', s(:test, nil, 'foo')) + + described_class.parse_with_cache('foo') + .should == s(:axis, 'child', s(:test, nil, 'foo')) + end + end +end