Class: Sexp
Overview
:nodoc:
Direct Known Subclasses
Defined Under Namespace
Classes: All, Any, Atom, Child, Include, 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.
-
.m(*values) ⇒ Object
Matches any atom who’s string representation matches the patterns passed in.
-
.not?(arg) ⇒ Boolean
Matches when sub-expression does not match.
-
.s(*args) ⇒ Object
Matches an S-Expression.
-
.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.
-
#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
. -
#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.
239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/sexp.rb', line 239 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.
212 213 214 215 216 217 218 219 220 |
# File 'lib/sexp.rb', line 212 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.
458 459 460 |
# File 'lib/sexp.rb', line 458 def self._ Wild.new end |
.___ ⇒ Object
Matches all remaining input.
See Remaining for examples.
469 470 471 |
# File 'lib/sexp.rb', line 469 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.
509 510 511 |
# File 'lib/sexp.rb', line 509 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.
498 499 500 |
# File 'lib/sexp.rb', line 498 def self.any *args Any.new(*args) end |
.atom ⇒ Object
Matches any atom.
See Atom for examples.
487 488 489 |
# File 'lib/sexp.rb', line 487 def self.atom Atom.new end |
.child(child) ⇒ Object
Matches anything that has a child matching the sub-expression.
See Child for examples.
531 532 533 |
# File 'lib/sexp.rb', line 531 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.
478 479 480 |
# File 'lib/sexp.rb', line 478 def self.include child # TODO: rename, name is generic ruby Include.new(child) end |
.m(*values) ⇒ Object
Matches any atom who’s string representation matches the patterns passed in.
See Pattern for examples.
551 552 553 554 555 556 557 558 559 560 561 562 |
# File 'lib/sexp.rb', line 551 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
Matches when sub-expression does not match.
This is also available via Matcher#-@.
See Not for examples.
520 521 522 |
# File 'lib/sexp.rb', line 520 def self.not? arg Not.new arg 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 ?
401 402 403 404 |
# File 'lib/sexp.rb', line 401 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.=~
379 380 381 382 |
# File 'lib/sexp.rb', line 379 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.
77 78 79 80 81 |
# File 'lib/sexp.rb', line 77 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:
83 84 85 |
# File 'lib/sexp.rb', line 83 def compact # :nodoc: self.delete_if(&:nil?) end |
#deep_each(&block) ⇒ Object
97 98 99 100 101 102 103 104 |
# File 'lib/sexp.rb', line 97 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.
109 110 111 |
# File 'lib/sexp.rb', line 109 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
.
116 117 118 119 120 121 122 123 |
# File 'lib/sexp.rb', line 116 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.
128 129 130 131 132 133 134 135 136 |
# File 'lib/sexp.rb', line 128 def each_sexp return enum_for(:each_sexp) unless block_given? self.each do |sexp| next unless Sexp === sexp yield sexp end 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.
142 143 144 145 146 147 148 149 150 |
# File 'lib/sexp.rb', line 142 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:
183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/sexp.rb', line 183 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
.
201 202 203 |
# File 'lib/sexp.rb', line 201 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
.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/sexp.rb', line 155 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 |
#inspect ⇒ Object Also known as: to_s
:nodoc:
174 175 176 177 178 179 180 181 |
# File 'lib/sexp.rb', line 174 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.
225 226 227 |
# File 'lib/sexp.rb', line 225 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.
232 233 234 |
# File 'lib/sexp.rb', line 232 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:
256 257 258 259 260 261 262 263 |
# File 'lib/sexp.rb', line 256 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.
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
# File 'lib/sexp.rb', line 428 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:
251 252 253 254 |
# File 'lib/sexp.rb', line 251 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?
390 391 392 393 |
# File 'lib/sexp.rb', line 390 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.
409 410 411 412 413 414 415 416 417 418 419 420 421 |
# File 'lib/sexp.rb', line 409 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.
283 284 285 |
# File 'lib/sexp.rb', line 283 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.
290 291 292 |
# File 'lib/sexp.rb', line 290 def sexp_body= v self[1..-1] = v end |
#sexp_type ⇒ Object Also known as: head
Returns the node type of the Sexp.
268 269 270 |
# File 'lib/sexp.rb', line 268 def sexp_type first end |
#sexp_type=(v) ⇒ Object
Sets the node type of the Sexp.
275 276 277 |
# File 'lib/sexp.rb', line 275 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.
301 302 303 304 |
# File 'lib/sexp.rb', line 301 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))
310 311 312 313 314 315 316 317 |
# File 'lib/sexp.rb', line 310 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
.
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/sexp.rb', line 322 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 |