Skip to content

Instantly share code, notes, and snippets.

@marcioAlmada
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save marcioAlmada/b0621c4798468ae37720 to your computer and use it in GitHub Desktop.
Save marcioAlmada/b0621c4798468ae37720 to your computer and use it in GitHub Desktop.
PHPP
--TEST--
Simple ignore test
--FILE--
HTML here, this should be preserved: @ "debug" public
<?php
%{
ignore @;
ignore public;
ignore "debug";
}% // this comment should be preserved
@test("debug");
class X {
public function test(){}
}
?>
--EXPECTF--
HTML here, this should be preserved: @ "debug" public
<?php
// this comment should be preserved
test();
class X {
function test(){}
}
?>
<?php declare(strict_types=1);
namespace phpp;
use Exception;
function parse($source) : string {
$tstream = tstream::fromSource($source);
$directives = new directives();
in_php: while ($tstream->valid()) {
$tstream->skip();
if ($tstream->try('%', '{')) {
$tstream->mark()->consume('%', '{');
goto in_preprocessor;
}
$directives->applyOn($tstream);
$tstream->next();
}
in_preprocessor: while ($tstream->valid()) {
$tstream->skip();
if ($tstream->try('}', '%')) {
$tstream->consume('}', '%');
$tstream->deleteFromMark();
goto in_php;
}
$directive_name = $tstream->consume('T_STRING')->value;
$directive = (directives::lookup($directive_name))::parse($tstream);
$directives->push($directive);
$tstream->next();
}
return (string) $tstream;
}
class tstream {
const SKIPPABLE = ['T_WHITESPACE', 'T_COMMENT', 'T_DOC_COMMENT', 'T_INLINE_HTML'];
protected $index = 0, $mark = [-1], $tokens;
private function __construct(array $tokens) {
if (! $tokens)
throw new Exception("Empty token stream.");
$this->tokens = $tokens;
if (reset($this->tokens)->type !== 'START')
array_unshift($this->tokens, new token(['START', null, null]));
if (end($this->tokens)->type !== 'END')
array_push($this->tokens, new token(['END', null, null]));
}
function __toString() : string {
return implode('', $this->tokens);
}
function current() : token {
return $this->tokens[$this->index];
}
function valid() {
return $this->current()->type !== 'END';
}
function step($step = 1) : token {
if ($this->valid()) $this->index += $step;
return $this->current();
}
function skip(array $skippable = self::SKIPPABLE) : token {
while (in_array($this->current()->type, $skippable)) $this->step();
return $this->current();
}
function next() : token {
$this->step();
$this->skip();
return $this->current();
}
function try(string ...$types) : bool {
$delta = $this->shift(...$types);
$this->index -= $delta; // rewind so try has no side effects
return $delta === count($types);
}
function consume(string ...$types) : token {
$delta = $this->shift(...$types);
if ($delta !== count($types)) {
$found = $this->current()->toError();
$expected = $types[$delta];
$this->index -= $delta; // rewind so bad consume has no side effects
throw new Exception(
"Unexpected {$found}, expected '{$expected}'.");
}
return $this->tokens[$this->index - ($delta ? 1 : 0)];
}
protected function shift(string ...$types) : int {
$index = $this->index;
foreach ($types as $type)
if ($this->current()->type === $type && $this->step()) continue;
else break;
return $this->index - $index;
}
function mark() : self {
$this->mark[] = $this->index;
return $this;
}
function deleteFromMark() : tstream {
return self::fromSlice(array_splice($this->tokens, ...$this->delta()));
}
protected function delta() : /* [int, int] */ array {
$mark = end($this->mark);
if (! isset($this->tokens[$mark]))
throw new Exception(
"Invalid token interval {$mark}...{$this->index}");
array_pop($this->mark);
$delta = ($this->index - $mark) ?: 1;
$this->index = $mark; // rewind to current mark
return [$this->index, $delta];
}
static function fromSource(string $source) {
$tokens = token_get_all($source) ?? [];
foreach ($tokens as $index => $token) $tokens[$index] = new token($token);
return self::fromSlice($tokens);
}
static function fromSlice(/** token[] */ array $tokens) {
return new self($tokens);
}
}
class token {
public $type, $value, $line;
protected $literal = false;
function __construct($token) {
if (is_array($token)) {
$token[0] = is_int($token[0]) ? token_name($token[0]) : $token[0];
list($this->type, $this->value, $this->line) = $token;
return;
}
$this->value = $this->type = $token;
$this->literal = true;
}
function toError() {
return $this->literal ? "'{$this->value}'"
: "{$this->type}({$this->value})";
}
function __toString() {
return (string) $this->value;
}
function __get($property) {
return $this->$property;
}
}
interface directive {
function __construct(token ...$token);
function __invoke(tstream $tstream) : bool;
}
class directives {
const LOOKUP = [
'rule' => rule::class,
'ignore' => ignore::class,
];
protected $directives = [];
function push(directive $directive) {
$this->directives[] = $directive;
}
function applyOn(tstream $tstream) {
foreach ($this->directives as $directive) $directive($tstream);
}
static function lookup(string $directive) {
if (! isset(static::LOOKUP[$directive]))
throw new Exception("Undefined directive {$directive}.");
return static::LOOKUP[$directive];
}
}
class ignore implements directive {
protected $ignorable;
function __construct(token ...$token) {
list($this->ignorable) = $token;
}
function __invoke(tstream $tstream) : bool {
return $tstream->current()->type === $this->ignorable->type
&& $tstream->current()->value === $this->ignorable->value
&& $tstream->mark()->deleteFromMark()
&& $tstream->step(-1);
}
static function parse(tstream $tstream) : directive {
$tstream->skip();
$ignorable = clone($tstream->current());
$tstream->next();
$tstream->consume(';');
return new self($ignorable);
}
}
class rule implements directive {
protected $name, $pattern, $expansion;
function __construct(token ...$token) {
list($this->name, $this->pattern, $this->expansion) = $tokens;
}
function __invoke(tstream $tstream) : bool {
// TODO
return false;
}
static function parse(tstream $tstream) : directive {
$tstream->skip();
$tstream->consume('{');
return new self(0, 0, 0);//($name, $pattern, $expansion);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment