The astute amongst you that have read it would remember their example on meta-programming that uses Perl to take an ordinary text file and generate Pascal and C source code from it. I thought i'd update it a bit and use Ruby to generate Java, and, well Ruby.
I did it in a similar way to the prag guys i.e. the "proper" way but then I got a little bit curious about the notion of source that generates source that generates source. Plus, I hadn't really dug into Ruby meta-programming and this seemed like something harmless to play with.
Seeing we all love Shoes, let's have a look at a file called Shoe.txt that in pretty much plain English, models a shoe.
1 C Shoe
2 M brand String
3 M colour String
4 M size int 7
5 M isTrendy boolean true
6 M scent Scent
7 E
For clarification, the beginning of each line identifies what it is modelling:
C is the name of the class
M is a member of the class, specifying it's name, type and default value
E signals the end of the class.
Pretty straight forward, eh.
So how do we turn that into:
Shoe.Java
1 class Shoe {
2 private String brand = null;
3 public String getBrand {
4 return brand;
5 }
6 public void setBrand(String brand)
7 this.brand = brand;
8 }
9
10 private String colour = null;
11 public String getColour {
12 return colour;
13 }
14 public void setColour(String colour)
15 this.colour = colour;
16 }
17
18 private int size = 7;
19 public int getSize {
20 return size;
21 }
22 public void setSize(int size)
23 this.size = size;
24 }
25
26 private boolean isTrendy = true;
27 public boolean getIstrendy {
28 return isTrendy;
29 }
30 public void setIstrendy(boolean isTrendy)
31 this.isTrendy = isTrendy;
32 }
33
34 private Scent scent = null;
35 public Scent getScent {
36 return scent;
37 }
38 public void setScent(Scent scent)
39 this.scent = scent;
40 }
41
42
43 }
and Shoe.rb?
1 class Shoe
2 attr_accessor :brand, :colour, :size, :isTrendy, :scent
3 def initialize
4 @colour = nil
5 @size = 7
6 @isTrendy = true
7 @scent = nil
8 end
9 end
Well, there is the smart way i.e. the pragmatic way, or there is the meta-meta-meta programming way.
language_generator.rb
1 langs = %w(ruby java)
2 class LangGen
3 end
4
5 langs.each do |lang|
6 LangGen.class_eval <<-LETS_DANCE
7 $first = true
8 $init = ""
9
10 def #{lang}_class_start(name)
11 out = "class " + eval(\"name.chomp\") + ' '
12 out << "{" if '#{lang}' == 'java'
13 out << "\n"
14 out
15 end
16
17 def #{lang}_class_end
18 out = ""
19 out << "\n}" if '#{lang}' == 'java'
20 if '#{lang}' == 'ruby'
21 out << $init
22 out << " end"
23 out << "\nend"
24 end
25 out
26 end
27
28 def #{lang}_members(name, type, value)
29 out = ""
30 if '#{lang}' == 'java'
31 value ||= 'null'
32 out << " private " + eval(\"type\")+' '+eval(\"name\")+' = '+
33 eval(\"value\")+";\n"
34 end
35
36 out << "" if '#{lang}' == 'ruby'
37 out
38 end
39
40 def #{lang}_accessors(name, type, value)
41 accessors = ""
42 if '#{lang}' == 'java'
43 accessors << " public " + eval(\"type\")+
44 " get"+eval(\"name.capitalize\")+" {\n"
45 accessors << " return "+eval(\"name\")+";\n"
46 accessors << " }\n"
47 accessors << " public void set"+eval(\"name.capitalize\")+ '('+
48 eval(\"type\")+ ' '+ eval(\"name\")+")\n"
49 accessors << " this."+eval(\"name\")+" = "+eval(\"name\")+";\n"
50 accessors << " }\n\n"
51 end
52 if '#{lang}' == 'ruby'
53 value ||= 'nil'
54 if $first
55 $init << "\n def initialize \n"
56 accessors << " attr_accessor :" + eval(\"name\")
57 end
58 accessors << ", :" + eval(\"name\") unless $first
59 $init << ' @'+ eval(\"name\")+' = '+eval(\"value\")+"\n" unless $first
60 $first = false
61 end
62 accessors
63 end
64
65 LETS_DANCE
66 end
67
68 gen = LangGen.new
69 langs.each do |lang|
70 File.open('Shoe.txt').each do |line|
71 if line =~ /^C/
72 print gen.send("#{lang}_class_start".to_sym, line.gsub(/^C\s+/,''))
73 end
74 print gen.send("#{lang}_class_end".to_sym) if line =~ /^E/
75 if line =~ /^M\s+(\w+)\s+(\w+)\s+(\w+)?/
76 name, type, value = $1, $2, $3
77 print gen.send("#{lang}_members".to_sym, name, type, value)
78 print gen.send("#{lang}_accessors".to_sym, name, type, value)
79 end
80 end
81 printf("\n"+'*'*50 + "\n")
82 end
6 comments:
I'm disappointed. I was expecting cool meta-programming stuff in the style of Lisp not Perl and Regular-Expressions stuff.
Ah well, this gives me a good topic for a blog post. I'll show you how it's done Lisp-style :P
Come back and tell me when you're done, i'd love to see it. Ruby-style Lisp or Lisp-style Lisp? Because if you're going to do it in Lisp then that kind of defeats the purpose of me playing with ruby meta-programming. Still, i'd like to see it though.
Posted my version here: http://neverfriday.com/blog/?p=15
It is the Scheme dialect of Lisp and it generates the code using macros. It also processes lists and while I didn't add the file-parsing function, it is trivial since it uses the read macro that Scheme defines.
Why don't you use a template file with ERB ?
Looks good Rudolf, i'd still like to see you get the attr_accessors in there :)
Stephane, good point. I always thought ERB was more tailored to HTML but it would do a good job with a problem like this and be degrees more readable. I was just generally mucking around. I didn't mean it to be taken as a "proper" or "best" solution to the problem outlined.
Post a Comment