Class: Sexp
- Defined in:
- lib/sexp.rb,
lib/strict_sexp.rb,
lib/sexp_matcher.rb
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.
-
#line_max ⇒ Object
Returns the maximum line number of the children of 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 sub-Sexp. -
#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
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:.
-
#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
Sets the Sexp body to new content.
-
#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:.
-
#value ⇒ Object
Return the value (last item) of a single element sexp (eg ‘s(:lit, 42)`).
Constructor Details
#initialize(*args) ⇒ Sexp
Create a new Sexp containing args
.
35 36 37 |
# File 'lib/sexp.rb', line 35 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.
255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/sexp.rb', line 255 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.
28 29 30 |
# File 'lib/sexp.rb', line 28 def comments @comments end |
#file ⇒ Object
Accessors for the file. Usually set by ruby_parser.
23 24 25 |
# File 'lib/sexp.rb', line 23 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.
228 229 230 231 232 233 234 235 236 |
# File 'lib/sexp.rb', line 228 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 |
#line_max ⇒ Object
Returns the maximum line number of the children of self.
241 242 243 |
# File 'lib/sexp.rb', line 241 def line_max @line_max ||= self.deep_each.map(&:line).compact.max 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
.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/sexp.rb', line 44 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:
76 77 78 |
# File 'lib/sexp.rb', line 76 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.
93 94 95 96 97 |
# File 'lib/sexp.rb', line 93 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:
99 100 101 |
# File 'lib/sexp.rb', line 99 def compact # :nodoc: self.delete_if(&:nil?) end |
#deep_each(&block) ⇒ Object
113 114 115 116 117 118 119 120 |
# File 'lib/sexp.rb', line 113 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.
125 126 127 |
# File 'lib/sexp.rb', line 125 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
.
132 133 134 135 136 137 138 139 |
# File 'lib/sexp.rb', line 132 def each_of_type t, &b return enum_for(:each_of_type, t) 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
Enumerates all sub-sexps skipping non-Sexp elements.
144 145 146 147 148 149 150 151 152 |
# File 'lib/sexp.rb', line 144 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
80 81 82 |
# File 'lib/sexp.rb', line 80 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.
158 159 160 161 162 163 164 165 166 |
# File 'lib/sexp.rb', line 158 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:
199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/sexp.rb', line 199 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
.
217 218 219 |
# File 'lib/sexp.rb', line 217 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
.
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/sexp.rb', line 171 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
84 85 86 |
# File 'lib/sexp.rb', line 84 def hash @hash ||= [self.class, *self].hash end |
#inspect ⇒ Object Also known as: to_s
:nodoc:
190 191 192 193 194 195 196 197 |
# File 'lib/sexp.rb', line 190 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 |
#map(&blk) ⇒ Object
:nodoc:
72 73 74 |
# File 'lib/sexp.rb', line 72 def map &blk # :nodoc: self.new._concat(super(&blk)) # ensures a sexp from map end |
#mass ⇒ Object
Returns the size of the sexp, flattened.
248 249 250 |
# File 'lib/sexp.rb', line 248 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.
63 64 65 66 67 68 69 70 |
# File 'lib/sexp.rb', line 63 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.line_max = self.line_max if defined?(@line_max) r.comments = self.comments if self.comments r end |
#pretty_print(q) ⇒ Object
:nodoc:
272 273 274 275 276 277 278 279 |
# File 'lib/sexp.rb', line 272 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:
267 268 269 270 |
# File 'lib/sexp.rb', line 267 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.
299 300 301 |
# File 'lib/sexp.rb', line 299 def sexp_body from = 1 self.new._concat(self[from..-1] || []) end |
#sexp_body=(v) ⇒ Object
Sets the Sexp body to new content.
306 307 308 |
# File 'lib/sexp.rb', line 306 def sexp_body= v self[1..-1] = v end |
#sexp_type ⇒ Object Also known as: head
Returns the node type of the Sexp.
284 285 286 |
# File 'lib/sexp.rb', line 284 def sexp_type first end |
#sexp_type=(v) ⇒ Object
Sets the node type of the Sexp.
291 292 293 |
# File 'lib/sexp.rb', line 291 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.
317 318 319 320 |
# File 'lib/sexp.rb', line 317 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))
326 327 328 329 330 331 332 333 |
# File 'lib/sexp.rb', line 326 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
.
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/sexp.rb', line 338 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 |
#to_a ⇒ Object
:nodoc:
368 369 370 |
# File 'lib/sexp.rb', line 368 def to_a # :nodoc: self.map { |o| Sexp === o ? o.to_a : o } end |
#value ⇒ Object
Return the value (last item) of a single element sexp (eg ‘s(:lit, 42)`).
377 378 379 380 |
# File 'lib/sexp.rb', line 377 def value raise "multi item sexp" if size > 2 last end |