Tester.php

<?php

namespace Tlf\Lexer\Test;

class Tester extends \Tlf\Tester {

    /**
     * you pass an array like this to runDirectiveTest($grammars, $thingies)
     *
     */
    protected $sample_thingies = [

        // name of test => 
        'Values.CloseArgList'=>[
            // starting ast
            'ast.type'=>'var_assign',
            // starting directive
            'start'=>['php_code'],
            // string to lexify
            'input'=>'"This")',
            // ast tree to expect
            'expect'=>[
                'value'=>'"This"',
                'declaration'=>'"This"',
            ],
        ],

        'Docblock.OneLine'=>[
            // starting directive
            'start'=>['/*'], 
            // string to lexify
            'input'=>"/* abc */",
            // check lexer->previous() for each key=>value()
            'expect.previous'=>[
                // `$lexer->previous('docblock') must == ['type'=>docblock,description=>abc]`
                "docblock"=> [
                    'type'=>'docblock',
                    'description'=>'abc',
                ],
            ],
        ],
    ];

    /**
     * See above sample
     *
     * @param $grammars an array of grammrs to run on the directives
     * @param $thingies an array of directive tests
     * 
     * @return an array of test results where key is test name & value is true/false for pass/fail
     */
    protected function runDirectiveTests($grammars, $thingies){
        $skipped = [];
        /** test descriptions that failed to pass*/
        $failures = [];
        $test_results = [];

        $success_count = 0;

        foreach ($thingies as $description=>$test){
            $startingAst = $test['ast']??null;
            if ($startingAst==null
                &&isset($test['ast.type'])
            ){
                $startingAst = ['type'=>$test['ast.type']];
            }
            $startingDirectives=$test['start'];
            if (!is_array($startingDirectives))$startingDirectives = [$startingDirectives];
            $inputString=$test['input']??file_get_contents($test['file']);

            // a sloppy way to add alternate expectations based upon 'previous'
            $expect=$test['expect'] ?? $test['expect.previous'];
            $compareTo = isset($test['expect']) ? 'tree' : 'previous';


            $run = $this->options['run']??null;
            if ($run===null||$run==$description
                || substr($run,-1)=='*' 
                    && substr($description, 0, (strlen($run) - 1) )
                        == substr($run,0,-1)
            ){
                $this->test($description);
                $lexer = new \Tlf\Lexer();
                if (isset($this->options['version'])){
                    $lexer->version = (float)$this->options['version'];
                } else {
                    $lexer->version = \Tlf\Lexer\Versions::_1;
                }
                if (isset($this->options['stop_loop'])){
                    $lexer->stop_loop = $this->options['stop_loop'];
                }
                foreach ($grammars as $g){
                    $lexer->addGrammar($g, null, false);
                }
                $lexer->addGrammar($grammars[0], 'this', false);

                if (isset($this->options['run']) && substr($run,-1)!='*')$lexer->debug = true;
                $tree = $this->parse($lexer, $inputString, $startingDirectives, $startingAst);

                if (($test['expect_failure']??false)===true){
                    $this->invert();
                    echo "\n  # This grammar feature isn't implemented yet";
                }


                if (isset($this->options['run']) && isset($this->options['print_full'])){
                    print_r($tree);
                    echo "\n\n\n";
                    exit;
                }

                // more of the sloppy way to add alternate expectations
                if ($compareTo=='tree'){
                    $succeeded = $this->compare($expect, $tree);
                    if (!$succeeded){
                        $failures[] = $description;
                    }
                } else if ($compareTo=='previous'){
                    foreach ($expect as $key=>$target){
                        $previous = $lexer->previous($key);
                        if ($previous instanceof \Tlf\Lexer\Ast){
                            $tree = $previous->getTree();
                        } else $tree = $previous;

                        $succeeded = $this->compare($target, $tree);
                        if (!$succeeded){
                            $failures[] = $description;
                        }
                        // $this->compare(var_export($target,true), var_export($tree, true));

                    }
                }


                if ($succeeded)$success_count++;

                if (($test['expect_failure']??false)===true){
                    $this->invert();
                }
                if (($test['is_bad_test']??false)!==false){
                    echo " \033[4;31m"."# BAD SUBTEST"."\033[0m\n";
                    echo '  '.$test['is_bad_test'];
                    echo "\n";
                    // echo "\n\n---------badd test -------------\n\n";
                }
                $test_results[$description] = $succeeded;
            } else {
                $skipped[] = $description;
            }
        }



        echo "\n\n";
        $skipCount = count($skipped);
        if ($skipCount>0){
            echo "$skipCount sub tests were skipped: ". implode(",  ", $skipped)."\n";
        }
        $failedCount = count($failures);
        if ($failedCount>0){
            echo "\n$failedCount sub tests failed: ". implode(",  ", $failures)."\n";
        }

        echo "\n";

        echo "\n$success_count subtests passed";

        echo "\n\n";
        
        echo "Specify `-run \"Test Description\"` to only run what you need";
        echo "\nSpecify `-print_full` print the full ast result when using `-run`";

        return $test_results;
    }

    /**
     * parse input and return an ast tree (without the ast type & the source)
     * @param $lexer a lexer instance
     * @param $toLex the text to lex
     * @param $startingDirectives an array of starting directives like `['php_code']`
     * @param $startingAst (optional) an array like `'type'=>'some_type'`
     */
    protected function parse(\Tlf\Lexer $lexer, string $toLex, array $startingDirectives, ?array $startingAst){
        foreach ($startingDirectives as $name){
            $parts = explode(':',$name);
            $namespace = count($parts)===2 ? $parts[0] : 'this';
            $grammar = $lexer->getGrammar($namespace);
            foreach ($grammar->getDirectives(':'.$name) as $directive){
                $lexer->addDirective($directive);
            }
        }
        
        if ($startingAst!=null){
            $startingAst = new \Tlf\Lexer\Ast($startingAst['type'], $startingAst);
        }

        $ast = $lexer->lex($toLex,$startingAst);

        $tree = $ast->getTree();
        unset($tree['type'], $tree['src']);

        return $tree;
    }
}