diff --git a/doc/sphinx/_templates/layout.html b/doc/sphinx/_templates/layout.html
index 70168830a6eb8721de1078cbf978c4d40a1269e5..b1b5d9ed714e7d11fe625b8b66ab63cc5047bee2 100644
--- a/doc/sphinx/_templates/layout.html
+++ b/doc/sphinx/_templates/layout.html
@@ -5,7 +5,7 @@
 {% block header_scripts %}
   <link rel="stylesheet" href="/doc/_static/sphinx_design.css" />
   <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
-  <script type="text/javascript" src="../_static/headers.js"></script>
+  <script type="text/javascript" src="/doc/_static/headers.js"></script>
   {{ super() }}
 {% endblock header_scripts %}
 
diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py
index a7cf79d0601437496f34229d2afaa3a32372d31a..9f057c24bf46ecf4be668b7a62f931b98d976304 100644
--- a/doc/sphinx/conf.py
+++ b/doc/sphinx/conf.py
@@ -33,8 +33,10 @@ extensions = [
     'sphinx.ext.todo',
     'sphinx.ext.mathjax',
     'sphinx.ext.ifconfig',
-    'ext.filters'
+    'ext.filters',
+    'ext.lua'
 ]
+primary_domain = 'lua'
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
diff --git a/doc/sphinx/ext/lua.py b/doc/sphinx/ext/lua.py
new file mode 100644
index 0000000000000000000000000000000000000000..a38fb526ddf510eee3fb59f1f4e6a313477a0b9e
--- /dev/null
+++ b/doc/sphinx/ext/lua.py
@@ -0,0 +1,847 @@
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Previously Haka Domain (https://github.com/haka-security/haka)
+# Written by Haka team
+
+import re
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+
+from sphinx import addnodes
+from sphinx.roles import XRefRole
+from sphinx.locale import l_, _
+from sphinx.domains import Domain, ObjType, Index
+from sphinx.directives import ObjectDescription
+from sphinx.util.nodes import make_refnode
+from sphinx.util.compat import Directive
+from sphinx.util.docfields import Field, GroupedField, TypedField
+from sphinx.writers.html import HTMLTranslator
+
+
+# Fix parameter display when using something different than '()'
+
+def new_visit_desc_parameterlist(self, node):
+    if hasattr(node, 'param_class'):
+        param_class = ' class="param_start_%s"' % node.param_class
+    else:
+        param_class = ''
+
+    if hasattr(node, 'param_start'):
+        value = node.param_start
+    else:
+        value = '('
+
+    self.body.append('<big%s>%s</big>' % (param_class, value))
+    self.first_param = 1
+    self.optional_param_level = 0
+    # How many required parameters are left.
+    self.required_params_left = sum([isinstance(c, addnodes.desc_parameter)
+                                     for c in node.children])
+    self.param_separator = node.child_text_separator
+
+def new_depart_desc_parameterlist(self, node):
+    if hasattr(node, 'param_class'):
+        param_class = ' class="param_end_%s"' % node.param_class
+    else:
+        param_class = ''
+
+    if hasattr(node, 'param_end'):
+        value = node.param_end
+    else:
+        value = ')'
+
+    self.body.append('<big%s>%s</big>' % (param_class, value))
+
+HTMLTranslator.visit_desc_parameterlist = new_visit_desc_parameterlist
+HTMLTranslator.depart_desc_parameterlist = new_depart_desc_parameterlist
+
+def _desc_parameterlist(argstart, argend):
+    node = addnodes.desc_parameterlist()
+    node.param_start = argstart
+    node.param_end = argend
+    return node
+
+def _pseudo_parse_arglist(signode, argstart, arglist, argend):
+    """"Parse" a list of arguments separated by commas.
+
+    Arguments can have "optional" annotations given by enclosing them in
+    brackets.  Currently, this will split at any comma, even if it's inside a
+    string literal (e.g. default argument value).
+    """
+    paramlist = _desc_parameterlist(argstart, argend)
+    stack = [paramlist]
+    for argument in arglist.split(','):
+        argument = argument.strip()
+        ends_open = ends_close = 0
+        while argument.startswith('['):
+            stack.append(addnodes.desc_optional())
+            stack[-2] += stack[-1]
+            argument = argument[1:].strip()
+
+        while argument.startswith(']'):
+            stack.pop()
+            argument = argument[1:].strip()
+
+        while argument.endswith(']'):
+            ends_close += 1
+            argument = argument[:-1].strip()
+
+        while argument.endswith('['):
+            ends_open += 1
+            argument = argument[:-1].strip()
+
+        if argument:
+            stack[-1] += addnodes.desc_parameter(argument, argument)
+            while ends_open:
+                stack.append(addnodes.desc_optional())
+                stack[-2] += stack[-1]
+                ends_open -= 1
+
+            while ends_close:
+                stack.pop()
+                ends_close -= 1
+
+    if len(stack) != 1:
+        raise IndexError
+
+    signode += paramlist
+
+
+# Lua objects
+
+class LuaObject(ObjectDescription):
+    """
+    Description of a general Lua object.
+    """
+    option_spec = {
+        'noindex': directives.flag,
+        'annotation': directives.unchanged,
+        'module': directives.unchanged,
+        'objtype': directives.unchanged,
+        'idxtype': directives.unchanged,
+        'idxctx': directives.unchanged,
+    }
+
+    lua_signature_re = re.compile(
+        r'''^ ([\w\.\:/\-]+[:.])?     # class name(s)
+              ([<>\w/\-/]+)  \s*        # thing name
+              (?: ([({])(.*)([)}]))?  # optional: arguments
+              (?:\s* -> \s* (.*))?    # optional: return annotation
+              $                       # and nothing more
+              ''', re.VERBOSE)
+
+    def needs_arglist(self):
+        """May return true if an empty argument list is to be generated even if
+        the document contains none."""
+        return False
+
+    def needs_module(self):
+        """May return true if the module name should be displayed."""
+        return self.context == None
+
+    def needs_class(self):
+        """May return true if the module name should be displayed."""
+        return self.context == None
+
+    def build_objtype(self):
+        return self.options.get('objtype') or "%s" % (self.__class__.typename)
+
+    def build_context(self, context):
+        if context:
+            return context[:-1], context[-1]
+        else:
+            return None, None
+
+    def parse_signature(self, sig):
+        m = self.__class__.lua_signature_re.match(sig)
+        if m is None:
+            raise ValueError
+
+        return m.groups()
+
+    def build_parameters(self, signode):
+        if not self.arglist:
+            if self.needs_arglist():
+                # for callables, add an empty parameter list
+                listnode = _desc_parameterlist(self.argstart, self.argend)
+                signode += listnode
+        else:
+            _pseudo_parse_arglist(signode, self.argstart, self.arglist, self.argend)
+
+    def build_signode(self, signode):
+        if self.context:
+            context = self.context + self.contextsep
+            signode += addnodes.desc_addname(context, context)
+
+        signode += addnodes.desc_name(self.name, self.name)
+        self.build_parameters(signode)
+
+        if self.retann:
+            signode += addnodes.desc_returns(self.retann, self.retann)
+
+    def handle_signature(self, sig, signode):
+        context, name, argstart, arglist, argend, retann = self.parse_signature(sig)
+
+        self.context, self.contextsep = self.build_context(context)
+        self.module = self.options.get('module', self.env.temp_data.get('lua:module'))
+        self.clsname = self.options.get('class', self.env.temp_data.get('lua:class'))
+        self.objtype = self.build_objtype()
+        self.idxtype = self.options.get('idxtype') or self.options.get('objtype')
+        self.name = name
+        self.argstart = argstart
+        self.arglist = arglist
+        self.argend = argend
+        self.retann = retann
+
+        add_module = True
+        fullname = name
+
+        signode['module'] = self.module
+        signode['class'] = self.context
+        signode['fullname'] = fullname
+
+        prefix = "%s " % (self.objtype)
+        signode += addnodes.desc_annotation(prefix, prefix)
+
+        if self.clsname and self.needs_class():
+            clsname = '%s:' % (self.clsname)
+            signode += addnodes.desc_addname(clsname, clsname)
+        elif self.module and self.needs_module():
+            modname = '%s.' % (self.module)
+            signode += addnodes.desc_addname(modname, modname)
+
+        self.build_signode(signode)
+
+        anno = self.options.get('annotation')
+        if anno:
+            signode += addnodes.desc_annotation(' ' + anno, ' ' + anno)
+
+        return {'fullname': fullname, 'context': self.context,
+                'objtype': self.objtype, 'idxctx': self.options.get('idxctx') or ""}
+
+    def add_target_and_index(self, names, sig, signode):
+        idxctx = self.options.get('idxctx')
+
+        ids = ['lua-%s' % (self.__class__.typename)]
+        if idxctx: ids.append(idxctx)
+        if self.context: ids.append(self.context)
+        elif self.clsname and self.needs_class(): ids.append(self.clsname)
+        elif self.module and self.needs_module(): ids.append(self.module)
+        ids.append(names['fullname'])
+
+        fullid = '.'.join(ids)
+#        self.state_machine.reporter.warning(fullid)
+
+        fullname = []
+        if self.clsname and self.needs_class(): fullname.append(self.clsname)
+        elif self.module and self.needs_module(): fullname.append(self.module)
+        if self.context: fullname.append(self.context)
+        fullname.append(names['fullname'])
+        fullname = '.'.join(fullname)
+
+        # We need to escape the '<>' to avoid display issue in HTML
+        fullname = fullname.replace("<", "&lt;")
+        fullname = fullname.replace(">", "&gt;")
+
+        if fullid not in self.state.document.ids:
+            objtype = self.objtype
+            if objtype == "": objtype = "%s" % (self.__class__.typename)
+
+            signode['names'].append(fullname)
+            signode['ids'].append(fullid)
+            signode['first'] = (not self.names)
+            self.state.document.note_explicit_target(signode)
+            objects = self.env.domaindata['lua']['objects']
+            objects[fullname] = (self.env.docname, objtype, fullid)
+
+        indextext = self.get_index_text(names)
+        if indextext:
+            self.indexnode['entries'].append(('single', indextext,
+                                              fullid, ''))
+
+    def get_index_name(self, names):
+        return names['fullname']
+
+    def get_index_type(self):
+        return None
+
+    def get_index_text(self, names):
+        ret = []
+
+        idxtype = self.idxtype or self.get_index_type()
+        if idxtype: ret.append(idxtype)
+
+        if self.context: ret.append("in %s" % (self.context))
+        if self.module and self.needs_module(): ret.append("in module %s" % (self.module))
+
+        return "%s (%s)" % (self.get_index_name(names), ' '.join(ret))
+
+
+class LuaClass(LuaObject):
+    doc_field_types = [
+        Field('extend', label=l_('Extends'), has_arg=False,
+              names=('extend',)),
+    ]
+
+    typename = l_("object")
+
+    def get_index_type(self):
+        return "%s" % (self.__class__.typename)
+
+    def before_content(self):
+        LuaObject.before_content(self)
+        if self.names:
+            self.env.temp_data['lua:class'] = self.names[0]['fullname']
+
+    def after_content(self):
+        LuaObject.after_content(self)
+        if self.names:
+            self.env.temp_data['lua:class'] = None
+
+class LuaFunction(LuaObject):
+    typename = l_("function")
+
+    doc_field_types = [
+        TypedField('parameter', label=l_('Parameters'),
+                   names=('param', 'parameter', 'arg', 'argument'),
+                   typerolename='obj', typenames=('paramtype', 'type', 'ptype')),
+        TypedField('returnvalues', label=l_('Returns'),
+                  names=('return', 'ret'), typerolename='obj',
+                  typenames=('rtype', 'type')),
+        Field('returnvalue', label=l_('Returns'), has_arg=False,
+              names=('returns')),
+        Field('returntype', label=l_('Return type'), has_arg=False,
+              names=('returntype',)),
+    ]
+
+    def build_objtype(self):
+        return self.options.get('objtype') or ""
+
+    def needs_arglist(self):
+        return True
+
+    def get_index_name(self, names):
+        return '%s()' % (names['fullname'])
+
+class LuaMethod(LuaFunction):
+    option_spec = dict(
+        abstract=directives.flag,
+        **LuaObject.option_spec
+    )
+
+    def build_objtype(self):
+        if 'abstract' in self.options:
+            return "abstract %s" % (self.options.get('objtype') or "")
+        else:
+            return self.options.get('objtype') or ""
+
+
+    def build_context(self, context):
+        if context:
+            return "%s" % (context[:-1]), context[-1]
+        else:
+            return None, None
+
+class LuaData(LuaObject):
+    typename = l_("data")
+
+    option_spec = dict(
+        readonly=directives.flag,
+        **LuaObject.option_spec
+    )
+
+    doc_field_types = [
+        Field('type', label=l_('Type'), has_arg=False,
+              names=('type',)),
+    ]
+
+    def build_objtype(self):
+        if 'readonly' in self.options:
+            return "const %s" % (self.options.get('objtype') or "")
+        else:
+            return self.options.get('objtype') or ""
+
+class LuaAttribute(LuaData):
+    typename = l_("attribute")
+
+    lua_class_re = re.compile(
+        r'''([\w\./\-]+):([\w\./\-]+)?
+        ''', re.VERBOSE)
+
+    def build_context(self, context):
+        if context:
+            m = LuaAttribute.lua_class_re.match(context[:-1])
+            if m:
+                classname, subcontext = m.groups()
+                if subcontext:
+                    return "<%s>.%s" % (classname, subcontext), '.'
+                else:
+                    return "<%s>" % (classname), '.'
+            else:
+                return "<%s>" % (context[:-1]), '.'
+        else:
+            return None, None
+
+class LuaOperator(LuaObject):
+    typename = l_("operator")
+
+    doc_field_types = [
+        TypedField('parameter', label=l_('Parameters'),
+                   names=('param', 'parameter', 'arg', 'argument'),
+                   typerolename='obj', typenames=('paramtype', 'type', 'ptype')),
+        TypedField('returnvalues', label=l_('Returns'),
+                  names=('return', 'ret'), typerolename='obj',
+                  typenames=('rtype', 'type')),
+        Field('returnvalue', label=l_('Returns'), has_arg=False,
+              names=('returns')),
+        Field('returntype', label=l_('Return type'), has_arg=False,
+              names=('returntype',)),
+    ]
+
+    lua_signature_unary_re = re.compile(
+        r'''^ ([\+\-\*/<>=\#]+) \s*   # operator
+              ([\w\./\-]+)            # class name(s)
+              (?:\s* -> \s* (.*))?    # optional: return annotation
+              $                       # and nothing more
+              ''', re.VERBOSE)
+
+    lua_signature_binary_re = re.compile(
+        r'''^ ([\w\./\-]+)            # class name(s)
+              \s* ([\+\-\*/<>=]+) \s* # operator
+              ([\w\./\-]+)?           # class name(s)
+              (?:\s* -> \s* (.*))?    # optional: return annotation
+              $                       # and nothing more
+              ''', re.VERBOSE)
+
+    lua_signature_index_re = re.compile(
+        r'''^ ([\w\./\-]+)            # class name(s)
+              (\[)(.*)(\])            # arguments
+              (?:\s* -> \s* (.*))?    # optional: return annotation
+              $                       # and nothing more
+              ''', re.VERBOSE)
+
+    lua_signature_newindex_re = re.compile(
+        r'''^ ([\w\./\-]+)            # class name(s)
+              (\[)(.*)(\])            # arguments
+              \s* = \s* (.*)          # return annotation
+              $                       # and nothing more
+              ''', re.VERBOSE)
+
+    lua_signature_convert_re = re.compile(
+        r'''^ ([\w/\-/]+) \s*         # thing name
+              \( ([\w\./\-]+) \)      # class name(s)
+              (?:\s* -> \s* (.*))?    # optional: return annotation
+              $                       # and nothing more
+              ''', re.VERBOSE)
+
+    def parse_signature(self, sig):
+        m = LuaOperator.lua_signature_unary_re.match(sig)
+        if m:
+            name, context, retann = m.groups()
+            self.type = 'unary'
+            return context, name, None, None, None, retann
+
+        m = LuaOperator.lua_signature_binary_re.match(sig)
+        if m:
+            context, name, _, retann = m.groups()
+            self.type = 'binary'
+            return context, name, None, None, None, retann
+
+        m = LuaOperator.lua_signature_index_re.match(sig)
+        if m:
+            context, argstart, arglist, argend, retann = m.groups()
+            self.type = 'index'
+            return context, '[]', argstart, arglist, argend, retann
+
+        m = LuaOperator.lua_signature_newindex_re.match(sig)
+        if m:
+            context, argstart, arglist, argend, retann = m.groups()
+            self.type = 'newindex'
+            return context, '[]', argstart, arglist, argend, retann
+
+        m = LuaOperator.lua_signature_convert_re.match(sig)
+        if m:
+            name, context, retann = m.groups()
+            self.type = 'convert'
+            return context, name, None, None, None, retann
+
+        raise ValueError
+
+    def build_context(self, context):
+        if context:
+            return "<%s>" % (context), ''
+        else:
+            return None, None
+
+    def build_objtype(self):
+        return self.options.get('objtype') or ""
+
+    def build_signode(self, signode):
+        if self.type == 'unary':
+            signode += addnodes.desc_name(self.name, self.name)
+
+            context = self.context + self.contextsep
+            signode += addnodes.desc_addname(context, context)
+
+            if self.retann:
+                signode += addnodes.desc_returns(self.retann, self.retann)
+
+        elif self.type == 'binary':
+            context = self.context + self.contextsep
+            name = " %s " % (self.name)
+
+            signode += addnodes.desc_addname(context, context)
+            signode += addnodes.desc_name(name, name)
+            signode += addnodes.desc_addname(context, context)
+
+            if self.retann:
+                signode += addnodes.desc_returns(self.retann, self.retann)
+
+        elif self.type == 'index' or self.type == 'newindex':
+            context = self.context + self.contextsep
+            signode += addnodes.desc_addname(context, context)
+
+            self.build_parameters(signode)
+
+            if self.retann:
+                if self.type == 'newindex':
+                    retann = " = %s" % (self.retann)
+                    signode += addnodes.desc_type(retann, retann)
+                else:
+                    signode += addnodes.desc_returns(self.retann, self.retann)
+
+        elif self.type == 'convert':
+            context = self.context + self.contextsep
+
+            signode += addnodes.desc_name(self.name, self.name)
+
+            paramlist = _desc_parameterlist('(', ')')
+            paramlist.append(addnodes.desc_addname(context, context))
+            signode.append(paramlist)
+
+            if self.retann:
+                signode += addnodes.desc_returns(self.retann, self.retann)
+
+
+
+class LuaModule(Directive):
+    """
+    Directive to mark description of a new module.
+    """
+
+    has_content = False
+    required_arguments = 1
+    optional_arguments = 0
+    final_argument_whitespace = False
+    option_spec = {
+        'platform': lambda x: x,
+        'synopsis': lambda x: x,
+        'noindex': directives.flag,
+        'deprecated': directives.flag,
+    }
+
+    def run(self):
+        env = self.state.document.settings.env
+        modname = self.arguments[0].strip()
+        noindex = 'noindex' in self.options
+        env.temp_data['lua:module'] = modname
+        ret = []
+        if not noindex:
+            env.domaindata['lua']['modules'][modname] = \
+                (env.docname, self.options.get('synopsis', ''),
+                 self.options.get('platform', ''), 'deprecated' in self.options)
+
+            ids = "lua-module.%s" % (modname)
+
+            # make a duplicate entry in 'objects' to facilitate searching for
+            # the module in LuaDomain.find_obj()
+            env.domaindata['lua']['objects'][modname] = (env.docname, 'module', ids)
+            targetnode = nodes.target('', '', ids=[ids],
+                                      ismod=True)
+            self.state.document.note_explicit_target(targetnode)
+            # the platform and synopsis aren't printed; in fact, they are only
+            # used in the modindex currently
+            ret.append(targetnode)
+            indextext = _('%s (module)') % modname
+            inode = addnodes.index(entries=[('single', indextext,
+                                             ids, '')])
+            ret.append(inode)
+        return ret
+
+class LuaCurrentModule(Directive):
+    """
+    This directive is just to tell Sphinx that we're documenting
+    stuff in module foo, but links to module foo won't lead here.
+    """
+    has_content = False
+    required_arguments = 1
+    optional_arguments = 0
+    final_argument_whitespace = False
+    option_spec = {}
+
+    def run(self):
+        env = self.state.document.settings.env
+        modname = self.arguments[0].strip()
+        if modname == 'None':
+            env.temp_data['lua:module'] = None
+        else:
+            env.temp_data['lua:module'] = modname
+        return []
+
+
+class LuaXRefRole(XRefRole):
+    def process_link(self, env, refnode, has_explicit_title, title, target):
+        refnode['lua:module'] = env.temp_data.get('lua:module')
+        refnode['lua:class'] = env.temp_data.get('lua:class')
+        if not has_explicit_title:
+            title = title.lstrip('.')   # only has a meaning for the target
+            target = target.lstrip('~') # only has a meaning for the title
+            # if the first character is a tilde, don't display the module/class
+            # parts of the contents
+            if title[0:1] == '~':
+                title = title[1:]
+                dot = max(title.rfind('.'), title.rfind(':'))
+                if dot != -1:
+                    title = title[dot+1:]
+        # if the first character is a dot, search more specific namespaces first
+        # else search builtins first
+        if target[0:1] == '.':
+            target = target[1:]
+            refnode['refspecific'] = True
+        return title, target
+
+class LuaModuleIndex(Index):
+    """
+    Index subclass to provide the Lua module index.
+    """
+    name = 'modindex'
+    localname = l_('Lua Module Index')
+    shortname = l_('modules')
+
+    def generate(self, docnames=None):
+        content = {}
+        # list of prefixes to ignore
+        ignores = self.domain.env.config['modindex_common_prefix']
+        ignores = sorted(ignores, key=len, reverse=True)
+        # list of all modules, sorted by module name
+        modules = sorted(self.domain.data['modules'].items(),
+                         key=lambda x: x[0].lower())
+        # sort out collapsable modules
+        prev_modname = ''
+        num_toplevels = 0
+        for modname, (docname, synopsis, platforms, deprecated) in modules:
+            if docnames and docname not in docnames:
+                continue
+
+            for ignore in ignores:
+                if modname.startswith(ignore):
+                    modname = modname[len(ignore):]
+                    stripped = ignore
+                    break
+            else:
+                stripped = ''
+
+            # we stripped the whole module name?
+            if not modname:
+                modname, stripped = stripped, ''
+
+            entries = content.setdefault(modname[0].lower(), [])
+
+            package = modname.split('.')[0]
+            if package != modname:
+                # it's a submodule
+                if prev_modname == package:
+                    # first submodule - make parent a group head
+                    entries[-1][1] = 1
+                elif not prev_modname.startswith(package):
+                    # submodule without parent in list, add dummy entry
+                    entries.append([stripped + package, 1, '', '', '', '', ''])
+                subtype = 2
+            else:
+                num_toplevels += 1
+                subtype = 0
+
+            qualifier = deprecated and _('Deprecated') or ''
+            entries.append([stripped + modname, subtype, docname,
+                            'module-' + stripped + modname, platforms,
+                            qualifier, synopsis])
+            prev_modname = modname
+
+        # apply heuristics when to collapse modindex at page load:
+        # only collapse if number of toplevel modules is larger than
+        # number of submodules
+        collapse = len(modules) - num_toplevels < num_toplevels
+
+        # sort by first letter
+        content = sorted(content.items())
+
+        return content, collapse
+
+
+# Lua domain
+
+class LuaDomain(Domain):
+    """Lua language domain."""
+    name = 'lua'
+    label = 'Lua'
+    object_types = {
+        'class':         ObjType(l_('class'),      'class',  'obj'),
+        'attribute':     ObjType(l_('attribute'),  'data',   'obj'),
+        'function':      ObjType(l_('function'),   'func',   'obj'),
+        'method':        ObjType(l_('method'),     'func',   'obj'),
+        'operator':      ObjType(l_('operator'),   'func',   'obj'),
+        'module':        ObjType(l_('module'),     'mod',    'obj'),
+        'data':          ObjType(l_('data'),       'data',   'obj'),
+    }
+
+    directives = {
+        'class':           LuaClass,
+        'function':        LuaFunction,
+        'method':          LuaMethod,
+        'operator':        LuaOperator,
+        'data':            LuaData,
+        'attribute':       LuaAttribute,
+        'module':          LuaModule,
+        'currentmodule':   LuaCurrentModule,
+    }
+    roles = {
+        'data':  LuaXRefRole(),
+        'func':  LuaXRefRole(fix_parens=True),
+        'class': LuaXRefRole(),
+        'mod':   LuaXRefRole(),
+    }
+    initial_data = {
+        'objects': {},     # fullname -> docname, objtype
+        'modules': {},     # modname -> docname, synopsis, platform, deprecated
+        'inheritance': {}, # class -> [ derived ]
+    }
+    indices = [
+        LuaModuleIndex,
+    ]
+
+    def clear_doc(self, docname):
+        for fullname, (fn, _, _) in list(self.data['objects'].items()):
+            if fn == docname:
+                del self.data['objects'][fullname]
+        for modname, (fn, _, _, _) in list(self.data['modules'].items()):
+            if fn == docname:
+                del self.data['modules'][modname]
+
+    def find_obj(self, env, modname, classname, name, type, searchmode=0):
+        # skip parens
+        if name[-2:] == '()':
+            name = name[:-2]
+
+        if not name:
+            return []
+
+        name = name.replace("<", "&lt;")
+        name = name.replace(">", "&gt;")
+
+        objects = self.data['objects']
+        matches = []
+
+        newname = None
+        if searchmode == 1:
+            objtypes = self.objtypes_for_role(type)
+            if modname and classname:
+                fullname = modname + '.' + classname + '.' + name
+                if fullname in objects and objects[fullname][1] in objtypes:
+                    newname = fullname
+            if not newname:
+                if modname and modname + '.' + name in objects and \
+                   objects[modname + '.' + name][1] in objtypes:
+                    newname = modname + '.' + name
+                elif name in objects and objects[name][1] in objtypes:
+                    newname = name
+                else:
+                    # "fuzzy" searching mode
+                    searchname = '.' + name
+                    matches = [(oname, objects[oname]) for oname in objects
+                               if oname.endswith(searchname)
+                               and objects[oname][1] in objtypes]
+        else:
+            # NOTE: searching for exact match, object type is not considered
+            if name in objects:
+                newname = name
+            elif type == 'mod':
+                # only exact matches allowed for modules
+                return []
+            elif classname and classname + '.' + name in objects:
+                newname = classname + '.' + name
+            elif modname and modname + '.' + name in objects:
+                newname = modname + '.' + name
+            elif modname and classname and \
+                     modname + '.' + classname + '.' + name in objects:
+                newname = modname + '.' + classname + '.' + name
+            # special case: builtin exceptions have module "exceptions" set
+            elif type == 'exc' and '.' not in name and \
+                 'exceptions.' + name in objects:
+                newname = 'exceptions.' + name
+            # special case: object methods
+            elif type in ('func', 'meth') and '.' not in name and \
+                 'object.' + name in objects:
+                newname = 'object.' + name
+        if newname is not None:
+            matches.append((newname, objects[newname]))
+        return matches
+
+    def resolve_xref(self, env, fromdocname, builder,
+                     type, target, node, contnode):
+
+        modname = node.get('lua:module')
+        clsname = node.get('lua:class')
+        searchmode = node.hasattr('refspecific') and 1 or 0
+        matches = self.find_obj(env, modname, clsname, target,
+                                type, searchmode)
+
+        if not matches:
+            # If type is 'obj', we don't want to display any WARNING.
+            # Otherwise, we have too many due to unknown type used
+            # as parameter type in function description (number, string...).
+            if type != "obj":
+                env.warn_node(
+                    'no target found for cross-reference '
+                    '%r' % (target), node)
+            return None
+        elif len(matches) > 1:
+            env.warn_node(
+                'more than one target found for cross-reference '
+                '%r: %s' % (target, ', '.join(match[0] for match in matches)),
+                node)
+        name, obj = matches[0]
+
+        return make_refnode(builder, fromdocname, obj[0], obj[2],
+                            contnode, name)
+
+    def get_objects(self):
+        for modname, info in self.data['modules'].items():
+            yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
+        for refname, (docname, type, id) in self.data['objects'].items():
+            yield (refname, refname, type, docname, id, 1)
+
+
+def setup(app):
+    app.add_domain(LuaDomain)
diff --git a/doc/sphinx/reference/box/box_index.rst b/doc/sphinx/reference/box/box_index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..8efeecd0c6a313358e282171201220f981838b77
--- /dev/null
+++ b/doc/sphinx/reference/box/box_index.rst
@@ -0,0 +1,638 @@
+.. include:: ../../directives.rst
+.. highlight:: lua
+
+-------------------------------------------------------------------------------
+                            Package `box.index`
+-------------------------------------------------------------------------------
+
+The ``box.index`` package provides read-only access for index definitions and
+index keys. Indexes are contained in ``box.space.space-name.index`` array within
+each space object. They provide an API for ordered iteration over tuples. This
+API is a direct binding to corresponding methods of index objects of type
+``box.index`` in the storage engine.
+
+.. module:: box.index
+
+.. data:: box.space.space-name.index.index-name.unique
+
+    true if the index is unique.
+
+    :rtype: boolean
+
+.. data:: box.space.space-name.index.index-name.type
+
+    Index type, 'TREE' or 'HASH' or 'BITSET' or 'RTREE'.
+
+    :rtype: string
+
+.. data:: box.space.space-name.index.index-name.parts
+
+    An array describing index key fields.
+
+    :rtype: table
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester.index.primary
+        ---
+        unique: true
+        parts:
+          0:
+            type: NUM
+            fieldno: 1
+        id: 0
+        space_id: 513
+        name: primary
+        type: TREE
+        ...
+
+.. function:: box.space.space-name.index[.index-name]:pairs(bitset-value | field-value..., iterator-type)
+
+    This method provides iteration support within an index. Parameter type is
+    used to identify the semantics of iteration. Different index types support
+    different iterators. The remaining arguments of the function are varying
+    and depend on the iteration type. For example, a TREE index maintains a
+    strict order of keys and can return all tuples in ascending or descending
+    order, starting from the specified key. Other index types, however, do not
+    support ordering.
+
+    To understand consistency of tuples returned by an iterator, it's essential
+    to know the principles of the Tarantool transaction processing subsystem. An
+    iterator in Tarantool does not own a consistent read view. Instead, each
+    procedure is granted exclusive access to all tuples and spaces until it
+    encounters a "context switch": by causing a write to disk, network, or by an
+    explicit call to :func:`fiber.yield`. When the execution flow returns
+    to the yielded procedure, the data set could have changed significantly.
+    Iteration, resumed after a yield point, does not preserve the read view,
+    but continues with the new content of the database.
+
+    :param type: iteration strategy as defined in tables below
+    :return: this method returns an iterator closure, i.e. a function which can
+             be used to get the next value on each invocation
+    :rtype:  function, tuple
+
+    :except: Selected iteration type is not supported in the subject index type,
+             or supplied parameters do not match iteration type.
+
+    Complexity Factors: Index size, Index type, Number of tuples accessed.
+
+    .. container:: table
+
+        **TREE iterator types**
+
+        +---------------+-----------+---------------------------------------------+
+        | Type          | Arguments | Description                                 |
+        +===============+===========+=============================================+
+        | box.index.ALL | none      | Iterate over all tuples in an index. Tuples |
+        | or 'ALL'      |           | are returned in ascending order of the key. |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.EQ  | field     | Equality iterator: iterate over all tuples  |
+        | or 'EQ'       | values    | where field values = key values. Parts of a |
+        |               |           | multi-part key need to be separated by      |
+        |               |           | commas.                                     |
+        |               |           |                                             |
+        |               |           | If the number of field values is less than  |
+        |               |           | the number of parts of a multi-part key,    |
+        |               |           | the missing field values are considered to  |
+        |               |           | be matching.                                |
+        |               |           |                                             |
+        |               |           | If there are multiple matches, then tuples  |
+        |               |           | are returned in ascending order by key.     |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.GT  | field     | Keys match if key values are greater than   |
+        | or 'GT'       | values    | field values. If the number of field values |
+        |               |           | is less than the number of parts of a       |
+        |               |           | multi-part key, the missing field values    |
+        |               |           | are considered to be matching. If the field |
+        |               |           | value is ``nil``, iteration starts from the |
+        |               |           | smallest key in the index. Tuples are       |
+        |               |           | returned in ascending order by key.         |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.REQ | field     | Reverse equality iterator. Matching is      |
+        | or 'REQ'      | values    | determined in the same way as for           |
+        |               |           | ``box.index.EQ``, but, if there are multiple|
+        |               |           | matches, then tuples are returned in        |
+        |               |           | descending order by key,                    |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.GE  | field     | Keys match if key values are greater than   |
+        | or 'GE'       | values    | or equal to field values. Tuples are        |
+        |               |           | returned in ascending order by key. If the  |
+        |               |           | field value is ``nil``, iteration starts    |
+        |               |           | from the first key in the index.            |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.LT  | field     | Keys match if key values are less than      |
+        | or 'LT'       | values    | field values. Tuples are returned in        |
+        |               |           | descending order by key. If the field value |
+        |               |           | is ``nil``, iteration starts from the last  |
+        |               |           | key in the index.                           |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.LE  | field     | Keys match if key values are less than or   |
+        | or 'LE'       | values    | equal to field values. Tuples are returned  |
+        |               |           | in descending order by key. If the field    |
+        |               |           | value is ``nil``, iteration starts from     |
+        |               |           | the last key in the index.                  |
+        +---------------+-----------+---------------------------------------------+
+
+        **HASH iterator types**
+
+        +---------------+-----------+---------------------------------------------+
+        | Type          | Arguments | Description                                 |
+        +===============+===========+=============================================+
+        | box.index.ALL | none      | Iterate over all tuples in an index. Tuples |
+        | or 'ALL'      |           | are returned in ascending order of the key. |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.EQ  | field     | Equality iterator: iterate over all tuples  |
+        | or 'EQ'       | values    | matching the key. Parts of a multi-part     |
+        |               |           | key need to be separated by commas.         |
+        |               |           |                                             |
+        |               |           | A HASH index only supports exact match:     |
+        |               |           | all parts of a key participating in the     |
+        |               |           | index must be provided.                     |
+        |               |           |                                             |
+        |               |           | HASH indexes are always unique.             |
+        +---------------+-----------+---------------------------------------------+
+        | box.index.GT  | field     | Keys match if hashed key values are greater |
+        | or 'GT'       | values    | than hashed field values. If the number of  |
+        |               |           | field values is less than the number of     |
+        |               |           | parts of a multi-part key, the result is an |
+        |               |           | error. Tuples are returned in ascending     |
+        |               |           | order by hashed key, so the order will      |
+        |               |           | appear to be random. Provided that the      |
+        |               |           | space is not being updated, the 'GT'        |
+        |               |           | iterator can be used to retrieve all        |
+        |               |           | tuples piece by piece, by supplying the     |
+        |               |           | last returned value from the previous       |
+        |               |           | range as the start field value for an       |
+        |               |           | iterator over the next range.               |
+        +---------------+-----------+---------------------------------------------+
+
+        **BITSET iterator types**
+
+        +----------------------------+-----------+---------------------------------------------+
+        | Type                       | Arguments | Description                                 |
+        +============================+===========+=============================================+
+        | box.index.ALL              | none      | Iterate over all tuples in an index. Tuples |
+        | or 'ALL'                   |           | are returned in ascending order of the      |
+        |                            |           | key's bitset, and so will appear to be      |
+        |                            |           | unordered.                                  |
+        +----------------------------+-----------+---------------------------------------------+
+        | box.index.EQ               | field     | Equality iterator: iterate over all tuples  |
+        | or 'EQ'                    | values    | matching the field values. If there are     |
+        |                            |           | multiple field values, they need to be      |
+        |                            |           | separated by commas.                        |
+        |                            |           |                                             |
+        |                            |           | BITSET indexes are always unique.           |
+        +----------------------------+-----------+---------------------------------------------+
+        | box.index.BITS_ALL_SET     | field     | Keys match if all of the bits specified in  |
+        |                            | values    | 'bit mask' are set.                         |
+        +----------------------------+-----------+---------------------------------------------+
+        | box.index.BITS_ANY_SET     | field     | Keys match if any of the bits specified in  |
+        |                            | values    | 'bit mask' is set.                          |
+        +----------------------------+-----------+---------------------------------------------+
+        | box.index.BITS_ALL_NOT_SET | field     | Keys match if none of the bits specified in |
+        |                            | values    | 'bit mask' is set.                          |
+        +----------------------------+-----------+---------------------------------------------+
+
+        .. _rtree-iterator:
+
+        **RTREE iterator types**
+
+        +--------------------+-----------+---------------------------------------------+
+        | Type               | Arguments | Description                                 |
+        +====================+===========+=============================================+
+        | box.index.ALL      | none      | All keys match. Tuples are returned in      |
+        | or 'ALL'           |           | ascending order of the primary key.         |
+        +--------------------+-----------+---------------------------------------------+
+        | box.index.EQ       | field     | Keys match if the rectangle defined by the  |
+        | or 'EQ'            | values    | field values is the same as the rectangle   |
+        |                    |           | defined by the key -- where "key" means     |
+        |                    |           | "the key in the RTREE index" and            |
+        |                    |           | "rectangle" means "rectangle as explained   |
+        |                    |           | in section RTREE_.                          |
+        +--------------------+-----------+---------------------------------------------+
+        | box.index.GT       | field     | Keys match if all points of the rectangle   |
+        | or 'GT'            | values    | defined by the field values are within the  |
+        |                    |           | rectangle defined by the key.               |
+        +--------------------+-----------+---------------------------------------------+
+        | box.index.GE       | field     | Keys match if all points of the rectangle   |
+        | or 'GE'            | values    | defined by the field values are within, or  |
+        |                    |           | at the side of, the rectangle defined by    |
+        |                    |           | the key.                                    |
+        +--------------------+-----------+---------------------------------------------+
+        | box.index.LT       | field     | Keys match if all points of the rectangle   |
+        | or 'LT'            | values    | defined by the key are within the rectangle |
+        |                    |           | defined by the field values.                |
+        +--------------------+-----------+---------------------------------------------+
+        | box.index.LE       | field     | Keys match if all points of the rectangle   |
+        | or 'LE'            | values    | defined by the key are within, or at the    |
+        |                    |           | side of, the rectangle defined by the field |
+        |                    |           | values.                                     |
+        +--------------------+-----------+---------------------------------------------+
+        | box.index.OVERLAPS | field     | Keys match if all points of the rectangle   |
+        | or 'OVERLAPS'      | values    | defined by the key are within, or at the    |
+        |                    |           | side of, the rectangle defined by the field |
+        |                    |           | values.                                     |
+        +--------------------+-----------+---------------------------------------------+
+        | box.index.NEIGHBOR | field     | Keys match if all points of the rectangle   |
+        | or 'NEIGHBOR'      | values    | defined by the key are within, or at the    |
+        |                    |           | side of, the rectangle defined by the field |
+        |                    |           | values.                                     |
+        +--------------------+-----------+---------------------------------------------+
+
+    .. code-block:: lua
+
+        tarantool> s = box.schema.space.create('space17')
+        ---
+        ...
+        tarantool> s:create_index('primary', {parts = {1, 'STR', 2, 'STR'}})
+        ---
+        ...
+        tarantool> s:insert{'C', 'C'}
+        ---
+        - ['C', 'C']
+        ...
+        tarantool> s:insert{'B', 'A'}
+        ---
+        - ['B', 'A']
+        ...
+        tarantool> s:insert{'C', '!'}
+        ---
+        - ['C', '!']
+        ...
+        tarantool> s:insert{'A', 'C'}
+        ---
+        - ['A', 'C']
+        ...
+        tarantool> console = require('console'); console.delimiter('!')
+        ---
+        ...
+        tarantool> function example()
+                 >   for _, tuple in
+                 >   s.index.primary:pairs(nil, {iterator = box.index.ALL}) do
+                 >     print(tuple)
+                 >   end
+                 > end!
+        ---
+        ...
+        tarantool> console.delimiter('')!
+        ---
+        ...
+        tarantool> example()
+        ['A', 'C']
+        ['B', 'A']
+        ['C', '!']
+        ['C', 'C']
+        ---
+        ...
+        tarantool> s:drop()
+        ---
+        ...
+
+.. function:: box.space.space-name[.index.index-name]:select({[field-value [, field-value ...]]}, {[option [, option ...]]})
+
+    This is is an alternative to box.space...select() which goes via a
+    particular index and can make use of additional parameters that specify the
+    iterator type, and the limit (that is, the maximum number of tuples to
+    return) and the offset (that is, which tuple to start with in the list).
+
+    :param lua-value field-value(s): values to be matched against the index key.
+    :param lua-value option(s): any or all of iterator=iterator-type
+                                limit=maximum-number-of-tuples,
+                                offset=start-tuple-number.
+
+    :return: the tuple or tuples that match the field values.
+    :rtype:  tuple set as a Lua table
+
+    .. code-block:: lua
+
+        -- Create a space named tester.
+        -- Create a unique index 'primary', which won't be needed for this example.
+        -- Create a non-unique index 'secondary' with an index on the second field.
+        -- Insert three tuples, values in field[2] equal to 'X', 'Y', and 'Z'.
+        -- Select all tuples where the secondary index keys are greater than 'X'.
+        box.schema.space.create('tester')
+        box.space.tester:create_index('primary', {parts = {1, 'NUM' }})
+        box.space.tester:create_index('secondary', {type = 'tree', unique = false, parts = {2, 'STR'}})
+        box.space.tester:insert{1,'X','Row with field[2]=X'}
+        box.space.tester:insert{2,'Y','Row with field[2]=Y'}
+        box.space.tester:insert{3,'Z','Row with field[2]=Z'}
+        box.space.tester.index.secondary:select({'X'}, {iterator = 'GT', limit = 1000})
+
+    The result will be a table of tuple and will look like this:
+
+    .. code-block:: yaml
+
+        ---
+        - - [2, 'Y', 'Row with field[2]=Y']
+          - [3, 'Z', 'Row with field[2]=Z']
+        ...
+
+    .. NOTE::
+
+        [.index.index-name] is optional. If it is omitted, then the assumed
+        index is the first (primary-key) index. Therefore, for the example
+        above, ``box.space.tester:select({1}, {iterator = 'GT'})`` would have
+        returned the same two rows, via the 'primary' index.
+
+    .. NOTE::
+
+        ``iterator = iterator type`` is optional. If it is omitted, then
+        ``iterator = 'EQ'`` is assumed.
+
+    .. NOTE::
+
+        ``field-value [, field-value ...]`` is optional. If it is omitted,
+        then every key in the index is considered to be a match, regardless of
+        iterator type. Therefore, for the example above,
+        ``box.space.tester:select{}`` will select every tuple in the tester
+        space via the first (primary-key) index.
+
+    .. NOTE::
+
+        ``box.space.space-name.index.index-name:select(...)[1]``. can be
+        replaced by ``box.space.space-name.index.index-name:get(...)``.
+        That is, get can be used as a convenient shorthand to get the first
+        tuple in the tuple set that would be returned by select. However,
+        if there is more than one tuple in the tuple set, then get returns
+        an error.
+
+.. function:: box.space.space-name.index.index-name:min([key-value])
+
+    Find the minimum value in the specified index.
+
+    :return: the tuple for the first key in the index. If optional
+             ``key-value`` is supplied, returns the first key which
+             is greater than or equal to ``key-value``.
+    :rtype:  tuple
+    :except: index is not of type 'TREE'.
+
+    Complexity Factors: Index size, Index type.
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester.index.primary:min()
+        ---
+        - ['Alpha!', 55, 'This is the first tuple!']
+        ...
+
+.. function:: box.space.space-name.index.index-name:max([key-value])
+
+    Find the maximum value in the specified index.
+
+    :return: the tuple for the last key in the index. If optional ``key-value``
+             is supplied, returns the last key which is less than or equal to
+             ``key-value``.
+    :rtype:  tuple
+    :except: index is not of type 'TREE'.
+
+    Complexity Factors: Index size, Index type.
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester.index.primary:max()
+        ---
+        - ['Gamma!', 55, 'This is the third tuple!']
+        ...
+
+
+.. function:: box.space.space-name.index.index-name:random(random-value)
+
+    Find a random value in the specified index. This method is useful when it's
+    important to get insight into data distribution in an index without having
+    to iterate over the entire data set.
+
+    :param integer random-value: an arbitrary non-negative integer.
+    :return: the tuple for the random key in the index.
+    :rtype:  tuple
+
+    Complexity Factors: Index size, Index type.
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester.index.secondary:random(1)
+        ---
+        - ['Beta!', 66, 'This is the second tuple!']
+        ...
+
+.. function:: box.space.space-name.index.index-name:count(key-value, options)
+
+    Iterate over an index, counting the number of
+    tuples which equal the provided search criteria.
+
+    :param lua-value key-value: the value which must match the key(s) in the
+                                specified index. The type may be a list of
+                                field-values, or a tuple containing only
+                                the field-values.
+
+    :return: the number of matching index keys. The ``index`` function
+             is only applicable for the memtx storage engine.
+    :rtype:  number
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester.index.primary:count(999)
+        ---
+        - 0
+        ...
+        tarantool> box.space.tester.index.primary:count('Alpha!', { iterator = 'LE' })
+        ---
+        - 1
+        ...
+
+.. function:: box.space.space-name.index.index-name:alter{options}
+
+    Alter an index.
+
+    :param table options: options list for create_index().
+    :return: nil
+
+    :except: If index-name doesn't exist.
+    :except: The first index cannot be changed to {unique = false}.
+    :except: The alter function is only applicable for the memtx storage engine.
+
+    .. code-block:: lua
+
+        tarantool> box.space.space55.index.primary:alter({type = 'HASH'})
+        ---
+        ...
+
+.. function:: space-name.index.index-name:drop()
+
+    Drop an index. Dropping a primary-key index has
+    a side effect: all tuples are deleted.
+
+    :return: nil.
+    :except: If index-name doesn't exist.
+
+    .. code-block:: lua
+
+        tarantool> box.space.space55.index.primary:drop()
+        ---
+        ...
+
+.. function:: space-name.index.index-name:rename(index-name)
+
+    Rename an index.
+
+    :param string index-name: new name for index.
+    :return: nil
+    :except: If index-name doesn't exist.
+
+    .. code-block:: lua
+
+        tarantool> box.space.space55.index.primary:rename('secondary')
+        ---
+        ...
+
+    Complexity Factors: Index size, Index type, Number of tuples accessed.
+
+
+===========================================================
+                         Example
+===========================================================
+
+This example will work with the sandbox configuration described in the preface.
+That is, there is a space named tester with a numeric primary key. The example
+function will:
+
+* select a tuple whose key value is 1000;
+* return an error if the tuple already exists and already has 3 fields;
+* Insert or replace the tuple with:
+    * field[1] = 1000
+    * field[2] = a uuid
+    * field[3] = number of seconds since 1970-01-01;
+* Get field[3] from what was replaced;
+* Format the value from field[3] as yyyy-mm-dd hh:mm:ss.ffff;
+* Return the formatted value.
+
+The function uses Tarantool box functions box.space...select,
+box.space...replace, fiber.time, uuid.str(). The function uses
+Lua functions os.date() and string.sub().
+
+.. code-block:: lua
+
+    console = require('console'); console.delimiter('!')
+    function example()
+      local a, b, c, table_of_selected_tuples, replaced_tuple, time_field
+      local formatted_time_field
+      local fiber = require('fiber')
+      table_of_selected_tuples = box.space.tester:select{1000}
+      if table_of_selected_tuples ~= nil then
+        if table_of_selected_tuples[1] ~= nil then
+          if #table_of_selected_tuples[1] == 3 then
+            box.error({code=1, reason='This tuple already has 3 fields'})
+          end
+        end
+      end
+      replaced_tuple = box.space.tester:replace
+        {1000,  require('uuid').str(), tostring(fiber.time())}
+      time_field = tonumber(replaced_tuple[3])
+      formatted_time_field = os.date("%Y-%m-%d %H:%M:%S", time_field)
+      c = time_field % 1
+      d = string.sub(c, 3, 6)
+      formatted_time_field = formatted_time_field .. '.' .. d
+      return formatted_time_field
+    end!
+    console.delimiter('')!
+
+... And here is what happens when one invokes the function:
+
+.. code-block:: lua
+
+    tarantool> box.space.tester:delete(1000)
+    ---
+    - 1000: {'264ee2da03634f24972be76c43808254', '1391037015.6809'}
+    ...
+    tarantool> example(1000)
+    ---
+    - 2014-01-29 16:11:51.1582
+    ...
+    tarantool> example(1000)
+    ---
+    - error: 'This tuple already has 3 fields'
+    ...
+
+.. _RTREE:
+
+===========================================================
+             Package `box.index` for RTREE
+===========================================================
+
+The :mod:`box.index` package may be used for spatial searches if the index type
+is RTREE. There are operations for searching ``rectangles``. Rectangles are
+described according to their X-axis (horizontal axis) and Y-axis (vertical axis)
+coordinates in a grid of arbitrary size. Here is a picture of four rectangles on
+a grid with 11 horizontal points and 11 vertical points:
+
+::
+
+               X AXIS
+               1   2   3   4   5   6   7   8   9   10  11
+            1
+            2  #-------+                                           <-Rectangle#1
+    Y AXIS  3  |       |
+            4  +-------#
+            5          #-----------------------+                   <-Rectangle#2
+            6          |                       |
+            7          |   #---+               |                   <-Rectangle#3
+            8          |   |   |               |
+            9          |   +---#               |
+            10         +-----------------------#
+            11                                     #               <-Rectangle#4
+
+The rectangles are defined according to this scheme: {X-axis coordinate of top
+left, Y-axis coordinate of top left, X-axis coordinate of bottom right, Y-axis
+coordinate of bottom right} -- or more succinctly: {x1,y1,x2,y2}. So in the
+picture ... Rectangle#1 starts at position 1 on the X axis and position 2 on
+the Y axis, and ends at position 3 on the X axis and position 4 on the Y axis,
+so its coordinates are {1,2,3,4}. Rectangle#2's coordinates are {3,5,9,10}.
+Rectangle#3's coordinates are {4,7,5,9}. And finally Rectangle#4's coordinates
+are {10,11,10,11}. Rectangle#4 is actually a "point" since it has zero width
+and zero height, so it could have been described with only two digits: {10,11}.
+
+Some relationships between the rectangles are: "Rectangle#1's nearest neighbor
+is Rectangle#2", and "Rectangle#3 is entirely inside Rectangle#2".
+
+Now let us create a space and add an RTREE index.
+
+.. code-block:: lua
+
+    s = box.schema.create_space('rectangles')
+    i = s:create_index('primary',{type='HASH',parts={1,'NUM'}})
+    r = s:create_index('spatial',{type='RTREE',unique=false,parts={2,'ARRAY'}})
+
+Field#1 doesn't matter, we just make it because we need a primary-key index.
+(RTREE indexes cannot be unique and therefore cannot be primary-key indexes.)
+The second field must be an "array", which means its values must represent
+{x,y} points or {x1,y1,x2,y2} rectangles. Now let us populate the table by
+inserting two tuples, containing the coordinates of Rectangle#2 and Rectangle#4.
+
+.. code-block:: lua
+
+    s:insert{1, {3,5,9,10}}
+    s:insert{2, {10,11}}
+
+And now, following the description of `RTREE iterator types`_, we can search the
+rectangles with these requests:
+
+.. _RTREE iterator types: rtree-iterator_
+
+.. code-block:: lua
+
+    r:select({10,11,10,11},{iterator='EQ'})   -- Request#1 (returns 1 tuple)
+    r:select({4,7,5,9},{iterator='GT'})       -- Request#2 (returns 1 tuple)
+    r:select({1,2,3,4},{iterator='NEIGHBOR'}) -- Request#3 (returns 2 tuples)
+
+Request#1 returns 1 tuple because the point {10,11} is the same as the rectangle
+{10,11,10,11} ("Rectangle#4" in the picture). Request#2 returns 1 tuple because
+the rectangle {4,7,5,9}, which was "Rectangle#3" in the picture, is entirely
+within{3,5,9,10} which was Rectangle#2. Request#3 returns 2 tuples, because the
+NEIGHBOR iterator always returns all tuples, and the first returned tuple will
+be {3,5,9,10} ("Rectangle#2" in the picture) because it is the closest neighbor
+of {1,2,3,4} ("Rectangle#1" in the picture).
+
+More examples of spatial searching are online in the file `R tree index quick
+start and usage`_.
+
+.. _R tree index quick start and usage: https://github.com/tarantool/tarantool/wiki/R-tree-index-quick-start-and-usage
+
diff --git a/doc/sphinx/reference/box/box_schema.rst b/doc/sphinx/reference/box/box_schema.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d603aefc25886fc20809ef52c18146d72d58fbeb
--- /dev/null
+++ b/doc/sphinx/reference/box/box_schema.rst
@@ -0,0 +1,64 @@
+.. include:: ../../directives.rst
+.. highlight:: lua
+
+-------------------------------------------------------------------------------
+                             Package `box.schema`
+-------------------------------------------------------------------------------
+
+.. module:: box.schema
+
+The ``box.schema`` package has one data-definition function: ``space.create()``.
+
+.. function:: space.create(space-name [, {options} ])
+
+    Create a space.
+
+    :param string space-name: name of space, which should not be a number and should not contain special characters
+    :param table options:
+
+    :return: space object
+    :rtype: userdata
+
+    .. container:: table
+
+        **Options for box.schema.space.create**
+
+        +---------------+--------------------+---------+---------------------+
+        | Name          | Effect             | Type    | Default             |
+        +===============+====================+=========+=====================+
+        | temporary     | space is temporary | boolean | false               |
+        +---------------+--------------------+---------+---------------------+
+        | id            | unique identifier  | number  | last space's id, +1 |
+        +---------------+--------------------+---------+---------------------+
+        | field_count   | fixed field count  | number  | 0 i.e. not fixed    |
+        +---------------+--------------------+---------+---------------------+
+        | if_not_exists | no error if        | boolean | false               |
+        |               | duplicate name     |         |                     |
+        +---------------+--------------------+---------+---------------------+
+        | engine        | storage package    | string  | 'memtx'             |
+        +---------------+--------------------+---------+---------------------+
+        | user          | user name          | string  | current user's name |
+        +---------------+--------------------+---------+---------------------+
+
+=================================================
+                    Example
+=================================================
+
+.. code-block:: lua
+
+    tarantool> s = box.schema.space.create('space55')
+    ---
+    ...
+    tarantool> s = box.schema.space.create('space55', {id = 555, temporary = false})
+    ---
+    - error: Space 'space55' already exists
+    ...
+    tarantool> s = box.schema.space.create('space55', {if_not_exists = true})
+    ---
+    ...
+
+After a space is created, usually the next step is to `create an index`_ for it,
+and then it is available for insert, select, and all the other :mod:`box.space`
+functions.
+
+.. _create an index: :func:`box.space.space-name:create_index`
diff --git a/doc/sphinx/reference/box/box_space.rst b/doc/sphinx/reference/box/box_space.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d32c28da35fbc6db76857877de7c2ebfc4144f97
--- /dev/null
+++ b/doc/sphinx/reference/box/box_space.rst
@@ -0,0 +1,788 @@
+.. include:: ../../directives.rst
+.. highlight:: lua
+
+-------------------------------------------------------------------------------
+                             Package `box.space`
+-------------------------------------------------------------------------------
+
+.. module:: box.space
+
+The ``box.space`` package has the data-manipulation functions ``select``,
+``insert``, ``replace``, ``update``, ``delete``, ``get``, ``put``. It also has
+members, such as id, and whether or not a space is enabled. Package source code
+is available in file
+`src/box/lua/box.lua <https://github.com/tarantool/tarantool/blob/master/src/box/lua/box.lua>`_.
+
+A list of all ``box.space`` functions follows, then comes a list of all
+``box.space`` members.
+
+.. function:: box.space.space-name:create_index(index-name [, {options} ])
+
+    Create an index. It is **mandatory** to create an index for a tuple set
+    before trying to insert tuples into it, or select tuples from it. The
+    first created index, which will be used as the primary-key index, must be
+    **unique**.
+
+    :param string index-name: name of index, which should not be a number and
+                              should not contain special characters;
+    :param table options:
+
+    :return: index object
+    :rtype:  userdata
+
+    .. container:: table
+
+        **Options for box.space...create_index**
+
+        +---------------+--------------------+-----------------------------+---------------------+
+        | Name          | Effect             | Type                        | Default             |
+        +===============+====================+=============================+=====================+
+        | type          | type of index      | string                      | 'TREE'              |
+        |               |                    | ('HASH',     'TREE',        |                     |
+        |               |                    | 'BITSET',   'RTREE')        |                     |
+        |               |                    |                             |                     |
+        |               |                    |                             |                     |
+        |               |                    |                             |                     |
+        +---------------+--------------------+-----------------------------+---------------------+
+        | id            | unique identifier  | number                      | last index's id, +1 |
+        +---------------+--------------------+-----------------------------+---------------------+
+        | unique        | index is unique    | boolean                     | true                |
+        +---------------+--------------------+-----------------------------+---------------------+
+        | if_not_exists | no error if        | boolean                     | false               |
+        |               | duplicate name     |                             |                     |
+        +---------------+--------------------+-----------------------------+---------------------+
+        | parts         | field-numbers  +   | ``{field_no, 'NUM'|'STR'}`` | ``{1, 'NUM'}``      |
+        |               | types              |                             |                     |
+        +---------------+--------------------+-----------------------------+---------------------+
+
+    Possible errors: too many parts. A type options other than TREE, or a
+    unique option other than unique, or a parts option with more than one
+    field component, is only applicable for the memtx storage engine.
+
+    .. code-block:: lua
+
+        tarantool> s = box.space.space55
+        ---
+        ...
+        tarantool> s:create_index('primary', {unique = true, parts = {1, 'NUM', 2, 'STR'}})
+        ---
+        ...
+
+.. function:: box.space.space-name:insert{field-value [, field-value ...]}
+
+    Insert a tuple into a space.
+
+    :param userdata      space-name:
+    :param lua-value field-value(s): fields of the new tuple.
+    :return: the inserted tuple
+    :rtype:  tuple
+
+    :except: If a tuple with the same unique-key value already exists,
+             returns ``ER_TUPLE_FOUND``.
+
+    .. code-block:: lua
+
+        box.space.tester:insert{5000,'tuple number five thousand'}
+
+.. function:: box.space.space-name:select{field-value [, field-value ...]}
+
+    Search for a tuple or a set of tuples in the given space.
+
+    :param userdata      space-name:
+    :param lua-value field-value(s): values to be matched against the index
+                                     key, which may be multi-part.
+
+    :return: the tuples whose primary-key fields are equal to the passed
+             field-values. If the number of passed field-values is less
+             than the number of fields in the primary key, then only the
+             passed field-values are compared, so ``select{1,2}`` will match
+             a tuple whose primary key is ``{1,2,3}``.
+    :rtype:  tuple
+
+    :except: No such space; wrong type.
+
+    Complexity Factors: Index size, Index type.
+
+    .. code-block:: lua
+
+        tarantool> s = box.schema.space.create('tmp', {temporary=true})
+        ---
+        ...
+        tarantool> s:create_index('primary',{parts = {1,'NUM', 2, 'STR'}})
+        ---
+        ...
+        tarantool> s:insert{1,'A'}
+        ---
+        - [1, 'A']
+        ...
+        tarantool> s:insert{1,'B'}
+        ---
+        - [1, 'B']
+        ...
+        tarantool> s:insert{1,'C'}
+        ---
+        - [1, 'C']
+        ...
+        tarantool> s:insert{2,'D'}
+        ---
+        - [2, 'D']
+        ...
+        tarantool> -- must equal both primary-key fields
+        tarantool> s:select{1,'B'}
+        ---
+        - - [1, 'B']
+        ...
+        tarantool> -- must equal only one primary-key field
+        tarantool> s:select{1}
+        ---
+        - - [1, 'A']
+          - [1, 'B']
+          - [1, 'C']
+        ...
+        tarantool> -- must equal 0 fields, so returns all tuples
+        tarantool> s:select{}
+        ---
+        - - [1, 'A']
+          - [1, 'B']
+          - [1, 'C']
+          - [2, 'D']
+        ...
+
+    For examples of complex ``select``s, where one can specify which index to
+    search and what condition to use (for example "greater than" instead of
+    "equal to") and how many tuples to return, see the later section
+    ``box.space.space-name[.index.index-name]:select``.
+
+.. function:: box.space.space-name:get{field-value [, field-value ...]}
+
+    Search for a tuple in the given space.
+
+    :param userdata      space-name:
+    :param lua-value field-value(s): values to be matched against the index
+                                     key, which may be multi-part.
+    :return: the selected tuple.
+    :rtype:  tuple
+
+    :except: If space-name does not exist.
+
+    Complexity Factors: Index size, Index type,
+    Number of indexes accessed, WAL settings.
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester:get{1}
+
+.. function:: box.space.space-name:drop()
+
+    Drop a space.
+
+    :return: nil
+    :except: If space-name does not exist.
+
+    Complexity Factors: Index size, Index type,
+    Number of indexes accessed, WAL settings.
+
+    .. code-block:: lua
+
+        tarantool> box.space.space_that_does_not_exist:drop()
+
+.. function:: box.space.space-name:rename(space-name)
+
+    Rename a space.
+
+    :param string space-name: new name for space.
+
+    :return: nil
+    :except: If space-name does not exist.
+
+    .. code-block:: lua
+
+        tarantool> box.space.space55:rename('space56')
+        ---
+        ...
+        tarantool> box.space.space56:rename('space55')
+        ---
+        ...
+
+.. function:: box.space.space-name:replace{field-value [, field-value ...]}
+              box.space.space-name:put{field-value [, field-value ...]}
+
+    Insert a tuple into a space. If a tuple with the same primary key already
+    exists, ``box.space...:replace()`` replaces the existing tuple with a new
+    one. The syntax variants ``box.space...:replace()`` and
+    ``box.space...:put()`` have the same effect; the latter is sometimes used
+    to show that the effect is the converse of ``box.space...:get()``.
+
+    :param userdata      space-name:
+    :param lua-value field-value(s): fields of the new tuple.
+
+    :return: the inserted tuple.
+    :rtype:  tuple
+
+    :except: If a different tuple with the same unique-key
+             value already exists, returns ``ER_TUPLE_FOUND``.
+             (This would only happen if there was a secondary
+             index. By default secondary indexes are unique.)
+
+    Complexity Factors: Index size, Index type,
+    Number of indexes accessed, WAL settings.
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester:replace{5000, 'New value'}
+
+.. function:: box.space.space-name:update(key, {{operator, field_no, value}, ...})
+
+    Update a tuple.
+
+    The ``update`` function supports operations on fields — assignment,
+    arithmetic (if the field is unsigned numeric), cutting and pasting
+    fragments of a field, deleting or inserting a field. Multiple
+    operations can be combined in a single update request, and in this
+    case they are performed atomically and sequentially. Each operation
+    requires specification of a field number. When multiple operations
+    are present, the field number for each operation is assumed to be
+    relative to the most recent state of the tuple, that is, as if all
+    previous operations in a multi-operation update have already been
+    applied. In other words, it is always safe to merge multiple update
+    invocations into a single invocation, with no change in semantics.
+
+    :param userdata space-name:
+    :param lua-value key: primary-key field values, must be passed as a Lua
+                          table if key is multi-part
+    :param table {operator, field_no, value}: a group of arguments for each
+            operation, indicating what the operation is, what field the
+            operation will apply to, and what value will be applied. For
+            some operations the field number can be -1, meaning the last
+            field in the tuple. Possible operators are: “+” for addition,
+            “-” for subtraction, “&” for bitwise AND, “|” for bitwise OR,
+            “^” for bitwise exclusive OR (XOR), “:” for string splice, “!”
+            for insert, “#” for delete. Thus in the instruction
+            ``s:update(44, {{'+',1,55},{'=',3,'x'}})`` the primary-key
+            value is 44, the operators are '+' and '=' meaning "add a value
+            to a field and then assign a value to a field", the first
+            affected field is field 1 and the value which will be added to
+            it is 55, the second affected field is field 3 and the value
+            which will be assigned to it is 'x'.
+
+    :return: the updated tuple.
+    :rtype:  tuple
+
+    :except: it is illegal to modify a primary-key field.
+
+    Complexity Factors: Index size, Index type, number of indexes accessed, WAL
+    settings.
+
+    .. code-block:: lua
+
+        -- Assume that the initial state of the database is ...
+        --   tester has one tuple set and one primary key whose type is 'NUM'.
+        --   There is one tuple, with field[1] = 999 and field[2] = 'A'.
+
+        -- In the following update ...
+        --   The first argument is tester, that is, the affected space is tester
+        --   The second argument is 999, that is, the affected tuple is identified by
+        --     primary key value = 999
+        --   The third argument is '=', that is, there is one operation, assignment
+        --     to a field
+        --   The fourth argument is 2, that is, the affected field is field[2]
+        --   The fifth argument is 'B', that is, field[2] contents change to 'B'
+        --   Therefore, after the following update, field[1] = 999 and field[2] = 'B'.
+        box.space.tester:update(999, {{'=', 2, 'B'}})
+
+        -- In the following update, the arguments are the same, except that ...
+        --   the key is passed as a Lua table (inside braces). This is unnecessary
+        --   when the primary key has only one field, but would be necessary if the
+        --   primary key had more than one field.
+        --   Therefore, after the following update, field[1] = 999 and field[2] = 'B'
+        --     (no change).
+        box.space.tester:update({999}, {{'=', 2, 'B'}})
+
+        -- In the following update, the arguments are the same, except that ...
+        --    The fourth argument is 3, that is, the affected field is field[3].
+        --    It is okay that, until now, field[3] has not existed. It gets added.
+        --    Therefore, after the following update, field[1] = 999, field[2] = 'B',
+        --      field[3] = 1.
+        box.space.tester:update({999}, {{'=', 3, 1}})
+
+        -- In the following update, the arguments are the same, except that ...
+        --    The third argument is '+', that is, the operation is addition rather
+        --      than assignment.
+        --    Since field[3] previously contained 1, this means we're adding 1 to 1.
+        --    Therefore, after the following update, field[1] = 999, field[2] = 'B',
+        --      field[3] = 2.
+        box.space.tester:update({999}, {{'+', 3, 1}})
+
+        -- In the following update ...
+        --    The idea is to modify two fields at once.
+        --    The formats are '|' and '=', that is, there are two operations, OR and
+        --      assignment.
+        --    The fourth and fifth arguments mean that field[3] gets ORed with 1.
+        --    The seventh and eighth arguments mean that field[2] gets assigned 'C'.
+        --    Therefore, after the following update, field[1] = 999, field[2] = 'C',
+        --      field[3] = 3.
+        box.space.tester:update({999}, {{'|', 3, 1}, {'=', 2, 'C'}})
+
+        -- In the following update ...
+        --    The idea is to delete field[2], then subtract 3 from field[3], but ...
+        --    after the delete, there is a renumbering -- so field[3] becomes field[2]
+        --    before we subtract 3 from it, and that's why the seventh argument is 2 not 3.
+        --    Therefore, after the following update, field[1] = 999, field[2] = 0.
+        box.space.tester:update({999}, {{'-- ', 2, 1}, {'-', 2, 3}})
+
+        -- In the following update ...
+        --    We're making a long string so that splice will work in the next example.
+        --    Therefore, after the following update, field[1] = 999, field[2] = 'XYZ'.
+        box.space.tester:update({999}, {{'=', 2, 'XYZ'}})
+
+        -- In the following update ...
+        --    The third argument is ':', that is, this is the example of splice.
+        --    The fourth argument is 2 because the change will occur in field[2].
+        --    The fifth argument is 2 because deletion will begin with the second byte.
+        --    The sixth argument is 1 because the number of bytes to delete is 1.
+        --    The seventh argument is '!!' because '!!' is to be added at this position.
+        --    Therefore, after the following update, field[1] = 999, field[2] = 'X!!Z'.
+        box.space.tester:update({999}, {{':', 2, 2, 1, '!!'}})
+
+.. function:: box.space.space-name:delete{field-value [, field-value ...]}
+
+    Delete a tuple identified by a primary key.
+
+    :param userdata space-name:
+    :param lua-value field-value(s): values to match against keys
+                                     in the primary index.
+
+    :return: the deleted tuple
+    :rtype:  tuple
+
+    Complexity Factors: Index size, Index type
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester:delete(0)
+        ---
+        - [0, 'My first tuple']
+        ...
+        tarantool> box.space.tester:delete(0)
+        ---
+        ...
+        tarantool> box.space.tester:delete('a')
+        ---
+        - error: 'Supplied key type of part 0 does not match index part type:
+          expected NUM'
+        ...
+
+.. data::     space-name.id
+
+    Ordinal space number. Spaces can be referenced by either name or
+    number. Thus, if space 'tester' has id = 800, then
+    ``box.space.tester:insert{0}`` and ``box.space[800]:insert{0}``
+    are equivalent requests.
+
+    :rtype: number
+
+.. data::     space-name.enabled
+
+    Whether or not this space is enabled.
+    The value is false if there is no index.
+
+    :rtype: boolean
+
+.. data::     space-name.field_count
+
+    The required field count for all tuples in this space. The field_count
+    can be set initially with
+    ``box.schema.space.create... field_count = new-field-count-value ...``.
+    The default value is 0, which means there is no required field count.
+
+    :rtype: number
+
+.. data::     space-name.index[]
+
+    A container for all defined indexes. An index is a Lua object of type
+    :mod:`box.index` with methods to search tuples and iterate over them in
+    predefined order.
+
+    :rtype: table
+
+    .. code-block: lua
+
+        tarantool> box.space.tester.id
+        ---
+        - 512
+        ...
+        tarantool> box.space.tester.field_count
+        ---
+        - 0
+        ...
+        tarantool> box.space.tester.index.primary.type
+        ---
+        - TREE
+        ...
+
+.. function:: box.space.space-name:len()
+
+    .. NOTE::
+
+        The ``len()`` function is only applicable for the memtx storage engine.
+
+    :return: Number of tuples in the space.
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester:len()
+        ---
+        - 2
+        ...
+
+.. function:: box.space.space-name:truncate()
+
+    Deletes all tuples.
+
+    Complexity Factors: Index size, Index type, Number of tuples accessed.
+
+    :return: nil
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester:truncate()
+        ---
+        ...
+        tarantool> box.space.tester:len()
+        ---
+        - 0
+        ...
+
+.. function:: box.space.space-name:inc{field-value [, field-value ...]}
+
+    Increments a counter in a tuple whose primary key matches the
+    ``field-value(s)``. The field following the primary-key fields
+    will be the counter. If there is no tuple matching the
+    ``field-value(s)``, a new one is inserted with initial counter
+    value set to 1.
+
+    :param userdata space-name:
+    :param lua-value field-value(s): values which must match the primary key.
+    :return: the new counter value
+    :rtype:  number
+
+    Complexity Factors: Index size, Index type, WAL settings.
+
+    .. code-block:: lua
+
+        tarantool> s = box.schema.space.create('forty_second_space')
+        ---
+        ...
+        tarantool> s:create_index('primary', {unique = true, parts = {1, 'NUM', 2, 'STR'}})
+        ---
+        ...
+        tarantool> box.space.forty_second_space:inc{1,'a'}
+        ---
+        - 1
+        ...
+        tarantool> box.space.forty_second_space:inc{1,'a'}
+        ---
+        - 2
+        ...
+
+.. function:: box.space.space-name:dec{field-value [, field-value ...]}
+
+    Decrements a counter in a tuple whose primary key matches the
+    ``field-value(s)``. The field following the primary-key fields
+    will be the counter. If there is no tuple matching the
+    ``field-value(s)``, a new one is not inserted. If the counter value drops
+    to zero, the tuple is deleted.
+
+    :param userdata space-name:
+    :param lua-value field-value(s): values which must match the primary key.
+    :return: the new counter value
+    :rtype:  number
+
+    Complexity Factors: Index size, Index type, WAL settings.
+
+    .. code-block:: lua
+
+        tarantool> s = box.schema.space.create('space19')
+        ---
+        ...
+        tarantool> s:create_index('primary', {unique = true, parts = {1, 'NUM', 2, 'STR'}})
+        ---
+        ...
+        tarantool> box.space.space19:insert{1,'a',1000}
+        ---
+        - [1, 'a', 1000]
+        ...
+        tarantool> box.space.space19:dec{1,'a'}
+        ---
+        - 999
+        ...
+        tarantool> box.space.space19:dec{1,'a'}
+        ---
+        - 998
+        ...
+
+.. function:: box.space.space-name:auto_increment{field-value [, field-value ...]}
+
+    Insert a new tuple using an auto-increment primary key. The space specified
+    by space-name must have a NUM primary key index of type TREE. The
+    primary-key field will be incremented before the insert.
+
+    :param userdata space-name:
+    :param lua-value field-value(s): values for the tuple's fields,
+                                     other than the primary-key field.
+
+    :return: the inserted tuple.
+    :rtype:  tuple
+
+    Complexity Factors: Index size, Index type,
+    Number of indexes accessed, WAL settings.
+
+    :except: index has wrong type or primary-key indexed field is not a number.
+
+    .. code-block:: lua
+
+        tarantool> box.space.tester:auto_increment{'Fld#1', 'Fld#2'}
+        ---
+        - [1, 'Fld#1', 'Fld#2']
+        ...
+        tarantool> box.space.tester:auto_increment{'Fld#3'}
+        ---
+        - [2, 'Fld#3']
+        ...
+
+.. function:: box.space.space-name:pairs()
+
+    A helper function to prepare for iterating over all tuples in a space.
+
+    :return: function which can be used in a for/end loop. Within the loop, a value is returned for each iteration.
+    :rtype:  function, tuple
+
+    .. code-block:: lua
+
+        tarantool> s = box.schema.space.create('space33')
+        ---
+        ...
+        tarantool> -- index 'X' has default parts {1,'NUM'}
+        tarantool> s:create_index('X', {})
+        ---
+        ...
+        tarantool> s:insert{0,'Hello my '}; s:insert{1,'Lua world'}
+        ---
+        ...
+        tarantool> tmp = ''; for k, v in s:pairs() do tmp = tmp .. v[2] end
+        ---
+        ...
+        tarantool> tmp
+        ---
+        - Hello my Lua world
+        ...
+
+.. data::     _schema
+
+    ``_schema`` is a system tuple set. Its single tuple contains these fields:
+    ``'version', major-version-number, minor-version-number``.
+
+    The following function will display all fields in all tuples of ``_schema``.
+
+    .. code-block:: lua
+
+        console = require('console'); console.delimiter('!')
+        function example()
+            local ta = {}, i, line
+            for k, v in box.space._schema:pairs() do
+                i = 1
+                line = ''
+                while i <= #v do line = line .. v[i] .. ' ' i = i + 1 end
+                table.insert(ta, line)
+            end
+            return ta
+        end!
+        console.delimiter('')!
+
+
+    Here is what ``example()`` returns in a typical installation:
+
+    .. code-block:: lua
+
+        tarantool> example()
+        ---
+        - - 'cluster 1ec4e1f8-8f1b-4304-bb22-6c47ce0cf9c6 '
+          - 'max_id 520 '
+          - 'version 1 6 '
+        ...
+
+.. data::     _space
+
+    ``_space`` is a system tuple set. Its tuples contain these fields:
+    ``id, uid, space-name, engine, field_count, temporary``.
+
+    The following function will display all simple fields
+    in all tuples of ``_space``.
+
+    .. code-block:: lua
+
+        console = require('console'); console.delimiter('!')
+        function example()
+            local ta = {}, i, line
+            for k, v in box.space._space:pairs() do
+                i = 1
+                line = ''
+                while i <= #v do
+                    if type(v[i]) ~= 'table' then
+                        line = line .. v[i] .. ' '
+                    end
+                    i = i + 1
+                end
+                table.insert(ta, line)
+            end
+            return ta
+        end!
+        console.delimiter('')!
+
+    Here is what ``example()`` returns in a typical installation:
+
+    .. code-block:: lua
+
+        tarantool> example()
+        ---
+        - - '272 1 _schema memtx 0  '
+          - '280 1 _space memtx 0  '
+          - '288 1 _index memtx 0  '
+          - '296 1 _func memtx 0  '
+          - '304 1 _user memtx 0  '
+          - '312 1 _priv memtx 0  '
+          - '320 1 _cluster memtx 0  '
+          - '512 1 tester memtx 0  '
+          - '513 1 origin sophia 0  '
+          - '514 1 archive memtx 0  '
+        ...
+
+.. data::     _index
+
+    ``_index`` is a system tuple set. Its tuples contain these fields:
+    ``space-id index-id index-name index-type index-is-unique
+    index-field-count [tuple-field-no, tuple-field-type ...]``.
+
+    The following function will display all fields in all tuples of _index.
+
+    .. code-block:: lua
+
+        console = require('console'); console.delimiter('!')
+        function example()
+            local ta = {}, i, line
+            for k, v in box.space._index:pairs() do
+                i = 1
+                line = ''
+                    while i <= #v do line = line .. v[i] .. ' ' i = i + 1 end
+                table.insert(ta, line)
+            end
+            return ta
+        end!
+        console.delimiter('')!
+
+    Here is what ``example()`` returns in a typical installation:
+
+    .. code-block:: lua
+
+        tarantool> example()
+        ---
+        - - '272 0 primary tree 1 1 0 str '
+          - '280 0 primary tree 1 1 0 num '
+          - '280 1 owner tree 0 1 1 num '
+          - '280 2 name tree 1 1 2 str '
+          - '288 0 primary tree 1 2 0 num 1 num '
+          - '288 2 name tree 1 2 0 num 2 str '
+          - '296 0 primary tree 1 1 0 num '
+          - '296 1 owner tree 0 1 1 num '
+          - '296 2 name tree 1 1 2 str '
+          - '304 0 primary tree 1 1 0 num '
+          - '304 1 owner tree 0 1 1 num '
+          - '304 2 name tree 1 1 2 str '
+          - '312 0 primary tree 1 3 1 num 2 str 3 num '
+          - '312 1 owner tree 0 1 0 num '
+          - '312 2 object tree 0 2 2 str 3 num '
+          - '320 0 primary tree 1 1 0 num '
+          - '320 1 uuid tree 1 1 1 str '
+          - '512 0 primary tree 1 1 0 num '
+          - '513 0 first tree 1 1 0 NUM '
+          - '514 0 first tree 1 1 0 STR '
+        ...
+
+.. data::     _user
+
+    ``_user`` is a new system tuple set for
+    support of the `authorization feature`_.
+
+.. data::     _priv
+
+    ``_priv`` is a new system tuple set for
+    support of the `authorization feature`_.
+
+.. data::     _cluster
+
+    ``_cluster`` is a new system tuple set
+    for support of the `replication feature`_.
+
+.. _authorization feature: http://tarantool.org/doc/user_guide.html#authentication
+.. _replication feature: http://tarantool.org/doc/user_guide.html#replication
+
+=================================================
+                     Example
+=================================================
+
+This function will illustrate how to look at all the spaces, and for each
+display: approximately how many tuples it contains, and the first field of
+its first tuple. The function uses Tarantool ``box.space`` functions ``len()``
+and ``pairs()``. The iteration through the spaces is coded as a scan of the
+``_space`` system tuple set, which contains metadata. The third field in
+``_space`` contains the space name, so the key instruction
+"``space_name = v[3]``" means "``space_name`` = the ``space_name`` field in
+the tuple of ``_space`` that we've just fetched with ``pairs()``". The function
+returns a table.
+
+.. code-block:: lua
+
+    console = require('console'); console.delimiter('!')
+    function example()
+        local tuple_count, space_name, line
+        local ta = {}
+        for k, v in box.space._space:pairs() do
+            space_name = v[3]
+            if box.space[space_name].index[0] ~= nil then
+                tuple_count = box.space[space_name]:len()
+            else
+                tuple_count = 0
+            end
+            line = space_name .. ' tuple_count =' .. tuple_count
+            if tuple_count > 0 then
+                for k1, v1 in box.space[space_name]:pairs() do
+                    line = line .. '. first field in first tuple = ' .. v1[1]
+                    break
+                end
+            end
+            table.insert(ta, line)
+        end
+        return ta
+    end!
+    console.delimiter('')!
+
+... And here is what happens when one invokes the function:
+
+.. code-block:: lua
+
+    tarantool> example()
+    ---
+    - - _schema tuple_count =3. first field in first tuple = cluster
+      - _space tuple_count =15. first field in first tuple = 272
+      - _index tuple_count =25. first field in first tuple = 272
+      - _func tuple_count =1. first field in first tuple = 1
+      - _user tuple_count =4. first field in first tuple = 0
+      - _priv tuple_count =6. first field in first tuple = 1
+      - _cluster tuple_count =1. first field in first tuple = 1
+      - tester tuple_count =2. first field in first tuple = 1
+      - origin tuple_count =0
+      - archive tuple_count =13. first field in first tuple = test_0@tarantool.org
+      - space55 tuple_count =0
+      - tmp tuple_count =0
+      - forty_second_space tuple_count =1. first field in first tuple = 1
+    ...
diff --git a/doc/sphinx/reference/box/index.rst b/doc/sphinx/reference/box/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..f2726d03d96c1bb9614381dba1d71356c9c46c74
--- /dev/null
+++ b/doc/sphinx/reference/box/index.rst
@@ -0,0 +1,87 @@
+-------------------------------------------------------------------------------
+                              The `box` library
+-------------------------------------------------------------------------------
+
+As well as executing Lua chunks or defining their own functions, users can exploit
+the Tarantool server's storage functionality with the ``box`` Lua library.
+
+=====================================================================
+                     Packages of the box library
+=====================================================================
+
+The contents of the box library can be inspected at runtime with ``box``, with
+no arguments. The packages inside the box library are:
+
+.. toctree::
+    :maxdepth: 1
+
+    box_schema
+    box_tuple
+    box_space
+    box_index
+    net_box
+    box_cfg
+    box_info
+    box_slab
+    box_stat
+
+Every package contains one or more Lua functions. A few packages contain
+members as well as functions. The functions allow data definition (create
+alter drop), data manipulation (insert delete update select replace), and
+introspection (inspecting contents of spaces, accessing server configuration).
+
+
+.. container:: table
+
+    **Complexity Factors that may affect data
+    manipulation functions in the box library**
+
+    +-------------------+-----------------------------------------------------+
+    | Index size        | The number of index keys is the same as the number  |
+    |                   | of tuples in the data set. For a TREE index, if     |
+    |                   | there are more keys then the lookup time will be    |
+    |                   | greater, although of course the effect is not       |
+    |                   | linear. For a HASH index, if there are more keys    |
+    |                   | then there is more RAM use, but the number of       |
+    |                   | low-level steps tends to remain constant.           |
+    +-------------------+-----------------------------------------------------+
+    | Index type        | Typically a HASH index is faster than a TREE index  |
+    |                   | if the number of tuples in the tuple set is greater |
+    |                   | than one.                                           |
+    +-------------------+-----------------------------------------------------+
+    | Number of indexes | Ordinarily only one index is accessed to retrieve   |
+    | accessed          | one tuple. But to update the tuple, there must be N |
+    |                   | accesses if the tuple set has N different indexes.  |
+    +-------------------+-----------------------------------------------------+
+    | Number of tuples  | A few requests, for example select, can retrieve    |
+    | accessed          | multiple tuples. This factor is usually less        |
+    |                   | important than the others.                          |
+    +-------------------+-----------------------------------------------------+
+    | WAL settings      | The important setting for the write-ahead log is    |
+    |                   | `wal_mode`_. If the setting causes no writing or    |
+    |                   | delayed writing, this factor is unimportant. If the |
+    |                   | settings causes every data-change request to wait   |
+    |                   | for writing to finish on a slow device, this factor |
+    |                   | is more important than all the others.              |
+    +-------------------+-----------------------------------------------------+
+
+.. _wal_mode: http://tarantool.org/doc/user_guide.html#wal_mode
+
+In the discussion of each data-manipulation function there will be a note about
+which Complexity Factors might affect the function's resource usage.
+
+=====================================================================
+            The two storage engines: memtx and sophia
+=====================================================================
+
+A storage engine is a set of very-low-level routines which actually store and
+retrieve tuple values. Tarantool offers a choice of two storage engines: memtx
+(the in-memory storage engine) and sophia (the on-disk storage engine).
+To specify that the engine should be sophia, add a clause: ``engine = 'sophia'``.
+The manual concentrates on memtx because it is the default and has been around
+longer. But sophia is a working key-value engine and will especially appeal to
+users who like to see data go directly to disk, so that recovery time might be
+shorter and database size might be larger. For architectural explanations and
+benchmarks, see sphia.org. On the other hand, sophia lacks some functions and
+options that are available with memtx. Where that is the case, the relevant
+description will contain the words "only applicable for the memtx storage engine".
diff --git a/doc/sphinx/reference/fiber-ipc.rst b/doc/sphinx/reference/fiber-ipc.rst
index 56b232e6f6bdbf1ef476ecf852c919a6f8ea941e..6b89ed1985b7154b061df06660d1874feb60f988 100644
--- a/doc/sphinx/reference/fiber-ipc.rst
+++ b/doc/sphinx/reference/fiber-ipc.rst
@@ -30,7 +30,7 @@ other Lua object. Use object-oriented syntax, for example
     :return: new channel.
     :rtype:  userdata
 
-.. class:: channel
+.. class:: channel_object
 
     .. method:: put(message[, timeout])
 
diff --git a/doc/sphinx/reference/index.rst b/doc/sphinx/reference/index.rst
index 5ef7cc4520053df59cfb5eb52e16eb2156613c6f..7782683952061970d7bcfc493969aaeade61abf7 100644
--- a/doc/sphinx/reference/index.rst
+++ b/doc/sphinx/reference/index.rst
@@ -1,5 +1,5 @@
 -------------------------------------------------------------------------------
-                              Library Reference
+                              Reference Manual
 -------------------------------------------------------------------------------
 
 Lua_ is a light-weight, multi-paradigm, embeddable language. Stored procedures
@@ -28,3 +28,4 @@ Lua functions can run in the background and perform administrative tasks.
     pickle
     other
     expirationd
+    box/index
diff --git a/doc/sphinx/reference/socket.rst b/doc/sphinx/reference/socket.rst
index 67f3c076dc6b126cec95ca0989d3ba4ef9b79e67..0ffdf70b0c96fbc3ca86472dd44fc1dd35e42ba3 100644
--- a/doc/sphinx/reference/socket.rst
+++ b/doc/sphinx/reference/socket.rst
@@ -61,7 +61,7 @@ are ``errno``, ``error``.
     |    teardown    +-------------+
     |                | close       |
     +----------------+-------------+
-    |                | errno       |
+    |                | error       |
     | error checking +-------------+
     |                | errno       |
     +----------------+-------------+
@@ -149,13 +149,12 @@ the function invocations will look like ``sock:function_name(...)``.
 
         socket.tcp_server('localhost', 3302, function () end).
 
-
 .. class:: socket_object
 
     .. method:: sysconnect(host, port)
 
         Connect a socket to a remote host. The argument values are the same as
-        in the `Linux man page <http://man7.org/linux/man-pages/man2/connect.2.html>`_.
+        in the Linux man page [1]_.
         The host must be an IP address.
 
         Parameters:
@@ -181,6 +180,7 @@ the function invocations will look like ``sock:function_name(...)``.
             sock:sysconnect('127.0.0.1', 80)
 
     .. method:: send(data)
+                write(data)
 
         Send data over a connected socket.
 
@@ -188,11 +188,6 @@ the function invocations will look like ``sock:function_name(...)``.
         :return: true if success, false if error.
         :rtype:  boolean
 
-        .. NOTE::
-
-            The function ``sock:write(...)`` has
-            the same parameters and same effect.
-
     .. method:: syswrite(size)
 
         Write as much as possible data to the socket buffer if non-blocking.
@@ -243,7 +238,7 @@ the function invocations will look like ``sock:function_name(...)``.
     .. method:: bind(host [, port])
 
         Bind a socket to the given host/port. A UDP socket after binding
-        can be used to receive data (see :meth:`socket_object.recvfrom()`).
+        can be used to receive data (see :func:`socket_object.recvfrom`).
         A TCP socket can be used to accept new connections, after it has
         been put in listen mode.
 
@@ -264,7 +259,7 @@ the function invocations will look like ``sock:function_name(...)``.
                         may be ``SOMAXCONN``.
 
         :return: true for success, false for error.
-        :rtype:  boolean
+        :rtype: boolean.
 
     .. method:: accept()
 
@@ -340,7 +335,7 @@ the function invocations will look like ``sock:function_name(...)``.
     .. method:: setsockopt(level, name, value)
 
         Set socket flags. The argument values are the same as in the
-        `Linux man page <http://man7.org/linux/man-pages/man2/setsockopt.2.html>`_.
+        Linux man page [2]_.
         The ones that Tarantool accepts are:
 
             * SO_ACCEPTCONN
@@ -379,7 +374,7 @@ the function invocations will look like ``sock:function_name(...)``.
     .. method:: linger([active])
 
         Set or clear the SO_LINGER flag. For a description of the flag, see
-        `Linux man page <http://man7.org/linux/man-pages/man1/loginctl.1.html>`_.
+        Linux man page [3]_.
 
         :param boolean active:
 
@@ -514,3 +509,8 @@ computer to communicate with itself, but shows that the system works.
 
 
 .. _luasocket: https://github.com/diegonehab/luasocket
+
+.. [1] http://man7.org/linux/man-pages/man2/connect.2.html
+.. [2] http://man7.org/linux/man-pages/man2/setsockopt.2.html
+.. [3] http://man7.org/linux/man-pages/man1/loginctl.1.html
+