InstructionProcessor.php

<?php

namespace Tlf\LexerTrait;

/**
 * Contains logic to process & execute directive instructions
 *
 */
trait InstructionProcessor {

    /**
     * when true, no more instructions will be processed for the current directive.
     */
    protected $haltInstructions = false;
    /**
     * when true, no more directives will be processed during the current loop
     */
    protected $haltAll = false;

    /**
     * Combine cli-style arguments with a php variable
     * - Pass `...` for each php var indice to be an argument
     * - Pass `// any comment` to do comments
     * - TODO: Pass `!` to expand the php var as a call like `_lexer:previous docblock`
     * - TODO: Pass `[]` to expand the php var into multiple separate instructions
     *
     * @arg $cliArgs the cli arguments (not the namespace:command)
     * @arg $phpArg the php variable declared in the instruction
     * @return an array of arguments
     */
    public function getInstructionArgs(array $cliArgs, $phpArg){
        $allArgs = []; 

        $appendPhpArg = true;

        foreach ($cliArgs as $arg){
            if ($arg=='...'){
                $allArgs = [...$allArgs, ...$phpArg];
                $appendPhpArg = false;
            } else if ($arg == '//'){
                break; 
            } 
            else if ($arg == '[]'){
                //expand php var so we run multiple commands
                throw new \Exception("`[]` is reserved for future features and cannot be used as a directive instruction argument yet.");
            }
            else if ($arg == '!'){
                $appendPhpArg = false;
                $allArgs[] = $this->executeMethodString($phpArg);
                //treat the phparg as a `_namespace:method arg1 arg2` call
                // throw new \Exception("`!` is reserved for future features and cannot be used as a directive instruction argument yet.");
            } 
            else {
                $allArgs[] = $arg;
            }
        }

        if ($phpArg===false||!$appendPhpArg)return $allArgs;

        $allArgs[] = $phpArg;
        return $allArgs;
    }

    /**
     * Execute the command
     * @param $command a string command like `namespace:command`
     * @param $args array of arguments
     * @return void
     */
    public function executeInstruction(string $command, array $args, $directive, $instructionSetName, &$list){
        $isn = $instructionSetName;

        //@export_start(Commands.NamespaceTargets)
        $namespaceTargets = [
            'lexer'=>$this,
            'token'=>$this->token,
            'ast'=>$this->getHead(),
        ];
        $grammarTargets = $this->grammars;
        $grammarTargets['this'] = $directive->_grammar ?? null;
        //@export_end(Commands.NamespaceTargets)

        $commandParts = explode(':', $command);


        if ($this->debug){
            $this->debug_instruction_set($command, $commandParts, $args);
        }


        if (count($commandParts)==1){
            $namespace = 'this';
            $method = 'execCommand';
            // $args = [$command, $args, $directive, $instructionSetName, $list];
            $this->execCommand($command, $args, $directive, $isn, $list);
            return;
        } 
        $namespace = $commandParts[0];
        $method = $commandParts[1];


        if (isset($namespaceTargets[$namespace])){
            $object = $namespaceTargets[$namespace];
            $object->$method(...$args);
            return;
        } 
        
        if (isset($grammarTargets[$namespace])){
            $object = $grammarTargets[$namespace];
            /** @tip Grammar methods receive `($lexer, $headAst, $token, $directive)` if no other args are present. */
            $object->$method($this, $this->getHead(), $this->token, $directive, $args);
            return;
        }

        throw new \Exception("No object found for '$namespace' in command '$command'.");
    }


    /**
     *
     * @param $directive a directive
     * @param $instructionSetName the name of the instruction set like 'start', 'match', or 'stop'
     */
    public function processInstructions(object $directive, string $instructionSetName, &$list){

        $instructions = $directive->$instructionSetName ?? null;
        if ($instructions==null)return;
        if ($this->debug){
            //terminal green, the message, then terminal color off
            echo "\n\033[0;32mProcess ".$directive->_name."[$instructionSetName]\033[0m";
        }
        
        /**
         * Parse and execute instructions
         *
         * @input $instructions an array of instructions on a directive. Keys starting with an underscore (`_`) are for meta data and are not processed 
         * @param $cliCommandString the string portion of an instruction. Can be a key, or can be the value for a numeric indice.
         * @param $phpArg if $cliCommandString is an array key, then $phpArg is its value. Otherwise, $phpArg is boolean `true`
         */
        foreach ($instructions as $cliCommandString=>$phpArg){
            if ($this->haltInstructions){
                break;
            } elseif ($phpArg===false
                ||$cliCommandString[0]=='_'){
                /** @tip the $phpArg being false means 'dont run this command' */
                /** @tip Keys starting with `_` are meta data and are skipped */ 
                // if ($this->debug){
                    // echo "\n  skip $cliCommandString";
                // }
                continue;
            }

            $cliCommandArgs = explode(' ',$cliCommandString);
            $args = $this->getInstructionArgs(array_slice($cliCommandArgs,1), $phpArg);


            // var_dump("EXECUTE ".$cliCommandArgs[0]);
            $this->executeInstruction($cliCommandArgs[0], $args, $directive, $instructionSetName, $list);
            // var_dump("DONE EXECUTE ".$cliCommandArgs[0]);
            // if ($this->haltInstructions){
                // break;
            // }
        }
    }

    public function debug_instruction_set($cliCommandString, $cliCommandParts, $args){
        $debug_str = '';
        $debug_args_src = $args;
        $debug_args = [];
        foreach ($debug_args_src as $debug_index=>$debug_value){
            if (is_array($debug_value)){
                $debug_args_str = '';
                foreach ($debug_value as $sub_index=>$sub_value){
                    if (!is_int($sub_index)){
                        $debug_args_str .= $sub_index.'=>';
                    }
                    if (is_array($sub_value)){$sub_value = '[array('.count($sub_value).')]';}
                    else if (strlen($sub_value)>15){
                        $sub_value = substr($sub_value,0,15).'...';
                    } 
                    if (is_string($sub_value))$sub_value = '"'.$sub_value.'"';
                    $debug_args_str .= $sub_value.', ';
                }
                $debug_args[] = $debug_args_str;
            } else if (is_object($debug_value)){
                $debug_args[] = get_class($debug_value);
            } else {
                $debug_args[] = $debug_value;
            }
        }
        $debug_values_str = implode(", ", $debug_args);
        $debug_arg_count = count($debug_args_src);
        echo "\n  $cliCommandString [args($debug_arg_count)] $debug_values_str"; 
        // print_r($allArgs);
        // exit;
    }
}