<?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;
}
}