Handling of predicates is delegated to 3 different methods:
* on_predicate_direct: for predicates such as foo[bar] and foo[x < y].
* on_predicate_temporary: for predicates that use the last() function
somewhere.
* on_predicate_index: for predicates that only contain a literal index,
foo[10] being an example.
This enables the compiler to use more optimized code depending on the
type of predicate while still being able to support last() and
position().
The code is currently still quite rough on the edges, this will be taken
care of in following commits.
This was a hack that never should've made it into the code in the first
place. This breaks some of the CSS specs, but this should be taken care
of when I refactor predicate support.
I can't quite recall why this throw was present in the Evaluator in the
first place, but removing it doesn't seem to break anything (in the
Evaluator). In fact, removing it actually fixes some of the CSS specs
that were broken when using the Compiler.
This is a shortcut for "!foo". Using this method one doesn't have to
worry about how the "!" operator binds. For example, this:
!foo.or(bar)
would be parsed/evaluated as this:
!(foo.or(bar))
when instead we want it to be this:
(!foo).or(bar)
Using explicit parenthesis leads to ugly code, so now we can do this
instead:
foo.not.or(bar)
This is a direct port of the same handler used in the Evaluator class.
The code is a bit rough on the edges but this will be cleaned up in
upcoming commits.
Previously the on_path method itself would specify a block to use when a
node was matched. This makes it impossible to customize this behaviour
which is needed when a path is used in an operator or predicate. By
letting the #compile() method specify the block other handlers can
overwrite it.
This also comes with some changes to the specs as the old behaviour of
the Evaluator was incorrect. The Evaluator would bail after matching a
single node but instead it's meant to continue until it runs out of
parent nodes.
This currently supports index predicates (e.g. "foo[10]") and predicates
using paths (e.g. foo[bar/baz]). The usage of boolean operators and more
complex expressions has not yet been tested as these are not yet
supported in the first place.
This currently comes with exactly 0% test coverage. Once I've
implemented all required handler methods I'll be updating the current
evaluator tests to use the compiler instead. This removes the need for
writing an entirely new set of tests.
Currently the compiler is only capable of compiling basic expressions
such as "foo", "foo/bar" and "foo[@x="y"]/bar".
This change is broken up in to two parts:
1. Using a Hash to track if a node is already in a NodeSet
2. Only calling take_ownership when an owner is set
== Using a Hash
Previously various methods such as NodeSet#push and NodeSet#unshift
would call Array#include? (on the internal "nodes" Array) to see if a
node is already present in the set. This is quite problematic
performance wise, especially for large NodeSets. In fact, for the
attached benchmark the vast majority of the time was spent in
Array#include? calls.
Because a NodeSet demands ordering of nodes and must be able to access
them by index (something Set can't do without relying on Enumerable), a
Hash is used to separately keep track of what nodes are in a NodeSet.
This means that checking the presence of a node is simply a matter of
checking a Hash key's presence.
== Calling take_ownership
The if-check for the "owner" variable has been moved out of the
"take_ownership" method and into the methods that call "take_ownership".
This ensures the method isn't called in the first place if no owner is
present, at the cost of slightly more code repetition. The same applies
to the "remove_ownership" method.
== Conclusion
The combined result is a speedup of about 50x when running the attached
concurrent_time_bench.rb benchmark.