name = $lang; // We're loading the JSON definition file as an \stdClass object instead of an associative array. This is being // done to take advantage of objects being pass by reference automatically in PHP whereas arrays are pass by // value. $json = file_get_contents($filePath); if ($json === false) { throw new \InvalidArgumentException("Language file inaccessible: $filePath"); } $this->mode = json_decode($json); } /** * @param string $name * * @return bool|Mode|null */ public function __get($name) { if ($name === 'mode') { @trigger_error('The "mode" property will be removed in highlight.php 10.x', E_USER_DEPRECATED); return $this->mode; } if ($name === 'caseInsensitive') { @trigger_error('Due to compatibility requirements with highlight.js, use "case_insensitive" instead.', E_USER_DEPRECATED); if (isset($this->mode->case_insensitive)) { return $this->mode->case_insensitive; } return false; } if (isset($this->mode->{$name})) { return $this->mode->{$name}; } return null; } /** * @param string $value * @param bool $global * * @return RegEx */ private function langRe($value, $global = false) { return RegExUtils::langRe($value, $global, $this->case_insensitive); } /** * Performs a shallow merge of multiple objects into one. * * @param Mode $params the objects to merge * @param array ...$_ * * @return Mode */ private function inherit($params, $_ = array()) { /** @var Mode $result */ $result = new \stdClass(); $objects = func_get_args(); $parent = array_shift($objects); foreach ($parent as $key => $value) { $result->{$key} = $value; } foreach ($objects as $object) { foreach ($object as $key => $value) { $result->{$key} = $value; } } return $result; } /** * @param Mode|null $mode * * @return bool */ private function dependencyOnParent($mode) { if (!$mode) { return false; } if (isset($mode->endsWithParent) && $mode->endsWithParent) { return $mode->endsWithParent; } return $this->dependencyOnParent(isset($mode->starts) ? $mode->starts : null); } /** * @param Mode $mode * * @return array */ private function expandOrCloneMode($mode) { if ($mode->variants && !$mode->cachedVariants) { $mode->cachedVariants = array(); foreach ($mode->variants as $variant) { $mode->cachedVariants[] = $this->inherit($mode, array('variants' => null), $variant); } } // EXPAND // if we have variants then essentially "replace" the mode with the variants // this happens in compileMode, where this function is called from if ($mode->cachedVariants) { return $mode->cachedVariants; } // CLONE // if we have dependencies on parents then we need a unique // instance of ourselves, so we can be reused with many // different parents without issue if ($this->dependencyOnParent($mode)) { return array($this->inherit($mode, array( 'starts' => $mode->starts ? $this->inherit($mode->starts) : null, ))); } // highlight.php does not have a concept freezing our Modes // no special dependency issues, just return ourselves return array($mode); } /** * @param Mode $mode * @param Mode|null $parent * * @return void */ private function compileMode($mode, $parent = null) { Mode::_normalize($mode); if ($mode->compiled) { return; } $mode->compiled = true; $mode->keywords = $mode->keywords ? $mode->keywords : $mode->beginKeywords; if ($mode->keywords) { $mode->keywords = $this->compileKeywords($mode->keywords, (bool) $this->case_insensitive); } $mode->lexemesRe = $this->langRe($mode->lexemes ? $mode->lexemes : "\w+", true); if ($parent) { if ($mode->beginKeywords) { $mode->begin = "\\b(" . implode("|", explode(" ", $mode->beginKeywords)) . ")\\b"; } if (!$mode->begin) { $mode->begin = "\B|\b"; } $mode->beginRe = $this->langRe($mode->begin); if ($mode->endSameAsBegin) { $mode->end = $mode->begin; } if (!$mode->end && !$mode->endsWithParent) { $mode->end = "\B|\b"; } if ($mode->end) { $mode->endRe = $this->langRe($mode->end); } $mode->terminator_end = $mode->end; if ($mode->endsWithParent && $parent->terminator_end) { $mode->terminator_end .= ($mode->end ? "|" : "") . $parent->terminator_end; } } if ($mode->illegal) { $mode->illegalRe = $this->langRe($mode->illegal); } if ($mode->relevance === null) { $mode->relevance = 1; } if (!$mode->contains) { $mode->contains = array(); } /** @var Mode[] $expandedContains */ $expandedContains = array(); foreach ($mode->contains as &$c) { if ($c instanceof \stdClass) { Mode::_normalize($c); } $expandedContains = array_merge($expandedContains, $this->expandOrCloneMode( $c === 'self' ? $mode : $c )); } $mode->contains = $expandedContains; /** @var Mode $contain */ foreach ($mode->contains as $contain) { $this->compileMode($contain, $mode); } if ($mode->starts) { $this->compileMode($mode->starts, $parent); } $terminators = new Terminators($this->case_insensitive); $mode->terminators = $terminators->_buildModeRegex($mode); Mode::_handleDeprecations($mode); } /** * @param array|string $rawKeywords * @param bool $caseSensitive * * @return array> */ private function compileKeywords($rawKeywords, $caseSensitive) { /** @var array> $compiledKeywords */ $compiledKeywords = array(); if (is_string($rawKeywords)) { $this->splitAndCompile("keyword", $rawKeywords, $compiledKeywords, $caseSensitive); } else { foreach ($rawKeywords as $className => $rawKeyword) { $this->splitAndCompile($className, $rawKeyword, $compiledKeywords, $caseSensitive); } } return $compiledKeywords; } /** * @param string $className * @param string $str * @param array> $compiledKeywords * @param bool $caseSensitive * * @return void */ private function splitAndCompile($className, $str, array &$compiledKeywords, $caseSensitive) { if ($caseSensitive) { $str = strtolower($str); } $keywords = explode(' ', $str); foreach ($keywords as $keyword) { $pair = explode('|', $keyword); $providedScore = isset($pair[1]) ? $pair[1] : null; $compiledKeywords[$pair[0]] = array($className, $this->scoreForKeyword($pair[0], $providedScore)); } } /** * @param string $keyword * @param string $providedScore * * @return int */ private function scoreForKeyword($keyword, $providedScore) { if ($providedScore) { return (int) $providedScore; } return $this->commonKeyword($keyword) ? 0 : 1; } /** * @param string $word * * @return bool */ private function commonKeyword($word) { return in_array(strtolower($word), self::$COMMON_KEYWORDS); } /** * Compile the Language definition. * * @param bool $safeMode * * @since 9.17.1.0 The 'safeMode' parameter was added. * * @return void */ public function compile($safeMode) { if ($this->compiled) { return; } $jr = new JsonRef(); $jr->decodeRef($this->mode); // self is not valid at the top-level if (isset($this->mode->contains) && !in_array("self", $this->mode->contains)) { if (!$safeMode) { throw new \LogicException("`self` is not supported at the top-level of a language."); } $this->mode->contains = array_filter($this->mode->contains, function ($mode) { return $mode !== "self"; }); } $this->compileMode($this->mode); } /** * @todo Remove in highlight.php 10.x * * @deprecated 9.16.0 This method should never have been exposed publicly as part of the API. * * @param \stdClass|null $e * * @return void */ public function complete(&$e) { Mode::_normalize($e); } }