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. here's some
examples:
harp:~ > cat input.r4
>
> # any valid ruby code can go here
>
> x = 42
>
> # any output produced is used in the generated output
>
> p x
>
any lines which are not marked with a leading '|' are copied verbetim to the
output
>
> # since the pre-processor language is ruby it can do anything
>
> 10.times{|i| p i}
>
harp:~ > r4 input.r4
42
any lines which are not marked with a leading '|' are copied verbetim to the
output
0
1
2
3
4
5
6
7
8
9
and, using r4's macro shortcut to generate c code:
harp:~ > cat input.c
>
> fields = %w( foo bar foobar barfoo )
>
> macro('field'){|name| "int #{ name };" }
>
> macro('setter') do |name|
> <<-c
> int set_#{ name }(self, value)
> object * self;
> int value;
> {
> return( self->#{ name } = value );
> }
> c
> end
>
> macro('getter') do |name|
> <<-c
> int get_#{ name }(self)
> object * self;
> {
> return( self->#{ name } );
> }
> c
> end
struct object {
>
> fields.each{|f| field f}
>
};
>
> fields.each{|f| setter(f); getter(f); }
>
harp:~ > r4 input.c
struct object {
int foo;
int bar;
int foobar;
int barfoo;
};
int set_foo(self, value)
object * self;
int value;
{
return( self->foo = value );
}
int get_foo(self)
object * self;
{
return( self->foo );
}
int set_bar(self, value)
object * self;
int value;
{
return( self->bar = value );
}
int get_bar(self)
object * self;
{
return( self->bar );
}
int set_foobar(self, value)
object * self;
int value;
{
return( self->foobar = value );
}
int get_foobar(self)
object * self;
{
return( self->foobar );
}
int set_barfoo(self, value)
object * self;
int value;
{
return( self->barfoo = value );
}
int get_barfoo(self)
object * self;
{
return( self->barfoo );
}
and, finally, the source for r4:
#! /usr/bin/env ruby
require 'tempfile'
script = Tempfile::new(File::basename(__FILE__))
script << DATA.read
pat = %r/^\s*\|(.*)$/
hdoc =
start_hdoc = lambda do
if hdoc.empty?
script << "puts <<-'" << hdoc.push("___code_#{ rand(2 ** 42) }___") << "'" << "\n"
end
end
end_hdoc = lambda do
unless hdoc.empty?
script << hdoc.pop << "\n"
end
end
ARGF.each do |line|
m = pat.match line
if m
code = m[1]
end_hdoc
script << code << "\n"
else
start_hdoc
script << line
end
end
end_hdoc
script.close
load script.path
__END__
class Object
def macro(m, &b)
klass = (Class === self ? self : (class << self; self; end))
klass.module_eval do
define_method(m){|*a| puts b.call(*a)}
end
end
end
obivious the char marking pre-processor lines could be anything - but other
than that there is special markup.
thoughts/comments/flames?
-a