public static function frame($input, $frame, $options = null)
    {
        $options = self::mergeOptions($options);

        $input = self::expand($input, $options);
        $frame = (is_string($frame))
            ? $options->documentLoader->loadDocument($frame)->document
            : $frame;

        if (false === is_object($frame)) {
            throw new JsonLdException(
                JsonLdException::UNSPECIFIED,
                'Invalid frame detected. It must be an object.',
                $frame
            );
        }

        $processor = new Processor($options);

        // Store the frame's context as $frame gets modified
        $frameContext = new JsonObject();
        if (property_exists($frame, '@context')) {
            $frameContext->{'@context'} = $frame->{'@context'};
        }

        // Expand the frame
        $processor->expand($frame, array(), null, true);

        // and optimize away default graph (@graph as the only property at the top-level object)
        if (is_object($frame) && property_exists($frame, '@graph') && (1 === count(get_object_vars($frame)))) {
            $frame = $frame->{'@graph'};
        }

        if (false === is_array($frame)) {
            $frame = array($frame);
        }

        // Frame the input document
        $result = $processor->frame($input, $frame);

        // Compact the result using the frame's active context
        return self::doCompact($result, $frameContext, $options, true);
    }