Commands.php

<?php

namespace Tlf\Lexer;

/**
 * Version 2 attempt at making an easily extensible command system
 *
 */
class Commands {

    public function __construct(protected \Tlf\Lexer $lexer){
    }

    /**
     * 
     */
    public function can_run(string $command_name): bool {
        $clean_command_name = str_replace('.','_',$command_name);
        $method_name = "run_".$clean_command_name;
        //var_dump($method_name);
        if (method_exists($this,$method_name))return true;
        return false;
    }


    /**
     * Execute a simple command thats defined inside our switch/case
     *
     * @param $command a string like `halt`, `match`, or one of many others
     * @param $args an array of arguments for the command
     * @param $directive the directive currently being executed
     * @param $isn the instruction set name currently being executed on the directive. Like `start` or `stop`
     * @return boolean true if the command execution was successful, false otherwise
     */
    public function run(string $command, array $args, object $directive, string $isn, array &$directiveList) {
        $clean_command_name = str_replace('.','_',$command);
        $method_name = "run_".$clean_command_name;
        if (!method_exists($this,$method_name)){
            throw new \BadMethodCallException("Instruction '$command' (method $method_name) does not exist on '".get_class($this)."'.");
        }

        $this->$method_name($command, $args, $directive, $isn, $directiveList);
    }

    /**
     *
     * Move the current directive to the start of the named stack. This directive must already be in the named stack.
     *
     * @usage `stack.move_to_front unstarted` - may be 'started', 'match', or 'unstarted'
     *
     * @arg name_of_stack The name of the stack to move this directive to the front of.
     */
    public function run_stack_move_to_front(string $command, array $args, object $directive, string $isn, array &$directiveList){

        $stack_name = $args[0];
        $directive_name = $directive->_name;
        $source_directive = $directiveList[$stack_name][$directive_name];
        unset($directiveList[$stack_name][$directive_name]);
        $directiveList[$stack_name] = [$directive_name => $source_directive] + $directiveList[$stack_name];
    }


    /**
     *
     * Get the previous 'key', which MUST be an object, and set `object->property = value` on that object.
     *
     * @usage `previous.assign [key] [property] [value]` 
     * @usage `'previous.assign [key] [property] !' =>  '_object:method'` 
     *
     * @arg1 key the key in the 'previous' key/value list
     * @arg2 property the property name on the object you're retrieving from the previous stack.
     * @arg3 the value to set to the object's property.
     */
    public function run_previous_obj_set(string $command, array $args, object $directive, string $isn, array &$directiveList){
        $key = $args[0];
        $property = $args[1];
        $value = $args[2];
        $obj = $this->lexer->previous($key);
        $obj->$property = $value;
    }

    /**
     *
     * Get the previous 'key', which MUST be an an AST object, and append value to the named array property. i.e. `object->property[] = value` on that object.
     *
     * @usage `previous.ast_push [key] [property] [value]` 
     * @usage `'previous.ast_push [key] [property] !' =>  '_object:method'` 
     *
     * @arg1 key the key in the 'previous' key/value list
     * @arg2 property the array property name on the object you're retrieving from the previous stack.
     * @arg3 the value to set to the object's property.
     */
    public function run_previous_ast_push(string $command, array $args, object $directive, string $isn, array &$directiveList){
        $key = $args[0];
        $property = $args[1];
        $value = $args[2];
        $obj = $this->lexer->previous($key);

        //if (!isset($obj->$property))$obj->$property = [];

        $obj->push($property, $value);


        //$obj->$property = $obj->$property + [$value];

        //var_dump($obj);
        //exit;

    }

    /**
     *
     * Trim whitespace from around the buffer. Does not modify the token state, except for the actual buffer.
     *
     * @usage `buffer.trim` 
     *
     */
    public function run_buffer_trim(string $command, array $args, object $directive, string $isn, array &$directiveList){
        $token = $this->lexer->token;
        $trimmed_buffer = trim($token->buffer());
        $token->setBuffer($trimmed_buffer);
    }

    /**
     *
     * Add a directive to the current directive layer's 'started' list.
     *
     * @usage `directive.add_started grammar:directive_name`. Can provide overrides, same as then
     *
     */
    public function run_directive_add_started(string $command, array $args, object $directive, string $isn, array &$directiveList){
        $targetDirectiveName = $args[0];
        $overrides = $args[1];
        if (!is_array($overrides))$overrides = [];


        // $grammar = $this->grammars[$directive->_grammar];
        $grammar = $directive->_grammar;


        $directives = $grammar->getDirectives($targetDirectiveName, $overrides);
        foreach ($directives as $d){
            $this->lexer->addDirective($d, true);
        }
    }


    /**
     * 
     */
    public function run_buffer_match_next(string $command, array $args, object $directive, string $isn, array &$directiveList){
        var_dump("buffer match next");
        exit;
    }

    /**
     * Halts a directive if the next characters match.
     *
     * @usage `'buffer.not_match_next [num_chars]' => '/regex/'`. 
     *
     * @arg int num_chars
     * @arg string regex pattern
     */
    public function run_buffer_not_match_next(string $command, array $args, object $directive, string $isn, array &$directiveList){
        //var_dump("buffer not match next");
        //exit;

        $num_chars = (int) $args[0];
        $pattern = (string) $args[1];

        $token = $this->lexer->getToken();
        $remainder = $token->remainder();
        $chars = substr($remainder,0,$num_chars);
        
        if (preg_match($pattern, $chars)){
            $this->lexer->haltInstructions();
            return;
        }
    }

    /**
     *
     * Halts a directive if the current buffer matches
     *
     * 'not_match' as in 'this must not match'
     *
     * @usage `not_match /regex/`. 
     *
     * @arg pattern a regex pattern
     *
     */
    public function run_not_match(string $command, array $args, object $directive, string $isn, array &$directiveList){
        $token = $this->lexer->getToken();
        $pattern = $args[0];

        if (preg_match($pattern, $token->buffer())){
            $this->lexer->haltInstructions();
            return;
        }
    }

}