// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use ast;
use codemap::{spanned, mk_sp};
use parse::common::*; //resolve bug?
use parse::token;
use parse::parser::Parser;
use parse::token::INTERPOLATED;

// a parser that can parse attributes.
pub trait parser_attr {
    fn parse_outer_attributes(&self) -> ~[ast::Attribute];
    fn parse_attribute(&self, permit_inner: bool) -> ast::Attribute;
    fn parse_inner_attrs_and_next(&self) ->
        (~[ast::Attribute], ~[ast::Attribute]);
    fn parse_meta_item(&self) -> @ast::MetaItem;
    fn parse_meta_seq(&self) -> ~[@ast::MetaItem];
    fn parse_optional_meta(&self) -> ~[@ast::MetaItem];
}

impl parser_attr for Parser {

    // Parse attributes that appear before an item
    fn parse_outer_attributes(&self) -> ~[ast::Attribute] {
        let mut attrs: ~[ast::Attribute] = ~[];
        loop {
            debug!("parse_outer_attributes: self.token=%?",
                   self.token);
            match *self.token {
              token::INTERPOLATED(token::nt_attr(*)) => {
                attrs.push(self.parse_attribute(false));
              }
              token::POUND => {
                if self.look_ahead(1, |t| *t != token::LBRACKET) {
                    break;
                }
                attrs.push(self.parse_attribute(false));
              }
              token::DOC_COMMENT(s) => {
                let attr = ::attr::mk_sugared_doc_attr(
                    self.id_to_str(s),
                    self.span.lo,
                    self.span.hi
                );
                if attr.node.style != ast::AttrOuter {
                  self.fatal("expected outer comment");
                }
                attrs.push(attr);
                self.bump();
              }
              _ => break
            }
        }
        return attrs;
    }

    // matches attribute = # [ meta_item ]
    //
    // if permit_inner is true, then a trailing `;` indicates an inner
    // attribute
    fn parse_attribute(&self, permit_inner: bool) -> ast::Attribute {
        debug!("parse_attributes: permit_inner=%? self.token=%?",
               permit_inner, self.token);
        let (span, value) = match *self.token {
            INTERPOLATED(token::nt_attr(attr)) => {
                assert!(attr.node.style == ast::AttrOuter);
                self.bump();
                (attr.span, attr.node.value)
            }
            token::POUND => {
                let lo = self.span.lo;
                self.bump();
                self.expect(&token::LBRACKET);
                let meta_item = self.parse_meta_item();
                self.expect(&token::RBRACKET);
                let hi = self.span.hi;
                (mk_sp(lo, hi), meta_item)
            }
            _ => {
                self.fatal(fmt!("expected `#` but found `%s`",
                                self.this_token_to_str()));
            }
        };
        let style = if permit_inner && *self.token == token::SEMI {
            self.bump();
            ast::AttrInner
        } else {
            ast::AttrOuter
        };
        return spanned {
            span: span,
            node: ast::Attribute_ {
                style: style,
                value: value,
                is_sugared_doc: false
            }
        };
    }

    // Parse attributes that appear after the opening of an item, each
    // terminated by a semicolon. In addition to a vector of inner attributes,
    // this function also returns a vector that may contain the first outer
    // attribute of the next item (since we can't know whether the attribute
    // is an inner attribute of the containing item or an outer attribute of
    // the first contained item until we see the semi).

    // matches inner_attrs* outer_attr?
    // you can make the 'next' field an Option, but the result is going to be
    // more useful as a vector.
    fn parse_inner_attrs_and_next(&self)
                                  -> (~[ast::Attribute], ~[ast::Attribute]) {
        let mut inner_attrs: ~[ast::Attribute] = ~[];
        let mut next_outer_attrs: ~[ast::Attribute] = ~[];
        loop {
            let attr = match *self.token {
                token::INTERPOLATED(token::nt_attr(*)) => {
                    self.parse_attribute(true)
                }
                token::POUND => {
                    if self.look_ahead(1, |t| *t != token::LBRACKET) {
                        // This is an extension
                        break;
                    }
                    self.parse_attribute(true)
                }
                token::DOC_COMMENT(s) => {
                    self.bump();
                    ::attr::mk_sugared_doc_attr(self.id_to_str(s),
                                                self.span.lo,
                                                self.span.hi)
                }
                _ => {
                    break;
                }
            };
            if attr.node.style == ast::AttrInner {
                inner_attrs.push(attr);
            } else {
                next_outer_attrs.push(attr);
                break;
            }
        }
        (inner_attrs, next_outer_attrs)
    }

    // matches meta_item = IDENT
    // | IDENT = lit
    // | IDENT meta_seq
    fn parse_meta_item(&self) -> @ast::MetaItem {
        let lo = self.span.lo;
        let name = self.id_to_str(self.parse_ident());
        match *self.token {
            token::EQ => {
                self.bump();
                let lit = self.parse_lit();
                let hi = self.span.hi;
                @spanned(lo, hi, ast::MetaNameValue(name, lit))
            }
            token::LPAREN => {
                let inner_items = self.parse_meta_seq();
                let hi = self.span.hi;
                @spanned(lo, hi, ast::MetaList(name, inner_items))
            }
            _ => {
                let hi = self.last_span.hi;
                @spanned(lo, hi, ast::MetaWord(name))
            }
        }
    }

    // matches meta_seq = ( COMMASEP(meta_item) )
    fn parse_meta_seq(&self) -> ~[@ast::MetaItem] {
        self.parse_seq(&token::LPAREN,
                       &token::RPAREN,
                       seq_sep_trailing_disallowed(token::COMMA),
                       |p| p.parse_meta_item()).node
    }

    fn parse_optional_meta(&self) -> ~[@ast::MetaItem] {
        match *self.token {
            token::LPAREN => self.parse_meta_seq(),
            _ => ~[]
        }
    }
}
