Class: Sexp
Overview
:nodoc:
Direct Known Subclasses
Defined Under Namespace
Classes: All, Any, Atom, Child, Include, Klass, MatchCollection, Matcher, Not, Pattern, Remaining, Sibling, Type, Wild
Constant Summary collapse
- UNASSIGNED =
Object.new
- @@array_types =
TODO: remove
[ :array, :args ]
Instance Attribute Summary collapse
-
#comments ⇒ Object
Optional comments above/aside this sexp.
-
#file ⇒ Object
Accessors for the file.
-
#line(n = UNASSIGNED) ⇒ Object
If passed a line number, sets the line and returns self.
Class Method Summary collapse
-
._ ⇒ Object
Matches any single item.
-
.___ ⇒ Object
Matches all remaining input.
-
.all(*args) ⇒ Object
Matches only when all sub-expressions match.
-
.any(*args) ⇒ Object
Matches when any of the sub-expressions match.
-
.atom ⇒ Object
Matches any atom.
-
.child(child) ⇒ Object
Matches anything that has a child matching the sub-expression.
-
.from_array(a) ⇒ Object
Creates a new Sexp from Array
a
. -
.include(child) ⇒ Object
Matches an expression or any expression that includes the child.
-
.k(klass) ⇒ Object
Matches an atom of the specified
klass
(or module). -
.m(*values) ⇒ Object
Matches any atom who’s string representation matches the patterns passed in.
-
.not?(arg) ⇒ Boolean
(also: -)
Matches when sub-expression does not match.
-
.q(*args) ⇒ Object
Matches an S-Expression.
- .s(*args) ⇒ Object
-
.t(name) ⇒ Object
Matches anything having the same sexp_type, which is the first value in a Sexp.
Instance Method Summary collapse
-
#/(pattern) ⇒ Object
Verifies that
pattern
is a Matcher and then dispatches to its #/ method. -
#==(obj) ⇒ Object
:nodoc:.
-
#=~(pattern) ⇒ Object
Verifies that
pattern
is a Matcher and then dispatches to its #=~ method. -
#array_type? ⇒ Boolean
Returns true if the node_type is
array
orargs
. -
#compact ⇒ Object
:nodoc:.
-
#deep_each(&block) ⇒ Object
Recursively enumerates the sexp yielding to
block
for every element. -
#depth ⇒ Object
Return the maximum depth of the sexp.
-
#each_of_type(t, &b) ⇒ Object
Enumeratates the sexp yielding to
b
when the node_type ==t
. -
#each_sexp ⇒ Object
Recursively enumerates all sub-sexps skipping non-Sexp elements.
- #eql?(o) ⇒ Boolean
-
#find_and_replace_all(from, to) ⇒ Object
Replaces all elements whose node_type is
from
withto
. -
#find_node(name, delete = false) ⇒ Object
:nodoc:.
-
#find_nodes(name) ⇒ Object
Find every node with type
name
. -
#gsub(pattern, repl) ⇒ Object
Replaces all Sexps matching
pattern
with Sexprepl
. - #hash ⇒ Object
-
#initialize(*args) ⇒ Sexp
constructor
Create a new Sexp containing
args
. -
#inspect ⇒ Object
(also: #to_s)
:nodoc:.
-
#line_max ⇒ Object
Returns the maximum line number of the children of self.
-
#map(&blk) ⇒ Object
:nodoc:.
-
#mass ⇒ Object
Returns the size of the sexp, flattened.
-
#method_missing(meth, delete = false) ⇒ Object
Returns the node named
node
, deleting it ifdelete
is true. -
#new(*body) ⇒ Object
Creates a new sexp with the new contents of
body
, but with the samefile
,line
, andcomment
as self. -
#pretty_print(q) ⇒ Object
:nodoc:.
-
#replace_sexp(pattern, &block) ⇒ Object
Recursively searches for the
pattern
yielding each match, and replacing it with the result of the block. -
#respond_to?(msg, private = false) ⇒ Boolean
:nodoc:.
-
#satisfy?(pattern) ⇒ Boolean
Verifies that
pattern
is a Matcher and then dispatches to its #satisfy? method. -
#search_each(pattern, &block) ⇒ Object
Recursively searches for the
pattern
yielding the matches. -
#sexp_body(from = 1) ⇒ Object
(also: #rest)
Returns the Sexp body (starting at
from
, defaulting to 1), ie the values without the node type. -
#sexp_body=(v) ⇒ Object
Returns the Sexp body, ie the values without the node type.
-
#sexp_type ⇒ Object
(also: #head)
Returns the node type of the Sexp.
-
#sexp_type=(v) ⇒ Object
Sets the node type of the Sexp.
-
#shift ⇒ Object
If run with debug, Sexp will raise if you shift on an empty Sexp.
-
#structure ⇒ Object
Returns the bare bones structure of the sexp.
-
#sub(pattern, repl) ⇒ Object
Replaces the Sexp matching
pattern
withrepl
. -
#to_a ⇒ Object
:nodoc:.
Constructor Details
#initialize(*args) ⇒ Sexp
Create a new Sexp containing args
.
30 31 32 |
# File 'lib/sexp.rb', line 30 def initialize *args super(args) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, delete = false) ⇒ Object
Returns the node named node
, deleting it if delete
is true.
247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/sexp.rb', line 247 def method_missing meth, delete = false r = find_node meth, delete if ENV["DEBUG"] then if r.nil? then warn "%p.method_missing(%p) => nil from %s" % [self, meth, caller.first] elsif ENV["VERBOSE"] warn "%p.method_missing(%p) from %s" % [self, meth, caller.first] end end r end |
Instance Attribute Details
#comments ⇒ Object
Optional comments above/aside this sexp. Usually set by ruby_parser.
23 24 25 |
# File 'lib/sexp.rb', line 23 def comments @comments end |
#file ⇒ Object
Accessors for the file. Usually set by ruby_parser.
18 19 20 |
# File 'lib/sexp.rb', line 18 def file @file end |
#line(n = UNASSIGNED) ⇒ Object
If passed a line number, sets the line and returns self. Otherwise returns the line number. This allows you to do message cascades and still get the sexp back.
220 221 222 223 224 225 226 227 228 |
# File 'lib/sexp.rb', line 220 def line n = UNASSIGNED if n != UNASSIGNED then raise ArgumentError, "setting %p.line %p" % [self, n] unless Integer === n @line = n self else @line ||= nil end end |
Class Method Details
._ ⇒ Object
Matches any single item.
See Wild for examples.
93 94 95 |
# File 'lib/sexp_matcher.rb', line 93 def self._ Wild.new end |
.___ ⇒ Object
Matches all remaining input.
See Remaining for examples.
104 105 106 |
# File 'lib/sexp_matcher.rb', line 104 def self.___ Remaining.new end |
.all(*args) ⇒ Object
Matches only when all sub-expressions match.
This is also available via Matcher#&.
See All for examples.
144 145 146 |
# File 'lib/sexp_matcher.rb', line 144 def self.all *args All.new(*args) end |
.any(*args) ⇒ Object
Matches when any of the sub-expressions match.
This is also available via Matcher#|.
See Any for examples.
133 134 135 |
# File 'lib/sexp_matcher.rb', line 133 def self.any *args Any.new(*args) end |
.atom ⇒ Object
Matches any atom.
See Atom for examples.
122 123 124 |
# File 'lib/sexp_matcher.rb', line 122 def self.atom Atom.new end |
.child(child) ⇒ Object
Matches anything that has a child matching the sub-expression.
See Child for examples.
170 171 172 |
# File 'lib/sexp_matcher.rb', line 170 def self.child child Child.new child end |
.from_array(a) ⇒ Object
Creates a new Sexp from Array a
.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/sexp.rb', line 37 def self.from_array a ary = Array === a ? a : [a] self.new.concat(ary.map { |x| case x when Sexp x when Array self.from_array(x) else x end }) end |
.include(child) ⇒ Object
Matches an expression or any expression that includes the child.
See Include for examples.
113 114 115 |
# File 'lib/sexp_matcher.rb', line 113 def self.include child # TODO: rename, name is generic ruby Include.new(child) end |
.k(klass) ⇒ Object
Matches an atom of the specified klass
(or module).
See Pattern for examples.
208 209 210 |
# File 'lib/sexp_matcher.rb', line 208 def self.k klass Klass.new klass end |
.m(*values) ⇒ Object
Matches any atom who’s string representation matches the patterns passed in.
See Pattern for examples.
190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/sexp_matcher.rb', line 190 def self.m *values res = values.map { |value| case value when Regexp then value else re = Regexp.escape value.to_s Regexp.new "\\A%s\\Z" % re end } Pattern.new Regexp.union(*res) end |
.not?(arg) ⇒ Boolean Also known as: -
Matches when sub-expression does not match.
This is also available via Matcher#-@.
See Not for examples.
155 156 157 |
# File 'lib/sexp_matcher.rb', line 155 def self.not? arg Not.new arg end |
.q(*args) ⇒ Object
Matches an S-Expression.
See Matcher for examples.
78 79 80 |
# File 'lib/sexp_matcher.rb', line 78 def self.q *args Matcher.new(*args) end |
Instance Method Details
#/(pattern) ⇒ Object
Verifies that pattern
is a Matcher and then dispatches to its #/ method.
TODO: rename grep? match_all ? find_all ?
30 31 32 33 |
# File 'lib/sexp_matcher.rb', line 30 def / pattern raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern pattern / self end |
#==(obj) ⇒ Object
:nodoc:
68 69 70 |
# File 'lib/sexp.rb', line 68 def == obj # :nodoc: obj.class == self.class and super # only because of a bug in ruby end |
#=~(pattern) ⇒ Object
Verifies that pattern
is a Matcher and then dispatches to its #=~ method.
See Matcher.=~
8 9 10 11 |
# File 'lib/sexp_matcher.rb', line 8 def =~ pattern raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern pattern =~ self end |
#array_type? ⇒ Boolean
Returns true if the node_type is array
or args
.
REFACTOR: to TypedSexp - we only care when we have units.
85 86 87 88 89 |
# File 'lib/sexp.rb', line 85 def array_type? warn "DEPRECATED: please file an issue if you actually use this. from #{caller.first}" type = self.sexp_type @@array_types.include? type end |
#compact ⇒ Object
:nodoc:
91 92 93 |
# File 'lib/sexp.rb', line 91 def compact # :nodoc: self.delete_if(&:nil?) end |
#deep_each(&block) ⇒ Object
105 106 107 108 109 110 111 112 |
# File 'lib/sexp.rb', line 105 def deep_each &block return enum_for(:deep_each) unless block_given? self.each_sexp do |sexp| next if block[sexp] == :skip sexp.deep_each(&block) end end |
#depth ⇒ Object
Return the maximum depth of the sexp. One-based.
117 118 119 |
# File 'lib/sexp.rb', line 117 def depth 1 + (each_sexp.map(&:depth).max || 0) end |
#each_of_type(t, &b) ⇒ Object
Enumeratates the sexp yielding to b
when the node_type == t
.
124 125 126 127 128 129 130 131 |
# File 'lib/sexp.rb', line 124 def each_of_type t, &b return enum_for(:each_of_type) unless block_given? each_sexp do | sexp | sexp.each_of_type(t, &b) yield sexp if sexp.sexp_type == t end end |
#each_sexp ⇒ Object
Recursively enumerates all sub-sexps skipping non-Sexp elements.
136 137 138 139 140 141 142 143 144 |
# File 'lib/sexp.rb', line 136 def each_sexp return enum_for(:each_sexp) unless block_given? self.each do |sexp| next unless Sexp === sexp yield sexp end end |
#eql?(o) ⇒ Boolean
72 73 74 |
# File 'lib/sexp.rb', line 72 def eql? o self.class == o.class && super end |
#find_and_replace_all(from, to) ⇒ Object
Replaces all elements whose node_type is from
with to
. Used only for the most trivial of rewrites.
150 151 152 153 154 155 156 157 158 |
# File 'lib/sexp.rb', line 150 def find_and_replace_all from, to each_with_index do | elem, index | if Sexp === elem then elem.find_and_replace_all(from, to) elsif elem == from self[index] = to end end end |
#find_node(name, delete = false) ⇒ Object
:nodoc:
191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/sexp.rb', line 191 def find_node name, delete = false # :nodoc: matches = find_nodes name case matches.size when 0 then nil when 1 then match = matches.first delete match if delete match else raise NoMethodError, "multiple nodes for #{name} were found in #{inspect}" end end |
#find_nodes(name) ⇒ Object
Find every node with type name
.
209 210 211 |
# File 'lib/sexp.rb', line 209 def find_nodes name each_sexp.find_all { |sexp| sexp.sexp_type == name } end |
#gsub(pattern, repl) ⇒ Object
Replaces all Sexps matching pattern
with Sexp repl
.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/sexp.rb', line 163 def gsub pattern, repl return repl if pattern == self new = self.map { |subset| case subset when Sexp then if Matcher === pattern && pattern.satisfy?(subset) then # TODO: make === be satisfy? maybe? repl.dup rescue repl else subset.gsub pattern, repl end else subset end } Sexp.from_array new end |
#hash ⇒ Object
76 77 78 |
# File 'lib/sexp.rb', line 76 def hash [self.class, *self].hash end |
#inspect ⇒ Object Also known as: to_s
:nodoc:
182 183 184 185 186 187 188 189 |
# File 'lib/sexp.rb', line 182 def inspect # :nodoc: sexp_str = self.map(&:inspect).join ", " if ENV["VERBOSE"] && line then "s(#{sexp_str}).line(#{line})" else "s(#{sexp_str})" end end |
#line_max ⇒ Object
Returns the maximum line number of the children of self.
233 234 235 |
# File 'lib/sexp.rb', line 233 def line_max @line_max ||= self.deep_each.map(&:line).max end |
#map(&blk) ⇒ Object
:nodoc:
64 65 66 |
# File 'lib/sexp.rb', line 64 def map &blk # :nodoc: self.new.concat(super(&blk)) # ensures a sexp from map end |
#mass ⇒ Object
Returns the size of the sexp, flattened.
240 241 242 |
# File 'lib/sexp.rb', line 240 def mass @mass ||= inject(1) { |t, s| Sexp === s ? t + s.mass : t } end |
#new(*body) ⇒ Object
Creates a new sexp with the new contents of body
, but with the same file
, line
, and comment
as self.
56 57 58 59 60 61 62 |
# File 'lib/sexp.rb', line 56 def new(*body) r = self.class.new.concat(body) # ensures a sexp from map r.file = self.file if self.file r.line = self.line if self.line r.comments = self.comments if self.comments r end |
#pretty_print(q) ⇒ Object
:nodoc:
264 265 266 267 268 269 270 271 |
# File 'lib/sexp.rb', line 264 def pretty_print q # :nodoc: nnd = ")" nnd << ".line(#{line})" if line && ENV["VERBOSE"] q.group(1, "s(", nnd) do q.seplist(self) {|v| q.pp v } end end |
#replace_sexp(pattern, &block) ⇒ Object
Recursively searches for the pattern
yielding each match, and replacing it with the result of the block.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/sexp_matcher.rb', line 57 def replace_sexp pattern, &block # TODO: rename to gsub? raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher return yield self if pattern.satisfy? self # TODO: Needs #new_from(*new_body) to copy file/line/comment self.class.new(*self.map { |subset| case subset when Sexp then subset.replace_sexp pattern, &block else subset end }) end |
#respond_to?(msg, private = false) ⇒ Boolean
:nodoc:
259 260 261 262 |
# File 'lib/sexp.rb', line 259 def respond_to? msg, private = false # :nodoc: # why do I need this? Because ruby 2.0 is broken. That's why. super end |
#satisfy?(pattern) ⇒ Boolean
Verifies that pattern
is a Matcher and then dispatches to its #satisfy? method.
TODO: rename match?
19 20 21 22 |
# File 'lib/sexp_matcher.rb', line 19 def satisfy? pattern raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern pattern.satisfy? self end |
#search_each(pattern, &block) ⇒ Object
Recursively searches for the pattern
yielding the matches.
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/sexp_matcher.rb', line 38 def search_each pattern, &block # TODO: rename to grep? raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher return enum_for(:search_each, pattern) unless block_given? if pattern.satisfy? self then yield self end self.each_sexp do |subset| subset.search_each pattern, &block end end |
#sexp_body(from = 1) ⇒ Object Also known as: rest
Returns the Sexp body (starting at from
, defaulting to 1), ie the values without the node type.
291 292 293 |
# File 'lib/sexp.rb', line 291 def sexp_body from = 1 self.new.concat(self[from..-1] || []) end |
#sexp_body=(v) ⇒ Object
Returns the Sexp body, ie the values without the node type.
298 299 300 |
# File 'lib/sexp.rb', line 298 def sexp_body= v self[1..-1] = v end |
#sexp_type ⇒ Object Also known as: head
Returns the node type of the Sexp.
276 277 278 |
# File 'lib/sexp.rb', line 276 def sexp_type first end |
#sexp_type=(v) ⇒ Object
Sets the node type of the Sexp.
283 284 285 |
# File 'lib/sexp.rb', line 283 def sexp_type= v self[0] = v end |
#shift ⇒ Object
If run with debug, Sexp will raise if you shift on an empty Sexp. Helps with debugging.
309 310 311 312 |
# File 'lib/sexp.rb', line 309 def shift raise "I'm empty" if self.empty? super end |
#structure ⇒ Object
Returns the bare bones structure of the sexp. s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))
318 319 320 321 322 323 324 325 |
# File 'lib/sexp.rb', line 318 def structure if Array === self.sexp_type then warn "NOTE: form s(s(:subsexp)).structure is deprecated. Removing in 5.0" s(:bogus, *self).structure # TODO: remove >= 4.2.0 else s(self.sexp_type, *each_sexp.map(&:structure)) end end |
#sub(pattern, repl) ⇒ Object
Replaces the Sexp matching pattern
with repl
.
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/sexp.rb', line 330 def sub pattern, repl return repl.dup if pattern == self return repl.dup if Matcher === pattern && pattern.satisfy?(self) done = false new = self.map do |subset| if done then subset else case subset when Sexp then if pattern == subset then done = true repl.dup rescue repl elsif Matcher === pattern && pattern.satisfy?(subset) then done = true repl.dup rescue repl else subset.sub pattern, repl end else subset end end end Sexp.from_array new end |