tt 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #!/usr/bin/env ruby
  2. require 'optparse'
  3. require 'rubygems'
  4. $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
  5. require 'treetop'
  6. require 'treetop/version'
  7. require 'treetop/polyglot'
  8. options = {}
  9. parser = OptionParser.new do |opts|
  10. exts = Treetop::Polyglot::VALID_GRAMMAR_EXT.collect { |i| '.' + i }
  11. opts.banner = "Treetop Parsing Expression Grammar (PEG) Comand Line Compiler"
  12. opts.define_head "Usage: tt [options] grammar_file[#{exts.join('|')}] ..."
  13. opts.separator ''
  14. opts.separator 'Examples:'
  15. opts.separator ' tt foo.tt # 1 grammar -> 1 parser source'
  16. opts.separator ' tt foo bar.treetop # 2 grammars -> 2 separate parsers'
  17. opts.separator ' tt -o alt_name.rb foo # alternately named output file'
  18. opts.separator ''
  19. opts.separator ''
  20. opts.separator 'NOTE: while treetop grammar files *must* have one of the following'
  21. opts.separator 'filename extensions, the extension name is not required when calling'
  22. opts.separator 'the compiler with grammar file names.'
  23. opts.separator ''
  24. opts.separator " Valid extensions: #{exts.join(', ')}"
  25. opts.separator ''
  26. opts.separator ''
  27. opts.separator 'Options:'
  28. opts.on('-o', '--output FILENAME', 'Write parser source to FILENAME') do |fn|
  29. options[:out_file] = fn
  30. end
  31. opts.on('-f', '--force', 'Overwrite existing output file(s)') do
  32. options[:force] = true
  33. end
  34. opts.on_tail('-v', '--version', 'Show Treetop version') do
  35. puts "Treetop v#{Treetop::VERSION::STRING}"
  36. exit
  37. end
  38. opts.on_tail('-h', '--help', 'Show this help message') do
  39. puts opts
  40. exit
  41. end
  42. end
  43. file_list = parser.parse!
  44. # check options and arg constraints
  45. if file_list.empty? || (options[:out_file] && file_list.size > 1)
  46. puts parser
  47. exit 1
  48. end
  49. def grammar_exist?(filename)
  50. if File.extname(filename).empty?
  51. Treetop::Polyglot::VALID_GRAMMAR_EXT.each do |ext|
  52. fn_ext = "#{filename}.#{ext}"
  53. return true if File.exist?(fn_ext) && !File.zero?(fn_ext)
  54. end
  55. end
  56. File.exist?(filename) && !File.zero?(filename)
  57. end
  58. def full_grammar_filename(filename)
  59. return filename if !File.extname(filename).empty?
  60. Treetop::Polyglot::VALID_GRAMMAR_EXT.each do |ext|
  61. fn_ext = "#{filename}.#{ext}"
  62. return fn_ext if File.exist?(fn_ext) && !File.zero?(fn_ext)
  63. end
  64. end
  65. def protect_output?(filename, forced=false)
  66. if !forced and
  67. File.exist?(filename) and
  68. (l=File.open(filename) { |f| f.gets rescue "" }) != Treetop::Compiler::AUTOGENERATED
  69. puts "ERROR: '#{filename}' output already exists; skipping compilation...\n"
  70. return true
  71. end
  72. false
  73. end
  74. compiler = Treetop::Compiler::GrammarCompiler.new
  75. while !file_list.empty?
  76. treetop_file = file_list.shift
  77. # handle nonexistent and existent grammar files mixed together
  78. if !grammar_exist?(treetop_file)
  79. puts "ERROR: input grammar file '#{treetop_file}' does not exist; continuing...\n"
  80. next
  81. end
  82. # try to compile
  83. treetop_file = full_grammar_filename(treetop_file)
  84. std_output_file = treetop_file.gsub(Treetop::Polyglot::VALID_GRAMMAR_EXT_REGEXP, '.rb')
  85. if options[:out_file]
  86. # explicit output file name option; never overwrite unless forced
  87. next if protect_output?(options[:out_file], options[:force])
  88. compiler.compile(treetop_file, options[:out_file])
  89. else
  90. # compile one input file from input file list option; never overwrite unless forced
  91. next if protect_output?(std_output_file, options[:force])
  92. compiler.compile(treetop_file)
  93. end
  94. end