CallResult ExtVM::call(CallParameters& _p)
{
    Executive e{m_s, envInfo(), m_sealEngine, depth + 1};
    if (!e.call(_p, gasPrice, origin))
    {
        go(depth, e, _p.onOp);
        e.accrueSubState(sub);
    }
    _p.gas = e.gas();

    return {transactionExceptionToEvmcStatusCode(e.getException()), e.takeOutput()};
}

// Aleth: Ethereum C++ client, tools and libraries.
#include "ExtVM.h"
#include "LastBlockHashesFace.h"
#include <boost/thread.hpp>
#include <exception>

using namespace dev;
using namespace dev::eth;

namespace // anonymous
{

static unsigned const c_depthLimit = 1024;

/// Upper bound of stack space needed by single CALL/CREATE execution. Set experimentally.
static size_t const c_singleExecutionStackSize = 100 * 1024;

/// Standard thread stack size.
static size_t const c_defaultStackSize =
#if defined(__linux)
     8 * 1024 * 1024;
#elif defined(_WIN32)
    16 * 1024 * 1024;
#else
    512 * 1024; // OSX and other OSs
#endif

/// Stack overhead prior to allocation.
static size_t const c_entryOverhead = 128 * 1024;

/// On what depth execution should be offloaded to additional separated stack space.
static unsigned const c_offloadPoint = (c_defaultStackSize - c_entryOverhead) / c_singleExecutionStackSize;

void goOnOffloadedStack(Executive& _e, OnOpFunc const& _onOp)
{
    // Set new stack size enouth to handle the rest of the calls up to the limit.
    boost::thread::attributes attrs;
    attrs.set_stack_size((c_depthLimit - c_offloadPoint) * c_singleExecutionStackSize);

    // Create new thread with big stack and join immediately.
    // TODO: It is possible to switch the implementation to Boost.Context or similar when the API is stable.
    boost::exception_ptr exception;
    boost::thread{attrs, [&]{
        try
        {
            _e.go(_onOp);
        }
        catch (...)
        {
            exception = boost::current_exception(); // Catch all exceptions to be rethrown in parent thread.
        }
    }}.join();
    if (exception)
        boost::rethrow_exception(exception);
}

void go(unsigned _depth, Executive& _e, OnOpFunc const& _onOp)
{
    // If in the offloading point we need to switch to additional separated stack space.
    // Current stack is too small to handle more CALL/CREATE executions.
    // It needs to be done only once as newly allocated stack space it enough to handle
    // the rest of the calls up to the depth limit (c_depthLimit).

    if (_depth == c_offloadPoint)
    {
        cnote << "Stack offloading (depth: " << c_offloadPoint << ")";
        goOnOffloadedStack(_e, _onOp);
    }
    else
        _e.go(_onOp);
}

evmc_status_code transactionExceptionToEvmcStatusCode(TransactionException ex) noexcept
{
    switch (ex)
    {
    case TransactionException::None:
        return EVMC_SUCCESS;

    case TransactionException::RevertInstruction:
        return EVMC_REVERT;

    case TransactionException::OutOfGas:
        return EVMC_OUT_OF_GAS;

    case TransactionException::BadInstruction:
        return EVMC_UNDEFINED_INSTRUCTION;

    case TransactionException::OutOfStack:
        return EVMC_STACK_OVERFLOW;

    case TransactionException::StackUnderflow:
        return EVMC_STACK_UNDERFLOW;

    case TransactionException ::BadJumpDestination:
        return EVMC_BAD_JUMP_DESTINATION;

    default:
        return EVMC_FAILURE;
    }
}

} // anonymous namespace

size_t ExtVM::codeSizeAt(Address _a)
{
    return m_s.codeSize(_a);
}

h256 ExtVM::codeHashAt(Address _a)
{
    return exists(_a) ? m_s.codeHash(_a) : h256{};
}

void ExtVM::setStore(u256 _n, u256 _v)
{
    m_s.setStorage(myAddress, _n, _v);
}

CreateResult ExtVM::create(u256 _endowment, u256& io_gas, bytesConstRef _code, Instruction _op, u256 _salt, OnOpFunc const& _onOp)
{
    Executive e{m_s, envInfo(), m_sealEngine, depth + 1};
    bool result = false;
    if (_op == Instruction::CREATE)
        result = e.createOpcode(myAddress, _endowment, gasPrice, io_gas, _code, origin);
    else
    {
        assert(_op == Instruction::CREATE2);
        result = e.create2Opcode(myAddress, _endowment, gasPrice, io_gas, _code, origin, _salt);
    }

    if (!result)
    {
        go(depth, e, _onOp);
        e.accrueSubState(sub);
    }
    io_gas = e.gas();
    return {transactionExceptionToEvmcStatusCode(e.getException()), e.takeOutput(), e.newAddress()};
}

void ExtVM::selfdestruct(Address _a)
{
    // Why transfer is not used here? That caused a consensus issue before (see Quirk #2 in
    // http://example.com). There is one test case
    // witnessing the current consensus
    m_s.addBalance(_a, m_s.balance(myAddress));
    m_s.setBalance(myAddress, 0);
    ExtVMFace::selfdestruct(_a);
}

h256 ExtVM::blockHash(u256 _number)
{
    u256 const currentNumber = envInfo().number();

    if (_number >= currentNumber || _number < (std::max<u256>(256, currentNumber) - 256))
        return h256();

    if (currentNumber < m_sealEngine.chainParams().experimentalForkBlock + 256)
    {
        h256 const parentHash = envInfo().header().parentHash();
        h256s const lastHashes = envInfo().lastHashes().precedingHashes(parentHash);

        assert(lastHashes.size() > (unsigned)(currentNumber - 1 - _number));
        return lastHashes[(unsigned)(currentNumber - 1 - _number)];
    }

    u256 const nonce = m_s.getNonce(caller);
    u256 const gas = 1000000;
    Transaction tx(0, 0, gas, c_blockhashContractAddress, toBigEndian(_number), nonce);
    tx.forceSender(caller);

    ExecutionResult res;
    std::tie(res, std::ignore) = m_s.execute(envInfo(), m_sealEngine, tx, Permanence::Reverted);
    return h256(res.output);
}


try
        {
            _e.go(_onOp);
        }
        catch (...)
        {
            exception = boost::current_exception(); // Catch all exceptions to be rethrown in parent thread.
        }

// Aleth: Ethereum C++ client, tools and libraries.
#include "ExtVM.h"
#include "LastBlockHashesFace.h"
#include <boost/thread.hpp>
#include <exception>

using namespace dev;
using namespace dev::eth;

namespace // anonymous
{

static unsigned const c_depthLimit = 1024;

/// Upper bound of stack space needed by single CALL/CREATE execution. Set experimentally.
static size_t const c_singleExecutionStackSize = 100 * 1024;

/// Standard thread stack size.
static size_t const c_defaultStackSize =
#if defined(__linux)
     8 * 1024 * 1024;
#elif defined(_WIN32)
    16 * 1024 * 1024;
#else
    512 * 1024; // OSX and other OSs
#endif

/// Stack overhead prior to allocation.
static size_t const c_entryOverhead = 128 * 1024;

/// On what depth execution should be offloaded to additional separated stack space.
static unsigned const c_offloadPoint = (c_defaultStackSize - c_entryOverhead) / c_singleExecutionStackSize;

void goOnOffloadedStack(Executive& _e, OnOpFunc const& _onOp)
{
    // Set new stack size enouth to handle the rest of the calls up to the limit.
    boost::thread::attributes attrs;
    attrs.set_stack_size((c_depthLimit - c_offloadPoint) * c_singleExecutionStackSize);

    // Create new thread with big stack and join immediately.
    // TODO: It is possible to switch the implementation to Boost.Context or similar when the API is stable.
    boost::exception_ptr exception;
    boost::thread{attrs, [&]{

    }}.join();
    if (exception)
        boost::rethrow_exception(exception);
}

void go(unsigned _depth, Executive& _e, OnOpFunc const& _onOp)
{
    // If in the offloading point we need to switch to additional separated stack space.
    // Current stack is too small to handle more CALL/CREATE executions.
    // It needs to be done only once as newly allocated stack space it enough to handle
    // the rest of the calls up to the depth limit (c_depthLimit).

    if (_depth == c_offloadPoint)
    {
        cnote << "Stack offloading (depth: " << c_offloadPoint << ")";
        goOnOffloadedStack(_e, _onOp);
    }
    else
        _e.go(_onOp);
}

evmc_status_code transactionExceptionToEvmcStatusCode(TransactionException ex) noexcept
{
    switch (ex)
    {
    case TransactionException::None:
        return EVMC_SUCCESS;

    case TransactionException::RevertInstruction:
        return EVMC_REVERT;

    case TransactionException::OutOfGas:
        return EVMC_OUT_OF_GAS;

    case TransactionException::BadInstruction:
        return EVMC_UNDEFINED_INSTRUCTION;

    case TransactionException::OutOfStack:
        return EVMC_STACK_OVERFLOW;

    case TransactionException::StackUnderflow:
        return EVMC_STACK_UNDERFLOW;

    case TransactionException ::BadJumpDestination:
        return EVMC_BAD_JUMP_DESTINATION;

    default:
        return EVMC_FAILURE;
    }
}

} // anonymous namespace

CallResult ExtVM::call(CallParameters& _p)
{
    Executive e{m_s, envInfo(), m_sealEngine, depth + 1};
    if (!e.call(_p, gasPrice, origin))
    {
        go(depth, e, _p.onOp);
        e.accrueSubState(sub);
    }
    _p.gas = e.gas();

    return {transactionExceptionToEvmcStatusCode(e.getException()), e.takeOutput()};
}

size_t ExtVM::codeSizeAt(Address _a)
{
    return m_s.codeSize(_a);
}

h256 ExtVM::codeHashAt(Address _a)
{
    return exists(_a) ? m_s.codeHash(_a) : h256{};
}

void ExtVM::setStore(u256 _n, u256 _v)
{
    m_s.setStorage(myAddress, _n, _v);
}

CreateResult ExtVM::create(u256 _endowment, u256& io_gas, bytesConstRef _code, Instruction _op, u256 _salt, OnOpFunc const& _onOp)
{
    Executive e{m_s, envInfo(), m_sealEngine, depth + 1};
    bool result = false;
    if (_op == Instruction::CREATE)
        result = e.createOpcode(myAddress, _endowment, gasPrice, io_gas, _code, origin);
    else
    {
        assert(_op == Instruction::CREATE2);
        result = e.create2Opcode(myAddress, _endowment, gasPrice, io_gas, _code, origin, _salt);
    }

    if (!result)
    {
        go(depth, e, _onOp);
        e.accrueSubState(sub);
    }
    io_gas = e.gas();
    return {transactionExceptionToEvmcStatusCode(e.getException()), e.takeOutput(), e.newAddress()};
}

void ExtVM::selfdestruct(Address _a)
{
    // Why transfer is not used here? That caused a consensus issue before (see Quirk #2 in
    // http://example.com). There is one test case
    // witnessing the current consensus
    m_s.addBalance(_a, m_s.balance(myAddress));
    m_s.setBalance(myAddress, 0);
    ExtVMFace::selfdestruct(_a);
}

h256 ExtVM::blockHash(u256 _number)
{
    u256 const currentNumber = envInfo().number();

    if (_number >= currentNumber || _number < (std::max<u256>(256, currentNumber) - 256))
        return h256();

    if (currentNumber < m_sealEngine.chainParams().experimentalForkBlock + 256)
    {
        h256 const parentHash = envInfo().header().parentHash();
        h256s const lastHashes = envInfo().lastHashes().precedingHashes(parentHash);

        assert(lastHashes.size() > (unsigned)(currentNumber - 1 - _number));
        return lastHashes[(unsigned)(currentNumber - 1 - _number)];
    }

    u256 const nonce = m_s.getNonce(caller);
    u256 const gas = 1000000;
    Transaction tx(0, 0, gas, c_blockhashContractAddress, toBigEndian(_number), nonce);
    tx.forceSender(caller);

    ExecutionResult res;
    std::tie(res, std::ignore) = m_s.execute(envInfo(), m_sealEngine, tx, Permanence::Reverted);
    return h256(res.output);
}


PsiThemeModel::PsiThemeModel(PsiThemeProvider *provider, QObject *parent) :
    QAbstractListModel(parent), provider(provider)
{
    connect(&themeWatcher, SIGNAL(resultReadyAt(int)), SLOT(onThreadedResultReadyAt(int)));
    connect(&themeWatcher, SIGNAL(finished()), SLOT(loadComplete()));
}

/*
 * psithememodel.cpp - just a model for theme views
 *
 *
 */

#include "psithememodel.h"

#include "psiiconset.h"
#include "psithememanager.h"
#include "textutil.h"

#include <QIcon>
#include <QPixmap>
#include <QtConcurrentMap>

class PsiThemeModel;

struct PsiThemeModel::Loader {
    Loader(PsiThemeProvider *provider_) : provider(provider_) { }

    typedef ThemeItemInfo result_type;

    ThemeItemInfo operator()(const QString &id)
    {
        ThemeItemInfo ti;
        ti.id       = id;
        Theme theme = provider->theme(id);
        if (theme.load()) {
            fillThemeInfo(ti, theme);
        } else {
            ti.title = ":-(";
        }

        qDebug("%s theme loading status: %s", qPrintable(theme.id()), ti.isValid ? "success" : "failure");
        return ti;
    }

    void asyncLoad(const QString &id, std::function<void(const ThemeItemInfo &)> loadCallback)
    {
        Theme theme = provider->theme(id);
        if (!theme.isValid() || !theme.load([this, theme, loadCallback](bool success) {
                qDebug("%s theme loading status: %s", qPrintable(theme.id()), success ? "success" : "failure");
                // TODO invent something smarter

                ThemeItemInfo ti;
                if (success) { // if loaded
                    fillThemeInfo(ti, theme);
                }
                loadCallback(ti);
            })) {
            loadCallback(ThemeItemInfo());
        }
    }

    void fillThemeInfo(ThemeItemInfo &ti, const Theme &theme) const
    {
        ti.id          = theme.id();
        ti.title       = theme.title();
        ti.version     = theme.version();
        ti.description = theme.description();
        ti.authors     = theme.authors();
        ti.creation    = theme.creation();
        ti.homeUrl     = theme.homeUrl();

        ti.hasPreview = theme.hasPreview();
        ti.isValid    = true;
        ti.isCurrent  = provider->current().id() == ti.id;
    }

    PsiThemeProvider *provider;
};

// PsiThemeModel

PsiThemeModel::~PsiThemeModel() { delete loader; }

void PsiThemeModel::onThreadedResultReadyAt(int index)
{
    ThemeItemInfo ti = themeWatcher.resultAt(index);
    if (ti.isValid) {
        beginInsertRows(QModelIndex(), themesInfo.size(), themesInfo.size());
        themesInfo.append(ti);
        endInsertRows();
    }
}

void PsiThemeModel::loadComplete() { qDebug("Themes loading finished"); }

void PsiThemeModel::load()
{
    Q_ASSERT(!loader);
    loader = new Loader(provider);
    if (provider->threadedLoading()) {
        themesFuture = QtConcurrent::mapped(provider->themeIds(), *loader);
        themeWatcher.setFuture(themesFuture);
    } else {
        QStringList ids = provider->themeIds();
        qDebug() << ids;
        for (const QString &id : ids) {
            loader->asyncLoad(id, [this](const ThemeItemInfo &ti) {
                if (ti.isValid) {
                    beginInsertRows(QModelIndex(), themesInfo.size(), themesInfo.size());
                    themesInfo.append(ti);
                    endInsertRows();
                }
            });
        }
    }
}

int PsiThemeModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return themesInfo.count();
}

QVariant PsiThemeModel::data(const QModelIndex &index, int role) const
{
    switch (role) {
    case Qt::DecorationRole: {
        auto pi = IconsetFactory::iconPtr(QString("clients/") + themesInfo[index.row()].id.section('/', 0, 0));
        if (pi)
            return pi->icon();
        return QVariant();
    }
    case Qt::ToolTipRole: {
        QStringList          toolTip;
        const ThemeItemInfo &ti = themesInfo[index.row()];

        if (!ti.description.isEmpty()) {
            toolTip += ti.description + "
";
        }
        if (!ti.version.isEmpty()) {
            toolTip += "" + tr("Version") + ": " + ti.version;
        }
        if (!ti.authors.isEmpty()) {
            toolTip += "" + tr("Authors") + ":";
            for (auto &a : ti.authors) {
                toolTip += QString("  ") + TextUtil::escape(a);
            }
        }
        if (!ti.creation.isEmpty()) {
            toolTip += "" + tr("Released on") + ": " + ti.creation;
        }
        if (!ti.homeUrl.isEmpty()) {
            toolTip += "" + tr("Home") + QString(": %2").arg(ti.homeUrl, ti.homeUrl);
        }
        if (!toolTip.isEmpty()) {
            return "" + toolTip.join("
") + "";
        }
        break;
    }
    case IdRole:
        return themesInfo[index.row()].id;
    case Qt::DisplayRole:
    case TitleRole:
        return themesInfo[index.row()].title;
    case HasPreviewRole: {
        return themesInfo[index.row()].hasPreview;
    }
    case IsCurrent:
        return themesInfo[index.row()].isCurrent;
    }
    return QVariant();
}

bool PsiThemeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid() || !provider) {
        return false;
    }

    if (role == IsCurrent) {
        if (value.toBool()) {
            provider->setCurrentTheme(themesInfo[index.row()].id);
            return true;
        } else {
            // TODO set fallback / default
        }
    }

    return false;
}

int PsiThemeModel::themeRow(const QString &id)
{
    int i = 0;
    for (const ThemeItemInfo &tii : qAsConst(themesInfo)) {
        if (tii.id == id) {
            return i;
        }
        i++;
    }
    return -1;
}


while (reg2.RegFind(string.c_str()) > -1)
  {
    int pos1 = reg2.GetSubStart(1);
    int pos2 = reg2.GetSubStart(2);
    std::string szHexValue(reg2.GetMatch(1));

    std::string replace = StringUtils::Format("%li", strtol(szHexValue.c_str(), NULL, 16));
    string.replace(string.begin()+pos1-2, string.begin()+pos2+reg2.GetSubLength(2), replace);
  }

/*
 *
 *
 */

#include "ScraperParser.h"

#include "addons/AddonManager.h"
#include "RegExp.h"
#include "HTMLUtil.h"
#include "addons/Scraper.h"
#include "URL.h"
#include "utils/StringUtils.h"
#include "log.h"
#include "CharsetConverter.h"
#ifdef HAVE_LIBXSLT
#include "utils/XSLTUtils.h"
#endif
#include "utils/XMLUtils.h"
#include <sstream>
#include <cstring>

using namespace ADDON;
using namespace XFILE;

CScraperParser::CScraperParser()
{
  m_pRootElement = NULL;
  m_document = NULL;
  m_SearchStringEncoding = "UTF-8";
  m_scraper = NULL;
  m_isNoop = true;
}

CScraperParser::CScraperParser(const CScraperParser& parser)
{
  m_pRootElement = NULL;
  m_document = NULL;
  m_SearchStringEncoding = "UTF-8";
  m_scraper = NULL;
  m_isNoop = true;
  *this = parser;
}

CScraperParser &CScraperParser::operator=(const CScraperParser &parser)
{
  if (this != &parser)
  {
    Clear();
    if (parser.m_document)
    {
      m_scraper = parser.m_scraper;
      m_document = new CXBMCTinyXML(*parser.m_document);
      LoadFromXML();
    }
    else
      m_scraper = NULL;
  }
  return *this;
}

CScraperParser::~CScraperParser()
{
  Clear();
}

void CScraperParser::Clear()
{
  m_pRootElement = NULL;
  delete m_document;

  m_document = NULL;
  m_strFile.clear();
}

bool CScraperParser::Load(const std::string& strXMLFile)
{
  Clear();

  m_document = new CXBMCTinyXML();

  if (!m_document)
    return false;

  m_strFile = strXMLFile;

  if (m_document->LoadFile(strXMLFile))
    return LoadFromXML();

  delete m_document;
  m_document = NULL;
  return false;
}

bool CScraperParser::LoadFromXML()
{
  if (!m_document)
    return false;

  m_pRootElement = m_document->RootElement();
  std::string strValue = m_pRootElement->ValueStr();
  if (strValue == "scraper")
  {
    TiXmlElement* pChildElement = m_pRootElement->FirstChildElement("CreateSearchUrl");
    if (pChildElement)
    {
      m_isNoop = false;
      if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding")))
        m_SearchStringEncoding = "UTF-8";
    }

    pChildElement = m_pRootElement->FirstChildElement("CreateArtistSearchUrl");
    if (pChildElement)
    {
      m_isNoop = false;
      if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding")))
        m_SearchStringEncoding = "UTF-8";
    }
    pChildElement = m_pRootElement->FirstChildElement("CreateAlbumSearchUrl");
    if (pChildElement)
    {
      m_isNoop = false;
      if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding")))
        m_SearchStringEncoding = "UTF-8";
    }

    return true;
  }

  delete m_document;
  m_document = NULL;
  m_pRootElement = NULL;
  return false;
}

void CScraperParser::ReplaceBuffers(std::string& strDest)
{
  // insert buffers
  size_t iIndex;
  for (int i=MAX_SCRAPER_BUFFERS-1; i>=0; i--)
  {
    iIndex = 0;
    std::string temp = StringUtils::Format("$$%i",i+1);
    while ((iIndex = strDest.find(temp,iIndex)) != std::string::npos)
    {
      strDest.replace(strDest.begin()+iIndex,strDest.begin()+iIndex+temp.size(),m_param[i]);
      iIndex += m_param[i].length();
    }
  }
  // insert settings
  iIndex = 0;
  while ((iIndex = strDest.find("$INFO[", iIndex)) != std::string::npos)
  {
    size_t iEnd = strDest.find("]", iIndex);
    std::string strInfo = strDest.substr(iIndex+6, iEnd - iIndex - 6);
    std::string strReplace;
    if (m_scraper)
      strReplace = m_scraper->GetSetting(strInfo);
    strDest.replace(strDest.begin()+iIndex,strDest.begin()+iEnd+1,strReplace);
    iIndex += strReplace.length();
  }
  // insert localize strings
  iIndex = 0;
  while ((iIndex = strDest.find("$LOCALIZE[", iIndex)) != std::string::npos)
  {
    size_t iEnd = strDest.find("]", iIndex);
    std::string strInfo = strDest.substr(iIndex+10, iEnd - iIndex - 10);
    std::string strReplace;
    if (m_scraper)
      strReplace = g_localizeStrings.GetAddonString(m_scraper->ID(), strtol(strInfo.c_str(),NULL,10));
    strDest.replace(strDest.begin()+iIndex,strDest.begin()+iEnd+1,strReplace);
    iIndex += strReplace.length();
  }
  iIndex = 0;
  while ((iIndex = strDest.find("\\n",iIndex)) != std::string::npos)
    strDest.replace(strDest.begin()+iIndex,strDest.begin()+iIndex+2,"\n");
}

void CScraperParser::ParseExpression(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend)
{
  std::string strOutput = XMLUtils::GetAttribute(element, "output");

  TiXmlElement* pExpression = element->FirstChildElement("expression");
  if (pExpression)
  {
    bool bInsensitive=true;
    const char* sensitive = pExpression->Attribute("cs");
    if (sensitive)
      if (stricmp(sensitive,"yes") == 0)
        bInsensitive=false; // match case sensitive

    CRegExp::utf8Mode eUtf8 = CRegExp::autoUtf8;
    const char* const strUtf8 = pExpression->Attribute("utf8");
    if (strUtf8)
    {
      if (stricmp(strUtf8, "yes") == 0)
        eUtf8 = CRegExp::forceUtf8;
      else if (stricmp(strUtf8, "no") == 0)
        eUtf8 = CRegExp::asciiOnly;
      else if (stricmp(strUtf8, "auto") == 0)
        eUtf8 = CRegExp::autoUtf8;
    }

    CRegExp reg(bInsensitive, eUtf8);
    std::string strExpression;
    if (pExpression->FirstChild())
      strExpression = pExpression->FirstChild()->Value();
    else
      strExpression = "(.*)";
    ReplaceBuffers(strExpression);
    ReplaceBuffers(strOutput);

    if (!reg.RegComp(strExpression.c_str()))
    {
      return;
    }

    bool bRepeat = false;
    const char* szRepeat = pExpression->Attribute("repeat");
    if (szRepeat)
      if (stricmp(szRepeat,"yes") == 0)
        bRepeat = true;

    const char* szClear = pExpression->Attribute("clear");
    if (szClear)
      if (stricmp(szClear,"yes") == 0)
        dest=""; // clear no matter if regexp fails

    bool bClean[MAX_SCRAPER_BUFFERS];
    GetBufferParams(bClean,pExpression->Attribute("noclean"),true);

    bool bTrim[MAX_SCRAPER_BUFFERS];
    GetBufferParams(bTrim,pExpression->Attribute("trim"),false);

    bool bFixChars[MAX_SCRAPER_BUFFERS];
    GetBufferParams(bFixChars,pExpression->Attribute("fixchars"),false);

    bool bEncode[MAX_SCRAPER_BUFFERS];
    GetBufferParams(bEncode,pExpression->Attribute("encode"),false);

    int iOptional = -1;
    pExpression->QueryIntAttribute("optional",&iOptional);

    int iCompare = -1;
    pExpression->QueryIntAttribute("compare",&iCompare);
    if (iCompare > -1)
      StringUtils::ToLower(m_param[iCompare-1]);
    std::string curInput = input;
    for (int iBuf=0;iBuf<MAX_SCRAPER_BUFFERS;++iBuf)
    {
      if (bClean[iBuf])
        InsertToken(strOutput,iBuf+1,"!!!CLEAN!!!");
      if (bTrim[iBuf])
        InsertToken(strOutput,iBuf+1,"!!!TRIM!!!");
      if (bFixChars[iBuf])
        InsertToken(strOutput,iBuf+1,"!!!FIXCHARS!!!");
      if (bEncode[iBuf])
        InsertToken(strOutput,iBuf+1,"!!!ENCODE!!!");
    }
    int i = reg.RegFind(curInput.c_str());
    while (i > -1 && (i < (int)curInput.size() || curInput.empty()))
    {
      if (!bAppend)
      {
        dest = "";
        bAppend = true;
      }
      std::string strCurOutput=strOutput;

      if (iOptional > -1) // check that required param is there
      {
        char temp[4];
        sprintf(temp,"\\%i",iOptional);
        std::string szParam = reg.GetReplaceString(temp);
        CRegExp reg2;
        reg2.RegComp("(.*)(\\\\\\(.*\\\\2.*)\\\\\\)(.*)");
        int i2=reg2.RegFind(strCurOutput.c_str());
        while (i2 > -1)
        {
          std::string szRemove(reg2.GetMatch(2));
          int iRemove = szRemove.size();
          int i3 = strCurOutput.find(szRemove);
          if (!szParam.empty())
          {
            strCurOutput.erase(i3+iRemove,2);
            strCurOutput.erase(i3,2);
          }
          else
            strCurOutput.replace(strCurOutput.begin()+i3,strCurOutput.begin()+i3+iRemove+2,"");

          i2 = reg2.RegFind(strCurOutput.c_str());
        }
      }

      int iLen = reg.GetFindLen();
      // nasty hack #1 - & means \0 in a replace string
      StringUtils::Replace(strCurOutput, "&","!!!AMPAMP!!!");
      std::string result = reg.GetReplaceString(strCurOutput.c_str());
      if (!result.empty())
      {
        std::string strResult(result);
        StringUtils::Replace(strResult, "!!!AMPAMP!!!","&");
        Clean(strResult);
        ReplaceBuffers(strResult);
        if (iCompare > -1)
        {
          std::string strResultNoCase = strResult;
          StringUtils::ToLower(strResultNoCase);
          if (strResultNoCase.find(m_param[iCompare-1]) != std::string::npos)
            dest += strResult;
        }
        else
          dest += strResult;
      }
      if (bRepeat && iLen > 0)
      {
        curInput.erase(0,i+iLen>(int)curInput.size()?curInput.size():i+iLen);
        i = reg.RegFind(curInput.c_str());
      }
      else
        i = -1;
    }
  }
}

void CScraperParser::ParseXSLT(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend)
{
#ifdef HAVE_LIBXSLT
  TiXmlElement* pSheet = element->FirstChildElement();
  if (pSheet)
  {
    XSLTUtils xsltUtils;
    std::string strXslt;
    strXslt << *pSheet;
    ReplaceBuffers(strXslt);

    if (!xsltUtils.SetInput(input))
      CLog::Log(LOGDEBUG, "could not parse input XML");

    if (!xsltUtils.SetStylesheet(strXslt))
      CLog::Log(LOGDEBUG, "could not parse stylesheet XML");

    xsltUtils.XSLTTransform(dest);
  }
#endif
}

TiXmlElement *FirstChildScraperElement(TiXmlElement *element)
{
  for (TiXmlElement *child = element->FirstChildElement(); child; child = child->NextSiblingElement())
  {
#ifdef HAVE_LIBXSLT
    if (child->ValueStr() == "XSLT")
      return child;
#endif
    if (child->ValueStr() == "RegExp")
      return child;
  }
  return NULL;
}

TiXmlElement *NextSiblingScraperElement(TiXmlElement *element)
{
  for (TiXmlElement *next = element->NextSiblingElement(); next; next = next->NextSiblingElement())
  {
#ifdef HAVE_LIBXSLT
    if (next->ValueStr() == "XSLT")
      return next;
#endif
    if (next->ValueStr() == "RegExp")
      return next;
  }
  return NULL;
}

void CScraperParser::ParseNext(TiXmlElement* element)
{
  TiXmlElement* pReg = element;
  while (pReg)
  {
    TiXmlElement* pChildReg = FirstChildScraperElement(pReg);
    if (pChildReg)
      ParseNext(pChildReg);
    else
    {
      TiXmlElement* pChildReg = pReg->FirstChildElement("clear");
      if (pChildReg)
        ParseNext(pChildReg);
    }

    int iDest = 1;
    bool bAppend = false;
    const char* szDest = pReg->Attribute("dest");
    if (szDest && strlen(szDest))
    {
      if (szDest[strlen(szDest)-1] == '+')
        bAppend = true;

      iDest = atoi(szDest);
    }

    const char *szInput = pReg->Attribute("input");
    std::string strInput;
    if (szInput)
    {
      strInput = szInput;
      ReplaceBuffers(strInput);
    }
    else
      strInput = m_param[0];

    const char* szConditional = pReg->Attribute("conditional");
    bool bExecute = true;
    if (szConditional)
    {
      bool bInverse=false;
      if (szConditional[0] == '!')
      {
        bInverse = true;
        szConditional++;
      }
      std::string strSetting;
      if (m_scraper && m_scraper->HasSettings())
        strSetting = m_scraper->GetSetting(szConditional);
      bExecute = bInverse != (strSetting == "true");
    }

    if (bExecute)
    {
      if (iDest-1 < MAX_SCRAPER_BUFFERS && iDest-1 > -1)
      {
#ifdef HAVE_LIBXSLT
        if (pReg->ValueStr() == "XSLT")
          ParseXSLT(strInput, m_param[iDest - 1], pReg, bAppend);
        else
#endif
          ParseExpression(strInput, m_param[iDest - 1],pReg,bAppend);
      }
      else
        CLog::Log(LOGERROR,"CScraperParser::ParseNext: destination buffer "
                           "out of bounds, skipping expression");
    }
    pReg = NextSiblingScraperElement(pReg);
  }
}

const std::string CScraperParser::Parse(const std::string& strTag,
                                       CScraper* scraper)
{
  TiXmlElement* pChildElement = m_pRootElement->FirstChildElement(strTag.c_str());
  if(pChildElement == NULL)
  {
    CLog::Log(LOGERROR,"%s: Could not find scraper function %s",__FUNCTION__,strTag.c_str());
    return "";
  }
  int iResult = 1; // default to param 1
  pChildElement->QueryIntAttribute("dest",&iResult);
  TiXmlElement* pChildStart = FirstChildScraperElement(pChildElement);
  m_scraper = scraper;
  ParseNext(pChildStart);
  std::string tmp = m_param[iResult-1];

  const char* szClearBuffers = pChildElement->Attribute("clearbuffers");
  if (!szClearBuffers || stricmp(szClearBuffers,"no") != 0)
    ClearBuffers();

  return tmp;
}

void CScraperParser::Clean(std::string& strDirty)
{
  size_t i = 0;
  std::string strBuffer;
  while ((i = strDirty.find("!!!CLEAN!!!",i)) != std::string::npos)
  {
    size_t i2;
    if ((i2 = strDirty.find("!!!CLEAN!!!",i+11)) != std::string::npos)
    {
      strBuffer = strDirty.substr(i+11,i2-i-11);
      std::string strConverted(strBuffer);
      HTML::CHTMLUtil::RemoveTags(strConverted);
      StringUtils::Trim(strConverted);
      strDirty.replace(i, i2-i+11, strConverted);
      i += strConverted.size();
    }
    else
      break;
  }
  i=0;
  while ((i = strDirty.find("!!!TRIM!!!",i)) != std::string::npos)
  {
    size_t i2;
    if ((i2 = strDirty.find("!!!TRIM!!!",i+10)) != std::string::npos)
    {
      strBuffer = strDirty.substr(i+10,i2-i-10);
      StringUtils::Trim(strBuffer);
      strDirty.replace(i, i2-i+10, strBuffer);
      i += strBuffer.size();
    }
    else
      break;
  }
  i=0;
  while ((i = strDirty.find("!!!FIXCHARS!!!",i)) != std::string::npos)
  {
    size_t i2;
    if ((i2 = strDirty.find("!!!FIXCHARS!!!",i+14)) != std::string::npos)
    {
      strBuffer = strDirty.substr(i+14,i2-i-14);
      std::wstring wbuffer;
      g_charsetConverter.utf8ToW(strBuffer, wbuffer, false, false, false);
      std::wstring wConverted;
      HTML::CHTMLUtil::ConvertHTMLToW(wbuffer,wConverted);
      g_charsetConverter.wToUTF8(wConverted, strBuffer, false);
      StringUtils::Trim(strBuffer);
      ConvertJSON(strBuffer);
      strDirty.replace(i, i2-i+14, strBuffer);
      i += strBuffer.size();
    }
    else
      break;
  }
  i=0;
  while ((i=strDirty.find("!!!ENCODE!!!",i)) != std::string::npos)
  {
    size_t i2;
    if ((i2 = strDirty.find("!!!ENCODE!!!",i+12)) != std::string::npos)
    {
      strBuffer = CURL::Encode(strDirty.substr(i + 12, i2 - i - 12));
      strDirty.replace(i, i2-i+12, strBuffer);
      i += strBuffer.size();
    }
    else
      break;
  }
}

void CScraperParser::ConvertJSON(std::string &string)
{
  CRegExp reg;
  reg.RegComp("\\\\u([0-f]{4})");
  while (reg.RegFind(string.c_str()) > -1)
  {
    int pos = reg.GetSubStart(1);
    std::string szReplace(reg.GetMatch(1));

    std::string replace = StringUtils::Format("&#x%s;", szReplace.c_str());
    string.replace(string.begin()+pos-2, string.begin()+pos+4, replace);
  }

  CRegExp reg2;
  reg2.RegComp("\\\\x([0-9]{2})([^\\\\]+;)");

  StringUtils::Replace(string, "\\\"","\"");
}

void CScraperParser::ClearBuffers()
{
  //clear all m_param strings
  for (int i=0;i<MAX_SCRAPER_BUFFERS;++i)
    m_param[i].clear();
}

void CScraperParser::GetBufferParams(bool* result, const char* attribute, bool defvalue)
{
  for (int iBuf=0;iBuf<MAX_SCRAPER_BUFFERS;++iBuf)
    result[iBuf] = defvalue;
  if (attribute)
  {
    std::vector<std::string> vecBufs;
    StringUtils::Tokenize(attribute,vecBufs,",");
    for (size_t nToken=0; nToken < vecBufs.size(); nToken++)
    {
      int index = atoi(vecBufs[nToken].c_str())-1;
      if (index < MAX_SCRAPER_BUFFERS)
        result[index] = !defvalue;
    }
  }
}

void CScraperParser::InsertToken(std::string& strOutput, int buf, const char* token)
{
  char temp[4];
  sprintf(temp,"\\%i",buf);
  size_t i2=0;
  while ((i2 = strOutput.find(temp,i2)) != std::string::npos)
  {
    strOutput.insert(i2,token);
    i2 += strlen(token) + strlen(temp);
    strOutput.insert(i2,token);
  }
}

void CScraperParser::AddDocument(const CXBMCTinyXML* doc)
{
  const TiXmlNode* node = doc->RootElement()->FirstChild();
  while (node)
  {
    m_pRootElement->InsertEndChild(*node);
    node = node->NextSibling();
  }
}


switch(vram.mapping) {
  case 0: return address;
  case 1: return address.bit( 8,15) <<  8 | address.bit(0,4) << 3 | address.bit(5,7);
  case 2: return address.bit( 9,15) <<  9 | address.bit(0,5) << 3 | address.bit(6,8);
  case 3: return address.bit(10,15) << 10 | address.bit(0,6) << 3 | address.bit(7,9);
  }

auto PPU::latchCounters() -> void {
  cpu.synchronize(ppu);
  io.hcounter = cpu.hdot();
  io.vcounter = cpu.vcounter();
  latch.counters = 1;
}

alwaysinline auto PPU::addressVRAM() const -> n16 {
  n16 address = vram.address;

  unreachable;
}

alwaysinline auto PPU::readVRAM() -> n16 {
  if(!io.displayDisable && cpu.vcounter() < vdisp()) return 0x0000;
  auto address = addressVRAM();
  return vram[address];
}

alwaysinline auto PPU::writeVRAM(n1 byte, n8 data) -> void {
  if(!io.displayDisable && cpu.vcounter() < vdisp()) return;
  auto address = addressVRAM();
  vram[address].byte(byte) = data;
}

alwaysinline auto PPU::readOAM(n10 address) -> n8 {
  if(!io.displayDisable && cpu.vcounter() < vdisp()) address = 0x0218;
  return obj.oam.read(address);
}

alwaysinline auto PPU::writeOAM(n10 address, n8 data) -> void {
  if(!io.displayDisable && cpu.vcounter() < vdisp()) address = 0x0218;
  return obj.oam.write(address, data);
}

alwaysinline auto PPU::readCGRAM(n1 byte, n8 address) -> n8 {
  if(!io.displayDisable
  && cpu.vcounter() > 0 && cpu.vcounter() < vdisp()
  && cpu.hcounter() >= 88 && cpu.hcounter() < 1096
  ) address = latch.cgramAddress;
  return dac.cgram[address].byte(byte);
}

alwaysinline auto PPU::writeCGRAM(n8 address, n15 data) -> void {
  if(!io.displayDisable
  && cpu.vcounter() > 0 && cpu.vcounter() < vdisp()
  && cpu.hcounter() >= 88 && cpu.hcounter() < 1096
  ) address = latch.cgramAddress;
  dac.cgram[address] = data;
}

auto PPU::readIO(n24 address, n8 data) -> n8 {
  cpu.synchronize(ppu);

  switch((n16)address) {

  case 0x2104: case 0x2105: case 0x2106: case 0x2108:
  case 0x2109: case 0x210a: case 0x2114: case 0x2115:
  case 0x2116: case 0x2118: case 0x2119: case 0x211a:
  case 0x2124: case 0x2125: case 0x2126: case 0x2128:
  case 0x2129: case 0x212a: {
    return ppu1.mdr;
  }

  //MPYL
  case 0x2134: {
    n24 result = (i16)mode7.a * (i8)(mode7.b >> 8);
    return ppu1.mdr = result.byte(0);
  }

  //MPYM
  case 0x2135: {
    n24 result = (i16)mode7.a * (i8)(mode7.b >> 8);
    return ppu1.mdr = result.byte(1);
  }

  //MYPH
  case 0x2136: {
    n24 result = (i16)mode7.a * (i8)(mode7.b >> 8);
    return ppu1.mdr = result.byte(2);
  }

  //SLHV
  case 0x2137: {
    if(cpu.pio().bit(7)) latchCounters();
    return data;  //CPU MDR
  }

  //OAMDATAREAD
  case 0x2138: {
    ppu1.mdr = readOAM(io.oamAddress++);
    obj.setFirstSprite();
    return ppu1.mdr;
  }

  //VMDATALREAD
  case 0x2139: {
    ppu1.mdr = latch.vram.byte(0);
    if(vram.mode == 0) {
      latch.vram = readVRAM();
      vram.address += vram.increment;
    }
    return ppu1.mdr;
  }

  //VMDATAHREAD
  case 0x213a: {
    ppu1.mdr = latch.vram.byte(1);
    if(vram.mode == 1) {
      latch.vram = readVRAM();
      vram.address += vram.increment;
    }
    return ppu1.mdr;
  }

  //CGDATAREAD
  case 0x213b: {
    if(io.cgramAddressLatch++ == 0) {
      ppu2.mdr.bit(0,7) = readCGRAM(0, io.cgramAddress);
    } else {
      ppu2.mdr.bit(0,6) = readCGRAM(1, io.cgramAddress++);
    }
    return ppu2.mdr;
  }

  //OPHCT
  case 0x213c: {
    if(latch.hcounter++ == 0) {
      ppu2.mdr.bit(0,7) = io.hcounter.bit(0,7);
    } else {
      ppu2.mdr.bit(0)   = io.hcounter.bit(8);
    }
    return ppu2.mdr;
  }

  //OPVCT
  case 0x213d: {
    if(latch.vcounter++ == 0) {
      ppu2.mdr.bit(0,7) = io.vcounter.bit(0,7);
    } else {
      ppu2.mdr.bit(0)   = io.vcounter.bit(8);
    }
    return ppu2.mdr;
  }

  //STAT77
  case 0x213e: {
    ppu1.mdr.bit(0,3) = ppu1.version;
    ppu1.mdr.bit(5)   = 0;
    ppu1.mdr.bit(6)   = obj.io.rangeOver;
    ppu1.mdr.bit(7)   = obj.io.timeOver;
    return ppu1.mdr;
  }

  //STAT78
  case 0x213f: {
    latch.hcounter = 0;
    latch.vcounter = 0;
    ppu2.mdr.bit(0,3) = ppu2.version;
    ppu2.mdr.bit(4)   = Region::PAL();  //0 = NTSC, 1 = PAL
    if(!cpu.pio().bit(7)) {
      ppu2.mdr.bit(6) = 1;
    } else {
      ppu2.mdr.bit(6) = latch.counters;
      latch.counters = 0;
    }
    ppu2.mdr.bit(7) = cpu.field();
    return ppu2.mdr;
  }

  }

  return data;
}

auto PPU::writeIO(n24 address, n8 data) -> void {
  cpu.synchronize(ppu);

  switch((n16)address) {

  //INIDISP
  case 0x2100: {
    if(io.displayDisable && cpu.vcounter() == vdisp()) obj.addressReset();
    io.displayBrightness = data.bit(0,3);
    io.displayDisable    = data.bit(7);
    return;
  }

  //OBSEL
  case 0x2101: {
    obj.io.tiledataAddress = data.bit(0,2) << 13;
    obj.io.nameselect      = data.bit(3,4);
    obj.io.baseSize        = data.bit(5,7);
    return;
  }

  //OAMADDL
  case 0x2102: {
    io.oamBaseAddress.bit(1,8) = data.bit(0,7);
    obj.addressReset();
    return;
  }

  //OAMADDH
  case 0x2103: {
    io.oamBaseAddress.bit(9) = data.bit(0);
    io.oamPriority = data.bit(7);
    obj.addressReset();
    return;
  }

  //OAMDATA
  case 0x2104: {
    n1 latchBit = io.oamAddress.bit(0);
    n10 address = io.oamAddress++;
    if(latchBit == 0) latch.oam = data;
    if(address.bit(9)) {
      writeOAM(address, data);
    } else if(latchBit == 1) {
      writeOAM((address & ~1) + 0, latch.oam);
      writeOAM((address & ~1) + 1, data);
    }
    obj.setFirstSprite();
    return;
  }

  //BGMODE
  case 0x2105: {
    io.bgMode       = data.bit(0,2);
    io.bgPriority   = data.bit(3);
    bg1.io.tileSize = data.bit(4);
    bg2.io.tileSize = data.bit(5);
    bg3.io.tileSize = data.bit(6);
    bg4.io.tileSize = data.bit(7);
    updateVideoMode();
    return;
  }

  //MOSAIC
  case 0x2106: {
    bool mosaicEnable   = mosaic.enable();
    bg1.io.mosaicEnable = data.bit(0);
    bg2.io.mosaicEnable = data.bit(1);
    bg3.io.mosaicEnable = data.bit(2);
    bg4.io.mosaicEnable = data.bit(3);
    mosaic.size         = data.bit(4,7) + 1;
    if(!mosaicEnable && mosaic.enable()) {
      //mosaic vcounter is reloaded when mosaic becomes enabled
      mosaic.vcounter = mosaic.size + 1;
    }
    return;
  }

  //BG1SC
  case 0x2107: {
    bg1.io.screenSize    = data.bit(0,1);
    bg1.io.screenAddress = data.bit(2,7) << 10;
    return;
  }

  //BG2SC
  case 0x2108: {
    bg2.io.screenSize    = data.bit(0,1);
    bg2.io.screenAddress = data.bit(2,7) << 10;
    return;
  }

  //BG3SC
  case 0x2109: {
    bg3.io.screenSize    = data.bit(0,1);
    bg3.io.screenAddress = data.bit(2,7) << 10;
    return;
  }

  //BG4SC
  case 0x210a: {
    bg4.io.screenSize    = data.bit(0,1);
    bg4.io.screenAddress = data.bit(2,7) << 10;
    return;
  }

  //BG12NBA
  case 0x210b: {
    bg1.io.tiledataAddress = data.bit(0,3) << 12;
    bg2.io.tiledataAddress = data.bit(4,7) << 12;
    return;
  }

  //BG34NBA
  case 0x210c: {
    bg3.io.tiledataAddress = data.bit(0,3) << 12;
    bg4.io.tiledataAddress = data.bit(4,7) << 12;
    return;
  }

  //BG1HOFS
  case 0x210d: {
    mode7.hoffset = data << 8 | latch.mode7;
    latch.mode7 = data;

    bg1.io.hoffset = data << 8 | latch.bgofsPPU1 & ~7 | latch.bgofsPPU2 & 7;
    latch.bgofsPPU1 = data;
    latch.bgofsPPU2 = data;
    return;
  }

  //BG1VOFS
  case 0x210e: {
    mode7.voffset = data << 8 | latch.mode7;
    latch.mode7 = data;

    bg1.io.voffset = data << 8 | latch.bgofsPPU1;
    latch.bgofsPPU1 = data;
    return;
  }

  //BG2HOFS
  case 0x210f: {
    bg2.io.hoffset = data << 8 | latch.bgofsPPU1 & ~7 | latch.bgofsPPU2 & 7;
    latch.bgofsPPU1 = data;
    latch.bgofsPPU2 = data;
    return;
  }

  //BG2VOFS
  case 0x2110: {
    bg2.io.voffset = data << 8 | latch.bgofsPPU1;
    latch.bgofsPPU1 = data;
    return;
  }

  //BG3HOFS
  case 0x2111: {
    bg3.io.hoffset = data << 8 | latch.bgofsPPU1 & ~7 | latch.bgofsPPU2 & 7;
    latch.bgofsPPU1 = data;
    latch.bgofsPPU2 = data;
    return;
  }

  //BG3VOFS
  case 0x2112: {
    bg3.io.voffset = data << 8 | latch.bgofsPPU1;
    latch.bgofsPPU1 = data;
    return;
  }

  //BG4HOFS
  case 0x2113: {
    bg4.io.hoffset = data << 8 | latch.bgofsPPU1 & ~7 | latch.bgofsPPU2 & 7;
    latch.bgofsPPU1 = data;
    latch.bgofsPPU2 = data;
    return;
  }

  //BG4VOFS
  case 0x2114: {
    bg4.io.voffset = data << 8 | latch.bgofsPPU1;
    latch.bgofsPPU1 = data;
    return;
  }

  //VMAIN
  case 0x2115: {
    static constexpr u32 size[4] = {1, 32, 128, 128};
    vram.increment = size[data.bit(0,1)];
    vram.mapping   = data.bit(2,3);
    vram.mode      = data.bit(7);
    return;
  }

  //VMADDL
  case 0x2116: {
    vram.address.bit(0,7) = data;
    latch.vram = readVRAM();
    return;
  }

  //VHADDH
  case 0x2117: {
    vram.address.bit(8,15) = data;
    latch.vram = readVRAM();
    return;
  }

  //VMDATAL
  case 0x2118: {
    writeVRAM(0, data);
    if(vram.mode == 0) vram.address += vram.increment;
    return;
  }

  //VMDATAH
  case 0x2119: {
    writeVRAM(1, data);
    if(vram.mode == 1) vram.address += vram.increment;
    return;
  }

  //M7SEL
  case 0x211a: {
    mode7.hflip  = data.bit(0);
    mode7.vflip  = data.bit(1);
    mode7.repeat = data.bit(6,7);
    return;
  }

  //M7A
  case 0x211b: {
    mode7.a = data << 8 | latch.mode7;
    latch.mode7 = data;
    return;
  }

  //M7B
  case 0x211c: {
    mode7.b = data << 8 | latch.mode7;
    latch.mode7 = data;
    return;
  }

  //M7C
  case 0x211d: {
    mode7.c = data << 8 | latch.mode7;
    latch.mode7 = data;
    return;
  }

  //M7D
  case 0x211e: {
    mode7.d = data << 8 | latch.mode7;
    latch.mode7 = data;
    return;
  }

  //M7X
  case 0x211f: {
    mode7.hcenter = data << 8 | latch.mode7;
    latch.mode7 = data;
    return;
  }

  //M7Y
  case 0x2120: {
    mode7.vcenter = data << 8 | latch.mode7;
    latch.mode7 = data;
    return;
  }

  //CGADD
  case 0x2121: {
    io.cgramAddress = data;
    io.cgramAddressLatch = 0;
    return;
  }

  //CGDATA
  case 0x2122: {
    if(io.cgramAddressLatch++ == 0) {
      latch.cgram = data;
    } else {
      writeCGRAM(io.cgramAddress++, data.bit(0,6) << 8 | latch.cgram);
    }
    return;
  }

  //W12SEL
  case 0x2123: {
    bg1.window.oneInvert = data.bit(0);
    bg1.window.oneEnable = data.bit(1);
    bg1.window.twoInvert = data.bit(2);
    bg1.window.twoEnable = data.bit(3);
    bg2.window.oneInvert = data.bit(4);
    bg2.window.oneEnable = data.bit(5);
    bg2.window.twoInvert = data.bit(6);
    bg2.window.twoEnable = data.bit(7);
    return;
  }

  //W34SEL
  case 0x2124: {
    bg3.window.oneInvert = data.bit(0);
    bg3.window.oneEnable = data.bit(1);
    bg3.window.twoInvert = data.bit(2);
    bg3.window.twoEnable = data.bit(3);
    bg4.window.oneInvert = data.bit(4);
    bg4.window.oneEnable = data.bit(5);
    bg4.window.twoInvert = data.bit(6);
    bg4.window.twoEnable = data.bit(7);
    return;
  }

  //WOBJSEL
  case 0x2125: {
    obj.window.oneInvert = data.bit(0);
    obj.window.oneEnable = data.bit(1);
    obj.window.twoInvert = data.bit(2);
    obj.window.twoEnable = data.bit(3);
    dac.window.oneInvert = data.bit(4);
    dac.window.oneEnable = data.bit(5);
    dac.window.twoInvert = data.bit(6);
    dac.window.twoEnable = data.bit(7);
    return;
  }

  //WH0
  case 0x2126: {
    window.io.oneLeft = data;
    return;
  }

  //WH1
  case 0x2127: {
    window.io.oneRight = data;
    return;
  }

  //WH2
  case 0x2128: {
    window.io.twoLeft = data;
    return;
  }

  //WH3
  case 0x2129: {
    window.io.twoRight = data;
    return;
  }

  //WBGLOG
  case 0x212a: {
    bg1.window.mask = data.bit(0,1);
    bg2.window.mask = data.bit(2,3);
    bg3.window.mask = data.bit(4,5);
    bg4.window.mask = data.bit(6,7);
    return;
  }

  //WOBJLOG
  case 0x212b: {
    obj.window.mask = data.bit(0,1);
    dac.window.mask = data.bit(2,3);
    return;
  }

  //TM
  case 0x212c: {
    bg1.io.aboveEnable = data.bit(0);
    bg2.io.aboveEnable = data.bit(1);
    bg3.io.aboveEnable = data.bit(2);
    bg4.io.aboveEnable = data.bit(3);
    obj.io.aboveEnable = data.bit(4);
    return;
  }

  //TS
  case 0x212d: {
    bg1.io.belowEnable = data.bit(0);
    bg2.io.belowEnable = data.bit(1);
    bg3.io.belowEnable = data.bit(2);
    bg4.io.belowEnable = data.bit(3);
    obj.io.belowEnable = data.bit(4);
    return;
  }

  //TMW
  case 0x212e: {
    bg1.window.aboveEnable = data.bit(0);
    bg2.window.aboveEnable = data.bit(1);
    bg3.window.aboveEnable = data.bit(2);
    bg4.window.aboveEnable = data.bit(3);
    obj.window.aboveEnable = data.bit(4);
    return;
  }

  //TSW
  case 0x212f: {
    bg1.window.belowEnable = data.bit(0);
    bg2.window.belowEnable = data.bit(1);
    bg3.window.belowEnable = data.bit(2);
    bg4.window.belowEnable = data.bit(3);
    obj.window.belowEnable = data.bit(4);
    return;
  }

  //CGWSEL
  case 0x2130: {
    dac.io.directColor   = data.bit(0);
    dac.io.blendMode     = data.bit(1);
    dac.window.belowMask = data.bit(4,5);
    dac.window.aboveMask = data.bit(6,7);
    return;
  }

  //CGADDSUB
  case 0x2131: {
    dac.io.colorEnable[Source::BG1 ] = data.bit(0);
    dac.io.colorEnable[Source::BG2 ] = data.bit(1);
    dac.io.colorEnable[Source::BG3 ] = data.bit(2);
    dac.io.colorEnable[Source::BG4 ] = data.bit(3);
    dac.io.colorEnable[Source::OBJ1] = 0;
    dac.io.colorEnable[Source::OBJ2] = data.bit(4);
    dac.io.colorEnable[Source::COL ] = data.bit(5);
    dac.io.colorHalve = data.bit(6);
    dac.io.colorMode  = data.bit(7);
    return;
  }

  //COLDATA
  case 0x2132: {
    if(data.bit(5)) dac.io.colorRed   = data.bit(0,4);
    if(data.bit(6)) dac.io.colorGreen = data.bit(0,4);
    if(data.bit(7)) dac.io.colorBlue  = data.bit(0,4);
    return;
  }

  //SETINI
  case 0x2133: {
    io.interlace     = data.bit(0);
    obj.io.interlace = data.bit(1);
    io.overscan      = data.bit(2);
    io.pseudoHires   = data.bit(3);
    io.extbg         = data.bit(6);
    updateVideoMode();
    return;
  }

  }
}

auto PPU::updateVideoMode() -> void {
  state.vdisp = !io.overscan ? 225 : 240;

  switch(io.bgMode) {
  case 0:
    bg1.io.mode = Background::Mode::BPP2;
    bg2.io.mode = Background::Mode::BPP2;
    bg3.io.mode = Background::Mode::BPP2;
    bg4.io.mode = Background::Mode::BPP2;
    memory::assign(bg1.io.priority, 8, 11);
    memory::assign(bg2.io.priority, 7, 10);
    memory::assign(bg3.io.priority, 2,  5);
    memory::assign(bg4.io.priority, 1,  4);
    memory::assign(obj.io.priority, 3,  6, 9, 12);
    break;

  case 1:
    bg1.io.mode = Background::Mode::BPP4;
    bg2.io.mode = Background::Mode::BPP4;
    bg3.io.mode = Background::Mode::BPP2;
    bg4.io.mode = Background::Mode::Inactive;
    if(io.bgPriority) {
      memory::assign(bg1.io.priority, 5,  8);
      memory::assign(bg2.io.priority, 4,  7);
      memory::assign(bg3.io.priority, 1, 10);
      memory::assign(obj.io.priority, 2,  3, 6,  9);
    } else {
      memory::assign(bg1.io.priority, 6,  9);
      memory::assign(bg2.io.priority, 5,  8);
      memory::assign(bg3.io.priority, 1,  3);
      memory::assign(obj.io.priority, 2,  4, 7, 10);
    }
    break;

  case 2:
    bg1.io.mode = Background::Mode::BPP4;
    bg2.io.mode = Background::Mode::BPP4;
    bg3.io.mode = Background::Mode::Inactive;
    bg4.io.mode = Background::Mode::Inactive;
    memory::assign(bg1.io.priority, 3, 7);
    memory::assign(bg2.io.priority, 1, 5);
    memory::assign(obj.io.priority, 2, 4, 6, 8);
    break;

  case 3:
    bg1.io.mode = Background::Mode::BPP8;
    bg2.io.mode = Background::Mode::BPP4;
    bg3.io.mode = Background::Mode::Inactive;
    bg4.io.mode = Background::Mode::Inactive;
    memory::assign(bg1.io.priority, 3, 7);
    memory::assign(bg2.io.priority, 1, 5);
    memory::assign(obj.io.priority, 2, 4, 6, 8);
    break;

  case 4:
    bg1.io.mode = Background::Mode::BPP8;
    bg2.io.mode = Background::Mode::BPP2;
    bg3.io.mode = Background::Mode::Inactive;
    bg4.io.mode = Background::Mode::Inactive;
    memory::assign(bg1.io.priority, 3, 7);
    memory::assign(bg2.io.priority, 1, 5);
    memory::assign(obj.io.priority, 2, 4, 6, 8);
    break;

  case 5:
    bg1.io.mode = Background::Mode::BPP4;
    bg2.io.mode = Background::Mode::BPP2;
    bg3.io.mode = Background::Mode::Inactive;
    bg4.io.mode = Background::Mode::Inactive;
    memory::assign(bg1.io.priority, 3, 7);
    memory::assign(bg2.io.priority, 1, 5);
    memory::assign(obj.io.priority, 2, 4, 6, 8);
    break;

  case 6:
    bg1.io.mode = Background::Mode::BPP4;
    bg2.io.mode = Background::Mode::Inactive;
    bg3.io.mode = Background::Mode::Inactive;
    bg4.io.mode = Background::Mode::Inactive;
    memory::assign(bg1.io.priority, 2, 5);
    memory::assign(obj.io.priority, 1, 3, 4, 6);
    break;

  case 7:
    if(!io.extbg) {
      bg1.io.mode = Background::Mode::Mode7;
      bg2.io.mode = Background::Mode::Inactive;
      bg3.io.mode = Background::Mode::Inactive;
      bg4.io.mode = Background::Mode::Inactive;
      memory::assign(bg1.io.priority, 2);
      memory::assign(obj.io.priority, 1, 3, 4, 5);
    } else {
      bg1.io.mode = Background::Mode::Mode7;
      bg2.io.mode = Background::Mode::Mode7;
      bg3.io.mode = Background::Mode::Inactive;
      bg4.io.mode = Background::Mode::Inactive;
      memory::assign(bg1.io.priority, 3);
      memory::assign(bg2.io.priority, 1, 5);
      memory::assign(obj.io.priority, 2, 4, 6, 7);
    }
    break;
  }
}


while(!DQ.empty() && DQ.back().first > A)
            DQ.pop_back();

#include <cstdio>
#include <queue>
#include <map>

using namespace std;

#define MAXN 10000000
#define MOD 1000000007

int minK[MAXN];
deque< pair<int, int> > DQ;

int main(){
    int N,K,Q;

    scanf("%d %d %d",&N,&K,&Q);

    long long a,b,d,e;
    int c,f,r,s,t,m,A;

    scanf("%lld %lld %d %lld %lld %d %d %d %d %d %d",&a,&b,&c,&d,&e,&f,&r,&s,&t,&m,&A);

    long long powt = t;

    DQ.push_back(make_pair(A,0));
    minK[0] = A;

    for(int i = 1;i < N;++i){
        powt *= t;
        if(powt >= s) powt %= s;

        if(powt <= r)
            A = (a * A % m * A + b * A + c) % m;
        else
            A = (d * A % m * A + e * A + f) % m;

        if(DQ.front().second == i - K)
            DQ.pop_front();

        DQ.push_back(make_pair(A,i));
        minK[i] = DQ.front().first;
    }

    int L1,La,Lc,Lm,D1,Da,Dc,Dm,L,R;

    scanf("%d %d %d %d %d %d %d %d",&L1,&La,&Lc,&Lm,&D1,&Da,&Dc,&Dm);

    long long S = 0,P = 1;

    for(int i = 0;i < Q;++i){
        L1 = ((long long)La * L1 + Lc) % Lm;
        D1 = ((long long)Da * D1 + Dc) % Dm;
        L = L1 + 1;
        R = min(L + K - 1 + D1,N);

        int ans = min(minK[R - 1],minK[L + K - 2]);
        S += ans;
        P = (P * ans) % MOD;
    }

    printf("%lld %lld\n",S,P);

    return 0;
}


WebAssemblyLowerBrUnless() : MachineFunctionPass(ID) {}

//                     The LLVM Compiler Infrastructure
/// \file
/// \brief This file lowers br_unless into br_if with an inverted condition.
/// br_unless is not currently in the spec, but it's very convenient for LLVM
/// to use. This pass allows LLVM to use it, for now.

#include "WebAssembly.h"
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
#include "WebAssemblyMachineFunctionInfo.h"
#include "WebAssemblySubtarget.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;

#define DEBUG_TYPE "wasm-lower-br_unless"

namespace {
class WebAssemblyLowerBrUnless final : public MachineFunctionPass {
  StringRef getPassName() const override {
    return "WebAssembly Lower br_unless";
  }

  void getAnalysisUsage(AnalysisUsage &AU) const override {
    AU.setPreservesCFG();
    MachineFunctionPass::getAnalysisUsage(AU);
  }

  bool runOnMachineFunction(MachineFunction &MF) override;

public:
  static char ID; // Pass identification, replacement for typeid

};
} // end anonymous namespace

char WebAssemblyLowerBrUnless::ID = 0;
FunctionPass *llvm::createWebAssemblyLowerBrUnless() {
  return new WebAssemblyLowerBrUnless();
}

bool WebAssemblyLowerBrUnless::runOnMachineFunction(MachineFunction &MF) {
  DEBUG(dbgs() << "********** Lowering br_unless **********\n"
                  "********** Function: "
               << MF.getName() << '\n');

  auto &MFI = *MF.getInfo<WebAssemblyFunctionInfo>();
  const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
  auto &MRI = MF.getRegInfo();

  for (auto &MBB : MF) {
    for (auto MII = MBB.begin(); MII != MBB.end();) {
      MachineInstr *MI = &*MII++;
      if (MI->getOpcode() != WebAssembly::BR_UNLESS)
        continue;

      unsigned Cond = MI->getOperand(1).getReg();
      bool Inverted = false;

      // Attempt to invert the condition in place.
      if (MFI.isVRegStackified(Cond)) {
        assert(MRI.hasOneDef(Cond));
        MachineInstr *Def = MRI.getVRegDef(Cond);
        switch (Def->getOpcode()) {
          using namespace WebAssembly;
        case EQ_I32: Def->setDesc(TII.get(NE_I32)); Inverted = true; break;
        case NE_I32: Def->setDesc(TII.get(EQ_I32)); Inverted = true; break;
        case GT_S_I32: Def->setDesc(TII.get(LE_S_I32)); Inverted = true; break;
        case GE_S_I32: Def->setDesc(TII.get(LT_S_I32)); Inverted = true; break;
        case LT_S_I32: Def->setDesc(TII.get(GE_S_I32)); Inverted = true; break;
        case LE_S_I32: Def->setDesc(TII.get(GT_S_I32)); Inverted = true; break;
        case GT_U_I32: Def->setDesc(TII.get(LE_U_I32)); Inverted = true; break;
        case GE_U_I32: Def->setDesc(TII.get(LT_U_I32)); Inverted = true; break;
        case LT_U_I32: Def->setDesc(TII.get(GE_U_I32)); Inverted = true; break;
        case LE_U_I32: Def->setDesc(TII.get(GT_U_I32)); Inverted = true; break;
        case EQ_I64: Def->setDesc(TII.get(NE_I64)); Inverted = true; break;
        case NE_I64: Def->setDesc(TII.get(EQ_I64)); Inverted = true; break;
        case GT_S_I64: Def->setDesc(TII.get(LE_S_I64)); Inverted = true; break;
        case GE_S_I64: Def->setDesc(TII.get(LT_S_I64)); Inverted = true; break;
        case LT_S_I64: Def->setDesc(TII.get(GE_S_I64)); Inverted = true; break;
        case LE_S_I64: Def->setDesc(TII.get(GT_S_I64)); Inverted = true; break;
        case GT_U_I64: Def->setDesc(TII.get(LE_U_I64)); Inverted = true; break;
        case GE_U_I64: Def->setDesc(TII.get(LT_U_I64)); Inverted = true; break;
        case LT_U_I64: Def->setDesc(TII.get(GE_U_I64)); Inverted = true; break;
        case LE_U_I64: Def->setDesc(TII.get(GT_U_I64)); Inverted = true; break;
        case EQ_F32: Def->setDesc(TII.get(NE_F32)); Inverted = true; break;
        case NE_F32: Def->setDesc(TII.get(EQ_F32)); Inverted = true; break;
        case EQ_F64: Def->setDesc(TII.get(NE_F64)); Inverted = true; break;
        case NE_F64: Def->setDesc(TII.get(EQ_F64)); Inverted = true; break;
        default: break;
        }
      }

      // If we weren't able to invert the condition in place. Insert an
      // instruction to invert it.
      if (!Inverted) {
        unsigned Tmp = MRI.createVirtualRegister(&WebAssembly::I32RegClass);
        BuildMI(MBB, MI, MI->getDebugLoc(), TII.get(WebAssembly::EQZ_I32), Tmp)
            .addReg(Cond);
        MFI.stackifyVReg(Tmp);
        Cond = Tmp;
        Inverted = true;
      }

      // The br_unless condition has now been inverted. Insert a br_if and
      // delete the br_unless.
      assert(Inverted);
      BuildMI(MBB, MI, MI->getDebugLoc(), TII.get(WebAssembly::BR_IF))
          .addOperand(MI->getOperand(0))
          .addReg(Cond);
      MBB.erase(MI);
    }
  }

  return true;
}


switch (st)
        {
          case Scenario::ExecutionStatus::Pending:
            doc.receiver.registerSync(parent);
            break;
          default:
            doc.receiver.unregisterSync(parent);
            break;
        }

#include "Event.hpp"

#include <Scenario/Process/Algorithms/Accessors.hpp>
#include <Scenario/Process/ScenarioInterface.hpp>
namespace RemoteControl
{
Event::Event(
    Scenario::EventModel& event,
    DocumentPlugin& doc,
    QObject* parent_comp)
    : Component{"EventComponent", parent_comp}
{
  auto si = dynamic_cast<Scenario::ScenarioInterface*>(event.parent());
  connect(
      &event,
      &Scenario::EventModel::statusChanged,
      this,
      [&, si](Scenario::ExecutionStatus st) {
        auto& parent = Scenario::parentTimeSync(event, *si);


while (probe != NULL)
	{
		FEvent *next = probe->Left;
		delete probe;
		probe = next;
	}

/*
** nodebuild_events.cpp
**
** A red-black tree for keeping track of segs that get touched by a splitter.
**
**---------------------------------------------------------------------------
** All rights reserved.
**
**---------------------------------------------------------------------------
**
*/

#include <string.h>
#include "doomtype.h"
#include "nodebuild.h"

FEventTree::FEventTree ()
: Root (&Nil), Spare (NULL)
{
	memset (&Nil, 0, sizeof(Nil));
}

FEventTree::~FEventTree ()
{
	FEvent *probe;

	DeleteAll ();
	probe = Spare;

}

void FEventTree::DeleteAll ()
{
	DeletionTraverser (Root);
	Root = &Nil;
}

void FEventTree::DeletionTraverser (FEvent *node)
{
	if (node != &Nil && node != NULL)
	{
		DeletionTraverser (node->Left);
		DeletionTraverser (node->Right);
		node->Left = Spare;
		Spare = node;
	}
}

FEvent *FEventTree::GetNewNode ()
{
	FEvent *node;

	if (Spare != NULL)
	{
		node = Spare;
		Spare = node->Left;
	}
	else
	{
		node = new FEvent;
	}
	return node;
}

void FEventTree::Insert (FEvent *z)
{
	FEvent *y = &Nil;
	FEvent *x = Root;

	while (x != &Nil)
	{
		y = x;
		if (z->Distance < x->Distance)
		{
			x = x->Left;
		}
		else
		{
			x = x->Right;
		}
	}
	z->Parent = y;
	if (y == &Nil)
	{
		Root = z;
	}
	else if (z->Distance < y->Distance)
	{
		y->Left = z;
	}
	else
	{
		y->Right = z;
	}
	z->Left = &Nil;
	z->Right = &Nil;
}

FEvent *FEventTree::Successor (FEvent *event) const
{
	if (event->Right != &Nil)
	{
		event = event->Right;
		while (event->Left != &Nil)
		{
			event = event->Left;
		}
		return event;
	}
	else
	{
		FEvent *y = event->Parent;
		while (y != &Nil && event == y->Right)
		{
			event = y;
			y = y->Parent;
		}
		return y;
	}
}

FEvent *FEventTree::Predecessor (FEvent *event) const
{
	if (event->Left != &Nil)
	{
		event = event->Left;
		while (event->Right != &Nil)
		{
			event = event->Right;
		}
		return event;
	}
	else
	{
		FEvent *y = event->Parent;
		while (y != &Nil && event == y->Left)
		{
			event = y;
			y = y->Parent;
		}
		return y;
	}
}

FEvent *FEventTree::FindEvent (double key) const
{
	FEvent *node = Root;

	while (node != &Nil)
	{
		if (node->Distance == key)
		{
			return node;
		}
		else if (node->Distance > key)
		{
			node = node->Left;
		}
		else
		{
			node = node->Right;
		}
	}
	return NULL;
}

FEvent *FEventTree::GetMinimum ()
{
	FEvent *node = Root;

	if (node == &Nil)
	{
		return NULL;
	}
	while (node->Left != &Nil)
	{
		node = node->Left;
	}
	return node;
}

void FEventTree::PrintTree (const FEvent *event) const
{
	// Use the CRT's sprintf so that it shares the same formatting as ZDBSP's output.
	char buff[100];
	if (event != &Nil)
	{
		PrintTree(event->Left);
		sprintf(buff, " Distance %g, vertex %d, seg %u\n",
			sqrt(event->Distance/4294967296.0), event->Info.Vertex, (unsigned)event->Info.FrontSeg);
		Printf(PRINT_LOG, "%s", buff);
		PrintTree(event->Right);
	}
}


CppUnit::Test*
MimeSnifferTest::Suite() {
	CppUnit::TestSuite *suite = new CppUnit::TestSuite();
	typedef CppUnit::TestCaller<MimeSnifferTest> TC;

	suite->addTest( new TC("Mime Sniffer::Scanner Test",
						   &MimeSnifferTest::ScannerTest) );
	suite->addTest( new TC("Mime Sniffer::Parser Test",
						   &MimeSnifferTest::ParserTest) );
	suite->addTest( new TC("Mime Sniffer::Sniffer Test",
						   &MimeSnifferTest::SnifferTest) );

	return suite;
}

// MimeSnifferTest.cpp

#include "MimeSnifferTest.h"

#include <cppunit/Test.h>
#include <cppunit/TestSuite.h>
#include <cppunit/TestCaller.h>
#include <sniffer/Rule.h>
#include <sniffer/Parser.h>
#include <DataIO.h>
#include <Mime.h>
#include <String.h>		// BString
#include <TestUtils.h>

#include <stdio.h>

#include <iostream>
using std::cout;
using std::endl;

using namespace BPrivate::Storage::Sniffer;

// Suite

// Scanner Test
void
MimeSnifferTest::ScannerTest() {
#if TEST_R5
	Outputf("(no tests actually performed for R5 version)\n");
#else	// TEST_R5

	// Internal TokenStream and CharStream classes

// Define some useful macros for dynamically allocating
// various Token classes
#define T(type) (new Token(type, -1))
#define S(str) (new StringToken(str, -1))
#define I(val) (new IntToken(val, -1))
#define F(val) (new FloatToken(val, -1))

	struct test_case {
		const char *rule;
		int tokenCount;
		Token *tokens[256];
	} testCases[] = {
		{ "'Hey'[]:", 4,
			{	S("Hey"),
				T(LeftBracket),
				T(RightBracket),
				T(Colon)
			}
		},
		{ "1", 1, { I(1) } },
		{ "1.0", 1, { F(1.0) } },

		{ "1.0 (\"ABCD\")", 4, { F(1.0), T(LeftParen), S("ABCD"), T(RightParen) } },
		{ "1.0 ('ABCD')", 4, { F(1.0), T(LeftParen), S("ABCD"), T(RightParen) } },
		{ "  1.0 ('ABCD')  ", 4, { F(1.0), T(LeftParen), S("ABCD"), T(RightParen) } },
		{ "0.8 [0:3] ('ABCDEFG' | 'abcdefghij')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ABCDEFG"),
				T(Divider),
				S("abcdefghij"),
				T(RightParen)
			}
		},
		{ "0.5([10]'ABCD'|[17]'abcd'|[13]'EFGH')", 17,
			{	F(0.5),
				T(LeftParen),
				T(LeftBracket),
				I(10),
				T(RightBracket),
				S("ABCD"),
				T(Divider),
				T(LeftBracket),
				I(17),
				T(RightBracket),
				S("abcd"),
				T(Divider),
				T(LeftBracket),
				I(13),
				T(RightBracket),
				S("EFGH"),
				T(RightParen)
			}
		},
		{ "0.5  \n   [0:3]  \t ('ABCD' \n | 'abcd' | 'EFGH')", 13,
			{	F(0.5),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ABCD"),
				T(Divider),
				S("abcd"),
				T(Divider),
				S("EFGH"),
				T(RightParen)
			}
		},
		{ "0.8 [  0  :  3  ] ('ABCDEFG' | 'abcdefghij')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ABCDEFG"),
				T(Divider),
				S("abcdefghij"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] ('ABCDEFG' & 'abcdefg')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ABCDEFG"),
				T(Ampersand),
				S("abcdefg"),
				T(RightParen)
			}
		},
		{ "1.0 ('ABCD') | ('EFGH')", 8,
			{	F(1.0),
				T(LeftParen),
				S("ABCD"),
				T(RightParen),
				T(Divider),
				T(LeftParen),
				S("EFGH"),
				T(RightParen)
			}
		},
		{ "1.0 [0:3] ('ABCD') | [2:4] ('EFGH')", 18,
			{	F(1.0),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ABCD"),
				T(RightParen),
				T(Divider),
				T(LeftBracket),
				I(2),
				T(Colon),
				I(4),
				T(RightBracket),
				T(LeftParen),
				S("EFGH"),
				T(RightParen)
			}
		},
		{ "0.8 [0:4] (\\077Mkj0x34 & 'abcdefgh')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(4),
				T(RightBracket),
				T(LeftParen),
				S("\077Mkj0x34"),
				T(Ampersand),
				S("abcdefgh"),
				T(RightParen)
			}
		},
		{ "0.8 [0:4] (\\077Mkj\\x34 & 'abcdefgh')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(4),
				T(RightBracket),
				T(LeftParen),
				S("\077Mkj\x34"),
				T(Ampersand),
				S("abcdefgh"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\077034 & 'abcd')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\077034"),
				T(Ampersand),
				S("abcd"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\077\\034 & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\077\034"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\77\\034 & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\077\034"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\7 & 'a')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\007"),
				T(Ampersand),
				S("a"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\"\\17\" & 'a')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\017"),
				T(Ampersand),
				S("a"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] ('\\17' & 'a')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\017"),
				T(Ampersand),
				S("a"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\g & 'a')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("g"),
				T(Ampersand),
				S("a"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\g&\\b)", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("g"),
				T(Ampersand),
				S("\b"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\g\\&b & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("g&b"),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (0x3457 & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\x34\x57"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\x34\\x57 & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\x34\x57"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (0xA4b7 & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\xA4\xb7"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\\xA4\\xb7 & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\xA4\xb7"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\"\\xA4\\xb7\" & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\xA4\xb7"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\'\\xA4\\xb7\' & 'ab')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("\xA4\xb7"),
				T(Ampersand),
				S("ab"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] ('ab\"' & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ab\""),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\"ab\\\"\" & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ab\""),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\"ab\\A\" & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("abA"),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\"ab'\" & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ab'"),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "0.8 [0:3] (\"ab\\\\\" & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(0),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("ab\\"),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "0.8 [-5:-3] (\"abc\" & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(-5),
				T(Colon),
				I(-3),
				T(RightBracket),
				T(LeftParen),
				S("abc"),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "0.8 [5:3] (\"abc\" & 'abc')", 11,
			{	F(0.8),
				T(LeftBracket),
				I(5),
				T(Colon),
				I(3),
				T(RightBracket),
				T(LeftParen),
				S("abc"),
				T(Ampersand),
				S("abc"),
				T(RightParen)
			}
		},
		{ "1.2 ('ABCD')", 4,
			{	F(1.2),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ ".2 ('ABCD')", 4,
			{	F(0.2),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "0. ('ABCD')", 4,
			{	F(0.0),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		// Signed integers
		{ "-1 ('ABCD')", 4,
			{	I(-1),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "+1 ('ABCD')", 4,
			{	I(1),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		// Unsigned extended floats
		{ "1E25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "1e25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "1E+25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "1e+25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "1E-25 ('ABCD')", 4,
			{	F(1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "1e-25 ('ABCD')", 4,
			{	F(1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		// Positive signed extended floats
		{ "+1E25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "+1e25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "+1E+25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "+1e+25 ('ABCD')", 4,
			{	F(1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "+1E-25 ('ABCD')", 4,
			{	F(1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "+1e-25 ('ABCD')", 4,
			{	F(1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		// Negative signed extended floats
		{ "-1E25 ('ABCD')", 4,
			{	F(-1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "-1e25 ('ABCD')", 4,
			{	F(-1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "-1E+25 ('ABCD')", 4,
			{	F(-1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "-1e+25 ('ABCD')", 4,
			{	F(-1e25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "-1E-25 ('ABCD')", 4,
			{	F(-1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "-1e-25 ('ABCD')", 4,
			{	F(-1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		// Miscellaneous extended floats
		{ ".1E-25 ('ABCD')", 4,
			{	F(0.1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "-.1e-25 ('ABCD')", 4,
			{	F(-0.1e-25),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		// Signed floats
		{ "-1.0 ('ABCD')", 4,
			{	F(-1.0),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		{ "+1.0 ('ABCD')", 4,
			{	F(1.0),
				T(LeftParen),
				S("ABCD"),
				T(RightParen)
			}
		},
		// The uber test
		{ "0 -0 +0 1 -2 +3 0. -0. +0. 1. -2. +3. 0.0 -0.1 +0.2 1.0 -2.1 +3.2 "
		  "0.e0 0.e-1 0.e+2 1.e1 2.e-2 3.e+3 -1.e1 -2.e-2 -3.e+3 +1.e1 +2.e-2 +3.e+3 "
		  "0.012345 1.23456 ( ) [ ] | & : -i "
		  " \"abcxyzABCXYZ_ ( ) [ ] | & : -i \t\n \\\" ' \\012\\0\\377\\x00\\x12\\xab\\xCD\\xeF\\x1A\\xb2 \" "
		  " 'abcxyzABCXYZ_ ( ) [ ] | & : -i \t\n \" \\' \\012\\0\\377\\x00\\x12\\xab\\xCD\\xeF\\x1A\\xb2 ' "
		  " \\000abc_xyz123\"'\"'456 \\xA1a1 \\!\\?\\\\ "
		  " 0x00 0x12 0xabCD 0xaBcD 0x0123456789aBcDeFfEdCbA", 50,
		  	{	I(0), I(0), I(0), I(1), I(-2), I(3), F(0.0), F(0.0), F(0.0),
		  			F(1.0), F(-2.0), F(3.0), F(0.0), F(-0.1), F(0.2), F(1.0), F(-2.1), F(3.2),
		  		F(0.0), F(0.0e-1), F(0.0e2), F(1.0e1), F(2.0e-2), F(3.0e3),
		  			F(-1.0e1), F(-2.0e-2), F(-3.0e3), F(1.0e1), F(2.0e-2), F(3.0e3),
		  		F(0.012345), F(1.23456), T(LeftParen), T(RightParen), T(LeftBracket),
		  			T(RightBracket), T(Divider), T(Ampersand), T(Colon), T(CaseInsensitiveFlag),
		  		S(std::string("abcxyzABCXYZ_ ( ) [ ] | & : -i \t\n \" ' \012\0\377\x00\x12\xab\xCD\xeF\x1A\xb2 ", 49)),
		  		S(std::string("abcxyzABCXYZ_ ( ) [ ] | & : -i \t\n \" ' \012\0\377\x00\x12\xab\xCD\xeF\x1A\xb2 ", 49)),
		  		S(std::string("\000abc_xyz123\"'\"'456", 18)),
		  		S("\241a1"),
		  		S("!?\\"),
		  		S(std::string("\x00", 1)), S("\x12"), S("\xAB\xCD"), S("\xAB\xCD"),
		  			S("\x01\x23\x45\x67\x89\xAB\xCD\xEF\xFE\xDC\xBA")
		  	}
		},
	};

// Undefine our nasty macros
#undef T
#undef S
#undef I
#undef F

	const int testCaseCount = sizeof(testCases) / sizeof(test_case);
	for (int i = 0; i < testCaseCount; i++) {
		NextSubTest();
		TokenStream stream;
		try {
			stream.SetTo(testCases[i].rule);

			CHK(stream.InitCheck() == B_OK);
			for (int j = 0; j < testCases[i].tokenCount; j++) {
				const Token *token = stream.Get();
				CHK(token);
/*
				cout << tokenTypeToString(token->Type()) << endl;

				if (token->Type() == CharacterString)
					cout << " token1 == " << token->String() << endl;
				if (testCases[i].tokens[j]->Type() == CharacterString)
					cout << " token2 == " << (testCases[i].tokens[j])->String() << endl;

				if (token->Type() == CharacterString)
				{
					const std::string &str = token->String();
					printf("parser: ");
					for (int i = 0; i < str.length(); i++)
						printf("%x ", str[i]);
					printf("\n");
				}
				if (testCases[i].tokens[j]->Type() == CharacterString)
				{
					const std::string &str = (testCases[i].tokens[j])->String();
					printf("tester: ");
					for (int i = 0; i < str.length(); i++)
						printf("%x ", str[i]);
					printf("\n");
				}

				switch (token->Type()) {
					case CharacterString:
						cout << " string == " << token->String() << endl;
						break;
					case Integer:
						cout << " int == " << token->Int() << endl;
						break;
					case FloatingPoint:
						cout << " float == " << token->Float() << endl;
						break;
				}
*/
				CHK(*token == *(testCases[i].tokens[j]));
				delete testCases[i].tokens[j];
			}
			CHK(stream.IsEmpty());
		} catch (Err *e) {
			CppUnit::Exception *err = new CppUnit::Exception(e->Msg());
			delete e;
			throw *err;
		}
	}

#endif	// !TEST_R5
}

// Parser Test
void
MimeSnifferTest::ParserTest() {
	// test a couple of valid and invalid rules
	struct test_case {
		const char	*rule;
		const char	*error;	// NULL, if valid
	} testCases[] = {
		// valid rules
		{ "1.0 (\"ABCD\")", NULL },
		{ "1.0 ('ABCD')", NULL },
		{ "  1.0 ('ABCD')  ", NULL },
		{ "0.8 [0:3] ('ABCDEFG' | 'abcdefghij')", NULL },
		{ "0.5([10]'ABCD'|[17]'abcd'|[13]'EFGH')", NULL } ,
		{ "0.5  \n   [0:3]  \t ('ABCD' \n | 'abcd' | 'EFGH')", NULL },
		{ "0.8 [  0  :  3  ] ('ABCDEFG' | 'abcdefghij')", NULL },
		{ "0.8 [0:3] ('ABCDEFG' & 'abcdefg')", NULL },
// These two rules are accepted by the R5 sniffer checker, but not
// by the parser. Thus, we're not accepting them with either.
		{ "0.8 [0:3] (\\077Mkl0x34 & 'abcdefgh')", NULL },
		{ "0.8 [0:3] (\\077034 & 'abcd')", NULL },
		{ "0.8 [0:3] (\\077\\034 & 'ab')", NULL },
		{ "0.8 [0:3] (\\77\\034 & 'ab')", NULL },
		{ "0.8 [0:3] (\\7 & 'a')", NULL },
		{ "0.8 [0:3] (\"\\17\" & 'a')", NULL },
		{ "0.8 [0:3] ('\\17' & 'a')", NULL },
		{ "0.8 [0:3] (\\g & 'a')", NULL },
		{ "0.8 [0:3] (\\g&\\b)", NULL },
		{ "0.8 [0:3] (\\g\\&b & 'abc')", NULL },
		{ "0.8 [0:3] (0x3457 & 'ab')", NULL },
		{ "0.8 [0:3] (0xA4b7 & 'ab')", NULL },
		{ "0.8 [0:3] ('ab\"' & 'abc')", NULL },
		{ "0.8 [0:3] (\"ab\\\"\" & 'abc')", NULL },
		{ "0.8 [0:3] (\"ab\\A\" & 'abc')", NULL },
		{ "0.8 [0:3] (\"ab'\" & 'abc')", NULL },
		{ "0.8 [0:3] (\"ab\\\\\" & 'abc')", NULL },
		{ "0.8 [-5:-3] (\"abc\" & 'abc')", NULL },
// Also accepted by the R5 sniffer but not the R5 parser. We reject.
		{ "1.0 ('ABCD')", NULL },
		{ ".2 ('ABCD')", NULL },
		{ "0. ('ABCD')", NULL },
		{ "1 ('ABCD')", NULL },
		{ "+1 ('ABCD')", NULL },
// We accept extended notation floating point numbers now, but
// not invalid priorities.
// R5 chokes on this rule :-(
#if !TEST_R5
		{ "1e-3 ('ABCD')", NULL },
#endif
		{ "+.003e2 ('ABCD')", NULL },
// This one too. See how much better our parser is? :-)
#if !TEST_R5
		{ "-123e-9999999999 ('ABCD')", NULL },	// Hooray for the stunning accuracy of floating point ;-)
#endif
		// invalid rules
		{ "0.0 ('')",
			"Sniffer pattern error: illegal empty pattern" },
		{ "('ABCD')",
			"Sniffer pattern error: match level expected" },
		{ "[0:3] ('ABCD')",
			"Sniffer pattern error: match level expected" },
		{ "0.8 [0:3] ( | 'abcdefghij')",
		  "Sniffer pattern error: missing pattern" },
		{ "0.8 [0:3] ('ABCDEFG' | )",
		  "Sniffer pattern error: missing pattern" },
		{ "[0:3] ('ABCD')",
			"Sniffer pattern error: match level expected" },
		{ "1.0 (ABCD')",
#if TEST_R5
			"Sniffer pattern error: misplaced single quote"
#else
			"Sniffer pattern error: invalid character 'A'"
#endif
		},
		{ "1.0 ('ABCD)",
#if TEST_R5
			"Sniffer pattern error: unterminated rule"
#else
			"Sniffer pattern error: unterminated single-quoted string"
#endif
		},
		{ "1.0 (ABCD)",
#if TEST_R5
			"Sniffer pattern error: missing pattern"
#else
			"Sniffer pattern error: invalid character 'A'"
#endif
		},
		{ "1.0 (ABCD 'ABCD')",
#if TEST_R5
			"Sniffer pattern error: missing pattern"
#else
			"Sniffer pattern error: invalid character 'A'"
#endif
		},
		{ "1.0 'ABCD')",
#if TEST_R5
			"Sniffer pattern error: missing pattern"
#else
			"Sniffer pattern error: missing pattern"
#endif
		},
		{ "1.0 ('ABCD'",
			"Sniffer pattern error: unterminated rule" },
		{ "1.0 'ABCD'",
#if TEST_R5
			"Sniffer pattern error: missing sniff pattern"
#else
			"Sniffer pattern error: missing pattern"
#endif
		},
		{ "0.5 [0:3] ('ABCD' | 'abcd' | [13] 'EFGH')",
		  	"Sniffer pattern error: missing pattern" },
		{ "0.5('ABCD'|'abcd'|[13]'EFGH')",
		  	"Sniffer pattern error: missing pattern" },
		{ "0.5[0:3]([10]'ABCD'|[17]'abcd'|[13]'EFGH')",
		  	"Sniffer pattern error: missing pattern" },
		{ "0.8 [0x10:3] ('ABCDEFG' | 'abcdefghij')",
		  	"Sniffer pattern error: pattern offset expected" },
		{ "0.8 [0:A] ('ABCDEFG' | 'abcdefghij')",
#if TEST_R5
		  	"Sniffer pattern error: pattern range end expected"
#else
			"Sniffer pattern error: invalid character 'A'"
#endif
		},
		{ "0.8 [0:3] ('ABCDEFG' & 'abcdefghij')",
		  	"Sniffer pattern error: pattern and mask lengths do not match" },
		{ "0.8 [0:3] ('ABCDEFG' & 'abcdefg' & 'xyzwmno')",
#if TEST_R5
		  	"Sniffer pattern error: unterminated rule"
#else
			"Sniffer pattern error: expecting '|', ')', or possibly '&'"
#endif
		},
		{ "0.8 [0:3] (\\g&b & 'a')",
#if TEST_R5
			"Sniffer pattern error: missing mask"
#else
			"Sniffer pattern error: invalid character 'b'"
#endif
		},
		{ "0.8 [0:3] (\\19 & 'a')",
		  	"Sniffer pattern error: pattern and mask lengths do not match" },
		{ "0.8 [0:3] (0x345 & 'ab')",
		  	"Sniffer pattern error: bad hex literal" },
		{ "0.8 [0:3] (0x3457M & 'abc')",
#if TEST_R5
		  	"Sniffer pattern error: expecting '|' or '&'"
#else
			"Sniffer pattern error: invalid character 'M'"
#endif
		},
		{ "0.8 [0:3] (0x3457\\7 & 'abc')",
#if TEST_R5
		  	"Sniffer pattern error: expecting '|' or '&'"
#else
			"Sniffer pattern error: expecting '|', ')', or possibly '&'"
#endif
		},

		// Miscellaneous tests designed to hit every remaining
		// relevant "throw new Err()" statement in the scanner.
		// R5 versions will come later...
#if !TEST_R5
		{ "\x03  ", "Sniffer pattern error: invalid character '\x03'" },
		{ "\"blah", "Sniffer pattern error: unterminated double-quoted string" },
		{ "0xThisIsNotAHexCode", "Sniffer pattern error: incomplete hex code" },
		{ "0xAndNeitherIsThis:-)", "Sniffer pattern error: bad hex literal" },
		{ ".NotAFloat", "Sniffer pattern error: incomplete floating point number" },
		{ "-NotANumber", "Sniffer pattern error: incomplete signed number" },
		{ "+NotANumber", "Sniffer pattern error: incomplete signed number" },

		{ "0.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "1.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ ".0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "1e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1.e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1.e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },

		{ "0.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "1.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ ".0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "1e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1.e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1.e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },

		{ "0.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "1.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ ".0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "1e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1.e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1.e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "-1.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
		{ "+1.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },

		{ "\\11\\", "Sniffer pattern error: incomplete escape sequence" },
		{ "\"Escape!! \\", "Sniffer pattern error: incomplete escape sequence" },
		{ "'Escape!! \\", "Sniffer pattern error: incomplete escape sequence" },

		{ "\\x", "Sniffer pattern error: incomplete escaped hex code" },
		{ "\\xNotAHexCode", "Sniffer pattern error: incomplete escaped hex code" },
		{ "\\xAlsoNotAHexCode", "Sniffer pattern error: incomplete escaped hex code" },
		{ "\\x0", "Sniffer pattern error: incomplete escaped hex code" },

		{ "1.0 (\\377)", NULL },
		{ "\\400", "Sniffer pattern error: invalid octal literal (octals must be between octal 0 and octal 377 inclusive)" },
		{ "\\777", "Sniffer pattern error: invalid octal literal (octals must be between octal 0 and octal 377 inclusive)" },
		{ "1.0 (\\800)", NULL },

		{ NULL, "Sniffer pattern error: NULL pattern" },

		{ "-2", "Sniffer pattern error: invalid priority" },
		{ "+2", "Sniffer pattern error: invalid priority" },

		{ "1.0", "Sniffer pattern error: missing expression" },
#endif	// !TEST_R5

//		{ "1E-25 ('ABCD')", "Sniffer pattern error: missing pattern" },
			// I don't currently understand what's wrong with the above rule... R5
			// rejects it though, for some reason.
	};
	const int testCaseCount = sizeof(testCases) / sizeof(test_case);
	BMimeType type;
	for (int32 i = 0; i < testCaseCount; i++) {
		NextSubTest();
		test_case &testCase = testCases[i];
		BString parseError;
		status_t error = BMimeType::CheckSnifferRule(testCase.rule,
													 &parseError);
		if (testCase.error == NULL) {
			if (error != B_OK) {
				cout << endl << "This sucker's gonna fail..."
				     << endl << "RULE: '" << testCase.rule << "'"
				     << endl << "ERROR: "
				     << endl << parseError.String()
				     << endl;
			}
			CHK(error == B_OK);
		} else {

/*
			if (parseError.FindLast(testCase.error) >= 0) {
				cout << " -- OKAY" << endl;
			} else {
				cout << " -- NOGO" << endl;
				cout << testCase.error << endl;
			}
*/
if (testCase.rule && error != B_BAD_MIME_SNIFFER_RULE) {
printf("rule: `%s'", testCase.rule);
RES(error);
}
			CHK(error == (testCase.rule ? B_BAD_MIME_SNIFFER_RULE : B_BAD_VALUE));
			CHK(parseError.FindLast(testCase.error) >= 0);
		}
	}
}

void dumpStr(const std::string &string, const char *label = NULL) {
	if (label)
		printf("%s: ", label);
	for (uint i = 0; i < string.length(); i++)
		printf("%x ", string[i]);
	printf("\n");
}

void
MimeSnifferTest::SnifferTest() {
#if TEST_R5
	Outputf("(no tests actually performed for R5 version)\n");
#else	// TEST_R5
	const char *rules[] = {
		// General tests
		"1.0 ('#include')",
		"0.0 [0:32] ('#include')",
		"0.e-230 [0:32] (\\#include | \\#ifndef)",
		".2 ([0:32] \"#include\" | [0] '#define' | [0:200] 'int main(')",
		"1.0 [0:32] ('<html>' | '<head>' | '<body>')",
		// Range tests
		"1.0 [0:9] ('rock')",
		"1.0 ([0:9] 'roll')",
		"1.0 ([0:9] 'rock' | [0:9] 'roll')",
		"1.0 [0:9] ('rock' | 'roll')",
		"1.0 ([0] 'rock')",
		"1.0 ([0] 'rock' | [0:9] 'roll')",
		"1.0 ([9] 'rock' | [10] 'roll')",
		// Mask, octal, and hex tests
		"1.0 (\\xFF\\xFF & '\\xF0\\xF0')",
		"1.0 ('\\33\\34' & \\xFF\\x00)",
		"1.0 (\\33\\34 & \"\\x00\\xFF\")",
		"1.0 (\\xFF & \\x05)",
		// Conjunctions
		"1.0 ([4] 'rock') ([9] 'roll')",
		"1.0 [5] ('roll') [10] ('rock')",
		"1.0 [4] ('rock' | 'roll') ([9] 'rock' | [10] 'roll')",
		// Case insensitivity tests
		"1.0 [4] (-i 'Rock' | 'Roll')",
		"1.0 [9] ('Rock' | -i 'Roll')",
		"1.0 (-i [4] 'Rock' | [9] 'Roll')",
		"1.0 ([9] 'Rock' | -i [4] 'Roll')",
	};
	const int ruleCount = sizeof(rules)/sizeof(char*);
	struct test_case {
		const std::string data;
		const bool	result[ruleCount];
	} tests[] = {

{
"#include <stdio.h>		\n\
#include <stdlib.h>		\n\
						\n\
int main() {			\n\
	return 0;			\n\
}						\n\
\n\
",	{	true, true, true, true, false,
		false, false, false, false, false, false, false,
		false, false, false, false,
		false, false, false,
		false, false, false, false
	}
},
{
"	#include <stdio.h>		\n\
	#include <stdlib.h>		\n\
						\n\
	int main() {			\n\
		return 0;			\n\
	}						\n\
\n\
",	{	false, true, true, true, false,
		false, false, false, false, false, false, false,
		false, false, false, false,
		false, false, false,
		false, false, false, false
	}
},
{
"#ifndef SOME_TEST_H		\n\
#define SOME_TEST_H			\n\
							\n\
void main();				\n\
							\n\
#endif	// SOME_TEST_H		\n\
							\n\
",	{	false, false, true, false, false,
		false, false, false, false, false, false, false,
		false, false, false, false,
		false, false, false,
		false, false, false, false
	}
},
{
"//------------------		\n\
// SomeTest.cpp				\n\
#include <stdio.h>			\n\
							\n\
int main() {				\n\
	return 0;				\n\
}							\n\
							\n\
",	{	false, false, false, true, false,
		false, false, false, false, false, false, false,
		false, false, false, true,
		false, false, false,
		false, false, false, false
	}
},
{
"<html>									\n\
<body bgcolor='#ffffff'>				\n\
HTML is boring as hell		<br>		\n\
when i write it too much	<br>		\n\
my head starts to swell		<br>		\n\
<br>									\n\
HTML is stupid and dumb		<br>		\n\
running through traffic		<br>		\n\
is ten times as fun			<br>		\n\
</body>									\n\
</html>									\n\
",	{	false, false, false, false, true,
		false, false, false, false, false, false, false,
		false, false, false, false,
		false, false, false,
		false, false, false, false
	}
},
{
"     rock&roll",		// 5,10
	{	false, false, false, false, false,
		true, false, true, true, false, false, true,
		false, false, false, false,
		false, false, false,
		false, false, false, false
	}
},
{
"    rock&roll",		// 4,9
	{ 	false, false, false, false, false,
		true, true, true, true, false, true, false,
		false, false, false, false,
		true, false, false,
		true, true, true, false
	}
},
{
"     roll&rock",		// 5,10
	{	false, false, false, false, false,
		false, true, true, true, false, true, false,
		false, false, false, false,
		false, true, false,
		false, false, false, false
	}
},
{
"    roll&rock",		// 4,9
	{ 	false, false, false, false, false,
		true, true, true, true, false, true, true,
		false, false, false, false,
		false, false, true,
		true, true, false, true
	}
},
{
"    ROCK&ROLL",		// 4,9
	{ 	false, false, false, false, false,
		false, false, false, false, false, false, false,
		false, false, false, false,
		false, false, false,
		true, true, true, false
	}
},
{
"    rOlL&RoCk",		// 4,9
	{ 	false, false, false, false, false,
		false, false, false, false, false, false, false,
		false, false, false, false,
		false, false, false,
		true, true, false, true
	}
},
{
"\xFF\xFF	FF FF",
	{	false, false, false, false, false,
		false, false, false, false, false, false, false,
		true, false, false, true,
		false, false, false,
		false, false, false, false
	}
},
{
"\xFA\xFA	FA FA",
	{	false, false, false, false, false,
		false, false, false, false, false, false, false,
		true, false, false, false,
		false, false, false,
		false, false, false, false
	}
},
{
"\xAF\xAF	AF AF",
	{	false, false, false, false, false,
		false, false, false, false, false, false, false,
		false, false, false, true,
		false, false, false,
		false, false, false, false
	}
},
{
std::string("\033\000	033 000", 10),	// Otherwise, it thinks the NULL is the end of the string
	{	false, false, false, false, false,
		false, false, false, false, false, false, false,
		false, true, false, false,
		false, false, false,
		false, false, false, false
	}
},
{
std::string("\000\034	000 034", 10),	// Otherwise, it thinks the NULL is the end of the string
	{	false, false, false, false, false,
		false, false, false, false, false, false, false,
		false, false, true, false,
		false, false, false,
		false, false, false, false
	}
},
{
"\033\034	033 034",
	{	false, false, false, false, false,
		false, false, false, false, false, false, false,
		false, true, true, false,
		false, false, false,
		false, false, false, false
	}
},
	};	// tests[]
	const int32 testCount = sizeof(tests)/sizeof(test_case);

	for (int i = 0; i < testCount; i++) {
		if (i > 0)
			NextSubTestBlock();
		test_case &test = tests[i];

		for (int j = 0; j < ruleCount; j++) {
			NextSubTest();
			Rule rule;
			BString errorMsg;
			status_t err = parse(rules[j], &rule, &errorMsg);
			if (err) {
			}
			CHK(err == B_OK);
			if (!err) {
				BMallocIO data;
				data.Write(test.data.data(), test.data.length());//strlen(test.data));
				bool match = rule.Sniff(&data);
				CHK(match == test.result[j]);
			}
		}
	}
#endif // !TEST_R5
}