Use MORDOR_THROW_EXCEPTION

Use MORDOR_THROW_EXCEPTION

Change-Id: Ia9eb6eaa261f7c84d4bb23e30eeb40faa5081ee4
Reviewed-on: https://gerrit.dechocorp.com/9132
Reviewed-by: Cody Cutrer <c4fe2b1d90ef8f2548c8aeabfa633434316f0305@mozy.com>

// Copyright (c) 2009 - Mozy, Inc.
/* To compile to .cpp:
   ragel uri.rl -G2 -o uri.cpp
*/

#include "mordor/pch.h"

#include "mordor/uri.h"

#include <sstream>

#include "mordor/ragel.h"
#include "mordor/string.h"
#include "mordor/version.h"

namespace Mordor {

static const std::string unreserved("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
static const std::string sub_delims("!$&'()*+,;=");
static const std::string scheme("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.");
static const std::string userinfo("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string host("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string pchar("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@");
static const std::string path("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/");
static const std::string segment_nc("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" "@");
static const std::string query("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/?");
static const std::string queryString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$'()*," ":@" "/?");

static std::string escape(const std::string& str, const std::string& allowedChars, bool spaceAsPlus = false)
{
    const char *hexdigits = "0123456789ABCDEF";
    std::string result(str);

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while(c < end)
    {
        if (allowedChars.find(*c) == std::string::npos) {
            if (!differed) {
                result.resize(c - str.c_str());
                differed = true;
            }
            if (*c == ' ' && spaceAsPlus) {
                result.append(1, '+');
            } else {
                result.append(1, '%');
                result.append(1, hexdigits[(unsigned char)*c >> 4]);
                result.append(1, hexdigits[*c & 0xf]);
            }
        } else {
            if (differed) {
                result.append(1, *c);
            }
        }
        ++c;
    }

    if (differed) {
        MORDOR_ASSERT(result.length() >= str.length());
    } else {
        MORDOR_ASSERT(result == str);
    }
    return result;
}

std::string unescape(const std::string& str, bool spaceAsPlus = false)
{
    std::string result = str;

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while (c < end)
    {
        if (*c == '%') {
            MORDOR_ASSERT(c + 2 < end);
            if (!differed) {
                result.resize(c - str.c_str());
                differed = true;
            }
            char decoded;
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded = (*c - 'a' + 10) << 4;
            else if (*c >= 'A' && *c <= 'F')
                decoded = (*c - 'A' + 10) << 4;
            else {
                MORDOR_ASSERT(*c >= '0' && *c <='9');
                decoded = (*c - '0') << 4;
            }
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded |= *c - 'a' + 10;
            else if (*c >= 'A' && *c <= 'F')
                decoded |= *c - 'A' + 10;
            else {
                MORDOR_ASSERT(*c >= '0' && *c <='9');
                decoded |= *c - '0';
            }
            result.append(1, decoded);
        } else if (*c == '+' && spaceAsPlus) {
            if (!differed) {
                result.resize(c - str.c_str());
                differed = true;
            }
            result.append(1, ' ');
        } else if (differed) {
            result.append(1, *c);
        }
        ++c;
    }
    return result;
}

std::string
URI::encode(const std::string &str, CharacterClass charClass)
{
    switch (charClass) {
        case UNRESERVED:
            return escape(str, unreserved, false);
        case QUERYSTRING:
            return escape(str, Mordor::queryString, true);
        default:
            MORDOR_NOTREACHED();
    }
}

std::string
URI::decode(const std::string &str, CharacterClass charClass)
{
    switch (charClass) {
        case UNRESERVED:
            return unescape(str, false);
        case QUERYSTRING:
            return unescape(str, true);
        default:
            MORDOR_NOTREACHED();
    }
}

%%{
    # See RFC 3986: http://www.ietf.org/rfc/rfc3986.txt

    machine uri_parser;

    gen_delims = ":" | "/" | "?" | "#" | "[" | "]" | "@";
    sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=";
    reserved = gen_delims | sub_delims;
    unreserved = alpha | digit | "-" | "." | "_" | "~";
    pct_encoded = "%" xdigit xdigit;

    action marku { mark = fpc; }
    action save_scheme
    {
        m_uri->scheme(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    scheme = (alpha | digit | "+" | "-" | ".")+ >marku %save_scheme;

    action save_port
    {
        if (fpc == mark)
            m_uri->authority.port(-1);
        else
            m_uri->authority.port(atoi(mark));
        mark = NULL;
    }
    action save_userinfo
    {
        m_uri->authority.userinfo(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }
    action save_host
    {
        if (mark != NULL) {
            m_uri->authority.host(unescape(std::string(mark, fpc - mark)));
            mark = NULL;
        }
    }

    userinfo = (unreserved | pct_encoded | sub_delims | ":")*;
    dec_octet = digit | [1-9] digit | "1" digit{2} | 2 [0-4] digit | "25" [0-5];
    IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
    h16 = xdigit{1,4};
    ls32 = (h16 ":" h16) | IPv4address;
    IPv6address = (                         (h16 ":"){6} ls32) |
                  (                    "::" (h16 ":"){5} ls32) |
                  ((             h16)? "::" (h16 ":"){4} ls32) |
                  (((h16 ":"){1} h16)? "::" (h16 ":"){3} ls32) |
                  (((h16 ":"){2} h16)? "::" (h16 ":"){2} ls32) |
                  (((h16 ":"){3} h16)? "::" (h16 ":"){1} ls32) |
                  (((h16 ":"){4} h16)? "::"              ls32) |
                  (((h16 ":"){5} h16)? "::"              h16 ) |
                  (((h16 ":"){6} h16)? "::"                  );
    IPvFuture = "v" xdigit+ "." (unreserved | sub_delims | ":")+;
    IP_literal = "[" (IPv6address | IPvFuture) "]";
    reg_name = (unreserved | pct_encoded | sub_delims)*;
    host = IP_literal | IPv4address | reg_name;
    port = digit*;

    authority = ( (userinfo %save_userinfo "@")? host >marku %save_host (":" port >marku %save_port)? ) >marku;

    action save_segment
    {
        m_path->segments.push_back(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
    segment = pchar* >marku %save_segment;
    segment_nz = pchar+ >marku %save_segment;
    segment_nz_nc = (pchar - ":")+ >marku %save_segment;

    action set_absolute
    {
        m_path->type = URI::Path::ABSOLUTE;
    }
    action set_relative
    {
        m_path->type = URI::Path::RELATIVE;
    }

    path_abempty = ("/" segment >set_absolute)*;
    path_absolute = "/" (segment_nz ("/" segment)*)?  >set_absolute;
    path_noscheme = segment_nz_nc >set_relative ("/" segment)*;
    path_rootless = segment_nz >set_relative ("/" segment)*;
    path_empty = "" %set_relative;
    path = (path_abempty | path_absolute | path_noscheme | path_rootless | path_empty);

    action save_query
    {
        m_uri->m_query = std::string(mark, fpc - mark);
        m_uri->m_queryDefined = true;
        mark = NULL;
    }
    action save_fragment
    {
        m_uri->fragment(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    query = (pchar | "/" | "?")* >marku %save_query;
    fragment = (pchar | "/" | "?")* >marku %save_fragment;

    hier_part = ("//" authority path_abempty) | path_absolute | path_rootless | path_empty;

    relative_part = ("//" authority path_abempty) | path_absolute | path_noscheme | path_empty;
    relative_ref = relative_part ( "?" query )? ( "#" fragment )?;

    absolute_URI = scheme ":" hier_part ( "?" query )? ;
    # Obsolete, but referenced from HTTP, so we translate
    relative_URI = relative_part ( "?" query )?;

    URI = scheme ":" hier_part ( "?" query )? ( "#" fragment )?;
    URI_reference = URI | relative_ref;
}%%

%%{
        machine uri_parser_proper;
        include uri_parser;
        main := URI_reference;
        write data;
}%%

class URIParser : public RagelParser
{
public:
    URIParser(URI& uri)
    {
        m_uri = &uri;
        m_path = &m_uri->path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
        %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
    }

public:
    bool complete() const
    {
        return false;
    }

    bool final() const
    {
        return cs >= uri_parser_proper_first_final;
    }

    bool error() const
    {
        return cs == uri_parser_proper_error;
    }

private:
    URI *m_uri;
    URI::Path *m_path;
};

%%{
    machine uri_path_parser;
    include uri_parser;
    main := path;
    write data;
}%%
class URIPathParser : public RagelParser
{
public:
    URIPathParser(URI::Path& path)
    {
        m_path = &path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
        %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
    }

public:
    bool complete() const
    {
        return false;
    }

    bool final() const
    {
        return cs >= uri_path_parser_first_final;
    }

    bool error() const
    {
        return cs == uri_path_parser_error;
    }

private:
    URI::Path *m_path;
};

URI::URI()
{
    reset();
}

URI::URI(const std::string& uri)
{
    reset();
    *this = uri;
}

URI::URI(const char *uri)
{
    reset();
    *this = uri;
}

URI::URI(const Buffer &uri)
{
    reset();
    *this = uri;
}

URI&
URI::operator=(const std::string& uri)
{
    reset();
    URIParser parser(*this);
    parser.run(uri);
    if (parser.error() || !parser.final())
        MORDOR_THROW_EXCEPTION(std::invalid_argument("uri"));
    return *this;
}

URI&
URI::operator=(const Buffer &uri)
{
    reset();
    URIParser parser(*this);
    parser.run(uri);
    if (parser.error() || !parser.final())
        MORDOR_THROW_EXCEPTION(std::invalid_argument("uri"));
    return *this;
}

void
URI::reset()
{
    schemeDefined(false);
    authority.hostDefined(false);
    path.type = Path::RELATIVE;
    path.segments.clear();
    queryDefined(false);
    fragmentDefined(false);
}

URI::Authority::Authority()
{
    userinfoDefined(false);
    hostDefined(false);
    portDefined(false);
}

void
URI::Authority::normalize(const std::string& defaultHost, bool emptyHostValid,
    int defaultPort, bool emptyPortValid)
{
    for(size_t i = 0; i < m_host.length(); ++i)
        m_host[i] = tolower(m_host[i]);
    if (m_port == defaultPort)
        m_port = -1;
    if (m_port == -1 && !emptyPortValid)
        m_portDefined = false;
    if (m_host == defaultHost)
        m_host.clear();
    if (m_host.empty() && !emptyHostValid && !m_userinfoDefined && !m_portDefined)
        m_hostDefined = false;
}

std::string
URI::Authority::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

std::string
URI::Path::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

static int boolcmp(bool lhs, bool rhs)
{
    if (!lhs && rhs)
        return -1;
    if (lhs && !rhs)
        return 1;
    return 0;
}

int
URI::Authority::cmp(const Authority &rhs) const
{
    int x = boolcmp(m_hostDefined, rhs.m_hostDefined);
    if (x != 0) return x;
    x = strcmp(m_host.c_str(), rhs.m_host.c_str());
    if (x != 0) return x;
    x = boolcmp(m_portDefined, rhs.m_portDefined);
    if (x != 0) return x;
    x = m_port - rhs.m_port;
    if (x != 0) return x;
    x = boolcmp(m_userinfoDefined, rhs.m_userinfoDefined);
    if (x != 0) return x;
    return strcmp(m_userinfo.c_str(), rhs.m_userinfo.c_str());
}

bool
URI::Authority::operator==(const Authority &rhs) const
{
    return cmp(rhs) == 0;
}

std::ostream&
operator<<(std::ostream& os, const URI::Authority& authority)
{
    MORDOR_ASSERT(authority.hostDefined());
    if (authority.userinfoDefined()) {
        os << escape(authority.userinfo(), userinfo) << "@";
    }
    os << escape(authority.host(), host);
    if (authority.portDefined()) {
        os << ":";
        if (authority.port() > 0) {
            os << authority.port();
        }
    }
    return os;
}

URI::Path::Path()
{
    type = RELATIVE;
}

URI::Path::Path(const char *path)
{
    *this = path;
}

URI::Path::Path(const std::string& path)
{
    *this = path;
}

URI::Path&
URI::Path::operator=(const std::string& path)
{
    type = RELATIVE;
    segments.clear();
    URIPathParser parser(*this);
    parser.run(path);
    if (parser.error() || !parser.final())
        MORDOR_THROW_EXCEPTION(std::invalid_argument("path"));
    return *this;
}

void
URI::Path::removeDotComponents()
{
    for(size_t i = 0; i < segments.size(); ++i) {
        if (segments[i] == ".") {
            if (i + 1 == segments.size()) {
                segments[i].clear();
                continue;
            } else {
                segments.erase(segments.begin() + i);
                --i;
                continue;
            }
        }
        if (segments[i] == "..") {
            if (i == 0) {
                segments.erase(segments.begin());
                --i;
                continue;
            }
            if (i + 1 == segments.size()) {
                segments.resize(segments.size() - 1);
                segments.back().clear();
                --i;
                continue;
            }
            segments.erase(segments.begin() + i - 1, segments.begin() + i + 1);
            i -= 2;
            continue;
        }
    }
}

void
URI::Path::normalize(bool emptyPathValid)
{
    removeDotComponents();
    if (segments.empty() && !emptyPathValid)
        type = ABSOLUTE;
}

void
URI::Path::merge(const Path& rhs)
{
    MORDOR_ASSERT(rhs.type == RELATIVE);
    if (!segments.empty()) {
        segments.pop_back();
        segments.insert(segments.end(), rhs.segments.begin(), rhs.segments.end());
    } else {
        segments = rhs.segments;
    }
}

URI::Path::path_serializer
URI::Path::serialize(bool schemeless) const
{
    path_serializer result;
    result.p = this;
    result.schemeless = schemeless;
    return result;
}

std::ostream&
operator<<(std::ostream& os, const URI::Path::path_serializer &p)
{
    if (p.p->segments.empty() && p.p->type == URI::Path::ABSOLUTE) {
        return os << "/";
    }
    for (size_t i = 0; i < p.p->segments.size(); ++i) {
        if (i != 0 || p.p->type == URI::Path::ABSOLUTE) {
            os << "/";
        }
        if (i == 0 && p.p->type == URI::Path::RELATIVE && p.schemeless) {
            os << escape(p.p->segments[i], segment_nc);
        } else {
            os << escape(p.p->segments[i], pchar);
        }
    }
    return os;
}

std::ostream&
operator<<(std::ostream& os, const URI::Path& path)
{
    return os << path.serialize();
}

int
URI::Path::cmp(const Path &rhs) const
{
    if (type == ABSOLUTE && rhs.type == RELATIVE)
        return -1;
    if (type == RELATIVE && rhs.type == ABSOLUTE)
        return 1;
    std::vector<std::string>::const_iterator itl, itr;
    itl = segments.begin(); itr = rhs.segments.begin();
    while (true) {
        if (itl == segments.end() && itr != rhs.segments.end())
            return -1;
        if (itl != segments.end() && itr == rhs.segments.end())
            return 1;
        if (itl == segments.end() && itr == rhs.segments.end())
            return 0;
        int x = strcmp(itl->c_str(), itr->c_str());
        if (x != 0) return x;
        ++itl; ++itr;
    }
}

bool
URI::Path::operator==(const Path &rhs) const
{
    return type == rhs.type && segments == rhs.segments;
}

void
URI::normalize()
{
    for (size_t i = 0; i < m_scheme.size(); ++i)
        m_scheme[i] = tolower(m_scheme[i]);

    if (m_scheme == "http" || m_scheme == "https") {
        authority.normalize("", false, m_scheme.size() == 4 ? 80 : 443, false);
        path.normalize();
    } else if (m_scheme == "file") {
        authority.normalize("localhost", true);
        path.normalize();
    } else if (m_scheme == "socks") {
        authority.normalize("", false, 1080, false);
        path.normalize();
    } else {
        authority.normalize();
        path.normalize();
    }
}

std::string
URI::query() const
{
    MORDOR_ASSERT(m_queryDefined);
    return unescape(m_query);
}

void
URI::query(const std::string &q)
{
    m_queryDefined = true;
    m_query = escape(q, Mordor::query);
}

std::string
URI::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

std::ostream&
operator<<(std::ostream& os, const URI& uri)
{
    if (uri.schemeDefined()) {
        os << escape(uri.scheme(), scheme) << ":";
    }

    if (uri.authority.hostDefined()) {
        os << "//" << uri.authority;
    }

    // Has scheme, but no authority, must ensure that an absolute path
    // doesn't begin with an empty segment (or could be mistaken for authority)
    if (uri.schemeDefined() && !uri.authority.hostDefined() &&
        uri.path.type == URI::Path::ABSOLUTE &&
        uri.path.segments.size() > 0 && uri.path.segments.front().empty()) {
        os << "/";
    }
    os << uri.path.serialize(!uri.schemeDefined());

    if (uri.queryDefined()) {
        os << "?" << uri.m_query;
    }

    if (uri.fragmentDefined()) {
        os << "#" << escape(uri.fragment(), query);
    }
    return os;
}

URI
URI::transform(const URI& base, const URI& relative)
{
    MORDOR_ASSERT(base.schemeDefined());

    URI target;
    if (relative.schemeDefined()) {
        target.scheme(relative.scheme());
        target.authority = relative.authority;
        target.path = relative.path;
        target.path.removeDotComponents();
        target.m_query = relative.m_query;
        target.m_queryDefined = relative.m_queryDefined;
    } else {
        if (relative.authority.hostDefined()) {
            target.authority = relative.authority;
            target.path = relative.path;
            target.path.removeDotComponents();
            target.m_query = relative.m_query;
            target.m_queryDefined = relative.m_queryDefined;
        } else {
            if (relative.path.isEmpty()) {
                target.path = base.path;
                if (relative.queryDefined()) {
                    target.query(relative.query());
                } else {
                    target.m_query = base.m_query;
                    target.m_queryDefined = base.m_queryDefined;
                }
            } else {
                if (relative.path.type == Path::ABSOLUTE) {
                    target.path = relative.path;
                } else {
                    target.path = base.path;
                    target.path.merge(relative.path);
                    if (!base.authority.hostDefined())
                        target.path.type = Path::ABSOLUTE;
                }
                target.path.removeDotComponents();
                target.m_query = relative.m_query;
                target.m_queryDefined = relative.m_queryDefined;
            }
            target.authority = base.authority;
        }
        target.scheme(base.scheme());
    }
    target.m_fragment = relative.m_fragment;
    target.m_fragmentDefined = relative.m_fragmentDefined;
    return target;
}

int
URI::cmp(const URI &rhs) const
{
    int x = boolcmp(m_schemeDefined, rhs.m_schemeDefined);
    if (x != 0) return x;
    x = strcmp(m_scheme.c_str(), rhs.m_scheme.c_str());
    if (x != 0) return x;
    x = authority.cmp(rhs.authority);
    if (x != 0) return x;
    x = path.cmp(rhs.path);
    if (x != 0) return x;
    x = boolcmp(m_queryDefined, rhs.m_queryDefined);
    if (x != 0) return x;
    x = strcmp(m_query.c_str(), rhs.m_query.c_str());
    if (x != 0) return x;
    x = boolcmp(m_fragmentDefined, rhs.m_fragmentDefined);
    if (x != 0) return x;
    return strcmp(m_fragment.c_str(), rhs.m_fragment.c_str());
}

bool
URI::operator<(const URI &rhs) const
{
    return cmp(rhs) < 0;
}

bool
URI::operator==(const URI &rhs) const
{
    return cmp(rhs) == 0;
}

%%{
    machine querystring_parser;

    action mark { mark = fpc; }
    action saveKey {
        m_iterator = m_qs.insert(std::make_pair(
            unescape(std::string(mark, fpc - mark), true), std::string()));
        mark = NULL;
    }
    action saveValue {
        MORDOR_ASSERT(m_iterator != m_qs.end());
        if (fpc - mark == 0 && m_iterator->first.empty())
            m_qs.erase(m_iterator);
        else
            m_iterator->second = unescape(std::string(mark, fpc - mark), true);
        m_iterator = m_qs.end();
        mark = NULL;
    }
    action saveNoValue {
        if (m_iterator != m_qs.end() && m_iterator->first.empty()) {
            m_qs.erase(m_iterator);
            mark = NULL;
        }
    }

    sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";";
    unreserved = alpha | digit | "-" | "." | "_" | "~";
    pct_encoded = "%" xdigit xdigit;
    pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
    querychar = (pchar | "/" | "?") -- '&' -- ';';
    key = querychar*;
    value = (querychar | '=')*;
    keyValue = key >mark %saveKey ('=' value >mark %saveValue)? %saveNoValue;
    main := keyValue? ( ('&' | ';') keyValue? )*;
    write data;
}%%

class QueryStringParser : public RagelParser
{
public:
    QueryStringParser(URI::QueryString &qs)
    : m_qs(qs),
      m_iterator(m_qs.end())
    {}

    void init()
    {
        RagelParser::init();
        %% write init;
    }

    void exec()
    {
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
        %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
    }

    bool complete() const { return false; }
    bool final() const
    {
        return cs >= querystring_parser_first_final;
    }

    bool error() const
    {
        return cs == querystring_parser_error;
    }

private:
    URI::QueryString &m_qs;
    URI::QueryString::iterator m_iterator;
};

URI::QueryString &
URI::QueryString::operator =(const std::string &string)
{
    clear();
    QueryStringParser parser(*this);
    parser.run(string);
    if (!parser.final() || parser.error())
        MORDOR_THROW_EXCEPTION(std::invalid_argument("Invalid QueryString"));
    return *this;
}

URI::QueryString &
URI::QueryString::operator =(Stream &stream)
{
    clear();
    QueryStringParser parser(*this);
    parser.run(stream);
    if (!parser.final() || parser.error())
        MORDOR_THROW_EXCEPTION(std::invalid_argument("Invalid QueryString"));
    return *this;
}

std::string
URI::QueryString::toString() const
{
    std::ostringstream os;
    for (const_iterator it = begin();
        it != end();
        ++it) {
        if (it != begin()) {
            os << '&';
        }
        os << escape(it->first, Mordor::queryString, true);
        if (!it->second.empty())
            os << '=' << escape(it->second, Mordor::queryString, true);
    }
    return os.str();
}

}

// Copyright (c) 2009 - Mozy, Inc.
/* To compile to .cpp:
   ragel uri.rl -G2 -o uri.cpp
*/

#include "mordor/pch.h"

#include "mordor/uri.h"

#include <sstream>

#include "mordor/ragel.h"
#include "mordor/string.h"
#include "mordor/version.h"

namespace Mordor {

static const std::string unreserved("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
static const std::string sub_delims("!$&'()*+,;=");
static const std::string scheme("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.");
static const std::string userinfo("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string host("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string pchar("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@");
static const std::string path("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/");
static const std::string segment_nc("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" "@");
static const std::string query("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/?");
static const std::string queryString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$'()*," ":@" "/?");

static std::string escape(const std::string& str, const std::string& allowedChars, bool spaceAsPlus = false)
{
    const char *hexdigits = "0123456789ABCDEF";
    std::string result(str);

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while(c < end)
    {
        if (allowedChars.find(*c) == std::string::npos) {
            if (!differed) {
                result.resize(c - str.c_str());
                differed = true;
            }
            if (*c == ' ' && spaceAsPlus) {
                result.append(1, '+');
            } else {
                result.append(1, '%');
                result.append(1, hexdigits[(unsigned char)*c >> 4]);
                result.append(1, hexdigits[*c & 0xf]);
            }
        } else {
            if (differed) {
                result.append(1, *c);
            }
        }
        ++c;
    }

    if (differed) {
        MORDOR_ASSERT(result.length() >= str.length());
    } else {
        MORDOR_ASSERT(result == str);
    }
    return result;
}

std::string unescape(const std::string& str, bool spaceAsPlus = false)
{
    std::string result = str;

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while (c < end)
    {
        if (*c == '%') {
            MORDOR_ASSERT(c + 2 < end);
            if (!differed) {
                result.resize(c - str.c_str());
                differed = true;
            }
            char decoded;
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded = (*c - 'a' + 10) << 4;
            else if (*c >= 'A' && *c <= 'F')
                decoded = (*c - 'A' + 10) << 4;
            else {
                MORDOR_ASSERT(*c >= '0' && *c <='9');
                decoded = (*c - '0') << 4;
            }
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded |= *c - 'a' + 10;
            else if (*c >= 'A' && *c <= 'F')
                decoded |= *c - 'A' + 10;
            else {
                MORDOR_ASSERT(*c >= '0' && *c <='9');
                decoded |= *c - '0';
            }
            result.append(1, decoded);
        } else if (*c == '+' && spaceAsPlus) {
            if (!differed) {
                result.resize(c - str.c_str());
                differed = true;
            }
            result.append(1, ' ');
        } else if (differed) {
            result.append(1, *c);
        }
        ++c;
    }
    return result;
}

std::string
URI::encode(const std::string &str, CharacterClass charClass)
{
    switch (charClass) {
        case UNRESERVED:
            return escape(str, unreserved, false);
        case QUERYSTRING:
            return escape(str, Mordor::queryString, true);
        default:
            MORDOR_NOTREACHED();
    }
}

std::string
URI::decode(const std::string &str, CharacterClass charClass)
{
    switch (charClass) {
        case UNRESERVED:
            return unescape(str, false);
        case QUERYSTRING:
            return unescape(str, true);
        default:
            MORDOR_NOTREACHED();
    }
}

%%{
    # See RFC 3986: http://www.ietf.org/rfc/rfc3986.txt

    machine uri_parser;

    gen_delims = ":" | "/" | "?" | "#" | "[" | "]" | "@";
    sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=";
    reserved = gen_delims | sub_delims;
    unreserved = alpha | digit | "-" | "." | "_" | "~";
    pct_encoded = "%" xdigit xdigit;

    action marku { mark = fpc; }
    action save_scheme
    {
        m_uri->scheme(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    scheme = (alpha | digit | "+" | "-" | ".")+ >marku %save_scheme;

    action save_port
    {
        if (fpc == mark)
            m_uri->authority.port(-1);
        else
            m_uri->authority.port(atoi(mark));
        mark = NULL;
    }
    action save_userinfo
    {
        m_uri->authority.userinfo(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }
    action save_host
    {
        if (mark != NULL) {
            m_uri->authority.host(unescape(std::string(mark, fpc - mark)));
            mark = NULL;
        }
    }

    userinfo = (unreserved | pct_encoded | sub_delims | ":")*;
    dec_octet = digit | [1-9] digit | "1" digit{2} | 2 [0-4] digit | "25" [0-5];
    IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
    h16 = xdigit{1,4};
    ls32 = (h16 ":" h16) | IPv4address;
    IPv6address = (                         (h16 ":"){6} ls32) |
                  (                    "::" (h16 ":"){5} ls32) |
                  ((             h16)? "::" (h16 ":"){4} ls32) |
                  (((h16 ":"){1} h16)? "::" (h16 ":"){3} ls32) |
                  (((h16 ":"){2} h16)? "::" (h16 ":"){2} ls32) |
                  (((h16 ":"){3} h16)? "::" (h16 ":"){1} ls32) |
                  (((h16 ":"){4} h16)? "::"              ls32) |
                  (((h16 ":"){5} h16)? "::"              h16 ) |
                  (((h16 ":"){6} h16)? "::"                  );
    IPvFuture = "v" xdigit+ "." (unreserved | sub_delims | ":")+;
    IP_literal = "[" (IPv6address | IPvFuture) "]";
    reg_name = (unreserved | pct_encoded | sub_delims)*;
    host = IP_literal | IPv4address | reg_name;
    port = digit*;

    authority = ( (userinfo %save_userinfo "@")? host >marku %save_host (":" port >marku %save_port)? ) >marku;

    action save_segment
    {
        m_path->segments.push_back(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
    segment = pchar* >marku %save_segment;
    segment_nz = pchar+ >marku %save_segment;
    segment_nz_nc = (pchar - ":")+ >marku %save_segment;

    action set_absolute
    {
        m_path->type = URI::Path::ABSOLUTE;
    }
    action set_relative
    {
        m_path->type = URI::Path::RELATIVE;
    }

    path_abempty = ("/" segment >set_absolute)*;
    path_absolute = "/" (segment_nz ("/" segment)*)?  >set_absolute;
    path_noscheme = segment_nz_nc >set_relative ("/" segment)*;
    path_rootless = segment_nz >set_relative ("/" segment)*;
    path_empty = "" %set_relative;
    path = (path_abempty | path_absolute | path_noscheme | path_rootless | path_empty);

    action save_query
    {
        m_uri->m_query = std::string(mark, fpc - mark);
        m_uri->m_queryDefined = true;
        mark = NULL;
    }
    action save_fragment
    {
        m_uri->fragment(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    query = (pchar | "/" | "?")* >marku %save_query;
    fragment = (pchar | "/" | "?")* >marku %save_fragment;

    hier_part = ("//" authority path_abempty) | path_absolute | path_rootless | path_empty;

    relative_part = ("//" authority path_abempty) | path_absolute | path_noscheme | path_empty;
    relative_ref = relative_part ( "?" query )? ( "#" fragment )?;

    absolute_URI = scheme ":" hier_part ( "?" query )? ;
    # Obsolete, but referenced from HTTP, so we translate
    relative_URI = relative_part ( "?" query )?;

    URI = scheme ":" hier_part ( "?" query )? ( "#" fragment )?;
    URI_reference = URI | relative_ref;
}%%

%%{
        machine uri_parser_proper;
        include uri_parser;
        main := URI_reference;
        write data;
}%%

class URIParser : public RagelParser
{
public:
    URIParser(URI& uri)
    {
        m_uri = &uri;
        m_path = &m_uri->path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
        %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
    }

public:
    bool complete() const
    {
        return false;
    }

    bool final() const
    {
        return cs >= uri_parser_proper_first_final;
    }

    bool error() const
    {
        return cs == uri_parser_proper_error;
    }

private:
    URI *m_uri;
    URI::Path *m_path;
};

%%{
    machine uri_path_parser;
    include uri_parser;
    main := path;
    write data;
}%%
class URIPathParser : public RagelParser
{
public:
    URIPathParser(URI::Path& path)
    {
        m_path = &path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
        %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
    }

public:
    bool complete() const
    {
        return false;
    }

    bool final() const
    {
        return cs >= uri_path_parser_first_final;
    }

    bool error() const
    {
        return cs == uri_path_parser_error;
    }

private:
    URI::Path *m_path;
};

URI::URI()
{
    reset();
}

URI::URI(const std::string& uri)
{
    reset();
    *this = uri;
}

URI::URI(const char *uri)
{
    reset();
    *this = uri;
}

URI::URI(const Buffer &uri)
{
    reset();
    *this = uri;
}

URI&
URI::operator=(const std::string& uri)
{
    reset();
    URIParser parser(*this);
    parser.run(uri);
    if (parser.error() || !parser.final())
        throw std::invalid_argument("uri");
    return *this;
}

URI&
URI::operator=(const Buffer &uri)
{
    reset();
    URIParser parser(*this);
    parser.run(uri);
    if (parser.error() || !parser.final())
        throw std::invalid_argument("uri");
    return *this;
}

void
URI::reset()
{
    schemeDefined(false);
    authority.hostDefined(false);
    path.type = Path::RELATIVE;
    path.segments.clear();
    queryDefined(false);
    fragmentDefined(false);
}

URI::Authority::Authority()
{
    userinfoDefined(false);
    hostDefined(false);
    portDefined(false);
}

void
URI::Authority::normalize(const std::string& defaultHost, bool emptyHostValid,
    int defaultPort, bool emptyPortValid)
{
    for(size_t i = 0; i < m_host.length(); ++i)
        m_host[i] = tolower(m_host[i]);
    if (m_port == defaultPort)
        m_port = -1;
    if (m_port == -1 && !emptyPortValid)
        m_portDefined = false;
    if (m_host == defaultHost)
        m_host.clear();
    if (m_host.empty() && !emptyHostValid && !m_userinfoDefined && !m_portDefined)
        m_hostDefined = false;
}

std::string
URI::Authority::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

std::string
URI::Path::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

static int boolcmp(bool lhs, bool rhs)
{
    if (!lhs && rhs)
        return -1;
    if (lhs && !rhs)
        return 1;
    return 0;
}

int
URI::Authority::cmp(const Authority &rhs) const
{
    int x = boolcmp(m_hostDefined, rhs.m_hostDefined);
    if (x != 0) return x;
    x = strcmp(m_host.c_str(), rhs.m_host.c_str());
    if (x != 0) return x;
    x = boolcmp(m_portDefined, rhs.m_portDefined);
    if (x != 0) return x;
    x = m_port - rhs.m_port;
    if (x != 0) return x;
    x = boolcmp(m_userinfoDefined, rhs.m_userinfoDefined);
    if (x != 0) return x;
    return strcmp(m_userinfo.c_str(), rhs.m_userinfo.c_str());
}

bool
URI::Authority::operator==(const Authority &rhs) const
{
    return cmp(rhs) == 0;
}

std::ostream&
operator<<(std::ostream& os, const URI::Authority& authority)
{
    MORDOR_ASSERT(authority.hostDefined());
    if (authority.userinfoDefined()) {
        os << escape(authority.userinfo(), userinfo) << "@";
    }
    os << escape(authority.host(), host);
    if (authority.portDefined()) {
        os << ":";
        if (authority.port() > 0) {
            os << authority.port();
        }
    }
    return os;
}

URI::Path::Path()
{
    type = RELATIVE;
}

URI::Path::Path(const char *path)
{
    *this = path;
}

URI::Path::Path(const std::string& path)
{
    *this = path;
}

URI::Path&
URI::Path::operator=(const std::string& path)
{
    type = RELATIVE;
    segments.clear();
    URIPathParser parser(*this);
    parser.run(path);
    if (parser.error() || !parser.final())
        throw std::invalid_argument("path");
    return *this;
}

void
URI::Path::removeDotComponents()
{
    for(size_t i = 0; i < segments.size(); ++i) {
        if (segments[i] == ".") {
            if (i + 1 == segments.size()) {
                segments[i].clear();
                continue;
            } else {
                segments.erase(segments.begin() + i);
                --i;
                continue;
            }
        }
        if (segments[i] == "..") {
            if (i == 0) {
                segments.erase(segments.begin());
                --i;
                continue;
            }
            if (i + 1 == segments.size()) {
                segments.resize(segments.size() - 1);
                segments.back().clear();
                --i;
                continue;
            }
            segments.erase(segments.begin() + i - 1, segments.begin() + i + 1);
            i -= 2;
            continue;
        }
    }
}

void
URI::Path::normalize(bool emptyPathValid)
{
    removeDotComponents();
    if (segments.empty() && !emptyPathValid)
        type = ABSOLUTE;
}

void
URI::Path::merge(const Path& rhs)
{
    MORDOR_ASSERT(rhs.type == RELATIVE);
    if (!segments.empty()) {
        segments.pop_back();
        segments.insert(segments.end(), rhs.segments.begin(), rhs.segments.end());
    } else {
        segments = rhs.segments;
    }
}

URI::Path::path_serializer
URI::Path::serialize(bool schemeless) const
{
    path_serializer result;
    result.p = this;
    result.schemeless = schemeless;
    return result;
}

std::ostream&
operator<<(std::ostream& os, const URI::Path::path_serializer &p)
{
    if (p.p->segments.empty() && p.p->type == URI::Path::ABSOLUTE) {
        return os << "/";
    }
    for (size_t i = 0; i < p.p->segments.size(); ++i) {
        if (i != 0 || p.p->type == URI::Path::ABSOLUTE) {
            os << "/";
        }
        if (i == 0 && p.p->type == URI::Path::RELATIVE && p.schemeless) {
            os << escape(p.p->segments[i], segment_nc);
        } else {
            os << escape(p.p->segments[i], pchar);
        }
    }
    return os;
}

std::ostream&
operator<<(std::ostream& os, const URI::Path& path)
{
    return os << path.serialize();
}

int
URI::Path::cmp(const Path &rhs) const
{
    if (type == ABSOLUTE && rhs.type == RELATIVE)
        return -1;
    if (type == RELATIVE && rhs.type == ABSOLUTE)
        return 1;
    std::vector<std::string>::const_iterator itl, itr;
    itl = segments.begin(); itr = rhs.segments.begin();
    while (true) {
        if (itl == segments.end() && itr != rhs.segments.end())
            return -1;
        if (itl != segments.end() && itr == rhs.segments.end())
            return 1;
        if (itl == segments.end() && itr == rhs.segments.end())
            return 0;
        int x = strcmp(itl->c_str(), itr->c_str());
        if (x != 0) return x;
        ++itl; ++itr;
    }
}

bool
URI::Path::operator==(const Path &rhs) const
{
    return type == rhs.type && segments == rhs.segments;
}

void
URI::normalize()
{
    for (size_t i = 0; i < m_scheme.size(); ++i)
        m_scheme[i] = tolower(m_scheme[i]);

    if (m_scheme == "http" || m_scheme == "https") {
        authority.normalize("", false, m_scheme.size() == 4 ? 80 : 443, false);
        path.normalize();
    } else if (m_scheme == "file") {
        authority.normalize("localhost", true);
        path.normalize();
    } else if (m_scheme == "socks") {
        authority.normalize("", false, 1080, false);
        path.normalize();
    } else {
        authority.normalize();
        path.normalize();
    }
}

std::string
URI::query() const
{
    MORDOR_ASSERT(m_queryDefined);
    return unescape(m_query);
}

void
URI::query(const std::string &q)
{
    m_queryDefined = true;
    m_query = escape(q, Mordor::query);
}

std::string
URI::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

std::ostream&
operator<<(std::ostream& os, const URI& uri)
{
    if (uri.schemeDefined()) {
        os << escape(uri.scheme(), scheme) << ":";
    }

    if (uri.authority.hostDefined()) {
        os << "//" << uri.authority;
    }

    // Has scheme, but no authority, must ensure that an absolute path
    // doesn't begin with an empty segment (or could be mistaken for authority)
    if (uri.schemeDefined() && !uri.authority.hostDefined() &&
        uri.path.type == URI::Path::ABSOLUTE &&
        uri.path.segments.size() > 0 && uri.path.segments.front().empty()) {
        os << "/";
    }
    os << uri.path.serialize(!uri.schemeDefined());

    if (uri.queryDefined()) {
        os << "?" << uri.m_query;
    }

    if (uri.fragmentDefined()) {
        os << "#" << escape(uri.fragment(), query);
    }
    return os;
}

URI
URI::transform(const URI& base, const URI& relative)
{
    MORDOR_ASSERT(base.schemeDefined());

    URI target;
    if (relative.schemeDefined()) {
        target.scheme(relative.scheme());
        target.authority = relative.authority;
        target.path = relative.path;
        target.path.removeDotComponents();
        target.m_query = relative.m_query;
        target.m_queryDefined = relative.m_queryDefined;
    } else {
        if (relative.authority.hostDefined()) {
            target.authority = relative.authority;
            target.path = relative.path;
            target.path.removeDotComponents();
            target.m_query = relative.m_query;
            target.m_queryDefined = relative.m_queryDefined;
        } else {
            if (relative.path.isEmpty()) {
                target.path = base.path;
                if (relative.queryDefined()) {
                    target.query(relative.query());
                } else {
                    target.m_query = base.m_query;
                    target.m_queryDefined = base.m_queryDefined;
                }
            } else {
                if (relative.path.type == Path::ABSOLUTE) {
                    target.path = relative.path;
                } else {
                    target.path = base.path;
                    target.path.merge(relative.path);
                    if (!base.authority.hostDefined())
                        target.path.type = Path::ABSOLUTE;
                }
                target.path.removeDotComponents();
                target.m_query = relative.m_query;
                target.m_queryDefined = relative.m_queryDefined;
            }
            target.authority = base.authority;
        }
        target.scheme(base.scheme());
    }
    target.m_fragment = relative.m_fragment;
    target.m_fragmentDefined = relative.m_fragmentDefined;
    return target;
}

int
URI::cmp(const URI &rhs) const
{
    int x = boolcmp(m_schemeDefined, rhs.m_schemeDefined);
    if (x != 0) return x;
    x = strcmp(m_scheme.c_str(), rhs.m_scheme.c_str());
    if (x != 0) return x;
    x = authority.cmp(rhs.authority);
    if (x != 0) return x;
    x = path.cmp(rhs.path);
    if (x != 0) return x;
    x = boolcmp(m_queryDefined, rhs.m_queryDefined);
    if (x != 0) return x;
    x = strcmp(m_query.c_str(), rhs.m_query.c_str());
    if (x != 0) return x;
    x = boolcmp(m_fragmentDefined, rhs.m_fragmentDefined);
    if (x != 0) return x;
    return strcmp(m_fragment.c_str(), rhs.m_fragment.c_str());
}

bool
URI::operator<(const URI &rhs) const
{
    return cmp(rhs) < 0;
}

bool
URI::operator==(const URI &rhs) const
{
    return cmp(rhs) == 0;
}

%%{
    machine querystring_parser;

    action mark { mark = fpc; }
    action saveKey {
        m_iterator = m_qs.insert(std::make_pair(
            unescape(std::string(mark, fpc - mark), true), std::string()));
        mark = NULL;
    }
    action saveValue {
        MORDOR_ASSERT(m_iterator != m_qs.end());
        if (fpc - mark == 0 && m_iterator->first.empty())
            m_qs.erase(m_iterator);
        else
            m_iterator->second = unescape(std::string(mark, fpc - mark), true);
        m_iterator = m_qs.end();
        mark = NULL;
    }
    action saveNoValue {
        if (m_iterator != m_qs.end() && m_iterator->first.empty()) {
            m_qs.erase(m_iterator);
            mark = NULL;
        }
    }

    sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";";
    unreserved = alpha | digit | "-" | "." | "_" | "~";
    pct_encoded = "%" xdigit xdigit;
    pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
    querychar = (pchar | "/" | "?") -- '&' -- ';';
    key = querychar*;
    value = (querychar | '=')*;
    keyValue = key >mark %saveKey ('=' value >mark %saveValue)? %saveNoValue;
    main := keyValue? ( ('&' | ';') keyValue? )*;
    write data;
}%%

class QueryStringParser : public RagelParser
{
public:
    QueryStringParser(URI::QueryString &qs)
    : m_qs(qs),
      m_iterator(m_qs.end())
    {}

    void init()
    {
        RagelParser::init();
        %% write init;
    }

    void exec()
    {
#ifdef MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
        %% write exec;
#ifdef MSVC
#pragma warning(pop)
#endif
    }

    bool complete() const { return false; }
    bool final() const
    {
        return cs >= querystring_parser_first_final;
    }

    bool error() const
    {
        return cs == querystring_parser_error;
    }

private:
    URI::QueryString &m_qs;
    URI::QueryString::iterator m_iterator;
};

URI::QueryString &
URI::QueryString::operator =(const std::string &string)
{
    clear();
    QueryStringParser parser(*this);
    parser.run(string);
    if (!parser.final() || parser.error())
        MORDOR_THROW_EXCEPTION(std::invalid_argument("Invalid QueryString"));
    return *this;
}

URI::QueryString &
URI::QueryString::operator =(Stream &stream)
{
    clear();
    QueryStringParser parser(*this);
    parser.run(stream);
    if (!parser.final() || parser.error())
        MORDOR_THROW_EXCEPTION(std::invalid_argument("Invalid QueryString"));
    return *this;
}

std::string
URI::QueryString::toString() const
{
    std::ostringstream os;
    for (const_iterator it = begin();
        it != end();
        ++it) {
        if (it != begin()) {
            os << '&';
        }
        os << escape(it->first, Mordor::queryString, true);
        if (!it->second.empty())
            os << '=' << escape(it->second, Mordor::queryString, true);
    }
    return os.str();
}

}


Don't try to prune SSA style spans if there's only one of them. (fixes crash on 0-byte subtitle lines)

/*
 *  SSARenderCodec.m
 *  Copyright (c) 2007 Perian Project
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation;
 *  version 2.1 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#import "SSATagParsing.h"
#import "Categories.h"

@implementation SSAStyleSpan
-(void)dealloc
{
	ATSUDisposeStyle(astyle);
	[super dealloc];
}
@end

@implementation SSARenderEntity
-(void)dealloc
{
	int i;
	for (i=0; i < style_count; i++) [styles[i] release];
	if (disposelayout) ATSUDisposeTextLayout(layout);
	free(text);
	free(styles);
	[nstext release];
	[super dealloc];
}

-(void)increasestyles
{
	style_count++;
	styles = realloc(styles, sizeof(SSAStyleSpan*[style_count]));
}
@end

%% machine SSAtag;
%% write data;

static void SetATSUStyleFlag(ATSUStyle style, ATSUAttributeTag t, Boolean v)
{
	ATSUAttributeTag tags[] = {t};
	ByteCount		 sizes[] = {sizeof(v)};
	ATSUAttributeValuePtr vals[] = {&v};

	ATSUSetAttributes(style,1,tags,sizes,vals);
}

static void SetATSUStyleOther(ATSUStyle style, ATSUAttributeTag t, ByteCount s, ATSUAttributeValuePtr v)
{
	ATSUAttributeTag tags[] = {t};
	ByteCount		 sizes[] = {s};
	ATSUAttributeValuePtr vals[] = {v};

	ATSUSetAttributes(style,1,tags,sizes,vals);
}

static void SetATSULayoutOther(ATSUTextLayout l, ATSUAttributeTag t, ByteCount s, ATSUAttributeValuePtr v)
{
	ATSUAttributeTag tags[] = {t};
	ByteCount		 sizes[] = {s};
	ATSUAttributeValuePtr vals[] = {v};

	ATSUSetLayoutControls(l,1,tags,sizes,vals);
}

static ATSURGBAlphaColor ParseColorTag(unsigned long c, float a)
{
	unsigned char r,g,b;
	r = c & 0xff;
	g = (c >> 8) & 0xff;
	b = (c >> 16) & 0xff;

	return (ATSURGBAlphaColor){r/255.,g/255.,b/255.,a};
}

static void PruneEmptyStyleSpans(SSARenderEntity *re)
{
	SSAStyleSpan *styles_new[re->style_count];
	size_t style_count_new = 0;
	int i;

	if (re->style_count <= 1) return;

	for (i = 0; i < re->style_count; i++) {
		if (re->styles[i]->range.length == 0) {
			[re->styles[i] release];
		} else {
			styles_new[style_count_new++] = re->styles[i];
		}
	}

	if (style_count_new != re->style_count) {
		re->styles = realloc(re->styles, sizeof(SSAStyleSpan*) * style_count_new);
		memcpy(re->styles, styles_new, sizeof(SSAStyleSpan*) * style_count_new);

		re->style_count = style_count_new;
	}
}

static void PruneIdenticalStyleSpans(SSARenderEntity *re)
{
	SSAStyleSpan *styles_new[re->style_count];

	if (re->multipleruns || re->style_count <= 1) return;

	size_t style_count_new = 1, remaining = re->style_count-1;
	int i=1;

	styles_new[0] = re->styles[0];

	while (remaining) {
		ATSUStyleComparison asc;
		ATSUCompareStyles(styles_new[style_count_new-1]->astyle,re->styles[i]->astyle,&asc);

		if (asc == kATSUStyleEquals) {
			styles_new[style_count_new-1]->range.length += re->styles[i]->range.length;
			[re->styles[i] release];
		} else {
			styles_new[style_count_new++] = re->styles[i];
		}

		i++;
		remaining--;
	}

	if (style_count_new != re->style_count) {
		re->styles = realloc(re->styles, sizeof(SSAStyleSpan*) * style_count_new);
		memcpy(re->styles, styles_new, sizeof(SSAStyleSpan*) * style_count_new);

		re->style_count = style_count_new;
	}
}

static void UpdateAlignment(int inum, int cur_posx, int *valign, int *halign, ATSUTextLayout cur_layout)
{
	int cur_halign, cur_valign;

	switch (inum)
	{case 1: case 4: case 7: cur_halign = S_LeftAlign; break;
	case 2: case 5: case 8: default: cur_halign = S_CenterAlign; break;
	case 3: case 6: case 9: cur_halign = S_RightAlign;}

	switch (inum)
	{case 1: case 2: case 3: default: cur_valign = S_BottomAlign; break;
	case 4: case 5: case 6: cur_valign = S_MiddleAlign; break;
	case 7: case 8: case 9: cur_valign = S_TopAlign;}

	*halign = cur_halign;
	*valign = cur_valign;
	Fract alignment;

	if (cur_posx != -1) {
		switch(cur_halign) {
			case S_LeftAlign:
				alignment = FloatToFract(0.);
				break;
			case S_CenterAlign: default:
				alignment = kATSUCenterAlignment;
				break;
			case S_RightAlign:
				alignment = FloatToFract(1.);
		}
		SetATSULayoutOther(cur_layout, kATSULineFlushFactorTag, sizeof(Fract), &alignment);
	}
}

NSArray *ParseSubPacket(NSString *str, SSADocument *ssa, Boolean plaintext)
{
	NSArray *linea;
	int i, j, pcount; unsigned len;
	NSMutableArray *rentities = [NSMutableArray array];

	if (plaintext) {
		linea = [NSArray arrayWithObject:str];
		pcount = 1;
	} else {
		linea =  [str componentsSeparatedByString:@"\n"];
		pcount = [linea count];
	}

	for (i = 0; i < pcount; i++) {
		SSARenderEntity *re = [[[SSARenderEntity alloc] init] autorelease];
		BOOL ldirty = NO;

		if (plaintext) {
			re->style = ssa->defaultstyle;
			re->layout = re->style->layout;
			re->layer = 0;
			re->marginl = re->style->marginl;
			re->marginr = re->style->marginl;
			re->marginv = re->style->marginl;
			re->usablewidth = re->style->usablewidth;
			re->halign = 1;
			re->valign = 0;
			re->nstext = [linea objectAtIndex:i];
			re->styles = NULL;
			re->disposelayout = NO;
		} else {
			NSArray *ar = [[linea objectAtIndex:i] componentsSeparatedByString:@"," count:9];

			if ([ar count] < 9) continue;

			re->layer = [[ar objectAtIndex:1] intValue];
			NSString *sn = [ar objectAtIndex:2];

			re->style = ssa->defaultstyle;

			for (j=0; j < ssa->stylecount; j++) {
				if ([sn isEqualToString:ssa->styles[j].name])  {
					re->style = &ssa->styles[j];
					break;
				}
			}

			re->marginl = [[ar objectAtIndex:5] intValue];
			re->marginr = [[ar objectAtIndex:6] intValue];
			re->marginv = [[ar objectAtIndex:7] intValue];

			if (re->marginl == 0) re->marginl = re->style->marginl; else ldirty = TRUE;
			if (re->marginr == 0) re->marginr = re->style->marginr; else ldirty = TRUE;
			if (re->marginv == 0) re->marginv = re->style->marginv; else ldirty = TRUE;

			if (ldirty) {
				ATSUTextMeasurement width;
				ATSUAttributeTag tag[] = {kATSULineWidthTag};
				ByteCount		 size[] = {sizeof(ATSUTextMeasurement)};
				ATSUAttributeValuePtr val[] = {&width};

				re->usablewidth = ssa->resX - re->marginl - re->marginr;
				ATSUCreateAndCopyTextLayout(re->style->layout,&re->layout);
				width = IntToFixed(re->usablewidth);

				ATSUSetLayoutControls(re->layout,1,tag,size,val);
				re->disposelayout = YES;
			} else {
				re->usablewidth = re->style->usablewidth;
				re->layout = re->style->layout;
				re->disposelayout = NO;
			}

			re->nstext = [ar objectAtIndex:8];
		}
		len = [re->nstext length];
		re->text = malloc(sizeof(unichar[len]));

		[re->nstext getCharacters:re->text];
		re->style_count = 0;
		re->posx = re->posy = -1;
		re->halign = re->style->halign;
		re->valign = re->style->valign;
		re->multipleruns=NO;
		re->is_shape=NO;

#define end_re \
		cur_range = (NSRange){strbegin - pb, p - strbegin};\
		cur_range.location -= outputoffset;\
		cur_range.length -= lengthreduce;\
		[re increasestyles];\
		re->styles[re->style_count-1] = [[SSAStyleSpan alloc] init];\
		re->styles[re->style_count-1]->outline = cur_outline;\
		re->styles[re->style_count-1]->shadow = cur_shadow;\
		re->styles[re->style_count-1]->astyle = cur_style;\
		re->styles[re->style_count-1]->range = cur_range;\
		re->styles[re->style_count-1]->outlineblur = cur_be;\
		re->styles[re->style_count-1]->color = cur_color;\
		parsetmp = [NSString stringWithCharacters:skipbegin length:p-skipbegin];\
		[output appendString:parsetmp]; \
		re->nstext = dtmp = output;\
		re->text = malloc(sizeof(unichar[[re->nstext length]]));\
		[re->nstext getCharacters:re->text];\
		[re->nstext retain];

		{
			NSRange cur_range = {0,0};
			ATSUStyle cur_style;
			ATSUTextLayout cur_layout;
			NSMutableString *output = [NSMutableString string], *dtmp;
			unichar *p = re->text, *pe = &re->text[len], *numbegin = p, *strbegin = p, *skipbegin = p, *intbegin = p, *pb = p, *posbegin=p, *strparambegin=p;
			float num, cur_outline = re->style->outline, cur_shadow = re->style->shadow, cur_scalex = re->style->scalex, cur_scaley = re->style->scaley;
			NSString *parsetmp;
			int cs, cur_valign=re->valign, cur_halign=re->halign, cur_posx=-1, cur_posy=-1;
			ssacolors cur_color = re->style->color;
			CGAffineTransform matrix;

			unsigned long inum=0;
			unsigned outputoffset=0, lengthreduce=0;
			BOOL flag=0, newLayout=FALSE, cur_be = FALSE;
			Fixed fixv;

			ATSUCreateAndCopyStyle(re->style->atsustyle,&cur_style);

			%%{
				alphtype unsigned short;

				action bold {SetATSUStyleFlag(cur_style, kATSUQDBoldfaceTag, flag);}

				action italic {SetATSUStyleFlag(cur_style, kATSUQDItalicTag, flag);}

				action uline {SetATSUStyleFlag(cur_style, kATSUQDUnderlineTag, flag);}

				action strike {SetATSUStyleFlag(cur_style, kATSUStyleStrikeThroughTag, flag);}

				action bordersize {cur_outline = num; re->multipleruns = YES;}

				action shadowsize {cur_shadow = num; re->multipleruns = YES;}

				action bluredge {cur_be = flag;}

				action frot {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}
					fixv = FloatToFixed(num);

					SetATSULayoutOther(cur_layout, kATSULineRotationTag, sizeof(Fixed), &fixv);
				}

				action fsize {
					num *= (72./96.); // scale from Windows 96dpi
					fixv = FloatToFixed(num);
					SetATSUStyleOther(cur_style, kATSUSizeTag, sizeof(fixv), &fixv);
				}

				action ftrack {
					fixv = FloatToFixed(num);
					SetATSUStyleOther(cur_style, kATSUTrackingTag, sizeof(fixv), &fixv);
				}

				action fscalex {
					cur_scalex = num;
					matrix = CGAffineTransformMakeScale(cur_scalex/100.,cur_scaley/100.);
					SetATSUStyleOther(cur_style, kATSUFontMatrixTag, sizeof(matrix), &matrix);
				}

				action fscaley {
					cur_scaley = num;
					matrix = CGAffineTransformMakeScale(cur_scalex/100.,cur_scaley/100.);
					SetATSUStyleOther(cur_style, kATSUFontMatrixTag, sizeof(matrix), &matrix);
				}

				action strp_begin {
					strparambegin = p;
				}

				action fontname {
					ATSUFontID	font;
					font = FMGetFontFromATSFontRef(ATSFontFindFromName((CFStringRef)[NSString stringWithCharacters:strparambegin length:p-strparambegin],kATSOptionFlagsDefault));
					SetATSUStyleOther(cur_style, kATSUFontTag, sizeof(ATSUFontID), &font);
				}

				action alignment {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}

					UpdateAlignment(inum, cur_posx, &cur_valign, &cur_halign, cur_layout);
				}

				action ssa_alignment {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}

					UpdateAlignment(SSA2ASSAlignment(inum), cur_posx, &cur_valign, &cur_halign, cur_layout);
				}

				action pos_begin {
					posbegin=p;
				}

				action pos_end {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}
					NSArray *coo = [[NSString stringWithCharacters:posbegin length:p-posbegin] componentsSeparatedByString:@","];
					cur_posx = [[coo objectAtIndex:0] intValue];
					cur_posy = [[coo objectAtIndex:1] intValue];

					Fract alignment = FloatToFract(0);

					SetATSULayoutOther(cur_layout, kATSULineFlushFactorTag, sizeof(Fract), &alignment);
				}

				action primarycolor {
					cur_color.primary = ParseColorTag(inum,cur_color.primary.alpha);
					re->multipleruns = YES;
				}

				action secondarycolor {
					cur_color.secondary = ParseColorTag(inum,cur_color.secondary.alpha);
					re->multipleruns = YES;
				}

				action outlinecolor {
					cur_color.outline = ParseColorTag(inum,cur_color.outline.alpha);
					re->multipleruns = YES;
				}

				action shadowcolor {
					cur_color.shadow = ParseColorTag(inum,cur_color.shadow.alpha);
					re->multipleruns = YES;
				}

				action primaryalpha {
					cur_color.primary.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action secondaryalpha {
					cur_color.secondary.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action outlinealpha {
					cur_color.outline.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action shadowalpha {
					cur_color.shadow.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action nl_handler {
					parsetmp = [NSString stringWithCharacters:skipbegin length:(p-2)-skipbegin];
					[output appendString:parsetmp];

					skipbegin = p;

					[output appendString:@"\n"];

					lengthreduce++;
				}

				action enter_tag {
					parsetmp = [NSString stringWithCharacters:skipbegin length:p-skipbegin];
					[output appendString:parsetmp];

					outputoffset += lengthreduce;
					lengthreduce = 0;

					skipbegin = p;

					cur_range = (NSRange){strbegin - pb, p - strbegin};
					cur_range.location -= outputoffset;
					cur_range.length -= lengthreduce;

					[re increasestyles];
					re->styles[re->style_count-1] = [[SSAStyleSpan alloc] init];
					re->styles[re->style_count-1]->outline = cur_outline;
					re->styles[re->style_count-1]->shadow = cur_shadow;
					re->styles[re->style_count-1]->astyle = cur_style;
					re->styles[re->style_count-1]->range = cur_range;
					re->styles[re->style_count-1]->outlineblur = cur_be;
					re->styles[re->style_count-1]->color = cur_color;
					ATSUCreateAndCopyStyle(cur_style,&cur_style);
				}

				action exit_tag {
					outputoffset += p - skipbegin;
					skipbegin = p;
					strbegin = p;

					if (newLayout) {
						newLayout = FALSE;
						end_re;

						if ([re->nstext length] != 0) [rentities addObject:re];

						SSARenderEntity *nre = [[SSARenderEntity alloc] init];
						nre->layer = re->layer;
						nre->style = re->style;
						nre->marginl = re->marginl; nre->marginr = re->marginr; nre->marginv = re->marginv; nre->usablewidth = re->usablewidth;
						nre->posx = cur_posx; nre->posy = cur_posy; nre->halign = cur_halign; nre->valign = cur_valign;
						nre->nstext = nil;
						nre->text = nil;
						nre->layout = cur_layout;
						nre->styles = malloc(0);
						nre->style_count = 0;
						nre->disposelayout = YES;
						nre->multipleruns = NO;
						nre->is_shape = re->is_shape;
						re = nre;
					}
				}

				action color_end {
					NSString *hexn = [NSString stringWithCharacters:intbegin length:p-intbegin];
					inum = strtoul([hexn UTF8String], NULL, 16);
				}

				action styleset {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}
					re->multipleruns = YES;
					ssastyleline *the_style = re->style;
					NSString *searchsn = [NSString stringWithCharacters:strparambegin length:p-strparambegin];

					if ([searchsn length] > 0) {
						for (j=0; j < ssa->stylecount; j++) {
							if ([searchsn isEqualToString:ssa->styles[j].name])  {
								the_style = &ssa->styles[j];
								break;
							}
						}
					}

					ATSUCopyAttributes(the_style->atsustyle,cur_style);
					cur_color = the_style->color;
					cur_outline = the_style->outline;
					cur_shadow = the_style->shadow;
					cur_posx = cur_posy = -1;
				}

				action skip_t_tag {
					while (p != pe && *p != ')' && *p != '}') p++;
					p++;
				}

				action draw_mode {
					re->is_shape = inum != 0;
				}

				flag = ([01] % {unichar fl = *(p-1); if (flag == '0' || flag == '1') flag = fl - '0';})? > {flag = 1;};
				num_ = "-"? digit+ ('.' digit*)?;
				num = num_ > {numbegin = p;} % {num = [[NSString stringWithCharacters:numbegin length:p-numbegin] doubleValue];};

				intnum = "-"? (digit+) > {intbegin = p;} % {inum = [[NSString stringWithCharacters:intbegin length:p-intbegin] intValue];};

				color = ("H"|"&"){,2} (xdigit+) > {intbegin = p;} % color_end "&";

				cmd_specific = (("pos(" [^)]+ > pos_begin ")" % pos_end)
								|"bord" num %bordersize
								|"b" flag %bold
								|"be" flag %bluredge
								|"i" flag %italic
								|"u" flag %uline
								|"s" flag %strike
								|"fs" num %fsize
								|"fsp" num %ftrack
								|"fscx" num %fscalex
								|"fscy" num %fscaley
								|("fr" "z"? num %frot)
								|("fn" [^\\}]* > strp_begin %fontname)
								|"shad" num %shadowsize
								|"a" intnum %ssa_alignment
								|"an" intnum %alignment
								|"c" color %primarycolor
								|"1c" color %primarycolor
								|"2c" color %secondarycolor
								|"3c" color %outlinecolor
								|"4c" color %shadowcolor
								|"1a" color %primaryalpha
								|"2a" color %secondaryalpha
								|"3a" color %outlinealpha
								|"4a" color %shadowalpha
								|("r" [^\\}]* > strp_begin %styleset)
								|("fe"|"k"|"kf"|"K"|"ko"|"q"|"fr"|"fad"|"move"|"clip"|"o") [^\\}]*
								|"p" num %draw_mode
								#|"t" [^)}]* # enabling this crashes ragel
								|"t(" % skip_t_tag
								);

				cmd = "\\"  cmd_specific ;

				tag = "{" ((cmd*) | ([^\\}]*)) "}";

				nl = "\\" [Nn];

				special = nl % nl_handler |
						 (tag > enter_tag % exit_tag);

				text = any*;
				main := (text :> special?)*;
			}%%

			%%write init;
			%%write exec;
			%%write eof;

			if (pb[len-1] == '}') skipbegin = p; // make up for how exit_tag isn't called if the } is the last char in the line

			end_re;

			PruneEmptyStyleSpans(re);
			PruneIdenticalStyleSpans(re);

			free(pb);
			if ([re->nstext length] != 0) [rentities addObject:re];
		}

	}

	return rentities;
}

/*
 *  SSARenderCodec.m
 *  Copyright (c) 2007 Perian Project
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation;
 *  version 2.1 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#import "SSATagParsing.h"
#import "Categories.h"

@implementation SSAStyleSpan
-(void)dealloc
{
	ATSUDisposeStyle(astyle);
	[super dealloc];
}
@end

@implementation SSARenderEntity
-(void)dealloc
{
	int i;
	for (i=0; i < style_count; i++) [styles[i] release];
	if (disposelayout) ATSUDisposeTextLayout(layout);
	free(text);
	free(styles);
	[nstext release];
	[super dealloc];
}

-(void)increasestyles
{
	style_count++;
	styles = realloc(styles, sizeof(SSAStyleSpan*[style_count]));
}
@end

%% machine SSAtag;
%% write data;

static void SetATSUStyleFlag(ATSUStyle style, ATSUAttributeTag t, Boolean v)
{
	ATSUAttributeTag tags[] = {t};
	ByteCount		 sizes[] = {sizeof(v)};
	ATSUAttributeValuePtr vals[] = {&v};

	ATSUSetAttributes(style,1,tags,sizes,vals);
}

static void SetATSUStyleOther(ATSUStyle style, ATSUAttributeTag t, ByteCount s, ATSUAttributeValuePtr v)
{
	ATSUAttributeTag tags[] = {t};
	ByteCount		 sizes[] = {s};
	ATSUAttributeValuePtr vals[] = {v};

	ATSUSetAttributes(style,1,tags,sizes,vals);
}

static void SetATSULayoutOther(ATSUTextLayout l, ATSUAttributeTag t, ByteCount s, ATSUAttributeValuePtr v)
{
	ATSUAttributeTag tags[] = {t};
	ByteCount		 sizes[] = {s};
	ATSUAttributeValuePtr vals[] = {v};

	ATSUSetLayoutControls(l,1,tags,sizes,vals);
}

static ATSURGBAlphaColor ParseColorTag(unsigned long c, float a)
{
	unsigned char r,g,b;
	r = c & 0xff;
	g = (c >> 8) & 0xff;
	b = (c >> 16) & 0xff;

	return (ATSURGBAlphaColor){r/255.,g/255.,b/255.,a};
}

static void PruneEmptyStyleSpans(SSARenderEntity *re)
{
	SSAStyleSpan *styles_new[re->style_count];
	size_t style_count_new = 0;
	int i;

	for (i = 0; i < re->style_count; i++) {
		if (re->styles[i]->range.length == 0) {
			[re->styles[i] release];
		} else {
			styles_new[style_count_new++] = re->styles[i];
		}
	}

	if (style_count_new != re->style_count) {
		re->styles = realloc(re->styles, sizeof(SSAStyleSpan*) * style_count_new);
		memcpy(re->styles, styles_new, sizeof(SSAStyleSpan*) * style_count_new);

		re->style_count = style_count_new;
	}
}

static void PruneIdenticalStyleSpans(SSARenderEntity *re)
{
	SSAStyleSpan *styles_new[re->style_count];
	size_t style_count_new = 1, remaining = re->style_count-1;
	int i=1;

	if (re->multipleruns) return;

	styles_new[0] = re->styles[0];

	while (remaining) {
		ATSUStyleComparison asc;
		ATSUCompareStyles(styles_new[style_count_new-1]->astyle,re->styles[i]->astyle,&asc);

		if (asc == kATSUStyleEquals) {
			styles_new[style_count_new-1]->range.length += re->styles[i]->range.length;
			[re->styles[i] release];
		} else {
			styles_new[style_count_new++] = re->styles[i];
		}

		i++;
		remaining--;
	}

	if (style_count_new != re->style_count) {
		re->styles = realloc(re->styles, sizeof(SSAStyleSpan*) * style_count_new);
		memcpy(re->styles, styles_new, sizeof(SSAStyleSpan*) * style_count_new);

		re->style_count = style_count_new;
	}
}

static void UpdateAlignment(int inum, int cur_posx, int *valign, int *halign, ATSUTextLayout cur_layout)
{
	int cur_halign, cur_valign;

	switch (inum)
	{case 1: case 4: case 7: cur_halign = S_LeftAlign; break;
	case 2: case 5: case 8: default: cur_halign = S_CenterAlign; break;
	case 3: case 6: case 9: cur_halign = S_RightAlign;}

	switch (inum)
	{case 1: case 2: case 3: default: cur_valign = S_BottomAlign; break;
	case 4: case 5: case 6: cur_valign = S_MiddleAlign; break;
	case 7: case 8: case 9: cur_valign = S_TopAlign;}

	*halign = cur_halign;
	*valign = cur_valign;
	Fract alignment;

	if (cur_posx != -1) {
		switch(cur_halign) {
			case S_LeftAlign:
				alignment = FloatToFract(0.);
				break;
			case S_CenterAlign: default:
				alignment = kATSUCenterAlignment;
				break;
			case S_RightAlign:
				alignment = FloatToFract(1.);
		}
		SetATSULayoutOther(cur_layout, kATSULineFlushFactorTag, sizeof(Fract), &alignment);
	}
}

NSArray *ParseSubPacket(NSString *str, SSADocument *ssa, Boolean plaintext)
{
	NSArray *linea;
	int i, j, pcount; unsigned len;
	NSMutableArray *rentities = [NSMutableArray array];

	if (plaintext) {
		linea = [NSArray arrayWithObject:str];
		pcount = 1;
	} else {
		linea =  [str componentsSeparatedByString:@"\n"];
		pcount = [linea count];
	}

	for (i = 0; i < pcount; i++) {
		SSARenderEntity *re = [[[SSARenderEntity alloc] init] autorelease];
		BOOL ldirty = NO;

		if (plaintext) {
			re->style = ssa->defaultstyle;
			re->layout = re->style->layout;
			re->layer = 0;
			re->marginl = re->style->marginl;
			re->marginr = re->style->marginl;
			re->marginv = re->style->marginl;
			re->usablewidth = re->style->usablewidth;
			re->halign = 1;
			re->valign = 0;
			re->nstext = [linea objectAtIndex:i];
			re->styles = NULL;
			re->disposelayout = NO;
		} else {
			NSArray *ar = [[linea objectAtIndex:i] componentsSeparatedByString:@"," count:9];

			if ([ar count] < 9) continue;

			re->layer = [[ar objectAtIndex:1] intValue];
			NSString *sn = [ar objectAtIndex:2];

			re->style = ssa->defaultstyle;

			for (j=0; j < ssa->stylecount; j++) {
				if ([sn isEqualToString:ssa->styles[j].name])  {
					re->style = &ssa->styles[j];
					break;
				}
			}

			re->marginl = [[ar objectAtIndex:5] intValue];
			re->marginr = [[ar objectAtIndex:6] intValue];
			re->marginv = [[ar objectAtIndex:7] intValue];

			if (re->marginl == 0) re->marginl = re->style->marginl; else ldirty = TRUE;
			if (re->marginr == 0) re->marginr = re->style->marginr; else ldirty = TRUE;
			if (re->marginv == 0) re->marginv = re->style->marginv; else ldirty = TRUE;

			if (ldirty) {
				ATSUTextMeasurement width;
				ATSUAttributeTag tag[] = {kATSULineWidthTag};
				ByteCount		 size[] = {sizeof(ATSUTextMeasurement)};
				ATSUAttributeValuePtr val[] = {&width};

				re->usablewidth = ssa->resX - re->marginl - re->marginr;
				ATSUCreateAndCopyTextLayout(re->style->layout,&re->layout);
				width = IntToFixed(re->usablewidth);

				ATSUSetLayoutControls(re->layout,1,tag,size,val);
				re->disposelayout = YES;
			} else {
				re->usablewidth = re->style->usablewidth;
				re->layout = re->style->layout;
				re->disposelayout = NO;
			}

			re->nstext = [ar objectAtIndex:8];
		}
		len = [re->nstext length];
		re->text = malloc(sizeof(unichar[len]));

		[re->nstext getCharacters:re->text];
		re->style_count = 0;
		re->posx = re->posy = -1;
		re->halign = re->style->halign;
		re->valign = re->style->valign;
		re->multipleruns=NO;
		re->is_shape=NO;

#define end_re \
		cur_range = (NSRange){strbegin - pb, p - strbegin};\
		cur_range.location -= outputoffset;\
		cur_range.length -= lengthreduce;\
		[re increasestyles];\
		re->styles[re->style_count-1] = [[SSAStyleSpan alloc] init];\
		re->styles[re->style_count-1]->outline = cur_outline;\
		re->styles[re->style_count-1]->shadow = cur_shadow;\
		re->styles[re->style_count-1]->astyle = cur_style;\
		re->styles[re->style_count-1]->range = cur_range;\
		re->styles[re->style_count-1]->outlineblur = cur_be;\
		re->styles[re->style_count-1]->color = cur_color;\
		parsetmp = [NSString stringWithCharacters:skipbegin length:p-skipbegin];\
		[output appendString:parsetmp]; \
		re->nstext = dtmp = output;\
		re->text = malloc(sizeof(unichar[[re->nstext length]]));\
		[re->nstext getCharacters:re->text];\
		[re->nstext retain];

		{
			NSRange cur_range = {0,0};
			ATSUStyle cur_style;
			ATSUTextLayout cur_layout;
			NSMutableString *output = [NSMutableString string], *dtmp;
			unichar *p = re->text, *pe = &re->text[len], *numbegin = p, *strbegin = p, *skipbegin = p, *intbegin = p, *pb = p, *posbegin=p, *strparambegin=p;
			float num, cur_outline = re->style->outline, cur_shadow = re->style->shadow, cur_scalex = re->style->scalex, cur_scaley = re->style->scaley;
			NSString *parsetmp;
			int cs, cur_valign=re->valign, cur_halign=re->halign, cur_posx=-1, cur_posy=-1;
			ssacolors cur_color = re->style->color;
			CGAffineTransform matrix;

			unsigned long inum=0;
			unsigned outputoffset=0, lengthreduce=0;
			BOOL flag=0, newLayout=FALSE, cur_be = FALSE;
			Fixed fixv;

			ATSUCreateAndCopyStyle(re->style->atsustyle,&cur_style);

			%%{
				alphtype unsigned short;

				action bold {SetATSUStyleFlag(cur_style, kATSUQDBoldfaceTag, flag);}

				action italic {SetATSUStyleFlag(cur_style, kATSUQDItalicTag, flag);}

				action uline {SetATSUStyleFlag(cur_style, kATSUQDUnderlineTag, flag);}

				action strike {SetATSUStyleFlag(cur_style, kATSUStyleStrikeThroughTag, flag);}

				action bordersize {cur_outline = num; re->multipleruns = YES;}

				action shadowsize {cur_shadow = num; re->multipleruns = YES;}

				action bluredge {cur_be = flag;}

				action frot {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}
					fixv = FloatToFixed(num);

					SetATSULayoutOther(cur_layout, kATSULineRotationTag, sizeof(Fixed), &fixv);
				}

				action fsize {
					num *= (72./96.); // scale from Windows 96dpi
					fixv = FloatToFixed(num);
					SetATSUStyleOther(cur_style, kATSUSizeTag, sizeof(fixv), &fixv);
				}

				action ftrack {
					fixv = FloatToFixed(num);
					SetATSUStyleOther(cur_style, kATSUTrackingTag, sizeof(fixv), &fixv);
				}

				action fscalex {
					cur_scalex = num;
					matrix = CGAffineTransformMakeScale(cur_scalex/100.,cur_scaley/100.);
					SetATSUStyleOther(cur_style, kATSUFontMatrixTag, sizeof(matrix), &matrix);
				}

				action fscaley {
					cur_scaley = num;
					matrix = CGAffineTransformMakeScale(cur_scalex/100.,cur_scaley/100.);
					SetATSUStyleOther(cur_style, kATSUFontMatrixTag, sizeof(matrix), &matrix);
				}

				action strp_begin {
					strparambegin = p;
				}

				action fontname {
					ATSUFontID	font;
					font = FMGetFontFromATSFontRef(ATSFontFindFromName((CFStringRef)[NSString stringWithCharacters:strparambegin length:p-strparambegin],kATSOptionFlagsDefault));
					SetATSUStyleOther(cur_style, kATSUFontTag, sizeof(ATSUFontID), &font);
				}

				action alignment {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}

					UpdateAlignment(inum, cur_posx, &cur_valign, &cur_halign, cur_layout);
				}

				action ssa_alignment {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}

					UpdateAlignment(SSA2ASSAlignment(inum), cur_posx, &cur_valign, &cur_halign, cur_layout);
				}

				action pos_begin {
					posbegin=p;
				}

				action pos_end {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}
					NSArray *coo = [[NSString stringWithCharacters:posbegin length:p-posbegin] componentsSeparatedByString:@","];
					cur_posx = [[coo objectAtIndex:0] intValue];
					cur_posy = [[coo objectAtIndex:1] intValue];

					Fract alignment = FloatToFract(0);

					SetATSULayoutOther(cur_layout, kATSULineFlushFactorTag, sizeof(Fract), &alignment);
				}

				action primarycolor {
					cur_color.primary = ParseColorTag(inum,cur_color.primary.alpha);
					re->multipleruns = YES;
				}

				action secondarycolor {
					cur_color.secondary = ParseColorTag(inum,cur_color.secondary.alpha);
					re->multipleruns = YES;
				}

				action outlinecolor {
					cur_color.outline = ParseColorTag(inum,cur_color.outline.alpha);
					re->multipleruns = YES;
				}

				action shadowcolor {
					cur_color.shadow = ParseColorTag(inum,cur_color.shadow.alpha);
					re->multipleruns = YES;
				}

				action primaryalpha {
					cur_color.primary.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action secondaryalpha {
					cur_color.secondary.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action outlinealpha {
					cur_color.outline.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action shadowalpha {
					cur_color.shadow.alpha = (255 - inum) / 255.f;
					re->multipleruns = YES;
				}

				action nl_handler {
					parsetmp = [NSString stringWithCharacters:skipbegin length:(p-2)-skipbegin];
					[output appendString:parsetmp];

					skipbegin = p;

					[output appendString:@"\n"];

					lengthreduce++;
				}

				action enter_tag {
					parsetmp = [NSString stringWithCharacters:skipbegin length:p-skipbegin];
					[output appendString:parsetmp];

					outputoffset += lengthreduce;
					lengthreduce = 0;

					skipbegin = p;

					cur_range = (NSRange){strbegin - pb, p - strbegin};
					cur_range.location -= outputoffset;
					cur_range.length -= lengthreduce;

					[re increasestyles];
					re->styles[re->style_count-1] = [[SSAStyleSpan alloc] init];
					re->styles[re->style_count-1]->outline = cur_outline;
					re->styles[re->style_count-1]->shadow = cur_shadow;
					re->styles[re->style_count-1]->astyle = cur_style;
					re->styles[re->style_count-1]->range = cur_range;
					re->styles[re->style_count-1]->outlineblur = cur_be;
					re->styles[re->style_count-1]->color = cur_color;
					ATSUCreateAndCopyStyle(cur_style,&cur_style);
				}

				action exit_tag {
					outputoffset += p - skipbegin;
					skipbegin = p;
					strbegin = p;

					if (newLayout) {
						newLayout = FALSE;
						end_re;

						if ([re->nstext length] != 0) [rentities addObject:re];

						SSARenderEntity *nre = [[SSARenderEntity alloc] init];
						nre->layer = re->layer;
						nre->style = re->style;
						nre->marginl = re->marginl; nre->marginr = re->marginr; nre->marginv = re->marginv; nre->usablewidth = re->usablewidth;
						nre->posx = cur_posx; nre->posy = cur_posy; nre->halign = cur_halign; nre->valign = cur_valign;
						nre->nstext = nil;
						nre->text = nil;
						nre->layout = cur_layout;
						nre->styles = malloc(0);
						nre->style_count = 0;
						nre->disposelayout = YES;
						nre->multipleruns = NO;
						nre->is_shape = re->is_shape;
						re = nre;
					}
				}

				action color_end {
					NSString *hexn = [NSString stringWithCharacters:intbegin length:p-intbegin];
					inum = strtoul([hexn UTF8String], NULL, 16);
				}

				action styleset {
					if (!newLayout) {
						newLayout = TRUE;
						ATSUCreateAndCopyTextLayout(re->layout,&cur_layout);
					}
					re->multipleruns = YES;
					ssastyleline *the_style = re->style;
					NSString *searchsn = [NSString stringWithCharacters:strparambegin length:p-strparambegin];

					if ([searchsn length] > 0) {
						for (j=0; j < ssa->stylecount; j++) {
							if ([searchsn isEqualToString:ssa->styles[j].name])  {
								the_style = &ssa->styles[j];
								break;
							}
						}
					}

					ATSUCopyAttributes(the_style->atsustyle,cur_style);
					cur_color = the_style->color;
					cur_outline = the_style->outline;
					cur_shadow = the_style->shadow;
					cur_posx = cur_posy = -1;
				}

				action skip_t_tag {
					while (p != pe && *p != ')' && *p != '}') p++;
					p++;
				}

				action draw_mode {
					re->is_shape = inum != 0;
				}

				flag = ([01] % {unichar fl = *(p-1); if (flag == '0' || flag == '1') flag = fl - '0';})? > {flag = 1;};
				num_ = "-"? digit+ ('.' digit*)?;
				num = num_ > {numbegin = p;} % {num = [[NSString stringWithCharacters:numbegin length:p-numbegin] doubleValue];};

				intnum = "-"? (digit+) > {intbegin = p;} % {inum = [[NSString stringWithCharacters:intbegin length:p-intbegin] intValue];};

				color = ("H"|"&"){,2} (xdigit+) > {intbegin = p;} % color_end "&";

				cmd_specific = (("pos(" [^)]+ > pos_begin ")" % pos_end)
								|"bord" num %bordersize
								|"b" flag %bold
								|"be" flag %bluredge
								|"i" flag %italic
								|"u" flag %uline
								|"s" flag %strike
								|"fs" num %fsize
								|"fsp" num %ftrack
								|"fscx" num %fscalex
								|"fscy" num %fscaley
								|("fr" "z"? num %frot)
								|("fn" [^\\}]* > strp_begin %fontname)
								|"shad" num %shadowsize
								|"a" intnum %ssa_alignment
								|"an" intnum %alignment
								|"c" color %primarycolor
								|"1c" color %primarycolor
								|"2c" color %secondarycolor
								|"3c" color %outlinecolor
								|"4c" color %shadowcolor
								|"1a" color %primaryalpha
								|"2a" color %secondaryalpha
								|"3a" color %outlinealpha
								|"4a" color %shadowalpha
								|("r" [^\\}]* > strp_begin %styleset)
								|("fe"|"k"|"kf"|"K"|"ko"|"q"|"fr"|"fad"|"move"|"clip"|"o") [^\\}]*
								|"p" num %draw_mode
								#|"t" [^)}]* # enabling this crashes ragel
								|"t(" % skip_t_tag
								);

				cmd = "\\"  cmd_specific ;

				tag = "{" ((cmd*) | ([^\\}]*)) "}";

				nl = "\\" [Nn];

				special = nl % nl_handler |
						 (tag > enter_tag % exit_tag);

				text = any*;
				main := (text :> special?)*;
			}%%

			%%write init;
			%%write exec;
			%%write eof;

			if (pb[len-1] == '}') skipbegin = p; // make up for how exit_tag isn't called if the } is the last char in the line

			end_re;

			PruneEmptyStyleSpans(re);
			PruneIdenticalStyleSpans(re);

			free(pb);
			if ([re->nstext length] != 0) [rentities addObject:re];
		}

	}

	return rentities;
}


parse null,false, true

parse null,false, true

// vim:syntax=ragel
#include <stdio.h>
#include "global.h"
#include "interpret.h"
#include "stralloc.h"
#include "mapping.h"
#include "svalue.h"
#include "array.h"
#include "builtin_functions.h"
#include "module.h"

char *_parse_JSON(char* p, char* pe,
#ifndef USE_PIKE_STACK
		  struct svalue *var,
#endif
		  struct string_builder *s);
char *_parse_JSON_mapping(char* p, char* pe,
#ifndef USE_PIKE_STACK
			  struct svalue *var,
#endif
			  struct string_builder *s);
char *_parse_JSON_array(char* p, char* pe,
#ifndef USE_PIKE_STACK
			struct svalue *var,
#endif
			struct string_builder *s);
char *_parse_JSON_number(char* p, char* pe,
#ifndef USE_PIKE_STACK
			 struct svalue *var,
#endif
			 struct string_builder *s);
char *_parse_JSON_string(char* p, char* pe,
#ifndef USE_PIKE_STACK
			 struct svalue *var,
#endif
			 struct string_builder *s);

#include "json_string.c"
#include "json_number.c"
#include "json_array.c"
#include "json_mapping.c"

%%{
    machine JSON;
    write data;

    action parse_string {
	i = _parse_JSON_string(fpc, pe,
#ifndef USE_PIKE_STACK
			       var,
#endif
			       s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    action parse_mapping {
	i = _parse_JSON_mapping(fpc, pe,
#ifndef USE_PIKE_STACK
				var,
#endif
				s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    action parse_array {
	i = _parse_JSON_array(fpc, pe,
#ifndef USE_PIKE_STACK
			      var,
#endif
			      s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    action parse_number {
	i = _parse_JSON_number(fpc, pe,
#ifndef USE_PIKE_STACK
			       var,
#endif
			       s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    number_start = [\-+.] | digit;
    array_start = '[';
    mapping_start = '{';
    string_start = '"';
    value_start = number_start | array_start | mapping_start | string_start;
    myspace = ' ';

    main := myspace* . (number_start >parse_number |
			string_start >parse_string |
			mapping_start >parse_mapping |
			array_start >parse_array |
			'true' @{ push_int(1); } |
			'false' @{ push_undefined(); } |
			'null' @{ push_int(0); } ) . myspace* %*{ fbreak; };
}%%

char *_parse_JSON(char *p, char *pe,
#ifndef USE_PIKE_STACK
		  struct svalue *var,
#endif
		  struct string_builder *s) {
    char *i = p;
    int cs;

    %% write init;
    %% write exec;

    if (
#ifndef USE_PIKE_STACK
	i != NULL &&
#endif
	cs >= JSON_first_final) {
	return p;
    }
#ifndef USE_PIKE_STACK
    Pike_error("Error parsing JSON at '%.*s'\n", MINIMUM((int)(pe - p), 10), p);
#endif
    return NULL;
}

/*! @module Public
 */

/*! @module Parser
 */

/*! @module PSYC
 */

/*! @decl array|mapping|string|float|int parse_JSON(string s)
 *!
 *! Parses a JSON-formatted string and returns the corresponding pike data type.
 */
PIKEFUN mixed parse(string data) {
    struct string_builder s;
    init_string_builder(&s, 1);
#ifndef USE_PIKE_STACK
    struct svalue *var;
#endif
    char *ret;
    // we wont be building more than one string at once.

#ifndef USE_PIKE_STACK
    var = (struct svalue *)malloc(sizeof(struct svalue));

    if (var == NULL) {
	Pike_error("malloc failed during JSON parse.\n");
    }

    memset(var, 0, sizeof(struct svalue));
#endif

    if (data->size_shift != 0) {
	Pike_error("Size shift != 0.");
	// no need to return. does a longjmp
    }

    pop_stack();
    ret = (char*)STR0(data);
    ret = _parse_JSON(ret, ret + data->len,
#ifndef USE_PIKE_STACK
		      var,
#endif
		      &s);

#ifndef USE_PIKE_STACK
    if (ret == NULL) {
	free(var);
	Pike_error("Error while parsing JSON!\n");
    }
#endif

#ifndef USE_PIKE_STACK
    push_svalue(var);
#endif
    return;
}

// vim:syntax=ragel
#include <stdio.h>
#include "global.h"
#include "interpret.h"
#include "stralloc.h"
#include "mapping.h"
#include "svalue.h"
#include "array.h"
#include "builtin_functions.h"
#include "module.h"

char *_parse_JSON(char* p, char* pe,
#ifndef USE_PIKE_STACK
		  struct svalue *var,
#endif
		  struct string_builder *s);
char *_parse_JSON_mapping(char* p, char* pe,
#ifndef USE_PIKE_STACK
			  struct svalue *var,
#endif
			  struct string_builder *s);
char *_parse_JSON_array(char* p, char* pe,
#ifndef USE_PIKE_STACK
			struct svalue *var,
#endif
			struct string_builder *s);
char *_parse_JSON_number(char* p, char* pe,
#ifndef USE_PIKE_STACK
			 struct svalue *var,
#endif
			 struct string_builder *s);
char *_parse_JSON_string(char* p, char* pe,
#ifndef USE_PIKE_STACK
			 struct svalue *var,
#endif
			 struct string_builder *s);

#include "json_string.c"
#include "json_number.c"
#include "json_array.c"
#include "json_mapping.c"

%%{
    machine JSON;
    write data;

    action parse_string {
	i = _parse_JSON_string(fpc, pe,
#ifndef USE_PIKE_STACK
			       var,
#endif
			       s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    action parse_mapping {
	i = _parse_JSON_mapping(fpc, pe,
#ifndef USE_PIKE_STACK
				var,
#endif
				s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    action parse_array {
	i = _parse_JSON_array(fpc, pe,
#ifndef USE_PIKE_STACK
			      var,
#endif
			      s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    action parse_number {
	i = _parse_JSON_number(fpc, pe,
#ifndef USE_PIKE_STACK
			       var,
#endif
			       s);
#ifndef USE_PIKE_STACK
	if (i == NULL) fbreak;
#endif
	fexec i;
    }

    number_start = [\-+.] | digit;
    array_start = '[';
    mapping_start = '{';
    string_start = '"';
    value_start = number_start | array_start | mapping_start | string_start;
    myspace = ' ';

    main := myspace* . (number_start >parse_number |
			string_start >parse_string |
			mapping_start >parse_mapping |
			array_start >parse_array |
			'true' |
			'false' |
			'null') . myspace* %*{ fbreak; };
}%%

char *_parse_JSON(char *p, char *pe,
#ifndef USE_PIKE_STACK
		  struct svalue *var,
#endif
		  struct string_builder *s) {
    char *i = p;
    int cs;

    %% write init;
    %% write exec;

    if (
#ifndef USE_PIKE_STACK
	i != NULL &&
#endif
	cs >= JSON_first_final) {
	return p;
    }
#ifndef USE_PIKE_STACK
    Pike_error("Error parsing JSON at '%.*s'\n", MINIMUM((int)(pe - p), 10), p);
#endif
    return NULL;
}

/*! @module Public
 */

/*! @module Parser
 */

/*! @module PSYC
 */

/*! @decl array|mapping|string|float|int parse_JSON(string s)
 *!
 *! Parses a JSON-formatted string and returns the corresponding pike data type.
 */
PIKEFUN mixed parse(string data) {
    struct string_builder s;
    init_string_builder(&s, 1);
#ifndef USE_PIKE_STACK
    struct svalue *var;
#endif
    char *ret;
    // we wont be building more than one string at once.

#ifndef USE_PIKE_STACK
    var = (struct svalue *)malloc(sizeof(struct svalue));

    if (var == NULL) {
	Pike_error("malloc failed during JSON parse.\n");
    }

    memset(var, 0, sizeof(struct svalue));
#endif

    if (data->size_shift != 0) {
	Pike_error("Size shift != 0.");
	// no need to return. does a longjmp
    }

    pop_stack();
    ret = (char*)STR0(data);
    ret = _parse_JSON(ret, ret + data->len,
#ifndef USE_PIKE_STACK
		      var,
#endif
		      &s);

#ifndef USE_PIKE_STACK
    if (ret == NULL) {
	free(var);
	Pike_error("Error while parsing JSON!\n");
    }
#endif

#ifndef USE_PIKE_STACK
    push_svalue(var);
#endif
    return;
}