示例#1
1
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;
}
示例#2
0
 function testPush()
 {
     $ts = TokenStream::fromSource("<?php 1 2 3 ");
     $ts->push(new Token(T_LNUMBER, '4'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $ts->push(new Token(T_LNUMBER, '5'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $this->assertEquals('<?php 1 2 3 4 5 ', (string) $ts);
     $ts = TokenStream::fromEmpty();
     $ts->push(new Token(T_STRING, 'A'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $ts->push(new Token(T_STRING, 'B'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $this->assertEquals('A B ', (string) $ts);
     $ts->extract($ts->index(), $ts->index()->next->next->next);
     $this->assertEquals(' ', (string) $ts);
     $ts->push(new Token(T_STRING, 'C'));
     $this->assertEquals(' C', (string) $ts);
 }
示例#3
0
 function testastNestedAst()
 {
     $ts = TokenStream::fromSource('<?php
         interface Foo
         {
             public abstract function foo();
             public abstract static function bar();
             function baz();
         }
     ');
     $modifier = either(token(T_PUBLIC), token(T_STATIC), token(T_PRIVATE));
     $modifiers = optional(either(chain($modifier, $modifier), $modifier));
     $ast = chain(token(T_OPEN_TAG), chain(token(T_INTERFACE), token(T_STRING)->as('name'), token('{'), optional(repeat(chain(optional(either(repeat(either(token(T_PUBLIC)->as('public'), token(T_STATIC)->as('static'), token(T_ABSTRACT)->as('abstract'))), always(new Token(T_PUBLIC, 'public'))->as('public')))->as('is'), token(T_FUNCTION), token(T_STRING)->as('name'), token('('), token(')'), token(';')), token('}')))->as('methods'), token('}'))->as('interface'))->parse($ts);
     $this->assertEquals('Foo', (string) $ast->{'interface name'});
     $this->assertEquals('foo', (string) $ast->{'interface methods 0 name'});
     $this->assertEquals('public', (string) $ast->{'interface methods 0 is public'});
     $this->assertEquals('abstract', (string) $ast->{'interface methods 0 is abstract'});
     $this->assertEmpty($ast->{'interface methods 0 is static'});
     $this->assertEquals('bar', (string) $ast->{'interface methods 1 name'});
     $this->assertEquals('public', (string) $ast->{'interface methods 1 is public'});
     $this->assertEquals('abstract', (string) $ast->{'interface methods 1 is abstract'});
     $this->assertEquals('static', (string) $ast->{'interface methods 1 is static'});
     $this->assertEquals('baz', (string) $ast->{'interface methods 2 name'});
     $this->assertEquals('public', (string) $ast->{'interface methods 2 is public'});
     $this->assertNull($ast->{'interface methods 2 is abstract'});
     $this->assertNull($ast->{'interface methods 2 is static'});
 }
示例#4
0
 function testPush()
 {
     $ts = TokenStream::fromSource("<?php 1 2 3 ");
     $ts->push(new Token(T_LNUMBER, '4'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $ts->push(new Token(T_LNUMBER, '5'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $this->assertEquals('<?php 1 2 3 4 5 ', (string) $ts);
     $ts = TokenStream::fromSource('<?php ');
     $ts->push(new Token(T_STRING, 'A'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $ts->push(new Token(T_STRING, 'B'));
     $ts->push(new Token(T_WHITESPACE, ' '));
     $this->assertEquals('<?php A B ', (string) $ts);
 }
示例#5
0
function yay_parse(string $source, string $salt) : string
{
    start:
    if ($gc = gc_enabled()) {
        gc_disable();
    }
    $ts = TokenStream::fromSource($source);
    $directives = new Directives();
    $cycle = new Cycle($salt);
    declaration:
    $from = $ts->index();
    if (!($token = $ts->current())) {
        goto end;
    }
    if ($token->contains('macro')) {
        $declaration = $token;
        $ts->next();
        goto tags;
    }
    goto any;
    tags:
    $tags = [];
    while (($token = $ts->current()) && preg_match('/^·\\w+$/', (string) $token)) {
        $tags[] = $token;
        $ts->next();
    }
    pattern:
    $index = $ts->index();
    $pattern = [];
    $level = 1;
    if (!($token = $ts->current()) || !$token->is('{')) {
        goto any;
    }
    $ts->next();
    while (($token = $ts->current()) && ($level += LAYER_DELIMITERS[$token->type()] ?? 0)) {
        $pattern[] = $token;
        $token = $ts->step();
    }
    if (!$token || !$token->is('}')) {
        $ts->jump($index);
        (new Error(new Expected(new Token('}')), $ts->current(), $ts->last()))->halt();
    }
    $ts->next();
    __:
    // >>
    $index = $ts->index();
    $operator = '>>';
    $max = strlen($operator);
    $buffer = '';
    while (mb_strlen($buffer) <= $max && ($token = $ts->current())) {
        $buffer .= (string) $token;
        $ts->step();
        if ($buffer === $operator) {
            $ts->skip(T_WHITESPACE);
            goto expansion;
        }
    }
    $ts->jump($index);
    (new Error(new Expected(new Token(token::OPERATOR, $operator)), $ts->current(), $ts->last()))->halt();
    expansion:
    $index = $ts->index();
    $expansion = [];
    $level = 1;
    if (!($token = $ts->current()) || !$token->is('{')) {
        $ts->jump($index);
        (new Error(new Expected(new Token('}')), $ts->current(), $ts->last()))->halt();
    }
    $ts->next();
    while (($token = $ts->current()) && ($level += LAYER_DELIMITERS[$token->type()] ?? 0)) {
        $expansion[] = $token;
        $token = $ts->step();
    }
    if (!$token || !$token->is('}')) {
        $ts->jump($index);
        (new Error(new Expected(new Token('}')), $ts->current(), $ts->last()))->halt();
    }
    // optional ';'
    $token = $ts->next();
    if ($token->is(';')) {
        $ts->next();
    }
    // cleanup
    $ts->unskip(...TokenStream::SKIPPABLE);
    $ts->skip(T_WHITESPACE);
    $ts->extract($from, $ts->index());
    $directives->add(new Macro($declaration->line(), $tags, $pattern, $expansion, $cycle));
    goto declaration;
    any:
    if ($token) {
        $directives->apply($ts);
        $ts->next();
    }
    goto declaration;
    end:
    $expansion = (string) $ts;
    if ($gc) {
        gc_enable();
    }
    return $expansion;
}
示例#6
0
function expand(TokenStream $ts, Context $context) : TokenStream
{
    $ts = TokenStream::fromSource(yay_parse('<?php ' . (string) $ts, $context->get('directives'), $context->get('blueContext')));
    $ts->shift();
    return $ts;
}