DocblockGrammar.php

<?php

namespace Tlf\Lexer;

class DocblockGrammar extends Grammar {

    protected $directives = [

        '/*'=>[
            'start'=>[
                'match'=>'/\\/\*/',
                'buffer.clear',
            ],
            'stop'=>[
                'match'=>'*/',
                // 'directive.pop 1',
                'rewind 2',
                'this:processDocblock',
                'forward 2',
                'buffer.clear',
            ]
        ],
    ];

    public function getNamespace(){
        return 'docblock';
    }

    public function onGrammarAdded($lexer){
    }
    public function onLexerStart($lexer, $ast, $token){
    }
    public function onLexerEnd($lexer, $ast, $token){
        $prev = $lexer->previous('docblock');
        if ($prev!==null){
            $ast->add('docblock', $prev);
        }
    }

    public function processDocblock($lexer, $ast, $token, $directive){
        // if ($lexer->loop_count==5483){
            // var_dump($lexer->loop_count);
            // var_dump("k");
            // echo "\n\n\n";
            // var_dump($token->buffer());
            // exit;
        // }
        // if (false){
        //     var_dump($lexer->loop_count);
        //     var_dump("k");
        //     echo "\n\n\n";
        //     var_dump($token->buffer());
        //     // exit;
        // }
        $body = $token->buffer();
        $lines = $this->cleanIndentation($body);


        $ast = $this->buildAstWithAttributes($lines);
        
        $lexer->setPrevious('docblock',$ast);
        if ($lexer->getHead()->_type=='expression'){
            $lexer->getHead()->set('docblock', $ast);
        }
    }

    public function buildAstWithAttributes($lines){
        // echo "\n\n\n++\n";
        $docblock = new Ast('docblock');
        $docblock->set('description', '');
        $curAttr = false;

        $head = $docblock;
        $key = 'description';
        $newLine = false;
        foreach ($lines as $index=>$line){
            if ($index>0)$newLine = true;
            if (preg_match('/^\s*\@([a-zA-Z\_0-9]+)[^a-zA-Z\_0-9]/',$line, $match)){
    
                if ($head->type=='attribute'){
                    $desc = $head->get('description');
                    $descLines = explode("\n", $desc);
                    while (trim($lastLine = array_pop($descLines)) == ''){
                    }
                    if (trim($lastLine)!=false){
                        $descLines[] = $lastLine;
                    }
                    $head->set('description', implode("\n", $descLines));
                }

                $line = substr($line, strlen($match[0])-1);
                $line = trim($line);
                $head = $match[1];
                $isAttr = true;
                $attr = new Ast('attribute');
                $attr->set('name', $match[1]);
                $attr->set('description', '');
                $head = $attr;
                $docblock->add('attribute', $attr);
                $newLine = false;
            }

            if ($newLine)$line = "\n$line";
            $head->append('description', $line);
        }

        // echo "\n\n\n++\n";
        return $docblock;
    }
    public function cleanIndentation($body){
        if (substr($body,0)=='*')$body = substr($body,1);
        // remove * from lines that only have whitespace and *
        $body = preg_replace("/^(\s*)\*(\s*)$/m", '\1 \2', $body);
        // remove * from all lines
        $body = preg_replace("/^(\s*)\*(\s*)/m", '\1 \2', $body);

        //separate first line
        $body = str_replace("\r\n", "\n", $body);
        $lines = explode("\n", $body);
        $firstLine = array_shift($lines);
        while (count($lines)>0&&trim($lines[0]) === ''){
            array_shift($lines);
        }

        // find smallest indent
        $smallestIndent = 9999;
        foreach ($lines as $index=>$line){
            if (trim($line)=='')continue;
            preg_match('/^(\s*)/', $line, $match);
            $indent = $match[1];
            if ($smallestIndent==-1)$smallestIndent = strlen($indent);
            else if ($smallestIndent>strlen($indent))$smallestIndent = strlen($indent);
        }
        // remove indent
        foreach ($lines as $index=>$line){
            if (strlen($line) < $smallestIndent)$lines[$index] = '';
            else $lines[$index] = substr($line,$smallestIndent);
        }
        // re-insert first line
        if (strlen($firstLine=trim($firstLine))>0){
            array_unshift($lines, trim($firstLine));
        }

        //remove trailing blank lines
        $lastLine = false;
        while (count($lines)>0 &&
            trim($lastLine = array_pop($lines))===''
        ){
            // if (trim($lastLine)=='')continue;
        }
        if (is_string($lastLine)){
            $lines[] = $lastLine;
        }

        return $lines;
    }
}