From: Bob Hutchison Date: 2005-09-12T02:54:17+09:00 Subject: Re: r4 - the simplest ruby pre-processor On Sep 11, 2005, at 7:38 AM, Christian Neukirchen wrote: > "Ara.T.Howard" writes: > > >> all this talk about pre-processors and erb got me thinking : what >> would it >> take to code up a ruby pre-procssors that could be used for any >> laguange. my >> first crack is 38 lines of ruby that pre-process any files on the >> command line >> (or stdin) using ruby as the macro language and a convenience >> function 'macro' >> which can be used to define ruby methods which return test that is >> used as >> output. functions declared this way need not do any explicit io. >> > > Alternative implementation ("r5"): > So, playing around a bit more, I've got something that solves a problem I have right now (with code generation)... using this scheme to add methods to an arbitrary class. Keeping with the theme, maybe call this r6? r6.rb ----------------------------- #! /usr/bin/env ruby def macro(name, &block) Kernel.send(:define_method, name) { |*args| puts block.call(args) } end def build_script(template_file_name, class_name, method_name) # Build a method (called 'method_name') in the class 'class_name' that # will execute the template (in the file 'template_file_name'). There will # be an optional argument that defaults to the empty string (and so this # method will, by default, build a new string representing the result). If # the argument is supplied, it must respond to the "<<" (append) method. # # The result variable is available in the template. To write to the result # from Ruby code, result << sprintf("hello %s", "world") in the template # will get its output where expected. File.open(template_file_name) do | file | r = " class #{class_name} def #{method_name}(result=\"\") result << \"\" " while line = file.gets if line[0] == ?| r << " #{line[1..-1]}" else r << " result << \"#{line.chomp}\\n\"\n" end end r << " result end end " end end ### ### and this is how it can be used... ### # Define a class to hold the template methods. There is an attribute 'message' # that can be set by the program invoking the template, and referred to by the # templates. You can put any attributes you want in there. class R6_Template attr_accessor :message end # Assume that the command line arguments are all specifying the name of a # template file. Open each file and pass it to the build script method. When # the script is built, 'eval' it. ARGV.each { | script_name | method_name = File::basename(script_name, ".*") the_script = build_script(script_name, "R6_Template", method_name) #puts the_script ## if you want to see what it looks like, uncomment this line eval the_script } # For illustrative purposes, go over the command line arguments and execute # the corresponding method defined above. template = R6_Template.new() ARGV.each { | script_name | method_name = File::basename(script_name, ".*") template.message = sprintf("this is script '%s'", method_name) puts "#{method_name}*******************" what = template.send(method_name); puts "{{{#{what}}}}*******************" } # This time call the play method directly. There must be a template # called 'play' (sans extenstion) for this to work. template.message = "this is script 'play' -- called explicitly" puts "!!play!!*******************" what = template.play() puts "{{{#{what}}}}*******************" # Now do the same thing as the illustrative loop above but writing to a file # with the script name and a ".out" extension. ARGV.each { | script_name | method_name = File::basename(script_name, ".*") template.message = sprintf("this is script '%s'", method_name) File.open(sprintf("%s.out", method_name), "w") { | file | template.send(method_name, file); } } # Write to a file called "play-x.out", again, there must be a play template # defined. template.message = "this is script 'play' -- called explicitly" File.open("play-x.out", "w") { | file | template.play(file) } # Build up a single string by applying all the templates long_string = "" ARGV.each { | script_name | method_name = File::basename(script_name, ".*") template.message = sprintf("this is script '%s'", method_name) what = template.send(method_name, long_string); } puts "!!{{{#{long_string}}}}!!*******************" ----------------------------- play.r6 ----------------------------- | | some_number = 42 | def gen_some_number | 42 + rand | end | Hello there (play)! #{@message} some number: [[[#{some_number}]]] gen some number: [[[#{gen_some_number}]]] ----------------------------- play_more.r6 ----------------------------- | | some_number = 42 | def gen_some_number | 42 + rand | end | Hello there(play_more)! #{@message} | | another_number = 99 | result << play() | some number: [[[#{some_number}]]] gen some number: [[[#{gen_some_number}]]] another number: [[[#{another_number}]]] play again:... #{play} ----------------------------- Note that the play_more template uses the play template (twice) I've also taken to calling them 'templates'... Cheers, Bob ---- Bob Hutchison -- blogs at Recursive Design Inc. -- Raconteur --