Compiles schema with `JSON Schema`_ draft-04.

    :param schema: obj to compile
    :type schema: Mapping
    :param pointer: uri of the schema
    :type pointer: Pointer, str
    :param context: context of this schema
    :type context: Context

    .. _`JSON Schema`: http://json-schema.org

def compile(schema, pointer, context, scope=None):
    """
    Compiles schema with `JSON Schema`_ draft-04.

    :param schema: obj to compile
    :type schema: Mapping
    :param pointer: uri of the schema
    :type pointer: Pointer, str
    :param context: context of this schema
    :type context: Context

    .. _`JSON Schema`: http://json-schema.org
    """

    schm = deepcopy(schema)

    scope = urljoin(scope or str(pointer), schm.pop('id', None))

    if '$ref' in schema:
        return ReferenceValidator(urljoin(scope, schema['$ref']), context)

    attrs = {}

    if 'additionalItems' in schm:
        subpointer = pointer_join(pointer, 'additionalItems')
        attrs['additional_items'] = schm.pop('additionalItems')
        if isinstance(attrs['additional_items'], dict):
            compiled = compile(attrs['additional_items'],
                               subpointer,
                               context,
                               scope)
            attrs['additional_items'] = compiled
        elif not isinstance(attrs['additional_items'], bool):
            raise CompilationError('wrong type for {}'.format('additional_items'), schema)  # noqa

    if 'additionalProperties' in schm:
        subpointer = pointer_join(pointer, 'additionalProperties')
        attrs['additional_properties'] = schm.pop('additionalProperties')
        if isinstance(attrs['additional_properties'], dict):
            compiled = compile(attrs['additional_properties'],
                               subpointer,
                               context,
                               scope)
            attrs['additional_properties'] = compiled
        elif not isinstance(attrs['additional_properties'], bool):
            raise CompilationError('wrong type for {}'.format('additional_properties'), schema)  # noqa

    if 'allOf' in schm:
        subpointer = pointer_join(pointer, 'allOf')
        attrs['all_of'] = schm.pop('allOf')
        if isinstance(attrs['all_of'], (list, tuple)):
            attrs['all_of'] = [compile(element, subpointer, context, scope) for element in attrs['all_of']]  # noqa
        else:
            # should be a boolean
            raise CompilationError('wrong type for {}'.format('allOf'), schema)  # noqa

    if 'anyOf' in schm:
        subpointer = pointer_join(pointer, 'anyOf')
        attrs['any_of'] = schm.pop('anyOf')
        if isinstance(attrs['any_of'], (list, tuple)):
            attrs['any_of'] = [compile(element, subpointer, context, scope) for element in attrs['any_of']]  # noqa
        else:
            # should be a boolean
            raise CompilationError('wrong type for {}'.format('anyOf'), schema)  # noqa

    if 'default' in schm:
        attrs['default'] = schm.pop('default')

    if 'dependencies' in schm:
        attrs['dependencies'] = schm.pop('dependencies')
        if not isinstance(attrs['dependencies'], dict):
            raise CompilationError('dependencies must be an object', schema)
        for key, value in attrs['dependencies'].items():
            if isinstance(value, dict):
                subpointer = pointer_join(pointer, 'dependencies', key)
                attrs['dependencies'][key] = compile(value,
                                                     subpointer,
                                                     context,
                                                     scope)
            elif not isinstance(value, sequence_types):
                raise CompilationError('dependencies must be an array or object', schema)  # noqa

    if 'enum' in schm:
        attrs['enum'] = schm.pop('enum')
        if not isinstance(attrs['enum'], sequence_types):
            raise CompilationError('enum must be a sequence', schema)

    if 'exclusiveMaximum' in schm:
        attrs['exclusive_maximum'] = schm.pop('exclusiveMaximum')
        if not isinstance(attrs['exclusive_maximum'], bool):
            raise CompilationError('exclusiveMaximum must be a boolean', schema)  # noqa

    if 'exclusiveMinimum' in schm:
        attrs['exclusive_minimum'] = schm.pop('exclusiveMinimum')
        if not isinstance(attrs['exclusive_minimum'], bool):
            raise CompilationError('exclusiveMinimum must be a boolean', schema)  # noqa

    if 'format' in schm:
        attrs['format'] = schm.pop('format')
        if not isinstance(attrs['format'], string_types):
            raise CompilationError('format must be a string', schema)

    if 'items' in schm:
        subpointer = pointer_join(pointer, 'items')
        attrs['items'] = schm.pop('items')
        if isinstance(attrs['items'], (list, tuple)):
            # each value must be a json schema
            attrs['items'] = [compile(element, subpointer, context, scope) for element in attrs['items']]  # noqa
        elif isinstance(attrs['items'], dict):
            # value must be a json schema
            attrs['items'] = compile(attrs['items'], subpointer, context, scope)  # noqa
        else:
            # should be a boolean
            raise CompilationError('wrong type for {}'.format('items'), schema)  # noqa

    if 'maximum' in schm:
        attrs['maximum'] = schm.pop('maximum')
        if not isinstance(attrs['maximum'], number_types):
            raise CompilationError('maximum must be a number', schema)

    if 'maxItems' in schm:
        attrs['max_items'] = schm.pop('maxItems')
        if not isinstance(attrs['max_items'], integer_types):
            raise CompilationError('maxItems must be integer', schema)

    if 'maxLength' in schm:
        attrs['max_length'] = schm.pop('maxLength')
        if not isinstance(attrs['max_length'], integer_types):
            raise CompilationError('maxLength must be integer', schema)

    if 'maxProperties' in schm:
        attrs['max_properties'] = schm.pop('maxProperties')
        if not isinstance(attrs['max_properties'], integer_types):
            raise CompilationError('maxProperties must be integer', schema)

    if 'minimum' in schm:
        attrs['minimum'] = schm.pop('minimum')
        if not isinstance(attrs['minimum'], number_types):
            raise CompilationError('minimum must be a number', schema)

    if 'minItems' in schm:
        attrs['min_items'] = schm.pop('minItems')
        if not isinstance(attrs['min_items'], integer_types):
            raise CompilationError('minItems must be integer', schema)

    if 'minLength' in schm:
        attrs['min_length'] = schm.pop('minLength')
        if not isinstance(attrs['min_length'], integer_types):
            raise CompilationError('minLength must be integer', schema)

    if 'minProperties' in schm:
        attrs['min_properties'] = schm.pop('minProperties')
        if not isinstance(attrs['min_properties'], integer_types):
            raise CompilationError('minProperties must be integer', schema)

    if 'multipleOf' in schm:
        attrs['multiple_of'] = schm.pop('multipleOf')
        if not isinstance(attrs['multiple_of'], number_types):
            raise CompilationError('multipleOf must be a number', schema)

    if 'not' in schm:
        attrs['not'] = schm.pop('not')
        if not isinstance(attrs['not'], dict):
            raise CompilationError('not must be an object', schema)
        subpointer = pointer_join(pointer, 'not')
        attrs['not'] = compile(attrs['not'], subpointer, context, scope)

    if 'oneOf' in schm:
        subpointer = pointer_join(pointer, 'oneOf')
        attrs['one_of'] = schm.pop('oneOf')
        if isinstance(attrs['one_of'], (list, tuple)):
            # each value must be a json schema
            attrs['one_of'] = [compile(element, subpointer, context, scope) for element in attrs['one_of']]  # noqa
        else:
            # should be a boolean
            raise CompilationError('wrong type for {}'.format('oneOf'), schema)

    if 'pattern' in schm:
        attrs['pattern'] = schm.pop('pattern')
        if not isinstance(attrs['pattern'], string_types):
            raise CompilationError('pattern must be a string', schema)

    if 'properties' in schm:
        attrs['properties'] = schm.pop('properties')
        if not isinstance(attrs['properties'], dict):
            raise CompilationError('properties must be an object', schema)
        for subname, subschema in attrs['properties'].items():
            subpointer = pointer_join(pointer, subname)
            compiled = compile(subschema, subpointer, context, scope)
            attrs['properties'][subname] = compiled

    if 'patternProperties' in schm:
        attrs['pattern_properties'] = schm.pop('patternProperties')
        if not isinstance(attrs['pattern_properties'], dict):
            raise CompilationError('patternProperties must be an object', schema)  # noqa
        for subname, subschema in attrs['pattern_properties'].items():
            subpointer = pointer_join(pointer, 'patternProperties', subname)
            compiled = compile(subschema, subpointer, context, scope)
            attrs['pattern_properties'][subname] = compiled

    if 'required' in schm:
        attrs['required'] = schm.pop('required')
        if not isinstance(attrs['required'], list):
            raise CompilationError('required must be a list', schema)
        if len(attrs['required']) < 1:
            raise CompilationError('required cannot be empty', schema)

    if 'type' in schm:
        attrs['type'] = schm.pop('type')
        if isinstance(attrs['type'], string_types):
            attrs['type'] = [attrs['type']]
        elif not isinstance(attrs['type'], sequence_types):
            raise CompilationError('type must be string or sequence', schema)

    if 'uniqueItems' in schm:
        attrs['unique_items'] = schm.pop('uniqueItems')
        if not isinstance(attrs['unique_items'], bool):
            raise CompilationError('type must be boolean', schema)

    return Draft04Validator(attrs, str(pointer), context.formats)