///////////////////////////////////////////////////////////////////////////////
// parse_charset.hpp
//
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_XPRESSIVE_DETAIL_DYNAMIC_PARSE_CHARSET_HPP_EAN_10_04_2005
#define BOOST_XPRESSIVE_DETAIL_DYNAMIC_PARSE_CHARSET_HPP_EAN_10_04_2005

// MS compatible compilers support #pragma once
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif

#include <boost/integer.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/throw_exception.hpp>
#include <boost/numeric/conversion/converter.hpp>
#include <boost/xpressive/detail/detail_fwd.hpp>
#include <boost/xpressive/detail/dynamic/parser_enum.hpp>
#include <boost/xpressive/detail/utility/literals.hpp>
#include <boost/xpressive/detail/utility/chset/chset.hpp>
#include <boost/xpressive/regex_constants.hpp>

namespace boost { namespace xpressive { namespace detail
{

enum escape_type
{
    escape_char
  , escape_mark
  , escape_class
};

///////////////////////////////////////////////////////////////////////////////
// escape_value
//
template<typename Char, typename Class>
struct escape_value
{
    Char ch_;
    int mark_nbr_;
    Class class_;
    escape_type type_;
};

///////////////////////////////////////////////////////////////////////////////
// char_overflow_handler
//
struct char_overflow_handler
{
    void operator ()(numeric::range_check_result result) const // throw(regex_error)
    {
        if(numeric::cInRange != result)
        {
            BOOST_THROW_EXCEPTION(
                regex_error(
                    regex_constants::error_escape
                  , "character escape too large to fit in target character type"
                )
            );
        }
    }
};

///////////////////////////////////////////////////////////////////////////////
// parse_escape
//
template<typename FwdIter, typename CompilerTraits>
escape_value<typename iterator_value<FwdIter>::type, typename CompilerTraits::regex_traits::char_class_type>
parse_escape(FwdIter &begin, FwdIter end, CompilerTraits &tr)
{
    using namespace regex_constants;
    typedef typename iterator_value<FwdIter>::type char_type;
    typedef typename CompilerTraits::regex_traits regex_traits;
    typedef typename regex_traits::char_class_type char_class_type;

    // define an unsigned type the same size as char_type
    typedef typename boost::uint_t<CHAR_BIT * sizeof(char_type)>::least uchar_t;
    BOOST_MPL_ASSERT_RELATION(sizeof(uchar_t), ==, sizeof(char_type));
    typedef numeric::conversion_traits<uchar_t, int> converstion_traits;

    BOOST_XPR_ENSURE_(begin != end, error_escape, "unexpected end of pattern found");
    numeric::converter<int, uchar_t, converstion_traits, char_overflow_handler> converter;
    escape_value<char_type,char_class_type> esc = { 0, 0, 0, escape_char };
    bool const icase = (0 != (regex_constants::icase_ & tr.flags()));
    regex_traits const &rxtraits = tr.traits();
    FwdIter tmp;

    esc.class_ = rxtraits.lookup_classname(begin, begin + 1, icase);
    if(0 != esc.class_)
    {
        esc.type_ = escape_class;
        return esc;
    }

    if(-1 != rxtraits.value(*begin, 8))
    {
        esc.ch_ = converter(toi(begin, end, rxtraits, 8, 0777));
        return esc;
    }

    switch(*begin)
    {
    // bell character
    case BOOST_XPR_CHAR_(char_type, 'a'):
        esc.ch_ = BOOST_XPR_CHAR_(char_type, '\a');
        ++begin;
        break;
    // escape character
    case BOOST_XPR_CHAR_(char_type, 'e'):
        esc.ch_ = converter(27);
        ++begin;
        break;
    // control character
    case BOOST_XPR_CHAR_(char_type, 'c'):
        BOOST_XPR_ENSURE_(++begin != end, error_escape, "unexpected end of pattern found");
        BOOST_XPR_ENSURE_
        (
            rxtraits.in_range(BOOST_XPR_CHAR_(char_type, 'a'), BOOST_XPR_CHAR_(char_type, 'z'), *begin)
         || rxtraits.in_range(BOOST_XPR_CHAR_(char_type, 'A'), BOOST_XPR_CHAR_(char_type, 'Z'), *begin)
          , error_escape
          , "invalid escape control letter; must be one of a-z or A-Z"
        );
        // Convert to character according to ECMA-262, section 15.10.2.10:
        esc.ch_ = converter(*begin % 32);
        ++begin;
        break;
    // formfeed character
    case BOOST_XPR_CHAR_(char_type, 'f'):
        esc.ch_ = BOOST_XPR_CHAR_(char_type, '\f');
        ++begin;
        break;
    // newline
    case BOOST_XPR_CHAR_(char_type, 'n'):
        esc.ch_ = BOOST_XPR_CHAR_(char_type, '\n');
        ++begin;
        break;
    // return
    case BOOST_XPR_CHAR_(char_type, 'r'):
        esc.ch_ = BOOST_XPR_CHAR_(char_type, '\r');
        ++begin;
        break;
    // horizontal tab
    case BOOST_XPR_CHAR_(char_type, 't'):
        esc.ch_ = BOOST_XPR_CHAR_(char_type, '\t');
        ++begin;
        break;
    // vertical tab
    case BOOST_XPR_CHAR_(char_type, 'v'):
        esc.ch_ = BOOST_XPR_CHAR_(char_type, '\v');
        ++begin;
        break;
    // hex escape sequence
    case BOOST_XPR_CHAR_(char_type, 'x'):
        BOOST_XPR_ENSURE_(++begin != end, error_escape, "unexpected end of pattern found");
        tmp = begin;
        esc.ch_ = converter(toi(begin, end, rxtraits, 16, 0xff));
        BOOST_XPR_ENSURE_(2 == std::distance(tmp, begin), error_escape, "invalid hex escape : "
            "must be \\x HexDigit HexDigit");
        break;
    // Unicode escape sequence
    case BOOST_XPR_CHAR_(char_type, 'u'):
        BOOST_XPR_ENSURE_(++begin != end, error_escape, "unexpected end of pattern found");
        tmp = begin;
        esc.ch_ = converter(toi(begin, end, rxtraits, 16, 0xffff));
        BOOST_XPR_ENSURE_(4 == std::distance(tmp, begin), error_escape, "invalid Unicode escape : "
            "must be \\u HexDigit HexDigit HexDigit HexDigit");
        break;
    // backslash
    case BOOST_XPR_CHAR_(char_type, '\\'):
        //esc.ch_ = BOOST_XPR_CHAR_(char_type, '\\');
        //++begin;
        //break;
    // all other escaped characters represent themselves
    default:
        esc.ch_ = *begin;
        ++begin;
        break;
    }

    return esc;
}

//////////////////////////////////////////////////////////////////////////
// parse_charset
//
template<typename FwdIter, typename RegexTraits, typename CompilerTraits>
inline void parse_charset
(
    FwdIter &begin
  , FwdIter end
  , compound_charset<RegexTraits> &chset
  , CompilerTraits &tr
)
{
    using namespace regex_constants;
    typedef typename RegexTraits::char_type char_type;
    typedef typename RegexTraits::char_class_type char_class_type;
    BOOST_ASSERT(begin != end);
    RegexTraits const &rxtraits = tr.traits();
    bool const icase = (0 != (regex_constants::icase_ & tr.flags()));
    FwdIter iprev = FwdIter();
    escape_value<char_type, char_class_type> esc = {0, 0, 0, escape_char};
    bool invert = false;

    // check to see if we have an inverse charset
    if(begin != end && token_charset_invert == tr.get_charset_token(iprev = begin, end))
    {
        begin = iprev;
        invert = true;
    }

    // skip the end token if-and-only-if it is the first token in the charset
    if(begin != end && token_charset_end == tr.get_charset_token(iprev = begin, end))
    {
        for(; begin != iprev; ++begin)
        {
            chset.set_char(*begin, rxtraits, icase);
        }
    }

    compiler_token_type tok;
    char_type ch_prev = char_type(), ch_next = char_type();
    bool have_prev = false;

    BOOST_XPR_ENSURE_(begin != end, error_brack, "unexpected end of pattern found");

    // remember the current position and grab the next token
    iprev = begin;
    tok = tr.get_charset_token(begin, end);
    do
    {
        BOOST_XPR_ENSURE_(begin != end, error_brack, "unexpected end of pattern found");

        if(token_charset_hyphen == tok && have_prev)
        {
            // remember the current position
            FwdIter iprev2 = begin;
            have_prev = false;

            // ch_prev is lower bound of a range
            switch(tr.get_charset_token(begin, end))
            {
            case token_charset_hyphen:
            case token_charset_invert:
                begin = iprev2; // un-get these tokens and fall through
            case token_literal:
                ch_next = *begin++;
                BOOST_XPR_ENSURE_(ch_prev <= ch_next, error_range, "invalid charset range");
                chset.set_range(ch_prev, ch_next, rxtraits, icase);
                continue;
            case token_charset_backspace:
                ch_next = char_type(8); // backspace
                BOOST_XPR_ENSURE_(ch_prev <= ch_next, error_range, "invalid charset range");
                chset.set_range(ch_prev, ch_next, rxtraits, icase);
                continue;
            case token_escape:
                esc = parse_escape(begin, end, tr);
                if(escape_char == esc.type_)
                {
                    BOOST_XPR_ENSURE_(ch_prev <= esc.ch_, error_range, "invalid charset range");
                    chset.set_range(ch_prev, esc.ch_, rxtraits, icase);
                    continue;
                }
            case token_charset_end: // fall through
            default:                // not a range.
                begin = iprev;      // backup to hyphen token
                chset.set_char(ch_prev, rxtraits, icase);
                chset.set_char(*begin++, rxtraits, icase);
                continue;
            }
        }

        if(have_prev)
        {
            chset.set_char(ch_prev, rxtraits, icase);
            have_prev = false;
        }

        switch(tok)
        {
        case token_charset_hyphen:
        case token_charset_invert:
        case token_charset_end:
        case token_posix_charset_end:
            begin = iprev; // un-get these tokens
            ch_prev = *begin++;
            have_prev = true;
            continue;

        case token_charset_backspace:
            ch_prev = char_type(8); // backspace
            have_prev = true;
            continue;

        case token_posix_charset_begin:
            {
                FwdIter tmp = begin, start = begin;
                bool invert = (token_charset_invert == tr.get_charset_token(tmp, end));
                if(invert)
                {
                    begin = start = tmp;
                }
                while(token_literal == (tok = tr.get_charset_token(begin, end)))
                {
                    tmp = ++begin;
                    BOOST_XPR_ENSURE_(begin != end, error_brack, "unexpected end of pattern found");
                }
                if(token_posix_charset_end == tok)
                {
                    char_class_type chclass = rxtraits.lookup_classname(start, tmp, icase);
                    BOOST_XPR_ENSURE_(0 != chclass, error_ctype, "unknown class name");
                    chset.set_class(chclass, invert);
                    continue;
                }
                begin = iprev; // un-get this token
                ch_prev = *begin++;
                have_prev = true;
            }
            continue;

        case token_escape:
            esc = parse_escape(begin, end, tr);
            if(escape_char == esc.type_)
            {
                ch_prev = esc.ch_;
                have_prev = true;
            }
            else if(escape_class == esc.type_)
            {
                char_class_type upper_ = lookup_classname(rxtraits, "upper");
                BOOST_ASSERT(0 != upper_);
                chset.set_class(esc.class_, rxtraits.isctype(*begin++, upper_));
            }
            else
            {
                BOOST_ASSERT(false);
            }
            continue;

        default:
            ch_prev = *begin++;
            have_prev = true;
            continue;
        }
    }
    while(BOOST_XPR_ENSURE_((iprev = begin) != end, error_brack, "unexpected end of pattern found"),
          token_charset_end != (tok = tr.get_charset_token(begin, end)));

    if(have_prev)
    {
        chset.set_char(ch_prev, rxtraits, icase);
    }

    if(invert)
    {
        chset.inverse();
    }
}

}}} // namespace boost::xpressive::detail

#endif
