抽象構文木ジェネレータ
こんな感じ
Expr: PlusExpr: rhs: Expr lhs: Expr MulExpr: rhs: Expr lhs: Expr MinusExpr: rhs: Expr lhs: Expr DivExpr: rhs: Expr lhs: Expr IntExpr: value: int
で、抽象構文木にしたいのを書いて、
$ ruby astgen.rb hoge.huga input.yaml
とかやると、ざざーっとクラス定義とVisitorを生成してくれるやつを作りました。勝手にPositionとか言うクラスを作って、開始位置終了位置ファイル名を保持するようになってるので、抽象構文木以外に使えるかどうかは、微妙。
ていうか、これ前も書いた気がするんだけどなぁ………どこやったんだろ。今日は再発明ばっかりだぜ。
インタフェースと実装クラスはpackageを分けるほうが良いような気がしますが、実装がめんどくさい、のは置いておいても、package名のエイリアスくらいできないといちいち完全修飾名で書かないといけないようなことにすぐなるような気がするので、それが嫌な感じ。inner classでなんとかできないかと思ってちょっと試したんだけど、Javaのinner classってそういう用途には使わないのか。
※みずしまくんに聞いてみたら、「そういうときは、staticなinner classを使うんですよー」とか教えてくれたので、直した。これでnamespaceがすっきり。なるほど。staticってそういう意味だったのか。
require 'optparse' require 'pp' require 'pathname' require 'yaml' class HelpException < RuntimeError end def format_type(typename, class_name) typename.gsub(/\bself\b/, class_name).gsub(/\bList\b/, "java.util.List") end def write_position(io, package_name) io.puts <<EOS package #{package_name}; public class Position { \tpublic final int startLine; \tpublic final int startColumn; \tpublic final int endLine; \tpublic final int endColumn; \tpublic final java.io.File file; \tpublic Position(int startLine, int startColumn, int endLine, int endColumn, java.io.File file) { \t\tthis.startLine = startLine; \t\tthis.startColumn = startColumn; \t\tthis.endLine = endLine; \t\tthis.endColumn = endColumn; \t\tthis.file = file; \t} \tpublic String toString() { \t\t return (String.format("File \\"%s\\", line %d-%d, characters %d-%d", \t\t\t\tthis.file, \t\t\t\tthis.startLine, this.endLine, \t\t\t\tthis.startColumn, this.endColumn)); \t} } EOS end def write_interface(io, package_name, class_name, children) io.puts "package #{package_name};" io.puts io.puts "public abstract class #{class_name} {" io.puts "\tpublic Position position;" io.puts io.puts "\tpublic #{class_name}(Position position) {" io.puts "\t\tthis.position = position;" io.puts "\t}" io.puts io.puts "\tpublic abstract <T> T visit(#{class_name}Visitor<T> visitor);" io.puts children.each {|child_name, members| write_class(io, package_name, class_name, child_name, members); io.puts } io.puts "}" end def write_class(io, package_name, super_class, class_name, members) io.puts "\tpublic static class #{class_name} extends #{super_class} {" members.each {|name, type| io.puts "\t\tpublic #{format_type(type, class_name)} #{name};" } io.puts constr_params = (["Position position"] + members.keys.collect {|name| "#{format_type(members[name], class_name)} #{name}" }).join(', ') io.puts "\t\tpublic #{class_name}(#{constr_params}) {" io.puts "\t\t\tsuper(position);" members.keys.each {|name| io.puts "\t\t\tthis.#{name} = #{name};" } io.puts "\t\t}" io.puts io.puts "\t\t@Override" io.puts "\t\tpublic <T> T visit(#{super_class}Visitor<T> visitor) {" io.puts "\t\t\treturn visitor.accept(this);" io.puts "\t\t}" io.puts "\t}" end def write_visitor(io, package_name, class_name, children) io.puts "package #{package_name};" io.puts io.puts "public interface #{class_name}Visitor<T> {" children.each {|c| object_name = c[0,1].downcase + c[1,c.length] io.puts "\tT accept(#{class_name}.#{c} #{object_name});" } io.puts "}" end opt = OptionParser.new() dir = Pathname(".") begin opt.on('-d [dir]') {|path| dir = Pathname(path) } opt.banner = "Usage: astgen [options] package_name input.yaml" opt.parse! raise HelpException unless ARGV.length == 2 package_name = ARGV[0] input = YAML.load_file(Pathname(ARGV[1])) srcdir = package_name.split(".").inject(dir) {|path, dir| newpath = path + dir newpath.mkdir unless newpath.exist? newpath } puts "output dir: #{srcdir}" puts "package name: #{package_name}" puts "input file: #{ARGV[1]}" (srcdir+"Position.java").open('w') {|io| write_position(io, package_name) } input.each {|k,v| interface_file = srcdir + "#{k}.java" interface_file.open('w') {|io| write_interface(io, package_name, k, v) } (srcdir+"#{k}Visitor.java").open('w') {|io| write_visitor(io, package_name, k, v.keys) } } rescue HelpException, OptionParser::InvalidOption puts opt end