Instructions.php

<?php

namespace Tlf\LexerTrait;

/**
 * Handles executing individual instructions/commands.
 * @todo move the mapped methods into their own trait
 */
trait Instructions {

    /**
     * @todo Add extensibility feature to add to the method map & the switch/case
     */
    public function execCommand($command, $args, $directive, $instructionSetName,  &$directiveList){

        $methodMap = $this->getCmdMethodMap();
        if (isset($methodMap[$command])){
            $method = $methodMap[$command];
            $this->$method($args, $directive, $instructionSetName, $directiveList);
            return;
        }
        $this->executeSwitchCommand($command, $args, $directive, $instructionSetName, $directiveList);

    }

    /**
     * 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 executeSwitchCommand($command, $args, $directive, $isn, &$directiveList){
        $arg1 = $args[0] ?? false;
        $token = $this->token;
        $ast = $this->getHead();
        $debug = $this->debug;


        //@export_start(Commands.SwitchCase)
        switch ($command){
            ///
            // comands for debugging
            ///
            case "debug.die":
            case "die":
                var_dump($args);
                exit;
                break;
            case "debug.print":
            case "print":
                var_dump($args);
                break;
            /**
             * Run commands of another directive. Does not run 'match' by default
             *
             * @arg :directive.isn
             * @arg literal string 'match' to keep the match command from the inherited directive
             *
             * @example directive.inherit :varchars.start
             */
            case "directive.inherit":
            case "inherit":
                $arg2 = $args[1]??'';
                $parts = explode('.', $arg1);
                $name = $parts[0];
                $isn = $parts[1];
                $directives = $directive->_grammar->getDirectives($name);
                foreach ($directives as $d){
                    if ($arg2!=='match'){
                        unset($d->$isn['match']);
                    }
                    // print_r($d
                    $this->processInstructions($d, $isn, $directiveList);
                    if ($arg2==='match'&&isset($d->$isn['_matches'])){
                        $directive->$isn['_matches'] = $d->$isn['_matches'];
                    }
                }
                echo "\n\033[0;32mContinue ".$directive->_name."\033[0m";
                break;
            //
            // commands with non-namespaced shorthands
            //
            case "directive.start":
            case "start":
                $this->directiveStarted($directive);
                break;
            case "directive.stop":
            case "stop":
                $this->directiveStopped($directive);
                break;
            case "token.rewind":
            case "rewind":
                $token->rewind($arg1);
                break;
            case "token.forward":
            case "forward":
                $token->forward($arg1);
                break;
            /**
             * Halt execution of current directive (don't run its following instructions). Useful for preventing overrides from being executed
             */
            case "directive.halt":
            case "halt":
                $this->haltInstructions();
                break;
            case "halt.all":
                $this->haltAll();
                break;
            //
            // namespaced commands
            //
            case "previous.append":
                if (!is_array($arg1))$arg1 = [$arg1];
                foreach ($arg1 as $index=>$keyForPrevious){
                    $this->appendToPrevious($keyForPrevious, $token->buffer());
                }
                break;
            case "previous.set":
                $value = $args[1] ?? $token->buffer();
                if ($value ===true)$value = $token->buffer();
                $this->setPrevious($arg1, $value);
                break;
            case "directive.stop_others":
                foreach ($directiveList['started'] as $started){
                    if ($started!=$directive){
                        $this->directiveStopped($started, $list);
                    }
                }
                break;
            case "directive.pop":
                $arg1 = (int)$arg1;
                if ($arg1===0)echo "\n    --no directives popped.";
                while ($arg1-- > 0){
                    $this->popDirectivesLayer();
                }
                break;

            //
            // buffer commands
            //
            case "buffer.clear":
                $token->clearBuffer();
                break;
            case "buffer.clearNext":
                $amount = (int)$arg1;
                $remove = 0;
                while ($amount-->0){
                    if ($token->next())$remove++;
                }
                $token->setBuffer(substr($token->buffer(),0,-$remove));
                break;
            case "buffer.appendChar":
                $token->setBuffer($token->buffer() . $arg1);
                break;
            //
            // ast commands
            //
            case "ast.pop":
                $this->popHead();
                break;
            case "ast.set":
                if (isset($args[1])){
                    $value = $this->executeMethodString($args[1]);
                } else $value = $token->buffer();
                $this->getHead()->set($arg1, $value);
                break;
            /**
             * Save the currrent buffer to the given key
             * @arg key to push to
             */
            case "ast.push":
                $key = $arg1;
                $toPush = $token->buffer();
                $ast = $this->getHead();
                $ast->add($key,$toPush);
                break;
            case "ast.append":
                $key = $arg1;
                if (isset($args[1])&&is_string($args[1])){
                    var_dump($args[1]);
                    $value = $this->executeMethodString($args[1]);
                } else $value = $token->buffer();
                $ast = $this->getHead();
                $src = $ast->get($key);
                $new = $src . $value;
                $ast->set($key, $new);
                break;

            default:
                throw new \Exception("\nAction '$command' not handled yet. Maybe it needs to be a callable. Prepend `this:` to call a method on your grammar.");
        }
        //@export_end(Commands.SwitchCase)
    }

    /**
     *
     * Convert `_object:method arg1 arg2` to a call to `$object->method(arg1,arg2)`
     *
     * @param $input any input from an instruction.
     * @return the $input or value returned by calling the object method
     *
     * @throws if the input starts with `_` but does not reference a valid object+method
     * @example `_lexer:previous docblock` will call & return `$lexer->previous('docblock')`
     */
    public function executeMethodString($input){
        if ($input[0]!='_')return $input;
        $command = substr($input,1);
        $parts = explode(":",$command);
        if (count($parts)==1)return $input;
        $objects = [
            'lexer'=>$this,
            'token'=>$this->token,
            'ast'=>$this->getHead(),
        ];

        $object = $objects[$parts[0]] ?? null;
        if ($object==null)throw new \Exception("No object found for '$input");
        $argParts = explode(' ',$parts[1]);
        $method = array_shift($argParts);
        if (!method_exists($object,$method))throw new \Exception("Method for '$input' not found");
        $realValue = $object->$method(...$argParts);
        return $realValue;
    }

}