use std::map::HashMap;
use parse::parser;
use diagnostic::span_handler;
use codemap::{codemap, span, expn_info, expanded_from};

// obsolete old-style #macro code:
//
//    syntax_expander, normal, macro_defining, macro_definer,
//    builtin
//
// new-style macro! tt code:
//
//    syntax_expander_tt, syntax_expander_tt_item, mac_result,
//    expr_tt, item_tt
//
// also note that ast::mac has way too many cases and can probably
// be trimmed down substantially.

// second argument is the span to blame for general argument problems
type syntax_expander_ =
    fn@(ext_ctxt, span, ast::mac_arg, ast::mac_body) -> @ast::expr;
// second argument is the origin of the macro, if user-defined
type syntax_expander = {expander: syntax_expander_, span: Option<span>};

type macro_def = {name: ~str, ext: syntax_extension};

// macro_definer is obsolete, remove when #old_macros go away.
type macro_definer =
    fn@(ext_ctxt, span, ast::mac_arg, ast::mac_body) -> macro_def;

type item_decorator =
    fn@(ext_ctxt, span, ast::meta_item, ~[@ast::item]) -> ~[@ast::item];

type syntax_expander_tt = {expander: syntax_expander_tt_, span: Option<span>};
type syntax_expander_tt_ = fn@(ext_ctxt, span, ~[ast::token_tree])
    -> mac_result;

type syntax_expander_tt_item
    = {expander: syntax_expander_tt_item_, span: Option<span>};
type syntax_expander_tt_item_
    = fn@(ext_ctxt, span, ast::ident, ~[ast::token_tree]) -> mac_result;

enum mac_result {
    mr_expr(@ast::expr),
    mr_item(@ast::item),
    mr_def(macro_def)
}

enum syntax_extension {

    // normal() is obsolete, remove when #old_macros go away.
    normal(syntax_expander),

    // macro_defining() is obsolete, remove when #old_macros go away.
    macro_defining(macro_definer),

    // #[auto_serialize] and such. will probably survive death of #old_macros
    item_decorator(item_decorator),

    // Token-tree expanders
    expr_tt(syntax_expander_tt),
    item_tt(syntax_expander_tt_item),
}

// A temporary hard-coded map of methods for expanding syntax extension
// AST nodes into full ASTs
fn syntax_expander_table() -> HashMap<~str, syntax_extension> {
    fn builtin(f: syntax_expander_) -> syntax_extension
        {normal({expander: f, span: None})}
    fn builtin_expr_tt(f: syntax_expander_tt_) -> syntax_extension {
        expr_tt({expander: f, span: None})
    }
    fn builtin_item_tt(f: syntax_expander_tt_item_) -> syntax_extension {
        item_tt({expander: f, span: None})
    }
    let syntax_expanders = HashMap();
    syntax_expanders.insert(~"macro",
                            macro_defining(ext::simplext::add_new_extension));
    syntax_expanders.insert(~"macro_rules",
                            builtin_item_tt(
                                ext::tt::macro_rules::add_new_extension));
    syntax_expanders.insert(~"fmt", builtin(ext::fmt::expand_syntax_ext));
    syntax_expanders.insert(
        ~"auto_serialize",
        item_decorator(ext::auto_serialize::expand_auto_serialize));
    syntax_expanders.insert(
        ~"auto_deserialize",
        item_decorator(ext::auto_serialize::expand_auto_deserialize));
    syntax_expanders.insert(~"env", builtin(ext::env::expand_syntax_ext));
    syntax_expanders.insert(~"concat_idents",
                            builtin(ext::concat_idents::expand_syntax_ext));
    syntax_expanders.insert(~"ident_to_str",
                            builtin(ext::ident_to_str::expand_syntax_ext));
    syntax_expanders.insert(~"log_syntax",
                            builtin_expr_tt(
                                ext::log_syntax::expand_syntax_ext));
    syntax_expanders.insert(~"ast",
                            builtin(ext::qquote::expand_ast));
    syntax_expanders.insert(~"line",
                            builtin(ext::source_util::expand_line));
    syntax_expanders.insert(~"col",
                            builtin(ext::source_util::expand_col));
    syntax_expanders.insert(~"file",
                            builtin(ext::source_util::expand_file));
    syntax_expanders.insert(~"stringify",
                            builtin(ext::source_util::expand_stringify));
    syntax_expanders.insert(~"include",
                            builtin(ext::source_util::expand_include));
    syntax_expanders.insert(~"include_str",
                            builtin(ext::source_util::expand_include_str));
    syntax_expanders.insert(~"include_bin",
                            builtin(ext::source_util::expand_include_bin));
    syntax_expanders.insert(~"module_path",
                            builtin(ext::source_util::expand_mod));
    syntax_expanders.insert(~"proto",
                            builtin_item_tt(ext::pipes::expand_proto));
    syntax_expanders.insert(
        ~"trace_macros",
        builtin_expr_tt(ext::trace_macros::expand_trace_macros));
    return syntax_expanders;
}

// One of these is made during expansion and incrementally updated as we go;
// when a macro expansion occurs, the resulting nodes have the backtrace()
// -> expn_info of their expansion context stored into their span.
trait ext_ctxt {
    fn codemap() -> codemap;
    fn parse_sess() -> parse::parse_sess;
    fn cfg() -> ast::crate_cfg;
    fn print_backtrace();
    fn backtrace() -> expn_info;
    fn mod_push(mod_name: ast::ident);
    fn mod_pop();
    fn mod_path() -> ~[ast::ident];
    fn bt_push(ei: codemap::expn_info_);
    fn bt_pop();
    fn span_fatal(sp: span, msg: &str) -> !;
    fn span_err(sp: span, msg: &str);
    fn span_warn(sp: span, msg: &str);
    fn span_unimpl(sp: span, msg: &str) -> !;
    fn span_bug(sp: span, msg: &str) -> !;
    fn bug(msg: &str) -> !;
    fn next_id() -> ast::node_id;
    pure fn trace_macros() -> bool;
    fn set_trace_macros(x: bool);
    /* for unhygienic identifier transformation */
    fn str_of(id: ast::ident) -> ~str;
    fn ident_of(st: ~str) -> ast::ident;
}

fn mk_ctxt(parse_sess: parse::parse_sess,
           cfg: ast::crate_cfg) -> ext_ctxt {
    type ctxt_repr = {parse_sess: parse::parse_sess,
                      cfg: ast::crate_cfg,
                      mut backtrace: expn_info,
                      mut mod_path: ~[ast::ident],
                      mut trace_mac: bool};
    impl ctxt_repr: ext_ctxt {
        fn codemap() -> codemap { self.parse_sess.cm }
        fn parse_sess() -> parse::parse_sess { self.parse_sess }
        fn cfg() -> ast::crate_cfg { self.cfg }
        fn print_backtrace() { }
        fn backtrace() -> expn_info { self.backtrace }
        fn mod_push(i: ast::ident) { self.mod_path.push(i); }
        fn mod_pop() { self.mod_path.pop(); }
        fn mod_path() -> ~[ast::ident] { return self.mod_path; }
        fn bt_push(ei: codemap::expn_info_) {
            match ei {
              expanded_from({call_site: cs, callie: callie}) => {
                self.backtrace =
                    Some(@expanded_from({
                        call_site: {lo: cs.lo, hi: cs.hi,
                                    expn_info: self.backtrace},
                        callie: callie}));
              }
            }
        }
        fn bt_pop() {
            match self.backtrace {
              Some(@expanded_from({call_site: {expn_info: prev, _}, _})) => {
                self.backtrace = prev
              }
              _ => self.bug(~"tried to pop without a push")
            }
        }
        fn span_fatal(sp: span, msg: &str) -> ! {
            self.print_backtrace();
            self.parse_sess.span_diagnostic.span_fatal(sp, msg);
        }
        fn span_err(sp: span, msg: &str) {
            self.print_backtrace();
            self.parse_sess.span_diagnostic.span_err(sp, msg);
        }
        fn span_warn(sp: span, msg: &str) {
            self.print_backtrace();
            self.parse_sess.span_diagnostic.span_warn(sp, msg);
        }
        fn span_unimpl(sp: span, msg: &str) -> ! {
            self.print_backtrace();
            self.parse_sess.span_diagnostic.span_unimpl(sp, msg);
        }
        fn span_bug(sp: span, msg: &str) -> ! {
            self.print_backtrace();
            self.parse_sess.span_diagnostic.span_bug(sp, msg);
        }
        fn bug(msg: &str) -> ! {
            self.print_backtrace();
            self.parse_sess.span_diagnostic.handler().bug(msg);
        }
        fn next_id() -> ast::node_id {
            return parse::next_node_id(self.parse_sess);
        }
        pure fn trace_macros() -> bool {
            self.trace_mac
        }
        fn set_trace_macros(x: bool) {
            self.trace_mac = x
        }

        fn str_of(id: ast::ident) -> ~str {
            *self.parse_sess.interner.get(id)
        }
        fn ident_of(st: ~str) -> ast::ident {
            self.parse_sess.interner.intern(@st)
        }
    }
    let imp: ctxt_repr = {
        parse_sess: parse_sess,
        cfg: cfg,
        mut backtrace: None,
        mut mod_path: ~[],
        mut trace_mac: false
    };
    move (imp as ext_ctxt)
}

fn expr_to_str(cx: ext_ctxt, expr: @ast::expr, error: ~str) -> ~str {
    match expr.node {
      ast::expr_lit(l) => match l.node {
        ast::lit_str(s) => return *s,
        _ => cx.span_fatal(l.span, error)
      },
      _ => cx.span_fatal(expr.span, error)
    }
}

fn expr_to_ident(cx: ext_ctxt, expr: @ast::expr, error: ~str) -> ast::ident {
    match expr.node {
      ast::expr_path(p) => {
        if vec::len(p.types) > 0u || vec::len(p.idents) != 1u {
            cx.span_fatal(expr.span, error);
        } else { return p.idents[0]; }
      }
      _ => cx.span_fatal(expr.span, error)
    }
}

fn get_mac_args_no_max(cx: ext_ctxt, sp: span, arg: ast::mac_arg,
                       min: uint, name: ~str) -> ~[@ast::expr] {
    return get_mac_args(cx, sp, arg, min, None, name);
}

fn get_mac_args(cx: ext_ctxt, sp: span, arg: ast::mac_arg,
                min: uint, max: Option<uint>, name: ~str) -> ~[@ast::expr] {
    match arg {
      Some(expr) => match expr.node {
        ast::expr_vec(elts, _) => {
            let elts_len = vec::len(elts);
              match max {
                Some(max) if ! (min <= elts_len && elts_len <= max) => {
                  cx.span_fatal(sp,
                                fmt!("#%s takes between %u and %u arguments.",
                                     name, min, max));
                }
                None if ! (min <= elts_len) => {
                  cx.span_fatal(sp, fmt!("#%s needs at least %u arguments.",
                                         name, min));
                }
                _ => return elts /* we are good */
              }
          }
        _ => {
            cx.span_fatal(sp, fmt!("#%s: malformed invocation", name))
        }
      },
      None => cx.span_fatal(sp, fmt!("#%s: missing arguments", name))
    }
}

fn get_mac_body(cx: ext_ctxt, sp: span, args: ast::mac_body)
    -> ast::mac_body_
{
    match (args) {
      Some(body) => body,
      None => cx.span_fatal(sp, ~"missing macro body")
    }
}

// Massage syntactic form of new-style arguments to internal representation
// of old-style macro args, such that old-style macro can be run and invoked
// using new syntax. This will be obsolete when #old_macros go away.
fn tt_args_to_original_flavor(cx: ext_ctxt, sp: span, arg: ~[ast::token_tree])
    -> ast::mac_arg {
    use ast::{matcher, matcher_, match_tok, match_seq, match_nonterminal};
    use parse::lexer::{new_tt_reader, reader};
    use tt::macro_parser::{parse_or_else, matched_seq,
                              matched_nonterminal};

    // these spans won't matter, anyways
    fn ms(m: matcher_) -> matcher {
        {node: m, span: {lo: 0u, hi: 0u, expn_info: None}}
    }
    let arg_nm = cx.parse_sess().interner.gensym(@~"arg");

    let argument_gram = ~[ms(match_seq(~[
        ms(match_nonterminal(arg_nm, parse::token::special_idents::expr, 0u))
    ], Some(parse::token::COMMA), true, 0u, 1u))];

    let arg_reader = new_tt_reader(cx.parse_sess().span_diagnostic,
                                   cx.parse_sess().interner, None, arg);
    let args =
        match parse_or_else(cx.parse_sess(), cx.cfg(), arg_reader as reader,
                          argument_gram).get(arg_nm) {
          @matched_seq(s, _) => {
            do s.map() |lf| {
                match *lf {
                  @matched_nonterminal(parse::token::nt_expr(arg)) =>
                    arg, /* whew! list of exprs, here we come! */
                  _ => fail ~"badly-structured parse result"
                }
            }
          },
          _ => fail ~"badly-structured parse result"
        };

    return Some(@{id: parse::next_node_id(cx.parse_sess()),
               callee_id: parse::next_node_id(cx.parse_sess()),
               node: ast::expr_vec(args, ast::m_imm), span: sp});
}

//
// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//
