1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
# String#to_proc # # See http://weblog.raganwald.com/2007/10/stringtoproc.html ( Subscribe in a reader) # # Ported from the String Lambdas in Oliver Steele's Functional Javascript # http://osteele.com/sources/javascript/functional/ # # This work is licensed under the MIT License: # # (c) 2007 Reginald Braithwaite # Portions Copyright (c) 2006 Oliver Steele # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. class String unless ''.respond_to?(:to_proc) def to_proc &block params = [] expr = self sections = expr.split(/\s*->\s*/m) if sections.length > 1 then eval sections.reverse!.inject { |e, p| "(Proc.new { |#{p.split(/\s/).join(', ')}| #{e} })" }, block && block.binding elsif expr.match(/\b_\b/) eval "Proc.new { |_| #{expr} }", block && block.binding else leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>\[]|!=)/m) rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m) if leftSection || rightSection then if (leftSection) then params.push('$left') expr = '$left' + expr end if (rightSection) then params.push('$right') expr = expr + '$right' end else self.gsub( /(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|self|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/, '' ).scan( /([a-z_$][a-z_$\d]*)/i ) do |v| params.push(v) unless params.include?(v) end end eval "Proc.new { |#{params.join(', ')}| #{expr} }", block && block.binding end end end end
Refactorings
No refactoring yet !
michiel
October 28, 2007, October 28, 2007 18:35, permalink
Ok, I've removed the nested ifs and I've introduced guards to quicky exit the method if possible. But I've made no attempt to parse the (uncommented) regular expressions, which do most of the actual work.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
class String unless ''.respond_to?(:to_proc) def to_proc(&block) evaller = lambda {|ex| eval(ex, block && block.binding) } prc_templ = lambda {|prm,ex| 'Proc.new { |' + prm.join(', ') + "| #{ex} }" } expr = self sections = expr.split(/\s*->\s*/m).reverse return eval(sections.inject { |e, p| evaller["(" + prc_templ[p.split(/\s/), e] + ")"] }) if sections.length > 1 return evaller[prc_templ[['_'],expr]] if expr.match(/\b_\b/) params = [] if expr.match(/^\s*(?:[+*\/%&|\^\.=<>\[]|!=)/m) params.push('$left') expr = '$left' + expr end if expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m) params.push('$right') expr += '$right' end if params.empty? x = self.gsub(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|self|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/, '') x.scan(/([a-z_$][a-z_$\d]*)/i) {|v| params.push(v) unless params.include?(v) } end evaller[prc_templ[params,expr]] end end end
macournoyer
October 30, 2007, October 30, 2007 14:24, permalink
I refactored it for the simplest and most common use cases, just chaining method calls, what Symbol#to_proc doesn't allow you to do
1 2 3 4 5 6 7 8
class String def to_proc eval "proc { |s| s#{self} }" end end (1..10).collect(&'*2') # => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] %w(hi there my friend).collect(&".reverse.capitalize") # => ["Ih", "Ereht", "Ym", "Dneirf"]
Reginald Braithwaite has a very interesting article on his blog at http://weblog.raganwald.com/2007/10/stringtoproc.html where he presents a port for Ruby of String Lambdas from Oliver Steeleās Functional Javascript. It's exceedingly cool :-)
Go read the article to see what it does first.
I thought I could post it here when I saw Giles Bowkett used pastie to submit a suggestion.