// Close the input file and throw an ExceptionParseError exception.
    void parse_error()
    {
        const size_t line_number = m_lexer.get_line_number();

        m_lexer.close();

        throw ExceptionParseError(line_number);
    }
    string parse_mesh_name()
    {
        string mesh_name;

        while (!m_lexer.is_eol())
        {
            const char* token;
            size_t token_length;

            m_lexer.accept_string(&token, &token_length);
            m_lexer.eat_blanks();

            if (!mesh_name.empty())
                mesh_name += ' ';

            mesh_name.append(token, token_length);
        }

        return mesh_name;
    }
    string parse_compound_identifier()
    {
        string identifier;

        m_lexer.eat_blanks();

        while (!m_lexer.is_eol())
        {
            const char* token;
            size_t token_length;

            m_lexer.accept_string(&token, &token_length);
            m_lexer.eat_blanks();

            if (!identifier.empty())
                identifier += ' ';

            identifier.append(token, token_length);
        }

        return identifier;
    }
    void parse_v_statement()
    {
        Vector3d v;

        m_lexer.eat_blanks();
        v.x = m_lexer.accept_double();

        m_lexer.eat_blanks();
        v.y = m_lexer.accept_double();

        m_lexer.eat_blanks();
        v.z = m_lexer.accept_double();

        m_lexer.eat_blanks();

        if (!m_lexer.is_eol())
            m_lexer.accept_double();

        m_vertices.push_back(v);
    }
    void parse_vt_statement()
    {
        Vector2d v;

        m_lexer.eat_blanks();
        v.x = m_lexer.accept_double();

        m_lexer.eat_blanks();
        v.y = m_lexer.accept_double();

        m_lexer.eat_blanks();
        if (!m_lexer.is_eol())
            m_lexer.accept_double();

        m_tex_coords.push_back(v);
    }
    void parse_vn_statement()
    {
        Vector3d n;

        m_lexer.eat_blanks();
        n.x = m_lexer.accept_double();

        m_lexer.eat_blanks();
        n.y = m_lexer.accept_double();

        m_lexer.eat_blanks();
        n.z = m_lexer.accept_double();

        m_normals.push_back(n);
    }
    void parse_o_g_statement()
    {
        m_lexer.eat_blanks();

        // Retrieve the name of the upcoming mesh.
        const string upcoming_mesh_name = parse_mesh_name();

        // Start a new mesh only if the name of the object or group actually changes.
        if (upcoming_mesh_name != m_mesh_name)
        {
            // End the current mesh.
            if (m_inside_mesh_def)
            {
                m_builder.end_mesh();
                m_inside_mesh_def = false;
            }

            clear_keep_memory(m_vertex_index_mapping);
            clear_keep_memory(m_tex_coord_index_mapping);
            clear_keep_memory(m_normal_index_mapping);

            m_mesh_name = upcoming_mesh_name;
        }
    }
    void parse_f_statement()
    {
        clear_keep_memory(m_face_vertex_indices);
        clear_keep_memory(m_face_tex_coord_indices);
        clear_keep_memory(m_face_normal_indices);

        while (true)
        {
            m_lexer.eat_blanks();

            if (m_lexer.is_eol())
                break;

            //
            // Recognized (epsilon)
            // Accept n
            //

            {
                const long n = m_lexer.accept_long();
                const size_t v = fix_index(n, m_vertices.size());
                m_face_vertex_indices.push_back(v);
            }

            //
            // Recognized n
            // Accept (epsilon), /
            //

            {
                const unsigned char c = m_lexer.get_char();
                if (m_lexer.is_space(c))
                    continue;
                else if (c == '/')
                    m_lexer.next_char();
                else parse_error();
            }

            //
            // Recognized n/
            // Accept /, n
            //

            {
                const unsigned char c = m_lexer.get_char();
                if (c == '/')
                {
                    m_lexer.next_char();
                    goto skip;
                }
                else
                {
                    const long n = m_lexer.accept_long();
                    const size_t vt = fix_index(n, m_tex_coords.size());
                    m_face_tex_coord_indices.push_back(vt);
                }
            }

            //
            // Recognized n/n
            // Accept (epsilon), /
            //

            {
                const unsigned char c = m_lexer.get_char();
                if (m_lexer.is_space(c))
                    continue;
                else if (c == '/')
                    m_lexer.next_char();
                else parse_error();
            }

          skip:

            //
            // Recognized n//, n/n/
            // Accept (epsilon), n
            //

            {
                const unsigned char c = m_lexer.get_char();
                if (m_lexer.is_space(c))
                    continue;
                else
                {
                    const long n = m_lexer.accept_long();
                    const size_t vn = fix_index(n, m_normals.size());
                    m_face_normal_indices.push_back(vn);
                }
            }
        }

        // Check whether the face is well-formed.
        const size_t vc = m_face_vertex_indices.size();
        const size_t tc = m_face_tex_coord_indices.size();
        const size_t nc = m_face_normal_indices.size();
        const bool well_formed =
                vc >= 3
            && (tc == 0 || tc == vc)
            && (nc == 0 || nc == vc);

        if (well_formed)
        {
            // The face is well-formed, insert it into the mesh.
            insert_face_into_mesh();
        }
        else
        {
            // The face is ill-formed, ignore it or abort parsing.
            if (m_options & StopOnInvalidFaceDef)
                throw ExceptionInvalidFaceDef(m_lexer.get_line_number());
        }
    }
    void parse_file()
    {
        while (true)
        {
            m_lexer.eat_blanks();

            // Handle end of file.
            if (m_lexer.is_eof())
                break;

            // Handle empty lines.
            if (m_lexer.is_eol())
            {
                m_lexer.accept_newline();
                continue;
            }

            const char* keyword;
            size_t keyword_length;

            m_lexer.accept_string(&keyword, &keyword_length);

            if (keyword_length == 1)
            {
                switch (keyword[0])
                {
                  case 'f':
                    parse_f_statement();
                    break;

                  case 'g':
                  case 'o':
                    parse_o_g_statement();
                    break;

                  case 'v':
                    parse_v_statement();
                    break;

                  default:
                    // Ignore unknown or unhandled statements.
                    m_lexer.eat_line();
                    continue;
                }
            }
            else if (keyword_length == 2)
            {
                switch (keyword[0] * 256 + keyword[1])
                {
                  case 'v' * 256 + 'n':
                    parse_vn_statement();
                    break;

                  case 'v' * 256 + 't':
                    parse_vt_statement();
                    break;

                  default:
                    // Ignore unknown or unhandled statements.
                    m_lexer.eat_line();
                    continue;
                }
            }
            else if (strncmp(keyword, "usemtl", keyword_length) == 0)
            {
                parse_usemtl_statement();
            }
            else
            {
                // Ignore unknown or unhandled statements.
                m_lexer.eat_line();
                continue;
            }

            m_lexer.eat_blanks();
            m_lexer.accept_newline();
        }

        // End the definition of the last object.
        if (m_inside_mesh_def)
            m_builder.end_mesh();
    }