/*
 * This file is part of the API Extractor project.
 *
 * Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 * Copyright 2005 Roberto Raggi <roberto@kdevelop.org>
 *
 * Contact: PySide team <contact@pyside.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#ifndef PP_ENGINE_BITS_H
#define PP_ENGINE_BITS_H

#include "pp.h"
#include <sys/stat.h>
#include <cstdio>
#include <iostream>

namespace rpp
{

inline std::string pp::fix_file_path(std::string const &filename) const
{
#if defined (PP_OS_WIN)
    std::string s = filename;
    for (std::string::iterator it = s.begin(); it != s.end(); ++it) {
        if (*it == '/')
            *it = '\\';
    }
    return s;
#else
    return filename;
#endif
}

inline bool pp::is_absolute(std::string const &filename) const
{
#if defined(PP_OS_WIN)
    return filename.length() >= 3
           && filename.at(1) == ':'
           && (filename.at(2) == '\\' || filename.at(2) == '/');
#else
    return filename.length() >= 1
           && filename.at(0) == '/';
#endif
}

template <typename _OutputIterator>
void pp::file(std::string const &filename, _OutputIterator __result)
{
    FILE *fp = std::fopen(filename.c_str(), "rb");
    if (fp != 0) {
        std::string was = env.current_file;
        env.current_file = filename;
        file(fp, __result);
        env.current_file = was;
    }
    //else
    //std::cerr << "** WARNING file ``" << filename << " not found!" << std::endl;
}

template <typename _OutputIterator>
void pp::file(FILE *fp, _OutputIterator __result)
{
    assert(fp != 0);

#if defined (HAVE_MMAP)
    struct stat st;
    fstat(FILENO(fp), &st);
    std::size_t size = st.st_size;
    char *buffer = 0;
    buffer = (char *) ::mmap(0, size, PROT_READ, MAP_SHARED, FILENO(fp), 0);
    fclose(fp);
    if (!buffer || buffer == (char*) - 1)
        return;
    this->operator()(buffer, buffer + size, __result);
    ::munmap(buffer, size);
#else
    std::string buffer;
    while (!feof(fp)) {
        char tmp[1024];
        int read = (int) fread(tmp, sizeof(char), 1023, fp);
        tmp[read] = '\0';
        buffer += tmp;
    }
    fclose(fp);
    this->operator()(buffer.c_str(), buffer.c_str() + buffer.size(), __result);
#endif
}

template <typename _InputIterator>
bool pp::find_header_protection(_InputIterator __first, _InputIterator __last, std::string *__prot)
{
    int was = env.current_line;

    while (__first != __last) {
        if (pp_isspace(*__first)) {
            if (*__first == '\n')
                ++env.current_line;

            ++__first;
        } else if (_PP_internal::comment_p(__first, __last)) {
            __first = skip_comment_or_divop(__first, __last);
            env.current_line += skip_comment_or_divop.lines;
        } else if (*__first == '#') {
            __first = skip_blanks(++__first, __last);
            env.current_line += skip_blanks.lines;

            if (__first != __last && *__first == 'i') {
                _InputIterator __begin = __first;
                __first = skip_identifier(__begin, __last);
                env.current_line += skip_identifier.lines;

                std::string __directive(__begin, __first);

                if (__directive == "ifndef") {
                    __first = skip_blanks(__first, __last);
                    env.current_line += skip_blanks.lines;

                    __begin = __first;
                    __first = skip_identifier(__first, __last);
                    env.current_line += skip_identifier.lines;

                    if (__begin != __first && __first != __last) {
                        __prot->assign(__begin, __first);
                        return true;
                    }
                }
            }
            break;
        } else
            break;
    }

    env.current_line = was;
    return false;
}

inline pp::PP_DIRECTIVE_TYPE pp::find_directive(char const *__directive, std::size_t __size) const
{
    switch (__size) {
    case 0:
        return PP_UNNAMED_DIRECTIVE;
    case 2:
        if (__directive[0] == 'i'
            && __directive[1] == 'f')
            return PP_IF;
        break;

    case 4:
        if (__directive[0] == 'e' && !strcmp(__directive, "elif"))
            return PP_ELIF;
        else if (__directive[0] == 'e' && !strcmp(__directive, "else"))
            return PP_ELSE;
        break;

    case 5:
        if (__directive[0] == 'i' && !strcmp(__directive, "ifdef"))
            return PP_IFDEF;
        else if (__directive[0] == 'u' && !strcmp(__directive, "undef"))
            return PP_UNDEF;
        else if (__directive[0] == 'e') {
            if (!strcmp(__directive, "endif"))
                return PP_ENDIF;
            else if (!strcmp(__directive, "error"))
                return PP_ERROR;
        }
        break;

    case 6:
        if (__directive[0] == 'i' && !strcmp(__directive, "ifndef"))
            return PP_IFNDEF;
        else if (__directive[0] == 'd' && !strcmp(__directive, "define"))
            return PP_DEFINE;
        else if (__directive[0] == 'p' && !strcmp(__directive, "pragma"))
            return PP_PRAGMA;
        break;

    case 7:
        if (__directive[0] == 'i' && !strcmp(__directive, "include"))
            return PP_INCLUDE;
        else if (!strcmp(__directive, "warning"))
            return PP_WARNING;
        break;

    case 12:
        if (__directive[0] == 'i' && !strcmp(__directive, "include_next"))
            return PP_INCLUDE_NEXT;
        break;

    default:
        break;
    }
    std::cerr << "** WARNING unknown directive '#" << __directive << "' at " << env.current_file << ":" << env.current_line << std::endl;
    return PP_UNKNOWN_DIRECTIVE;
}

inline bool pp::file_isdir(std::string const &__filename) const
{
    struct stat __st;
#if defined(PP_OS_WIN)
    if (stat(__filename.c_str(), &__st) == 0)
        return (__st.st_mode & _S_IFDIR) == _S_IFDIR;
    else
        return false;
#else
    if (lstat(__filename.c_str(), &__st) == 0)
        return (__st.st_mode & S_IFDIR) == S_IFDIR;
    else
        return false;
#endif
}

inline bool pp::file_exists(std::string const &__filename) const
{
    struct stat __st;
#if defined(PP_OS_WIN)
    return stat(__filename.c_str(), &__st) == 0;
#else
    return lstat(__filename.c_str(), &__st) == 0;
#endif
}

inline FILE *pp::find_include_file(std::string const &__input_filename, std::string *__filepath,
                                   INCLUDE_POLICY __include_policy, bool __skip_current_path) const
{
    assert(__filepath != 0);
    assert(! __input_filename.empty());

    __filepath->assign(__input_filename);

    if (is_absolute(*__filepath))
        return std::fopen(__filepath->c_str(), "r");

    if (! env.current_file.empty())
        _PP_internal::extract_file_path(env.current_file, __filepath);

    if (__include_policy == INCLUDE_LOCAL && ! __skip_current_path) {
        std::string __tmp(*__filepath);
        __tmp += __input_filename;

        if (file_exists(__tmp) && !file_isdir(__tmp)) {
            __filepath->append(__input_filename);
            return std::fopen(__filepath->c_str(), "r");
        }
    }

    std::vector<std::string>::const_iterator it = include_paths.begin();

    if (__skip_current_path) {
        it = std::find(include_paths.begin(), include_paths.end(), *__filepath);

        if (it != include_paths.end())
            ++it;

        else
            it = include_paths.begin();
    }

    for (; it != include_paths.end(); ++it) {
        if (__skip_current_path && it == include_paths.begin())
            continue;

        __filepath->assign(*it);
        __filepath->append(__input_filename);

        if (file_exists(*__filepath) && !file_isdir(*__filepath))
            return std::fopen(__filepath->c_str(), "r");

#ifdef Q_OS_MAC
        // try in Framework path on Mac, if there is a path in front
        // ### what about escaped slashes?
        size_t slashPos = __input_filename.find('/');
        if (slashPos != std::string::npos) {
            __filepath->assign(*it);
            __filepath->append(__input_filename.substr(0, slashPos));
            __filepath->append(".framework/Headers/");
            __filepath->append(__input_filename.substr(slashPos + 1, std::string::npos));
            std::cerr << *__filepath << "\n";

            if (file_exists(*__filepath) && !file_isdir(*__filepath))
                return fopen(__filepath->c_str(), "r");
        }
#endif // Q_OS_MAC
    }

    return 0;
}

template <typename _InputIterator, typename _OutputIterator>
_InputIterator pp::handle_directive(char const *__directive, std::size_t __size,
                                    _InputIterator __first, _InputIterator __last, _OutputIterator __result)
{
    __first = skip_blanks(__first, __last);

    PP_DIRECTIVE_TYPE d = find_directive(__directive, __size);
    switch (d) {
    case PP_UNNAMED_DIRECTIVE:
        /* There are many boost headers that include the character '#'
         * at the beginning of any line and just do nothing else with
         * that unnamed directive. Well, as that's not an error so
         * we'll just ignore this unnamed directive for now.
         */
        ++__last;
        return ++__first;
    case PP_DEFINE:
        if (! skipping())
            return handle_define(__first, __last);
        break;

    case PP_INCLUDE:
    case PP_INCLUDE_NEXT:
        if (! skipping())
            return handle_include(d == PP_INCLUDE_NEXT, __first, __last, __result);
        break;

    case PP_UNDEF:
        if (! skipping())
            return handle_undef(__first, __last);
        break;

    case PP_ELIF:
        return handle_elif(__first, __last);

    case PP_ELSE:
        return handle_else(__first, __last);

    case PP_ENDIF:
        return handle_endif(__first, __last);

    case PP_IF:
        return handle_if(__first, __last);

    case PP_IFDEF:
        return handle_ifdef(false, __first, __last);

    case PP_IFNDEF:
        return handle_ifdef(true, __first, __last);

    default:
        break;
    }

    return __first;
}

template <typename _InputIterator, typename _OutputIterator>
_InputIterator pp::handle_include(bool __skip_current_path, _InputIterator __first, _InputIterator __last,
                                  _OutputIterator __result)
{
    if (pp_isalpha(*__first) || *__first == '_') {
        pp_macro_expander expand_include(env);
        std::string name;
        name.reserve(255);
        expand_include(__first, __last, std::back_inserter(name));
        std::string::iterator it = skip_blanks(name.begin(), name.end());
        if (it != name.end() && !(*it == '<' || *it == '"')) {
            std::cerr << "** WARNING APIExtractor does not support the use "
                         "of #include directives without passing either "
                         "\"<path/to/header.h>\" or \"./path/to/header.h\", "
                         "for example. Invalid use at " << env.current_file
                      << ":" << env.current_line << "." << std::endl;
            return __last;
        }

        handle_include(__skip_current_path, it, name.end(), __result);
        return __first;
    }

    assert(*__first == '<' || *__first == '"');
    int quote = (*__first == '"') ? '"' : '>';
    ++__first;

    _InputIterator end_name = __first;
    for (; end_name != __last; ++end_name) {
        assert(*end_name != '\n');

        if (*end_name == quote)
            break;
    }

    std::string filename(__first, end_name);

#ifdef PP_OS_WIN
    std::replace(filename.begin(), filename.end(), '/', '\\');
#endif

    std::string filepath;
    FILE *fp = find_include_file(filename, &filepath, quote == '>' ? INCLUDE_GLOBAL : INCLUDE_LOCAL, __skip_current_path);

#if defined (PP_HOOK_ON_FILE_INCLUDED)
    PP_HOOK_ON_FILE_INCLUDED(env.current_file, fp ? filepath : filename, fp);
#endif

    if (fp != 0) {
        std::string old_file = env.current_file;
        env.current_file = filepath;
        int __saved_lines = env.current_line;

        env.current_line = 1;
        //output_line (env.current_file, 1, __result);

        file(fp, __result);

        // restore the file name and the line position
        env.current_file = old_file;
        env.current_line = __saved_lines;

        // sync the buffer
        _PP_internal::output_line(env.current_file, env.current_line, __result);
    }
#ifndef RPP_JAMBI
//   else
//     std::cerr << "*** WARNING " << filename << ": No such file or directory" << std::endl;
#endif

    return __first;
}

template <typename _InputIterator, typename _OutputIterator>
void pp::operator()(_InputIterator __first, _InputIterator __last, _OutputIterator __result)
{
#ifndef PP_NO_SMART_HEADER_PROTECTION
    std::string __prot;
    __prot.reserve(255);
    pp_fast_string __tmp(__prot.c_str(), __prot.size());

    if (find_header_protection(__first, __last, &__prot)
        && env.resolve(&__tmp) != 0) {
        // std::cerr << "** DEBUG found header protection:" << __prot << std::endl;
        return;
    }
#endif

    env.current_line = 1;
    char __buffer[512];

    while (true) {
        __first = skip_blanks(__first, __last);
        env.current_line += skip_blanks.lines;

        if (__first == __last)
            break;
        else if (*__first == '#') {
            assert(*__first == '#');
            __first = skip_blanks(++__first, __last);
            env.current_line += skip_blanks.lines;

            _InputIterator end_id = skip_identifier(__first, __last);
            env.current_line += skip_identifier.lines;
            std::size_t __size = end_id - __first;

            assert(__size < 512);
            char *__cp = __buffer;
            std::copy(__first, end_id, __cp);
            __cp[__size] = '\0';

            end_id = skip_blanks(end_id, __last);
            __first = skip(end_id, __last);

            int was = env.current_line;
            (void) handle_directive(__buffer, __size, end_id, __first, __result);

            if (env.current_line != was) {
                env.current_line = was;
                _PP_internal::output_line(env.current_file, env.current_line, __result);
            }
        } else if (*__first == '\n') {
            // ### compress the line
            *__result++ = *__first++;
            ++env.current_line;
        } else if (skipping())
            __first = skip(__first, __last);
        else {
            _PP_internal::output_line(env.current_file, env.current_line, __result);
            __first = expand(__first, __last, __result);
            env.current_line += expand.lines;

            if (expand.generated_lines)
                _PP_internal::output_line(env.current_file, env.current_line, __result);
        }
    }
}

inline pp::pp(pp_environment &__env):
        env(__env), expand(env)
{
    iflevel = 0;
    _M_skipping[iflevel] = 0;
    _M_true_test[iflevel] = 0;
}

inline std::back_insert_iterator<std::vector<std::string> > pp::include_paths_inserter()
{
    return std::back_inserter(include_paths);
}

inline std::vector<std::string>::iterator pp::include_paths_begin()
{
    return include_paths.begin();
}

inline std::vector<std::string>::iterator pp::include_paths_end()
{
    return include_paths.end();
}

inline std::vector<std::string>::const_iterator pp::include_paths_begin() const
{
    return include_paths.begin();
}

inline std::vector<std::string>::const_iterator pp::include_paths_end() const
{
    return include_paths.end();
}

inline void pp::push_include_path(std::string const &__path)
{
    if (__path.empty() || __path [__path.size() - 1] != PATH_SEPARATOR) {
        std::string __tmp(__path);
        __tmp += PATH_SEPARATOR;
        include_paths.push_back(__tmp);
    }

    else
        include_paths.push_back(__path);
}

template <typename _InputIterator>
_InputIterator pp::handle_define(_InputIterator __first, _InputIterator __last)
{
    pp_macro macro;
#if defined (PP_WITH_MACRO_POSITION)
    macro.file = pp_symbol::get(env.current_file);
#endif
    std::string definition;

    __first = skip_blanks(__first, __last);
    _InputIterator end_macro_name = skip_identifier(__first, __last);
    pp_fast_string const *macro_name = pp_symbol::get(__first, end_macro_name);
    __first = end_macro_name;

    if (__first != __last && *__first == '(') {
        macro.function_like = true;
        macro.formals.reserve(5);

        __first = skip_blanks(++__first, __last);  // skip '('
        _InputIterator arg_end = skip_identifier(__first, __last);
        if (__first != arg_end)
            macro.formals.push_back(pp_symbol::get(__first, arg_end));

        __first = skip_blanks(arg_end, __last);

        if (*__first == '.') {
            macro.variadics = true;
            while (*__first == '.')
                ++__first;
        }

        while (__first != __last && *__first == ',') {
            __first = skip_blanks(++__first, __last);

            arg_end = skip_identifier(__first, __last);
            if (__first != arg_end)
                macro.formals.push_back(pp_symbol::get(__first, arg_end));

            __first = skip_blanks(arg_end, __last);

            if (*__first == '.') {
                macro.variadics = true;
                while (*__first == '.')
                    ++__first;
            }
        }

        assert(*__first == ')');
        ++__first;
    }

    __first = skip_blanks(__first, __last);

    /* Note: Sometimes one can include a path between brackets (for
     * e.g., when defining a macro or so) so that we cannot simply
     * ignore that. The in_path variable will handle this situation.
     */
    bool in_path = false;
    while (__first != __last && *__first != '\n') {
        if ((*__first == '<' || *__first == '"') &&
            (*(__first + 1) != '*' && *(__first + 1) != '/')) {
            in_path = true;
            goto skip_path;
        }

        if (in_path) {
            if (*__first == '>' || *__first == '"') {
                in_path = false;
                goto skip_path;
            } else if (*__first == ',' || *__first == ' ' || *__first == '\\') {
                in_path = false;
                continue;
            }
        }

        if (*__first == '/') {
            if (*(__first + 1) != '*' && *(__first + 1) != '/') {
                in_path = true;
                goto skip_path;
            } else {
                __first = skip_comment_or_divop(__first, __last);
                env.current_line += skip_comment_or_divop.lines;
                if (__first == __last)
                    break;
            }
        }

        if (*__first == '\\') {
            _InputIterator __begin = __first;
            __begin = skip_blanks(++__begin, __last);

            if (__begin != __last && *__begin == '\n') {
                ++macro.lines;
                __first = skip_blanks(++__begin, __last);
                definition += ' ';
                continue;
            }
        }

skip_path:
        definition += *__first++;
    }

    macro.definition = pp_symbol::get(definition);
    env.bind(macro_name, macro);

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::skip(_InputIterator __first, _InputIterator __last)
{
    pp_skip_string_literal skip_string_literal;
    pp_skip_char_literal skip_char_literal;

    while (__first != __last && *__first != '\n') {
        if (*__first == '/') {
            __first = skip_comment_or_divop(__first, __last);
            env.current_line += skip_comment_or_divop.lines;
        } else if (*__first == '"') {
            __first = skip_string_literal(__first, __last);
            env.current_line += skip_string_literal.lines;
        } else if (*__first == '\'') {
            __first = skip_char_literal(__first, __last);
            env.current_line += skip_char_literal.lines;
        } else if (*__first == '\\') {
            __first = skip_blanks(++__first, __last);
            env.current_line += skip_blanks.lines;

            if (__first != __last && *__first == '\n') {
                ++__first;
                ++env.current_line;
            }
        } else
            ++__first;
    }

    return __first;
}

inline bool pp::test_if_level()
{
    bool result = !_M_skipping[iflevel++];
    _M_skipping[iflevel] = _M_skipping[iflevel - 1];
    _M_true_test[iflevel] = false;
    return result;
}

inline int pp::skipping() const
{
    return _M_skipping[iflevel];
}

template <typename _InputIterator>
_InputIterator pp::eval_primary(_InputIterator __first, _InputIterator __last, Value *result)
{
    bool expect_paren = false;
    int token;
    __first = next_token(__first, __last, &token);

    switch (token) {
    case TOKEN_NUMBER:
        result->set_long(token_value);
        break;

    case TOKEN_UNUMBER:
        result->set_ulong(token_uvalue);
        break;

    case TOKEN_DEFINED:
        __first = next_token(__first, __last, &token);

        if (token == '(') {
            expect_paren = true;
            __first = next_token(__first, __last, &token);
        }

        if (token != TOKEN_IDENTIFIER) {
            std::cerr << "** WARNING expected ``identifier'' found:" << char(token) << std::endl;
            result->set_long(0);
            break;
        }

        result->set_long(env.resolve(token_text->c_str(), token_text->size()) != 0);

        next_token(__first, __last, &token);  // skip '('

        if (expect_paren) {
            _InputIterator next = next_token(__first, __last, &token);
            if (token != ')')
                std::cerr << "** WARNING expected ``)''" << std::endl;
            else
                __first = next;
        }
        break;

    case TOKEN_IDENTIFIER:
        result->set_long(0);
        break;

    case '-':
        __first = eval_primary(__first, __last, result);
        result->set_long(- result->l);
        return __first;

    case '+':
        __first = eval_primary(__first, __last, result);
        return __first;

    case '!':
        __first = eval_primary(__first, __last, result);
        result->set_long(result->is_zero());
        return __first;

    case '(':
        __first = eval_constant_expression(__first, __last, result);
        next_token(__first, __last, &token);

        if (token != ')') {
            std::cerr << "** WARNING expected ``)'' = " << token << " (at "
                      << env.current_file << ":" << env.current_line
                      << ")." << std::endl;
        } else {
            __first = next_token(__first, __last, &token);
        }
        break;

    default:
        result->set_long(0);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_multiplicative(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_primary(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == '*' || token == '/' || token == '%') {
        Value value;
        __first = eval_primary(next, __last, &value);

        if (token == '*')
            result->op_mult(value);
        else if (token == '/') {
            if (value.is_zero()) {
                std::cerr << "** WARNING division by zero" << std::endl;
                result->set_long(0);
            } else
                result->op_div(value);
        } else {
            if (value.is_zero()) {
                std::cerr << "** WARNING division by zero" << std::endl;
                result->set_long(0);
            } else
                result->op_mod(value);
        }
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_additive(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_multiplicative(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == '+' || token == '-') {
        Value value;
        __first = eval_multiplicative(next, __last, &value);

        if (token == '+')
            result->op_add(value);
        else
            result->op_sub(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_shift(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_additive(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == TOKEN_LT_LT || token == TOKEN_GT_GT) {
        Value value;
        __first = eval_additive(next, __last, &value);

        if (token == TOKEN_LT_LT)
            result->op_lhs(value);
        else
            result->op_rhs(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_relational(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_shift(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == '<'
           || token == '>'
           || token == TOKEN_LT_EQ
           || token == TOKEN_GT_EQ) {
        Value value;
        __first = eval_shift(next, __last, &value);

        switch (token) {
        default:
            assert(0);
            break;

        case '<':
            result->op_lt(value);
            break;

        case '>':
            result->op_gt(value);
            break;

        case TOKEN_LT_EQ:
            result->op_le(value);
            break;

        case TOKEN_GT_EQ:
            result->op_ge(value);
            break;
        }
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_equality(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_relational(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == TOKEN_EQ_EQ || token == TOKEN_NOT_EQ) {
        Value value;
        __first = eval_relational(next, __last, &value);

        if (token == TOKEN_EQ_EQ)
            result->op_eq(value);
        else
            result->op_ne(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_and(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_equality(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == '&') {
        Value value;
        __first = eval_equality(next, __last, &value);
        result->op_bit_and(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_xor(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_and(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == '^') {
        Value value;
        __first = eval_and(next, __last, &value);
        result->op_bit_xor(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_or(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_xor(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == '|') {
        Value value;
        __first = eval_xor(next, __last, &value);
        result->op_bit_or(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_logical_and(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_or(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == TOKEN_AND_AND) {
        Value value;
        __first = eval_or(next, __last, &value);
        result->op_and(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_logical_or(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_logical_and(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    while (token == TOKEN_OR_OR) {
        Value value;
        __first = eval_logical_and(next, __last, &value);
        result->op_or(value);
        next = next_token(__first, __last, &token);
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_constant_expression(_InputIterator __first, _InputIterator __last, Value *result)
{
    __first = eval_logical_or(__first, __last, result);

    int token;
    _InputIterator next = next_token(__first, __last, &token);

    if (token == '?') {
        Value left_value;
        __first = eval_constant_expression(next, __last, &left_value);
        __first = skip_blanks(__first, __last);

        __first = next_token(__first, __last, &token);
        if (token == ':') {
            Value right_value;
            __first = eval_constant_expression(__first, __last, &right_value);

            *result = !result->is_zero() ? left_value : right_value;
        } else {
            std::cerr << "** WARNING expected ``:'' = " << int (token) << std::endl;
            *result = left_value;
        }
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::eval_expression(_InputIterator __first, _InputIterator __last, Value *result)
{
    return __first = eval_constant_expression(skip_blanks(__first, __last), __last, result);
}

template <typename _InputIterator>
_InputIterator pp::handle_if(_InputIterator __first, _InputIterator __last)
{
    if (test_if_level()) {
        pp_macro_expander expand_condition(env);
        std::string condition;
        condition.reserve(255);
        expand_condition(skip_blanks(__first, __last), __last, std::back_inserter(condition));

        Value result;
        result.set_long(0);
        eval_expression(condition.c_str(), condition.c_str() + condition.size(), &result);

        _M_true_test[iflevel] = !result.is_zero();
        _M_skipping[iflevel] = result.is_zero();
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::handle_else(_InputIterator __first, _InputIterator /*__last*/)
{
    if (iflevel == 0 && !skipping()) {
        std::cerr << "** WARNING #else without #if" << std::endl;
    } else if (iflevel > 0 && _M_skipping[iflevel - 1]) {
        _M_skipping[iflevel] = true;
    } else {
        _M_skipping[iflevel] = _M_true_test[iflevel];
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::handle_elif(_InputIterator __first, _InputIterator __last)
{
    assert(iflevel > 0);

    if (iflevel == 0 && !skipping()) {
        std::cerr << "** WARNING #else without #if" << std::endl;
    } else if (!_M_true_test[iflevel] && !_M_skipping[iflevel - 1]) {
        Value result;
        __first = eval_expression(__first, __last, &result);
        _M_true_test[iflevel] = !result.is_zero();
        _M_skipping[iflevel] = result.is_zero();
    } else {
        _M_skipping[iflevel] = true;
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::handle_endif(_InputIterator __first, _InputIterator /*__last*/)
{
    if (iflevel == 0 && !skipping()) {
        std::cerr << "** WARNING #endif without #if" << std::endl;
    } else {
        _M_skipping[iflevel] = 0;
        _M_true_test[iflevel] = 0;

        --iflevel;
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::handle_ifdef(bool check_undefined, _InputIterator __first, _InputIterator __last)
{
    if (test_if_level()) {
        _InputIterator end_macro_name = skip_identifier(__first, __last);

        std::size_t __size;
#if defined(__SUNPRO_CC)
        std::distance(__first, end_macro_name, __size);
#else
        __size = std::distance(__first, end_macro_name);
#endif
        assert(__size < 256);

        char __buffer [256];
        std::copy(__first, end_macro_name, __buffer);

        bool value = env.resolve(__buffer, __size) != 0;

        __first = end_macro_name;

        if (check_undefined)
            value = !value;

        _M_true_test[iflevel] = value;
        _M_skipping[iflevel] = !value;
    }

    return __first;
}

template <typename _InputIterator>
_InputIterator pp::handle_undef(_InputIterator __first, _InputIterator __last)
{
    __first = skip_blanks(__first, __last);
    _InputIterator end_macro_name = skip_identifier(__first, __last);
    assert(end_macro_name != __first);

    std::size_t __size;
#if defined(__SUNPRO_CC)
    std::distance(__first, end_macro_name, __size);
#else
    __size = std::distance(__first, end_macro_name);
#endif

    assert(__size < 256);

    char __buffer [256];
    std::copy(__first, end_macro_name, __buffer);

    pp_fast_string const __tmp(__buffer, __size);
    env.unbind(&__tmp);

    __first = end_macro_name;

    return __first;
}

template <typename _InputIterator>
char pp::peek_char(_InputIterator __first, _InputIterator __last)
{
    if (__first == __last)
        return 0;

    return *++__first;
}

template <typename _InputIterator>
_InputIterator pp::next_token(_InputIterator __first, _InputIterator __last, int *kind)
{
    __first = skip_blanks(__first, __last);

    if (__first == __last) {
        *kind = 0;
        return __first;
    }

    char ch = *__first;
    char ch2 = peek_char(__first, __last);

    switch (ch) {
    case '/':
        if (ch2 == '/' || ch2 == '*') {
            __first = skip_comment_or_divop(__first, __last);
            return next_token(__first, __last, kind);
        }
        ++__first;
        *kind = '/';
        break;

    case '<':
        ++__first;
        if (ch2 == '<') {
            ++__first;
            *kind = TOKEN_LT_LT;
        } else if (ch2 == '=') {
            ++__first;
            *kind = TOKEN_LT_EQ;
        } else
            *kind = '<';

        return __first;

    case '>':
        ++__first;
        if (ch2 == '>') {
            ++__first;
            *kind = TOKEN_GT_GT;
        } else if (ch2 == '=') {
            ++__first;
            *kind = TOKEN_GT_EQ;
        } else
            *kind = '>';

        return __first;

    case '!':
        ++__first;
        if (ch2 == '=') {
            ++__first;
            *kind = TOKEN_NOT_EQ;
        } else
            *kind = '!';

        return __first;

    case '=':
        ++__first;
        if (ch2 == '=') {
            ++__first;
            *kind = TOKEN_EQ_EQ;
        } else
            *kind = '=';

        return __first;

    case '|':
        ++__first;
        if (ch2 == '|') {
            ++__first;
            *kind = TOKEN_OR_OR;
        } else
            *kind = '|';

        return __first;

    case '&':
        ++__first;
        if (ch2 == '&') {
            ++__first;
            *kind = TOKEN_AND_AND;
        } else
            *kind = '&';

        return __first;

    default:
        if (pp_isalpha(ch) || ch == '_') {
            _InputIterator end = skip_identifier(__first, __last);
            _M_current_text.assign(__first, end);

            token_text = &_M_current_text;
            __first = end;

            if (*token_text == "defined")
                *kind = TOKEN_DEFINED;
            else
                *kind = TOKEN_IDENTIFIER;
        } else if (pp_isdigit(ch)) {
            _InputIterator end = skip_number(__first, __last);
            std::string __str(__first, __last);
            char ch = __str [__str.size() - 1];
            if (ch == 'u' || ch == 'U') {
                token_uvalue = strtoul(__str.c_str(), 0, 0);
                *kind = TOKEN_UNUMBER;
            } else {
                token_value = strtol(__str.c_str(), 0, 0);
                *kind = TOKEN_NUMBER;
            }
            __first = end;
        } else
            *kind = *__first++;
    }

    return __first;
}

} // namespace rpp

#endif // PP_ENGINE_BITS_H

// kate: space-indent on; indent-width 2; replace-tabs on;
