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