#!/usr/bin/env ruby

require 'optparse'

MESSAGES = {
  # Lexer errors
  :unicode_point_too_large => 'invalid Unicode codepoint (too large)',
  :invalid_escape          => 'invalid escape character syntax',
  :incomplete_escape       => 'incomplete character syntax',
  :invalid_hex_escape      => 'invalid hex escape',
  :invalid_unicode_escape  => 'invalid Unicode escape',
  :unterminated_unicode    => 'unterminated Unicode escape',
  :escape_eof              => 'escape sequence meets end of file',
  :string_eof              => 'unterminated string meets end of file',
  :regexp_options          => 'unknown regexp options: {}',
  :cvar_name               => "`{}' is not allowed as a class variable name",
  :ivar_name               => "`{}' is not allowed as an instance variable name",
  :trailing_in_number      => "trailing `{}' in number",
  :empty_numeric           => 'numeric literal without digits',
  :invalid_octal           => 'invalid octal digit',
  :no_dot_digit_literal    => 'no .<digit> floating literal anymore; put 0 before dot',
  :bare_backslash          => 'bare backslash only allowed before newline',
  :unexpected              => "unexpected `{}'",
  :embedded_document       => 'embedded document meets end of file (and they embark on a romantic journey)',

  # Lexer warnings
  :invalid_escape_use      => 'invalid character syntax; use ?{}',
  :ambiguous_literal       => 'ambiguous first argument; put parentheses or a space even after the operator',
  :ambiguous_prefix        => "`{}' interpreted as argument prefix",

  # Parser errors
  :nth_ref_alias           => 'cannot define an alias for a back-reference variable',
  :begin_in_method         => 'BEGIN in method',
  :backref_assignment      => 'cannot assign to a back-reference variable',
  :invalid_assignment      => 'cannot assign to a keyword',
  :module_name_const       => 'class or module name must be a constant literal',
  :unexpected_token        => 'unexpected token: {}',
  :argument_const          => 'formal argument cannot be a constant',
  :argument_ivar           => 'formal argument cannot be an instance variable',
  :argument_gvar           => 'formal argument cannot be a global variable',
  :argument_cvar           => 'formal argument cannot be a class variable',
  :duplicate_argument      => 'duplicate argument name',
  :empty_symbol            => 'empty symbol literal',
  :odd_hash                => 'odd number of entries for a hash',
  :singleton_literal       => 'cannot define a singleton method for a literal',
  :dynamic_const           => 'dynamic constant assignment',
  :const_reassignment      => 'constant re-assignment',
  :module_in_def           => 'module definition in method body',
  :class_in_def            => 'class definition in method body',
  :unexpected_percent_str  => '{}: unknown type of percent-literal',
  :block_and_blockarg      => 'both block argument and literal block are passed',
  :masgn_as_condition      => 'multiple assignment in conditional context',
  :block_given_to_yield    => 'block given to yield',
  :invalid_regexp          => '{}',

  # Parser warnings
  :useless_else            => 'else without rescue is useless',

  # Parser errors that are not Ruby errors
  :invalid_encoding        => 'literal contains escape sequences incompatible with UTF-8',

  # Rewriter diagnostics
  :invalid_action          => 'cannot {}',
  :clobbered               => 'clobbered by: {}',

  # TypedRuby diagnostics
  :not_static_cpath_in_geninst => "Type name in generic instance must be a static constant path"
}.freeze

def camelize(sym)
  sym.to_s.split('_').collect(&:capitalize).join
end

def generate_rs(out)
  out.puts "// This file is autogenerated by diagnostics.rb"
  out.puts "// DO NOT MODIFY"
  out.puts "#[repr(C)]"
  out.puts "#[derive(Debug)]"
  out.puts "#[derive(PartialEq)]"
  out.puts "pub enum Error {"
  MESSAGES.each do |err, _|
    out.puts "    #{camelize(err)},"
  end
  out.puts "}"
  out.puts "\n\n"

  out.puts "impl fmt::Display for Diagnostic {"
  out.puts "    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {"
  out.puts "        let arg1 = match self.data { Some(ref str) => str.as_str(), None => \"[??]\" };"
  out.puts "        match self.error {"
  MESSAGES.each do |err, msg|
    args = ", arg1" if msg.include? '{}'
    out.puts "            Error::#{camelize(err)} => write!(f, #{msg.inspect}#{args}),"
  end
  out.puts "        }"
  out.puts "    }"
  out.puts "}"
end

def generate_cpp(out)
  out.puts "// This file is autogenerated by diagnostics.rb"
  out.puts "// DO NOT MODIFY"
  out.puts "enum class dclass {"
  out.puts MESSAGES.map { |err, _| "\t#{camelize(err)}" }.join(",\n")
  out.puts "};"
end

OptionParser.new do |opts|
  opts.banner = "Usage: ruby diagnostics.rb [--cpp=FILE] [--rs=FILE]"
  opts.on("--cpp [FILE]") do |file|
    file = file ? File.open(file, "w") : $stdout
    abort("failed to open '#{file}'") unless file
    generate_cpp(file)
  end

  opts.on("--rs [FILE]") do |file|
    file = file ? File.open(file, "w") : $stdout
    abort("failed to open '#{file}'") unless file
    generate_rs(file)
  end
end.parse!