function yay_parse(string $source, Directives $directives = null, BlueContext $blueContext = null) : string { if ($gc = gc_enabled()) { gc_disable(); } // important optimization! static $globalDirectives = null; if (null === $globalDirectives) { $globalDirectives = new ArrayObject(); } $directives = $directives ?: new Directives(); $blueContext = $blueContext ?: new BlueContext(); $cg = (object) ['ts' => TokenStream::fromSource($source), 'directives' => $directives, 'cycle' => new Cycle($source), 'globalDirectives' => $globalDirectives, 'blueContext' => $blueContext]; foreach ($cg->globalDirectives as $d) { $cg->directives->add($d); } traverse(midrule(function (TokenStream $ts) use($directives, $blueContext) { $token = $ts->current(); tail_call: if (null === $token) { return; } // skip when something looks like a new macro to be parsed if ('macro' === (string) $token) { return; } // here we do the 'magic' to match and expand userland macros $directives->apply($ts, $token, $blueContext); $token = $ts->next(); goto tail_call; }), consume(chain(token(T_STRING, 'macro')->as('declaration'), optional(repeat(rtoken('/^·\\w+$/')))->as('tags'), lookahead(token('{')), commit(chain(braces()->as('pattern'), operator('>>'), braces()->as('expansion')))->as('body'), optional(token(';'))), CONSUME_DO_TRIM)->onCommit(function (Ast $macroAst) use($cg) { $scope = Map::fromEmpty(); $tags = Map::fromValues(array_map('strval', $macroAst->{'tags'})); $pattern = new Pattern($macroAst->{'declaration'}->line(), $macroAst->{'body pattern'}, $tags, $scope); $expansion = new Expansion($macroAst->{'body expansion'}, $tags, $scope); $macro = new Macro($tags, $pattern, $expansion, $cg->cycle); $cg->directives->add($macro); // allocate the userland macro // allocate the userland macro globally if it's declared as global if ($macro->tags()->contains('·global')) { $cg->globalDirectives[] = $macro; } }))->parse($cg->ts); $expansion = (string) $cg->ts; if ($gc) { gc_enable(); } return $expansion; }
private function layer(string $start, string $end, Parser $parser, $cg) : Parser { return chain(token($start), rtoken('/^···(\\w+)$/')->as('label'), commit(token($end)))->onCommit(function (Ast $result) use($parser, $cg) { $id = $this->lookupCapture($result->label); $cg->parsers[] = (clone $parser)->as($id); }); }
private function mutate(TokenStream $ts, Ast $context, Cycle $cycle, Directives $directives, BlueContext $blueContext) : TokenStream { if ($this->constant) { return $ts; } static $states, $parser; $states = $states ?? new Stack(); $parser = $parser ?? traverse(token(Token::CLOAKED), consume(chain(rtoken('/^··\\w+$/')->as('expander'), either(parentheses(), braces())->as('args')))->onCommit(function (Ast $result) use($states) { $cg = $states->current(); $expander = $result->expander; if (\count($result->args) === 0) { $cg->this->fail(self::E_EMPTY_EXPANDER_SLICE, (string) $expander, $expander->line()); } $context = Map::fromKeysAndValues(['scope' => $cg->cycle->id(), 'directives' => $cg->directives, 'blueContext' => $cg->blueContext]); $expansion = TokenStream::fromSlice($result->args); $mutation = $cg->this->mutate(clone $expansion, $cg->context, $cg->cycle, $cg->directives, $cg->blueContext); $mutation = $cg->this->lookupExpander($expander)($mutation, $context); $cg->ts->inject($mutation); }), consume(chain(rtoken('/^·\\w+|···\\w+$/')->as('label'), operator('···'), optional(parentheses()->as('delimiters')), braces()->as('expansion')))->onCommit(function (Ast $result) use($states) { $cg = $states->current(); $context = $cg->this->lookupContext($result->label, $cg->context, self::E_UNDEFINED_EXPANSION); $expansion = TokenStream::fromSlice($result->expansion); $delimiters = $result->delimiters; // normalize single context if (array_values($context) !== $context) { $context = [$context]; } foreach (array_reverse($context) as $i => $subContext) { $mutation = $cg->this->mutate(clone $expansion, (new Ast(null, $subContext))->withParent($cg->context), $cg->cycle, $cg->directives, $cg->blueContext); if ($i !== 0) { foreach ($delimiters as $d) { $mutation->push($d); } } $cg->ts->inject($mutation); } }), consume(rtoken('/^(T_\\w+·\\w+|·\\w+|···\\w+)$/')->as('label'))->onCommit(function (Ast $result) use($states) { $cg = $states->current(); $context = $cg->this->lookupContext($result->label, $cg->context, self::E_UNDEFINED_EXPANSION); if ($context instanceof Token) { $cg->ts->inject(TokenStream::fromSequence($context)); } elseif (is_array($context) && \count($context)) { $tokens = []; array_walk_recursive($context, function (Token $token) use(&$tokens) { $tokens[] = $token; }); $cg->ts->inject(TokenStream::fromSlice($tokens)); } })); $cg = (object) ['ts' => $ts, 'context' => $context, 'directives' => $directives, 'cycle' => $cycle, 'this' => $this, 'blueContext' => $blueContext]; $states->push($cg); $parser->parse($cg->ts); $states->pop(); $cg->ts->reset(); if ($this->cloaked) { traverse(consume(token(Token::CLOAKED))->onCommit(function (Ast $result) use($cg) { $cg->ts->inject(TokenStream::fromSourceWithoutOpenTag((string) $result->token())); }))->parse($cg->ts); $cg->ts->reset(); } return $cg->ts; }
function testRtokenOnError() { $ts = TokenStream::fromSource("<?php T_ T_TEST "); $this->parseSuccess($ts, rtoken('/<\\?php /'), "T_OPEN_TAG(<?php )"); $this->parseError($ts, rtoken('/^T_\\w+$/'), "Unexpected T_STRING(T_) on line 1, expected MATCH(/^T_\\w+\$/)."); }