#!/usr/bin/env ruby require 'optparse' require 'rubygems' $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'treetop' require 'treetop/version' require 'treetop/polyglot' options = {} parser = OptionParser.new do |opts| exts = Treetop::Polyglot::VALID_GRAMMAR_EXT.collect { |i| '.' + i } opts.banner = "Treetop Parsing Expression Grammar (PEG) Comand Line Compiler" opts.define_head "Usage: tt [options] grammar_file[#{exts.join('|')}] ..." opts.separator '' opts.separator 'Examples:' opts.separator ' tt foo.tt # 1 grammar -> 1 parser source' opts.separator ' tt foo bar.treetop # 2 grammars -> 2 separate parsers' opts.separator ' tt -o alt_name.rb foo # alternately named output file' opts.separator '' opts.separator '' opts.separator 'NOTE: while treetop grammar files *must* have one of the following' opts.separator 'filename extensions, the extension name is not required when calling' opts.separator 'the compiler with grammar file names.' opts.separator '' opts.separator " Valid extensions: #{exts.join(', ')}" opts.separator '' opts.separator '' opts.separator 'Options:' opts.on('-o', '--output FILENAME', 'Write parser source to FILENAME') do |fn| options[:out_file] = fn end opts.on('-f', '--force', 'Overwrite existing output file(s)') do options[:force] = true end opts.on_tail('-v', '--version', 'Show Treetop version') do puts "Treetop v#{Treetop::VERSION::STRING}" exit end opts.on_tail('-h', '--help', 'Show this help message') do puts opts exit end end file_list = parser.parse! # check options and arg constraints if file_list.empty? || (options[:out_file] && file_list.size > 1) puts parser exit 1 end def grammar_exist?(filename) if File.extname(filename).empty? Treetop::Polyglot::VALID_GRAMMAR_EXT.each do |ext| fn_ext = "#{filename}.#{ext}" return true if File.exist?(fn_ext) && !File.zero?(fn_ext) end end File.exist?(filename) && !File.zero?(filename) end def full_grammar_filename(filename) return filename if !File.extname(filename).empty? Treetop::Polyglot::VALID_GRAMMAR_EXT.each do |ext| fn_ext = "#{filename}.#{ext}" return fn_ext if File.exist?(fn_ext) && !File.zero?(fn_ext) end end def protect_output?(filename, forced=false) if !forced and File.exist?(filename) and File.open(filename) { |f| ![(f.gets rescue ''), (f.gets rescue '')].include? Treetop::Compiler::AUTOGENERATED } puts "ERROR: '#{filename}' output already exists; skipping compilation...\n" return true end false end compiler = Treetop::Compiler::GrammarCompiler.new while !file_list.empty? treetop_file = file_list.shift # handle nonexistent and existent grammar files mixed together if !grammar_exist?(treetop_file) puts "ERROR: input grammar file '#{treetop_file}' does not exist; continuing...\n" next end # try to compile treetop_file = full_grammar_filename(treetop_file) std_output_file = treetop_file.gsub(Treetop::Polyglot::VALID_GRAMMAR_EXT_REGEXP, '.rb') if options[:out_file] # explicit output file name option; never overwrite unless forced next if protect_output?(options[:out_file], options[:force]) compiler.compile(treetop_file, options[:out_file]) else # compile one input file from input file list option; never overwrite unless forced next if protect_output?(std_output_file, options[:force]) compiler.compile(treetop_file) end end