Module: Parslet::Atoms::CanFlatten
- Included in:
- Base
- Defined in:
- lib/parslet/atoms/can_flatten.rb
Overview
A series of helper functions that have the common topic of flattening result values into the intermediary tree that consists of Ruby Hashes and Arrays.
This module has one main function, #flatten, that takes an annotated structure as input and returns the reduced form that users expect from Atom#parse.
NOTE: Since all of these functions are just that, functions without side effects, they are in a module and not in a class. Its hard to draw the line sometimes, but this is beyond.
Instance Method Summary collapse
-
#flatten(value, named = false) ⇒ Object
Takes a mixed value coming out of a parslet and converts it to a return value for the user by dropping things and merging hashes.
-
#flatten_repetition(list, named) ⇒ Object
private
Flatten results from a repetition of a single parslet.
-
#flatten_sequence(list) ⇒ Object
private
Flatten results from a sequence of parslets.
-
#foldl(list, &block) ⇒ Object
Lisp style fold left where the first element builds the basis for an inject.
- #merge_fold(l, r) ⇒ Object private
-
#warn_about_duplicate_keys(h1, h2) ⇒ Object
That annoying warning ‘Duplicate subtrees while merging result’ comes from here.
Instance Method Details
#flatten(value, named = false) ⇒ Object
Takes a mixed value coming out of a parslet and converts it to a return value for the user by dropping things and merging hashes.
Named is set to true if this result will be embedded in a Hash result from naming something using .as(...)
. It changes the folding semantics of repetition.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/parslet/atoms/can_flatten.rb', line 23 def flatten(value, named=false) # Passes through everything that isn't an array of things return value unless value.instance_of? Array # Extracts the s-expression tag tag, *tail = value # Merges arrays: result = tail. map { |e| flatten(e) } # first flatten each element case tag when :sequence return flatten_sequence(result) when :maybe return named ? result.first : result.first || '' when :repetition return flatten_repetition(result, named) end fail "BUG: Unknown tag #{tag.inspect}." end |
#flatten_repetition(list, named) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Flatten results from a repetition of a single parslet. named indicates whether the user has named the result or not. If the user has named the results, we want to leave an empty list alone - otherwise it is turned into an empty string.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/parslet/atoms/can_flatten.rb', line 104 def flatten_repetition(list, named) if list.any? { |e| e.instance_of?(Hash) } # If keyed subtrees are in the array, we'll want to discard all # strings inbetween. To keep them, name them. return list.select { |e| e.instance_of?(Hash) } end if list.any? { |e| e.instance_of?(Array) } # If any arrays are nested in this array, flatten all arrays to this # level. return list. select { |e| e.instance_of?(Array) }. flatten(1) end # Consistent handling of empty lists, when we act on a named result return [] if named && list.empty? # If there are only strings, concatenate them and return that. foldl(list) { |s,e| s+e } end |
#flatten_sequence(list) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Flatten results from a sequence of parslets.
58 59 60 61 62 |
# File 'lib/parslet/atoms/can_flatten.rb', line 58 def flatten_sequence(list) foldl(list.compact) { |r, e| # and then merge flat elements merge_fold(r, e) } end |
#foldl(list, &block) ⇒ Object
Lisp style fold left where the first element builds the basis for an inject.
49 50 51 52 |
# File 'lib/parslet/atoms/can_flatten.rb', line 49 def foldl(list, &block) return '' if list.empty? list[1..-1].inject(list.first, &block) end |
#merge_fold(l, r) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/parslet/atoms/can_flatten.rb', line 64 def merge_fold(l, r) # equal pairs: merge. ---------------------------------------------------- if l.class == r.class if l.is_a?(Hash) warn_about_duplicate_keys(l, r) return l.merge(r) else return l + r end end # unequal pairs: hoist to same level. ------------------------------------ # Maybe classes are not equal, but both are stringlike? if l.respond_to?(:to_str) && r.respond_to?(:to_str) # if we're merging a String with a Slice, the slice wins. return r if r.respond_to? :to_slice return l if l.respond_to? :to_slice fail "NOTREACHED: What other stringlike classes are there?" end # special case: If one of them is a string/slice, the other is more important return l if r.respond_to? :to_str return r if l.respond_to? :to_str # otherwise just create an array for one of them to live in return l + [r] if r.class == Hash return [l] + r if l.class == Hash fail "Unhandled case when foldr'ing sequence." end |
#warn_about_duplicate_keys(h1, h2) ⇒ Object
That annoying warning ‘Duplicate subtrees while merging result’ comes from here. You should add more ‘.as(…)’ names to your intermediary tree.
129 130 131 132 133 134 135 |
# File 'lib/parslet/atoms/can_flatten.rb', line 129 def warn_about_duplicate_keys(h1, h2) d = h1.keys & h2.keys unless d.empty? warn "Duplicate subtrees while merging result of \n #{self.inspect}\nonly the values"+ " of the latter will be kept. (keys: #{d.inspect})" end end |