private function shortcode(array &$names)
 {
     $name = null;
     $offset = null;
     $setName = function (array $token) use(&$name) {
         $name = $token[1];
     };
     $setOffset = function (array $token) use(&$offset) {
         $offset = $token[2];
     };
     if (!$this->match(self::TOKEN_OPEN, $setOffset, true)) {
         return false;
     }
     if (!$this->match(self::TOKEN_STRING, $setName, false)) {
         return false;
     }
     if ($this->lookahead(self::TOKEN_STRING, null)) {
         return false;
     }
     if (!preg_match_all('~^' . RegexBuilderUtility::buildNameRegex() . '$~us', $name, $matches)) {
         return false;
     }
     $this->match(self::TOKEN_WS);
     if (false === ($bbCode = $this->bbCode())) {
         return false;
     }
     if (false === ($parameters = $this->parameters())) {
         return false;
     }
     // self-closing
     if ($this->match(self::TOKEN_MARKER, null, true)) {
         if (!$this->match(self::TOKEN_CLOSE)) {
             return false;
         }
         return array($this->getObject($name, $parameters, $bbCode, $offset, null, $this->getBacktrack()));
     }
     // just-closed or with-content
     if (!$this->match(self::TOKEN_CLOSE)) {
         return false;
     }
     $this->beginBacktrack();
     $names[] = $name;
     list($content, $shortcodes, $closingName) = $this->content($names);
     if (null !== $closingName && $closingName !== $name) {
         array_pop($names);
         array_pop($this->backtracks);
         array_pop($this->backtracks);
         return $closingName;
     }
     if (false === $content || $closingName !== $name) {
         $this->backtrack(false);
         $text = $this->backtrack(false);
         return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
     }
     $content = $this->getBacktrack();
     if (!$this->close($names)) {
         return false;
     }
     return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
 }
 private function parseSingle($text, $offset)
 {
     preg_match($this->singleShortcodeRegex, $text, $matches, PREG_OFFSET_CAPTURE);
     $name = $matches['name'][0];
     if (!preg_match('~^' . RegexBuilderUtility::buildNameRegex() . '$~us', $name)) {
         return null;
     }
     $parameters = isset($matches['parameters'][0]) ? $this->parseParameters($matches['parameters'][0]) : array();
     $bbCode = isset($matches['bbCode'][0]) && $matches['bbCode'][1] !== -1 ? $this->extractValue($matches['bbCode'][0]) : null;
     $content = isset($matches['content'][0]) && $matches['content'][1] !== -1 ? $matches['content'][0] : null;
     return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
 }
 /**
  * @param string $text
  *
  * @return ParsedShortcode[]
  */
 public function parse($text)
 {
     $names = $this->names ? implode('|', array_map('preg_quote', $this->names)) : RegexBuilderUtility::buildNameRegex();
     $regex = str_replace('<NAMES>', $names, static::$shortcodeRegex);
     preg_match_all($regex, $text, $matches, PREG_OFFSET_CAPTURE);
     $shortcodes = array();
     $count = count($matches[0]);
     for ($i = 0; $i < $count; $i++) {
         $name = $matches[2][$i][0];
         $parameters = static::parseParameters($matches[3][$i][0]);
         $content = $matches[5][$i][0] ?: null;
         $match = $matches[0][$i][0];
         $offset = mb_strlen(substr($text, 0, $matches[0][$i][1]), 'utf-8');
         $shortcode = new Shortcode($name, $parameters, $content, null);
         $shortcodes[] = new ParsedShortcode($shortcode, $match, $offset);
     }
     return $shortcodes;
 }