states) . '.
*
* @param string $text
* @param string $textLength
* @param string $textPos
* @return array
*/
public function findDelimiter' . $state . '($text, $textLength, $textPos)
{
' . (!empty($delimiters) ? sprintf('static $delimiters = %s;', $this->getVarValueSource($delimiters)) : '') . '
$buffer = false;
while ($textPos < $textLength) {
' . (!empty($partSource) ? sprintf('$part = %s;', $partSource) : '') . '
' . (!empty($letterSource) ? sprintf('$letter = %s;', $letterSource) : '') . '
' . $conditionsSource . '
$buffer .= ' . $bufferSource . ';
$textPos++;
}
return array(-1, -1, $buffer);
}
';
// Removes traling whitespaces and unnecessary empty lines
$source = preg_replace('~\n{3,}~', "\n\n", preg_replace('~\t+\n~', "\n", $source));
return $source;
}
/**
* Optimizes the lexer definition.
*
* @return \FSHL\Generator
* @throws \RuntimeException If the lexer definition is wrong.
*/
private function optimize()
{
$i = 0;
foreach (array_keys($this->lexer->getStates()) as $stateName) {
if (self::STATE_QUIT === $stateName) {
continue;
}
$this->states[$stateName] = $i;
$i++;
}
$this->states[self::STATE_RETURN] = $i++;
$this->states[self::STATE_QUIT] = $i++;
foreach ($this->lexer->getStates() as $stateName => $state) {
$stateId = $this->states[$stateName];
$this->classes[$stateId] = $state[self::STATE_INDEX_CLASS];
$this->flags[$stateId] = $state[self::STATE_INDEX_FLAGS];
$this->data[$stateId] = $state[self::STATE_INDEX_DATA];
if (is_array($state[self::STATE_INDEX_DIAGRAM])) {
$i = 0;
foreach ($state[self::STATE_INDEX_DIAGRAM] as $delimiter => $trans) {
$transName = $trans[self::STATE_DIAGRAM_INDEX_STATE];
if (self::STATE_SELF === $transName) {
$transName = array_search($stateId, $this->states);
}
if (!isset($this->states[$transName])) {
throw new \RuntimeException(sprintf('Unknown state in transition %s [%s] => %s', $stateName, $delimiter, $transName));
}
$this->delimiters[$stateId][$i] = $delimiter;
$trans[self::STATE_DIAGRAM_INDEX_STATE] = $this->states[$transName];
$this->trans[$stateId][$i] = $trans;
$i++;
}
} else {
$this->delimiters[$stateId] = null;
$this->trans[$stateId] = null;
}
}
if (!isset($this->states[$this->lexer->getInitialState()])) {
throw new \RuntimeException(sprintf('Unknown initial state "%s"', $this->lexer->getInitialState()));
}
return $this;
}
/**
* Returns a variable source.
*
* @param string $name
* @param mixed $value
* @return string
*/
private function getVarSource($name, $value)
{
return sprintf("\t\t%s = %s;\n", $name, $this->getVarValueSource($value));
}
/**
* Returns a variable value source.
*
* @param mixed $value
* @param integer $level
* @return string
*/
private function getVarValueSource($value, $level = 0)
{
if (is_array($value)) {
$tmp = '';
$line = 0;
$total = 0;
foreach ($value as $k => $v) {
if ($line > 25) {
$tmp .= ",\n\t\t" . str_repeat("\t", $level);
$line = 0;
} elseif (0 !== $total) {
$tmp .= ', ';
}
$tmp .= $this->getVarValueSource($k, $level + 1) . ' => ' . $this->getVarValueSource($v, $level + 1);
$line++;
$total++;
}
return "array(\n\t\t\t" . str_repeat("\t", $level) . $tmp . "\n" . str_repeat("\t", $level) . "\t\t)";
} elseif (is_string($value) && preg_match('~[\\t\\n\\r]~', $value)) {
$export = var_export($value, true);
$export = str_replace(array("\t", "\n", "\r"), array('\t', '\n', '\r'), $export);
return '"' . substr($export, 1, -1) . '"';
} else {
return var_export($value, true);
}
}
}
FSHL-2.1.0/FSHL/Highlighter.php 0000666 0000000 0000000 00000026706 12022713116 012651 0 ustar setOutput($output)
->setOptions($options, $tabIndentWidth);
}
/**
* Highlightes a string.
*
* @param string $text
* @param \FSHL\Lexer $lexer
* @return string
* @throws \RuntimeException If no lexer is set.
*/
public function highlight($text, Lexer $lexer = null)
{
// Sets the lexer
$initialLexer = $this->lexer;
if (null !== $lexer) {
$this->setLexer($lexer);
}
// No lexer
if (null === $this->lexer) {
throw new \RuntimeException('No lexer set');
}
// Prepares the text
$text = str_replace(array("\r\n", "\r"), "\n", (string) $text);
$textLength = strlen($text);
$textPos = 0;
// Parses the text
$output = array();
$fragment = '';
$maxLineWidth = 0;
$line = 1;
$char = 0;
if ($this->options & self::OPTION_LINE_COUNTER) {
// Right aligment of line counter
$maxLineWidth = strlen(substr_count($text, "\n") + 1);
$fragment .= $this->line($line, $maxLineWidth);
}
$newLexerName = $lexerName = $this->lexer->language;
$newState = $state = $this->lexer->initialState;
$this->stack = array();
while (true) {
list($transitionId, $delimiter, $buffer) = $this->lexer->{'findDelimiter' . $state}($text, $textLength, $textPos);
// Some data may be collected before getPart reaches the delimiter, we must output this before other processing
if (false !== $buffer) {
$bufferLength = strlen($buffer);
$textPos += $bufferLength;
$char += $bufferLength;
$fragment .= $this->template($buffer, $state);
if (isset($fragment[8192])) {
$output[] = $fragment;
$fragment = '';
}
}
if (-1 === $transitionId) {
// End of stream
break;
}
// Processes received delimiter as string
$prevLine = $line;
$prevChar = $char;
$prevTextPos = $textPos;
$delimiterLength = strlen($delimiter);
$textPos += $delimiterLength;
$char += $delimiterLength;
// Adds line counter and tab indentation
$addLine = false;
if ("\n" === $delimiter[$delimiterLength - 1]) {
// Line counter
$line++;
$char = 0;
if ($this->options & self::OPTION_LINE_COUNTER) {
$addLine = true;
$actualLine = $line;
}
} elseif ("\t" === $delimiter && ($this->options & self::OPTION_TAB_INDENT)) {
// Tab indentation
$i = $char % $this->tabIndentWidth;
$delimiter = $this->tabs[$i][0];
$char += $this->tabs[$i][1];
}
// Gets new state from the transitions table
$newState = $this->lexer->trans[$state][$transitionId][Generator::STATE_DIAGRAM_INDEX_STATE];
if ($newState === $this->lexer->returnState) {
// Chooses mode of delimiter processing
if (Generator::BACK === $this->lexer->trans[$state][$transitionId][Generator::STATE_DIAGRAM_INDEX_MODE]) {
$line = $prevLine;
$char = $prevChar;
$textPos = $prevTextPos;
} else {
$fragment .= $this->template($delimiter, $state);
if ($addLine) {
$fragment .= $this->line($actualLine, $maxLineWidth);
}
if (isset($fragment[8192])) {
$output[] = $fragment;
$fragment = '';
}
}
// Get state from the context stack
if ($item = $this->popState()) {
list($newLexerName, $state) = $item;
// If previous context was in a different lexer, switch the lexer too
if ($newLexerName !== $lexerName) {
$this->setLexerByName($newLexerName);
$lexerName = $newLexerName;
}
} else {
$state = $this->lexer->initialState;
}
continue;
}
// Chooses mode of delimiter processing
$type = $this->lexer->trans[$state][$transitionId][Generator::STATE_DIAGRAM_INDEX_MODE];
if (Generator::BACK === $type) {
$line = $prevLine;
$char = $prevChar;
$textPos = $prevTextPos;
} else {
$fragment .= $this->template($delimiter, Generator::NEXT === $type ? $newState : $state);
if ($addLine) {
$fragment .= $this->line($actualLine, $maxLineWidth);
}
if (isset($fragment[8192])) {
$output[] = $fragment;
$fragment = '';
}
}
// Switches between lexers (transition to embedded language)
if ($this->lexer->flags[$newState] & Generator::STATE_FLAG_NEWLEXER) {
if ($newState === $this->lexer->quitState) {
// Returns to the previous lexer
if ($item = $this->popState()) {
list($newLexerName, $state) = $item;
if ($newLexerName !== $lexerName) {
$this->setLexerByName($newLexerName);
$lexerName = $newLexerName;
}
} else {
$state = $this->lexer->initialState;
}
} else {
// Switches to the embedded language
$newLexerName = $this->lexer->data[$newState];
$this->pushState($lexerName, $this->lexer->trans[$newState] ? $newState : $state);
$this->setLexerByName($newLexerName);
$lexerName = $newLexerName;
$state = $this->lexer->initialState;
}
continue;
}
// If newState is marked with recursion flag (alias call), push current state to the context stack
if (($this->lexer->flags[$newState] & Generator::STATE_FLAG_RECURSION) && $state !== $newState) {
$this->pushState($lexerName, $state);
}
// Change the state
$state = $newState;
}
// Adds template end
$fragment .= $this->output->template('', null);
$output[] = $fragment;
// Restore lexer
$this->lexer = $initialLexer;
return implode('', $output);
}
/**
* Sets the output mode.
*
* @param \FSHL\Output $output
* @return \FSHL\Highlighter
*/
public function setOutput(Output $output)
{
$this->output = $output;
return $this;
}
/**
* Sets options.
*
* @param integer $options
* @param integer $tabIndentWidth
* @return \FSHL\Highlighter
*/
public function setOptions($options = self::OPTION_DEFAULT, $tabIndentWidth = 4)
{
$this->options = $options;
if (($this->options & self::OPTION_TAB_INDENT) && $tabIndentWidth > 0) {
// Precalculate a table for tab indentation
$t = ' ';
$ti = 0;
for ($i = $tabIndentWidth; $i; $i--) {
$this->tabs[$i % $tabIndentWidth] = array($t, $ti++);
$t .= ' ';
}
$this->tabIndentWidth = $tabIndentWidth;
} else {
$this->options &= ~self::OPTION_TAB_INDENT;
}
return $this;
}
/**
* Sets the default lexer.
*
* @param \FSHL\Lexer $lexer
* @return \FSHL\Highlighter
*/
public function setLexer(Lexer $lexer)
{
// Generates the lexer cache on fly, if the lexer cache doesn't exist
if (!$this->findCache($lexer->getLanguage())) {
$this->generateCache($lexer);
}
return $this;
}
/**
* Sets the current lexer by name.
*
* @param string $lexerName
* @return \FSHL\Highlighter
* @throws \InvalidArgumentException If the class for given lexer doesn't exist.
*/
private function setLexerByName($lexerName)
{
// Generates the lexer cache on fly, if the lexer cache doesn't exist
if (!$this->findCache($lexerName)) {
// Finds the lexer
$lexerClass = '\\FSHL\\Lexer\\' . $lexerName;
if (!class_exists($lexerClass)) {
throw new \InvalidArgumentException(sprintf('The class for "%s" lexer doesn\'t exist', $lexerName));
}
$lexer = new $lexerClass();
// Generates the lexer cache on fly
$this->generateCache($lexer);
}
return $this;
}
/**
* Tries to find the lexer cache.
*
* @param string $lexerName
* @return boolean
*/
private function findCache($lexerName)
{
// Lexer has been used before
if (isset($this->lexers[$lexerName])) {
$this->lexer = $this->lexers[$lexerName];
return true;
}
// Loads lexer cache
$lexerCacheClass = '\\FSHL\\Lexer\\Cache\\' . $lexerName;
if (class_exists($lexerCacheClass)) {
$this->lexers[$lexerName] = new $lexerCacheClass();
$this->lexer = $this->lexers[$lexerName];
return true;
}
return false;
}
/**
* Generates the lexer cache on fly.
*
* @param \FSHL\Lexer $lexer
* @return \FSHL\Highlighter
*/
private function generateCache(Lexer $lexer)
{
$generator = new Generator($lexer);
try {
$generator->saveToCache();
} catch (\RuntimeException $e) {
$file = tempnam(sys_get_temp_dir(), 'fshl');
file_put_contents($file, $generator->getSource());
require_once $file;
unlink($file);
}
$lexerName = $lexer->getLanguage();
$lexerCacheClass = '\\FSHL\\Lexer\\Cache\\' . $lexerName;
$this->lexers[$lexerName] = new $lexerCacheClass();
$this->lexer = $this->lexers[$lexerName];
return $this;
}
/**
* Outputs a word.
*
* @param string $part
* @param string $state
* @return string
*/
private function template($part, $state)
{
if ($this->lexer->flags[$state] & Generator::STATE_FLAG_KEYWORD) {
$normalized = Generator::CASE_SENSITIVE === $this->lexer->keywords[Generator::KEYWORD_INDEX_CASE_SENSITIVE] ? $part : strtolower($part);
if (isset($this->lexer->keywords[Generator::KEYWORD_INDEX_LIST][$normalized])) {
return $this->output->keyword($part, $this->lexer->keywords[Generator::KEYWORD_INDEX_CLASS] . $this->lexer->keywords[Generator::KEYWORD_INDEX_LIST][$normalized]);
}
}
return $this->output->template($part, $this->lexer->classes[$state]);
}
/**
* Outputs a line.
*
* @param integer $line
* @param integer $maxLineWidth
* @return string
*/
private function line($line, $maxLineWidth)
{
return $this->output->template(str_pad($line, $maxLineWidth, ' ', STR_PAD_LEFT) . ': ', 'line');
}
/**
* Pushes a state to the context stack.
*
* @param string $lexerName
* @param string $state
* @return \FSHL\Highlighter
*/
private function pushState($lexerName, $state)
{
array_unshift($this->stack, array($lexerName, $state));
return $this;
}
/**
* Pops a state from the context stack.
*
* @return array|null
*/
private function popState()
{
return array_shift($this->stack);
}
}
FSHL-2.1.0/FSHL/Lexer.php 0000666 0000000 0000000 00000002555 12022713116 011466 0 ustar setLexer(new \FSHL\Lexer\Php());
echo '';
echo $highlighter->highlight('');
echo '
';
?>
```
Or
```php
';
echo $highlighter->highlight('', new \FSHL\Lexer\Php());
echo '';
?>
```
### Stylesheet ###
A nice default stylesheet is located in the `style.css` file.
## Requirements ##
FSHL requires PHP 5.3 or later.
## Authors ##
* [Jaroslav Hanslík](https://github.com/kukulich)
* Juraj 'hvge' Durech