1 # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2 # Redistribution and use in source and binary forms, with or without
3 # modification, are permitted provided that the following conditions are met:
5 # 1. Redistributions of source code must retain the above copyright notice,
6 # this list of conditions and the following disclaimer.
7 # 2. Redistributions in binary form must reproduce the above copyright notice,
8 # this list of conditions and the following disclaimer in the documentation
9 # and/or other materials provided with the distribution.
11 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21 # POSSIBILITY OF SUCH DAMAGE.
26 # The MemoizingCache class memoizes the outcomes of parse operations. The
27 # functionality is implemented as a separate class so as to minimize the
28 # amount of "contamination" of other classes by memoizing code, and to allow
29 # memoizing to be cleanly turned on or off at will. If a MemoizingCache is
30 # passed to a Parslet, ParsletCombination or Predicate as a value for the
31 # :memoizer key in the options hash passed to a parse method, the class
32 # implementing that method will call the parse method on the cache rather
33 # than proceeding normally. The cache will either propagate the previously
34 # memoized result, or will defer back to the original class to obtain the
35 # result. A circular dependency is avoided by setting the :skip_memoizer flag
36 # in the options dictionary. If no MemoizingCache is passed then normal
37 # program flow takes place.
39 # Singleton class that serves as a default value for unset keys in a Hash.
46 # The results of parse operations are stored (memoized) in a cache, keyed
47 # on a unique identifier comprising the Parslet, ParsletCombination or
48 # Predicate used in the parse operation, the location of the operation
49 # (the line_start and column_start), and the skipping override (if any).
52 # - ParseErrors raised during parsing
53 # - SkippedSubstringExceptions raised during parsing
54 # - :ZeroWidthParseSuccess symbols thrown during parsing
55 # - :AndPredicateSuccess symbols thrown during parsing
56 # - :NotPredicateSuccess symbols thrown during parsing
57 # - String instances returned as parse results
58 # - MatchDataWrapper instance returned as parse results
59 # - Array instances containing ordered collections of parse results
60 # - Node subclass instances containing AST productions
61 @cache = Hash.new NoValueForKey.instance
64 # The receiver checks whether there is already a stored result
65 # corresponding to that a unique identifier that specifies the
66 # "coordinates" of a parsing operation (location, parseable, skipping
67 # override). If found propogates the result directly to the caller rather
68 # than performing the parse method all over again. Here "propagation" means
69 # re-raising parse errors, re-throwing symbols, and returning object
70 # references. If not found, performs the parsing operation and stores the
71 # result in the cache before propagating it.
72 def parse string, options = {}
73 raise ArgumentError if string.nil?
75 # construct a unique identifier
76 identifier = [options[:parseable], options[:line_start], options[:column_start]]
77 identifier << options[:origin] if options.has_key? :origin
78 identifier << options[:skipping_override] if options.has_key? :skipping_override
80 if (result = @cache[identifier]) != NoValueForKey.instance
81 if result.kind_of? Symbol
83 elsif result.kind_of? Exception
89 # first time for this parseable/location/skipping_override (etc)
90 # combination; capture result and propagate
91 catch :NotPredicateSuccess do
92 catch :AndPredicateSuccess do
93 catch :ZeroWidthParseSuccess do
95 options[:ignore_memoizer] = true
97 # short-circuit left recursion here rather than infinite
99 if options[:parseable].kind_of? SymbolParslet
100 check_left_recursion(options[:parseable], options)
101 @last_seen_symbol_parslet = options[:parseable]
102 @last_seen_symbol_parslet_location = [options[:line_start], options[:column_start]]
105 return @cache[identifier] = options[:parseable].memoizing_parse(string, options) # store and return
106 rescue Exception => e
107 raise @cache[identifier] = e # store and re-raise
110 throw @cache[identifier] = :ZeroWidthParseSuccess # store and re-throw
112 throw @cache[identifier] = :AndPredicateSuccess # store and re-throw
114 throw @cache[identifier] = :NotPredicateSuccess # store and re-throw
118 def check_left_recursion parseable, options = {}
119 if parseable.kind_of? SymbolParslet and
120 @last_seen_symbol_parslet == parseable and
121 @last_seen_symbol_parslet_location == [options[:line_start], options[:column_start]]
122 raise LeftRecursionException
125 end # class MemoizingCache