private function compile()
    {
        $options = $replaces = [];
        $pattern = rtrim(ltrim($this->pattern, ':/'), '/');

        // correct [/ first occurrence]
        if (strpos($pattern, '[/') === 0) {
            $pattern = '[' . substr($pattern, 2);
        }

        if (preg_match_all('/<(\w+):?(.*?)?>/', $pattern, $matches)) {
            $variables = array_combine($matches[1], $matches[2]);

            foreach ($variables as $key => $segment) {
                $segment = $this->prepareSegment($key, $segment);
                $replaces["<$key>"] = "(?P<$key>$segment)";
                $options[] = $key;
            }
        }

        $template = preg_replace('/<(\w+):?.*?>/', '<\1>', $pattern);
        $options = array_fill_keys($options, null);

        foreach ($this->constrains as $key => $value) {
            if ($value instanceof Autofill) {
                // only forces value replacement, not required to be presented as parameter
                continue;
            }

            if (!array_key_exists($key, $options)) {
                throw new ConstrainException(sprintf(
                    "Route `%s` does not define routing parameter `<%s>`.",
                    $this->pattern,
                    $key
                ));
            }
        }

        $this->compiled = '/^' . strtr($template, $replaces + self::PATTERN_REPLACES) . '$/iu';
        $this->template = stripslashes(str_replace('?', '', $template));
        $this->options = $options;
    }