Minor changes

This commit is contained in:
Netkas 2023-03-05 13:57:57 -05:00
parent 05cc358eea
commit e9d033044c
27 changed files with 949 additions and 94 deletions

2
.idea/php.xml generated
View file

@ -15,6 +15,8 @@
<path value="/etc/ncc" /> <path value="/etc/ncc" />
<path value="/var/ncc/packages/net.nosial.optslib=1.0.1" /> <path value="/var/ncc/packages/net.nosial.optslib=1.0.1" />
<path value="/var/ncc/packages/net.nosial.loglib=1.0.0" /> <path value="/var/ncc/packages/net.nosial.loglib=1.0.0" />
<path value="/var/ncc/packages/net.nosial.rtex_filesystem=1.0.0" />
<path value="/var/ncc/packages/com.cheprasov.php_redis_client=1.10.0" />
</include_path> </include_path>
</component> </component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" /> <component name="PhpProjectSharedConfiguration" php_language_level="8.2" />

View file

@ -6,6 +6,7 @@
<entryData> <entryData>
<resourceRoots> <resourceRoots>
<path value="file://$PROJECT_DIR$/instructions" /> <path value="file://$PROJECT_DIR$/instructions" />
<path value="file://$PROJECT_DIR$/types" />
</resourceRoots> </resourceRoots>
</entryData> </entryData>
</entry> </entry>

BIN
InstructionInterface.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

View file

@ -10,13 +10,28 @@ or you are executing programs written by users,
but you don't want to give them the ability to but you don't want to give them the ability to
execute arbitrary code on your system execute arbitrary code on your system
## Table of Contents
<!-- TOC -->
* [RTEX Engine](#rtex-engine)
* [Table of Contents](#table-of-contents)
* [How does it work?](#how-does-it-work)
* [Instructions](#instructions)
* [Methods & Namespaces](#methods--namespaces)
* [Builtin Instruction Sets](#builtin-instruction-sets)
* [License](#license)
* [Contributing](#contributing)
<!-- TOC -->
-----------------------------------------------------------------------------
## How does it work? ## How does it work?
RTEX Engine operates on the simple principle of executing RTEX Engine operates on the simple principle of executing
instructions in order, and then moving on to the next instructions in order, and then moving on to the next
instruction. instruction.
[README.md](README.md)
### Instructions ### Instructions
Instructions are simply associative arrays that contain Instructions are simply associative arrays that contain
@ -35,8 +50,6 @@ functions do not provide a way to interact with the host
system or the filesystem, but you can extend the engine system or the filesystem, but you can extend the engine
with your own methods and namespaces with your own methods and namespaces
#### JSON Representation
Since instructions are associative arrays, they can be Since instructions are associative arrays, they can be
represented in JSON, which is a common format for data represented in JSON, which is a common format for data
transfer. The following is an example of a set of transfer. The following is an example of a set of
@ -86,6 +99,7 @@ instructions that is shown above in JSON format:
] ]
``` ```
### Methods & Namespaces ### Methods & Namespaces
Methods are different to instructions in the terms Methods are different to instructions in the terms
@ -106,6 +120,18 @@ when they are invoked.
***Note:*** This functionality is a WIP, more ***Note:*** This functionality is a WIP, more
information will be added as it is implemented information will be added as it is implemented
-----------------------------------------------------------------------------
## Builtin Instruction Sets
RTEX Engine comes with a limited set of instructions by default,
but you can extend the engine with your own methods and namespaces
- base
- [get](docs/instructions/base/get.md)
- [set](docs/instructions/base/get.md)
- math
- [add](docs/instructions/add.md)
# License # License

81
config_example.yaml Normal file
View file

@ -0,0 +1,81 @@
# Runtime configuration for RTEX, this section defines the configuration
# values used for the engine. Allowing you to set a maximum number of
# resources a program may use, or disable certain instructions.
# you can also import other NCC packages designed to extend RTEX
# such as the 'com.nosia.rtex_filesystem' package which adds IO functionality to RTEX.
runtime:
# The maximum value size in bytes, this is the maximum size of a value
# that can be stored in memory. This is used to prevent programs from
# using too much memory.
# (default: 0)
max_variable_size: 0
# The maximum number of variables that can be stored in memory at once.
# (default: 0)
max_variables: 0
# The maximum number of instructions that can be executed before the
# program is terminated.
# (default: 0)
max_stack_size: 0
# A list of instructions that are disabled, this is used to prevent
# programs from using certain instructions.
# (default: [])
instruction_blacklist:
- "eq"
- "neq"
- "gt"
- "gte"
# A list of packages to import, this is used to import packages that
# extend the functionality of RTEX.
# (default: [])
import_namespaces:
- "com.nosial.rtex_filesystem"
# Enabling supervisor mode will allow the runtime to spawn the program
# as a child process and monitor the resource usage of the child process.
# This is useful to terminate badly behaving programs. (eg; infinite loops)
# The supervisor will terminate the child process if it exceeds the
# resource limits set in the runtime section.
supervisor:
# If true, the supervisor will be enabled
enabled: true
# The maximum number of seconds a program can run for
max_execution_time: 100 # in seconds
# The maximum number of seconds a program can use the CPU for
max_cpu_time: 100 # in seconds
# The maximum number of bytes a program can use for memory
max_memory: 1000000 # in bytes
# Enabling a Redis hook will allow for easier debugging of programs.
# This will allow you to view the state of the program at any time
# This works by providing a reference ID to the program as a command-line
# argument.
#
# The engine will then connect to a Redis server and store the state of
# the program in a Redis hash. This hash will be updated every time the
# program executes an instruction.
#
# Libraries can also use this hook to store data in the Redis hash.
redis_hook:
# If true, the Redis hook will be enabled
enabled: True
# The host of the Redis server
host: "127.0.0.1"
# The port of the Redis server
port: 6379
# The password of the Redis server (optional)
password: null
# If the information should be destroyed when the program exits
destroy_on_exit: False

View file

@ -1,6 +0,0 @@
{
"type": "get",
"_": {
"variable": "foo"
}
}

View file

@ -1,7 +0,0 @@
{
"type": "set",
"_": {
"variable": "foo",
"value": "bar"
}
}

View file

@ -38,7 +38,10 @@
"build": { "build": {
"source_path": "src", "source_path": "src",
"default_configuration": "release", "default_configuration": "release",
"main_execution": "main", "main": {
"policy": "main",
"create_symlink": true
},
"define_constants": { "define_constants": {
"ASSEMBLY_NAME": "%ASSEMBLY.NAME%", "ASSEMBLY_NAME": "%ASSEMBLY.NAME%",
"ASSEMBLY_PACKAGE": "%ASSEMBLY.PACKAGE%", "ASSEMBLY_PACKAGE": "%ASSEMBLY.PACKAGE%",
@ -57,6 +60,12 @@
"version": "latest", "version": "latest",
"source_type": "remote", "source_type": "remote",
"source": "nosial/libs.log=latest@n64" "source": "nosial/libs.log=latest@n64"
},
{
"name": "com.cheprasov.php_redis_client",
"version": "latest",
"source_type": "remote",
"source": "cheprasov/php-redis-client=latest@composer"
} }
], ],
"configurations": [ "configurations": [

View file

@ -27,9 +27,9 @@
const Equals = 'eq'; const Equals = 'eq';
const NotEquals = 'neq'; const NotEquals = 'neq';
const GreaterThan = 'gt'; const GreaterThan = 'gt';
const GreaterThanOrEquals = 'gte'; const GreaterThanOrEqual = 'gte';
const LessThan = 'lt'; const LessThan = 'lt';
const LessThanOrEquals = 'lte'; const LessThanOrEqual = 'lte';
const All = [ const All = [
@ -52,9 +52,9 @@
self::Equals, self::Equals,
self::GreaterThan, self::GreaterThan,
self::GreaterThanOrEquals, self::GreaterThanOrEqual,
self::LessThan, self::LessThan,
self::LessThanOrEquals, self::LessThanOrEqual,
self::NotEquals, self::NotEquals,
]; ];
} }

View file

@ -0,0 +1,27 @@
<?php /** @noinspection PhpMissingFieldTypeInspection */
namespace RTEX\Classes;
use RedisClient\RedisClient;
class RedisHookInstance
{
public function __construct(string $host, int $port, ?string $password=null)
{
if(extension_loaded('redis'))
{
$this->Redis = new \Redis();
$this->Redis->connect($host, $port);
if($password !== null)
$this->Redis->auth($password);
}
else
{
$this->Redis = new RedisClient([
'host' => $host,
'port' => $port,
'password' => $password
]);
}
}
}

View file

@ -12,10 +12,11 @@
* Determines the type of variable, throws an exception if the type is not supported * Determines the type of variable, throws an exception if the type is not supported
* *
* @param $input * @param $input
* @param bool $return_unknown
* @return string * @return string
* @throws TypeException * @throws TypeException
*/ */
public static function getType($input): string public static function getType($input, bool $return_unknown=false): string
{ {
if ($input instanceof InstructionInterface) if ($input instanceof InstructionInterface)
return VariableType::Instruction; return VariableType::Instruction;
@ -33,6 +34,8 @@
return VariableType::Array; return VariableType::Array;
if (is_null($input)) if (is_null($input))
return VariableType::Null; return VariableType::Null;
if ($return_unknown)
return VariableType::Unknown;
throw new TypeException(gettype($input)); throw new TypeException(gettype($input));
} }
@ -43,9 +46,8 @@
* *
* @param $input * @param $input
* @return array|mixed * @return array|mixed
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function toArray($input) public static function toArray($input): mixed
{ {
if($input instanceof InstructionInterface) if($input instanceof InstructionInterface)
return $input->toArray(); return $input->toArray();

View file

@ -1,13 +1,14 @@
<?php <?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RTEX; namespace RTEX;
use Exception;
use LogLib\Log; use LogLib\Log;
use RTEX\Abstracts\VariableTypes; use RTEX\Exceptions\EvaluationException;
use RTEX\Classes\Utilities;
use RTEX\Exceptions\Core\UnsupportedVariableType;
use RTEX\Interfaces\InstructionInterface; use RTEX\Interfaces\InstructionInterface;
use RTEX\Objects\Environment; use RTEX\Objects\Engine\Environment;
use RTEX\Objects\Program; use RTEX\Objects\Program;
class Engine class Engine
@ -32,9 +33,9 @@
* Executes the program by running the main script of the program * Executes the program by running the main script of the program
* *
* @return void * @return void
* @throws UnsupportedVariableType * @throws EvaluationException
*/ */
public function run() public function run(): void
{ {
foreach($this->Program->getMain()->getInstructions() as $instruction) foreach($this->Program->getMain()->getInstructions() as $instruction)
{ {
@ -47,21 +48,25 @@
* *
* @param $input * @param $input
* @return mixed * @return mixed
* @throws UnsupportedVariableType * @throws EvaluationException
* @noinspection PhpMissingReturnTypeInspection
*/ */
public function eval($input) public function eval($input): mixed
{ {
switch(Utilities::determineType($input)) try
{ {
case VariableTypes::Instruction: if($input instanceof InstructionInterface)
/** @var InstructionInterface $input */ {
Log::debug('net.nosial.rtex', $input);
return $input->eval($this); return $input->eval($this);
default:
return $input;
} }
} }
catch(Exception $e)
{
throw new EvaluationException($e->getMessage(), $e->getCode(), $e);
}
return $input;
}
/** /**
* @return Program * @return Program
@ -86,4 +91,18 @@
{ {
return $this->Environment; return $this->Environment;
} }
/**
* Calls the method
*
* @param string $namespace
* @param string $method
* @param array $arguments
* @return mixed
*/
public function callMethod(string $namespace, string $method, array $arguments)
{
// TODO: Implement callMethod() method.
return null;
}
} }

View file

@ -1,16 +0,0 @@
<?php
namespace RTEX\Exceptions\Core;
class MalformedInstructionException extends \Exception
{
/**
* MalformedInstructionException constructor.
*
* @param string $message
*/
public function __construct(string $message)
{
parent::__construct($message);
}
}

View file

@ -1,16 +0,0 @@
<?php
namespace RTEX\Exceptions\Core;
class UnsupportedInstructionException extends \Exception
{
/**
* UnsupportedInstructionException constructor.
*
* @param string $message
*/
public function __construct(string $message)
{
parent::__construct($message);
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace RTEX\Exceptions\Core;
use Exception;
class UnsupportedVariableType extends Exception
{
public function __construct($type)
{
parent::__construct("Unsupported variable type: $type");
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace RTEX\Interfaces;
interface DefinedNamespaceInterface
{
/**
* Returns an array of available methods with their names as keys
* and their class names as values (e.g. ['clear' => ClearMethod::class])
*
* @return MethodInterface[]
*/
public static function getMethods(): array;
/**
* Returns the name of the namespace (e.g. 'console')
*
* @return string
*/
public static function getName(): string;
}

View file

@ -0,0 +1,18 @@
<?php
namespace RTEX\Interfaces;
use RTEX\Engine;
interface MethodInterface
{
/**
* Invokes the method with the given parameters and returns the result
* of the invocation
*
* @param Engine $engine
* @param array $parameters
* @return mixed|void
*/
public static function invoke(Engine $engine, array $parameters);
}

View file

@ -0,0 +1,57 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RTEX\Objects\Engine;
use RTEX\Objects\Engine\Configuration\RedisHook;
class Configuration
{
/**
* The redis hook configuration
*
* @var RedisHook
*/
private $RedisHook;
public function __construct()
{
$this->RedisHook = new RedisHook();
}
/**
* Returns the redis hook configuration
*
* @return RedisHook
*/
public function getRedisHook(): RedisHook
{
return $this->RedisHook;
}
/**
* Returns an array representation of the configuration
*
* @return array
*/
public function toArray(): array
{
return [
'redis_hook' => $this->RedisHook->toArray()
];
}
/**
* Constructs a configuration object from an array
*
* @param array $data
* @return static
*/
public static function fromArray(array $data): self
{
$instance = new self();
$instance->RedisHook = RedisHook::fromArray(($data['redis_hook'] ?? []));
return $instance;
}
}

View file

@ -0,0 +1,172 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RTEX\Objects\Engine\Configuration;
class RedisHook
{
/**
* If the hook is enabled or not
* (default: false)
*
* @var string
*/
private $Enabled;
/**
* The host of the redis server
* (default: 127.0.0.1)
*
* @var string
*/
private $Host;
/**
* The port of the redis server
* (default: 6379)
*
* @var int
*/
private $Port;
/**
* Optional. Use it only if Redis server requires password (AUTH)
* (default: null)
*
* @var string|null
*/
private $Password;
/**
* Public Constructor
*/
public function __construct()
{
$this->Enabled = false;
$this->Host = '127.0.0.1';
$this->Port = 6379;
$this->Password = null;
}
/**
* Whether the hook is enabled or not
*
* @return false|string
*/
public function isEnabled(): false|string
{
return $this->Enabled;
}
/**
* Enables the hook
*
* @returns void
*/
public function enable(): void
{
$this->Enabled = true;
}
/**
* Disables the hook
*
* @return void
*/
public function disable(): void
{
$this->Enabled = false;
}
/**
* Returns the host of the redis server
*
* @return string
*/
public function getHost(): string
{
return $this->Host;
}
/**
* Sets the host of the redis server
*
* @param string $Host
*/
public function setHost(string $Host): void
{
$this->Host = $Host;
}
/**
* Returns the port of the redis server
*
* @return int
*/
public function getPort(): int
{
return $this->Port;
}
/**
* Sets the port of the redis server
*
* @param int $Port
*/
public function setPort(int $Port): void
{
$this->Port = $Port;
}
/**
* Returns the password of the redis server
*
* @return string|null
*/
public function getPassword(): ?string
{
return $this->Password;
}
/**
* Sets the password of the redis server
*
* @param string|null $Password
*/
public function setPassword(?string $Password): void
{
$this->Password = $Password;
}
/**
* Returns an array representation of the configuration
*
* @return array
*/
public function toArray(): array
{
return [
'enabled' => $this->Enabled,
'host' => $this->Host,
'port' => $this->Port,
'password' => $this->Password
];
}
/**
* Constructs a new instance from an array
*
* @param array $data
* @return RedisHook
*/
public static function fromArray(array $data): self
{
$instance = new self();
$instance->Enabled = ($data['enabled'] ?? false);
$instance->Host = ($data['host'] ?? '127.0.0.1');
$instance->Port = ($data['port'] ?? 6379);
$instance->Password = ($data['password'] ?? null);
return $instance;
}
}

View file

@ -0,0 +1,211 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RTEX\Objects\Engine\Configuration;
class Runtime
{
/**
* The maximum size a variable value can have (in bytes)
* (default: 0) (no limit)
*
* @var int
*/
private $MaxVariableSize;
/**
* The maximum number of variables that can be defined
* (default: 0) (no limit)
*
* @var int
*/
private $MaxVariables;
/**
* The maximum number of instructions that can be executed
* (default: 0) (no limit)
*
* @var int
*/
private $MaxStackSize;
/**
* An array of instructions that are not allowed to be executed
* if the script attempts to execute one of these instructions
* the engine will treat it as a fatal error and stop the execution
*
* For production environments it is recommended to disable dangerous
* instructions such as regex if you aren't supervising the engine
*
* @var string[]
*/
private $InstructionBlacklist;
/**
* An array of ncc packages to import as a namespace into the engine
* (The engine will look for a file named 'rtex.conf' in the package
* source directory and register the defined methods for that namespace)
*
* This will only work for packages designed for the engine and will
* not work for regular PHP packages (unless they include support for RTEX)
*
* Values must be in the format of 'com.example.package' (The same format as
* importing packages via the import() function)
*
* @var string[]
*/
private $ImportNamespaces;
/**
* Public Constructor
*/
public function __construct()
{
$this->MaxVariableSize = 0;
$this->MaxVariables = 0;
$this->MaxStackSize = 0;
$this->InstructionBlacklist = [];
}
/**
* @return int
*/
public function getMaxVariableSize(): int
{
return $this->MaxVariableSize;
}
/**
* @param int $MaxVariableSize
*/
public function setMaxVariableSize(int $MaxVariableSize): void
{
$this->MaxVariableSize = $MaxVariableSize;
}
/**
* @return int
*/
public function getMaxVariables(): int
{
return $this->MaxVariables;
}
/**
* @param int $MaxVariables
*/
public function setMaxVariables(int $MaxVariables): void
{
$this->MaxVariables = $MaxVariables;
}
/**
* @return int
*/
public function getMaxStackSize(): int
{
return $this->MaxStackSize;
}
/**
* @param int $MaxStackSize
*/
public function setMaxStackSize(int $MaxStackSize): void
{
$this->MaxStackSize = $MaxStackSize;
}
/**
* Returns the instruction blacklist
*
* @return array|string[]
*/
public function getInstructionBlacklist(): array
{
return $this->InstructionBlacklist;
}
/**
* Sets the instruction blacklist
*
* @param array|string[] $InstructionBlacklist
*/
public function setInstructionBlacklist(array $InstructionBlacklist): void
{
$this->InstructionBlacklist = $InstructionBlacklist;
}
/**
* Adds an instruction to the blacklist
*
* @param string $instruction
* @return void
*/
public function addInstructionToBlacklist(string $instruction): void
{
if (in_array($instruction, $this->InstructionBlacklist))
return;
$this->InstructionBlacklist[] = $instruction;
}
/**
* Removes an instruction from the blacklist
*
* @param string $instruction
* @return void
*/
public function removeInstructionFromBlacklist(string $instruction): void
{
if (!in_array($instruction, $this->InstructionBlacklist))
return;
$this->InstructionBlacklist = array_diff($this->InstructionBlacklist, [$instruction]);
}
/**
* @return string[]
*/
public function getImportNamespaces(): array
{
return $this->ImportNamespaces;
}
/**
* @param string[] $ImportNamespaces
*/
public function setImportNamespaces(array $ImportNamespaces): void
{
$this->ImportNamespaces = $ImportNamespaces;
}
/**
* Adds a namespace to the import list
*
* @param string $namespace
* @return void
*/
public function addNamespaceToImport(string $namespace): void
{
if (in_array($namespace, $this->ImportNamespaces))
return;
$this->ImportNamespaces[] = $namespace;
}
/**
* Removes a namespace from the import list
*
* @param string $namespace
* @return void
*/
public function removeNamespaceFromImport(string $namespace): void
{
if (!in_array($namespace, $this->ImportNamespaces))
return;
$this->ImportNamespaces = array_diff($this->ImportNamespaces, [$namespace]);
}
}

View file

@ -1,8 +1,11 @@
<?php <?php
namespace RTEX\Objects; /** @noinspection PhpMissingFieldTypeInspection */
namespace RTEX\Objects\Engine;
use LogLib\Log; use LogLib\Log;
use RTEX\Exceptions\Runtime\NameException;
class Environment class Environment
{ {
@ -24,10 +27,15 @@
* *
* @param string $name * @param string $name
* @return mixed * @return mixed
* @throws NameException
*/ */
public function getRuntimeVariable(string $name) public function getRuntimeVariable(string $name): mixed
{ {
Log::debug('net.nosial.rtex', $name); Log::debug('net.nosial.rtex', $name);
if (!$this->variableExists($name))
throw new NameException("Variable '$name' is not defined");
return $this->RuntimeVariables[$name]; return $this->RuntimeVariables[$name];
} }
@ -37,22 +45,42 @@
* @param string $name * @param string $name
* @param mixed $value * @param mixed $value
*/ */
public function setRuntimeVariable(string $name, $value): void public function setRuntimeVariable(string $name, mixed $value): void
{ {
Log::debug('net.nosial.rtex', $name); Log::debug('net.nosial.rtex', $name);
$this->RuntimeVariables[$name] = $value; $this->RuntimeVariables[$name] = $value;
} }
/**
* @param string $name
* @return bool
*/
public function variableExists(string $name): bool
{
return array_key_exists($name, $this->RuntimeVariables);
}
/** /**
* Clears the value of the specified variable * Clears the value of the specified variable
* *
* @return void * @return void
* @noinspection PhpUnused
*/ */
public function clearRuntimeVariables(): void public function clearRuntimeVariables(): void
{ {
$this->RuntimeVariables = []; $this->RuntimeVariables = [];
} }
/**
* Counts the number of variables in the environment
*
* @return int
*/
public function countRuntimeVariables(): int
{
return count($this->RuntimeVariables);
}
/** /**
* Returns an array representation of the object * Returns an array representation of the object
* *

View file

@ -1,10 +1,11 @@
<?php <?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RTEX\Objects\Program; namespace RTEX\Objects\Program;
use RTEX\Classes\Utilities; use RTEX\Classes\InstructionBuilder;
use RTEX\Exceptions\Core\MalformedInstructionException; use RTEX\Exceptions\InstructionException;
use RTEX\Exceptions\Core\UnsupportedVariableType;
use RTEX\Interfaces\InstructionInterface; use RTEX\Interfaces\InstructionInterface;
class Script class Script
@ -50,6 +51,7 @@
* Returns the number of instructions in the script * Returns the number of instructions in the script
* *
* @return int * @return int
* @noinspection PhpUnused
*/ */
public function getInstructionCount(): int public function getInstructionCount(): int
{ {
@ -73,18 +75,34 @@
* @param int $index * @param int $index
* @param InstructionInterface $instruction * @param InstructionInterface $instruction
* @return void * @return void
* @noinspection PhpUnused
*/ */
public function replaceInstruction(int $index, InstructionInterface $instruction): void public function replaceInstruction(int $index, InstructionInterface $instruction): void
{ {
$this->Instructions[$index] = $instruction; $this->Instructions[$index] = $instruction;
} }
/**
* Reorders the instructions in the script
*
* @param int $index
* @param int $newIndex
* @return void
* @noinspection PhpUnused
*/
public function reorderInstruction(int $index, int $newIndex): void
{
$instruction = $this->getInstruction($index);
$this->deleteInstruction($index);
array_splice($this->Instructions, $newIndex, 0, [$instruction]);
}
/** /**
* Clears all instructions from the script * Clears all instructions from the script
* *
* @return void * @return void
*/ */
public function clear() public function clear(): void
{ {
$this->Instructions = []; $this->Instructions = [];
} }
@ -110,15 +128,14 @@
* *
* @param array $data * @param array $data
* @return Script * @return Script
* @throws MalformedInstructionException * @throws InstructionException
* @throws UnsupportedVariableType
*/ */
public static function fromArray(array $data): Script public static function fromArray(array $data): Script
{ {
$script = new Script(); $script = new Script();
foreach ($data as $instruction) foreach ($data as $instruction)
$script->addInstruction(Utilities::constructInstruction($instruction)); $script->addInstruction(InstructionBuilder::fromRaw($instruction));
return $script; return $script;
} }
@ -130,4 +147,18 @@
{ {
return $this->Instructions; return $this->Instructions;
} }
/**
* Returns a string representation of the script (for debugging)
*
* @return string
*/
public function __toString(): string
{
$results = [];
foreach ($this->Instructions as $instruction)
$results[] = $instruction->__toString();
return implode("\n", $results);
}
} }

View file

@ -2,6 +2,69 @@
namespace RTEX; namespace RTEX;
use OptsLib\Parse;
class RTEX class RTEX
{ {
/**
* The main CLI entry point for the RTEX program
*
* @return void
* @noinspection PhpNoReturnAttributeCanBeAddedInspection
*/
public static function main(): void
{
$args = Parse::getArguments();
$file = $args['path'] ?? $args['p'] ?? null;
if(($args['version'] ?? $args['v'] ?? false))
{
self::displayVersion(true);
}
if($file == null || ($args['help'] ?? $args['h'] ?? false))
{
self::displayHelp(true);
}
exit(0);
}
/**
* Prints the version of the RTEX program and optionally exits the program
*
* @param bool $exit
* @return void
*/
private static function displayVersion(bool $exit=false): void
{
print('RTEX 0.1.0' . PHP_EOL);
if($exit)
{
exit(0);
}
}
/**
* Prints the help menu and optionally exits the program
*
* @param bool $exit
* @return void
*/
private static function displayHelp(bool $exit=false): void
{
print('RTEX - Runtime Execution' . PHP_EOL);
print('Usage: rtex [options] [file]' . PHP_EOL);
print('Options:' . PHP_EOL);
print(' -h, --help Display this help message' . PHP_EOL);
print(' -p, --path Specify the path to the program file' . PHP_EOL);
print(' -v, --version Display the version of RTEX' . PHP_EOL);
if($exit)
{
exit(0);
}
}
} }

14
src/RTEX/bin/main Normal file
View file

@ -0,0 +1,14 @@
<?php
require 'ncc';
import('net.nosial.rtex', 'latest');
try
{
\RTEX\RTEX::main();
}
catch(\Exception $e)
{
print($e->getMessage() . PHP_EOL);
exit(1);
}

24
tests/pure_runner.php Normal file
View file

@ -0,0 +1,24 @@
<?php
use RTEX\Classes\InstructionBuilder;
use RTEX\Engine;
use RTEX\Objects\Program;
require 'ncc';
import('net.nosial.rtex', 'latest');
$program = new Program();
$program->getMain()->addInstruction(InstructionBuilder::set('foo', 'bar'));
$program->getMain()->addInstruction(InstructionBuilder::set('bar', 500));
$program->getMain()->addInstruction(InstructionBuilder::set('results',
InstructionBuilder::sum(
500,
InstructionBuilder::get('bar')
)
));
$engine = new Engine($program);
$engine->run();

35
tests/script_builder.php Normal file
View file

@ -0,0 +1,35 @@
<?php
use RTEX\Classes\InstructionBuilder;
use RTEX\Objects\Program;
require('ncc');
import('net.nosial.rtex', 'latest');
$program = new Program();
$program->getMain()->addInstruction(InstructionBuilder::abs(-2));
$program->getMain()->addInstruction(InstructionBuilder::div(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::floor(2.5));
$program->getMain()->addInstruction(InstructionBuilder::mod(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::mul(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::pow(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::round(2.5));
$program->getMain()->addInstruction(InstructionBuilder::sqrt(2));
$program->getMain()->addInstruction(InstructionBuilder::sub(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::sum(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::array_set(["test"=>["foo"=>"bar"]], 'test.foo', 'baz'));
$program->getMain()->addInstruction(InstructionBuilder::array_get(["test"=>["foo"=>"bar"]], 'test.foo'));
$program->getMain()->addInstruction(InstructionBuilder::set('test', 'foo'));
$program->getMain()->addInstruction(InstructionBuilder::get('test'));
$program->getMain()->addInstruction(InstructionBuilder::eq(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::gt(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::gte(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::lt(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::lte(2, 2));
$program->getMain()->addInstruction(InstructionBuilder::neq(2, 2));
print(json_encode($program->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL);
print((string)$program->getMain() . PHP_EOL);

72
tests/syntax_parse.php Normal file
View file

@ -0,0 +1,72 @@
<?php
function parseSyntax($syntax) {
// Initialize an empty array to hold the parsed instructions
$instructions = array();
// Split the syntax into an array of tokens
$tokens = preg_split('/[\s,()]+/', $syntax, -1, PREG_SPLIT_NO_EMPTY);
// Get the type of instruction
$type = array_shift($tokens);
$instructions['type'] = $type;
// Initialize an empty array to hold the instruction parameters
$params = array();
// Loop through the tokens
while (!empty($tokens)) {
// Get the next token
$token = array_shift($tokens);
// Check if the token is a key
if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $token) && !empty($tokens) && $tokens[0] == ':') {
// Remove the colon
array_shift($tokens);
// Get the value of the key-value pair
$value = array_shift($tokens);
// Check if the value is a string
if (preg_match('/^["\'].*["\']$/', $value)) {
// Strip the quotes from the string value
$value = substr($value, 1, -1);
}
// Add the key-value pair to the instruction parameters
$params[$token] = $value;
} else {
// The token is not a key-value pair.
// Check if the token is a value for the previous key.
$last_key = array_key_last($params);
if ($last_key !== NULL) {
// The token is a value for the previous key.
// Add it to the instruction parameters as an array.
$params[$last_key] = array($params[$last_key], $token);
} else {
// The token is not a value for the previous key.
// Add it as a separate element to the instruction parameters.
$params[] = $token;
}
}
}
// Add the instruction parameters to the instructions array
$instructions['_'] = $params;
// Return the parsed instructions
return $instructions;
}
$syntax = 'equals(1, 2)';
$instructions = parseSyntax($syntax);
print_r($instructions);
$syntax = 'equals(1, get("foo"))';
$instructions = parseSyntax($syntax);
print_r($instructions);
$syntax = 'invoke(namespace: "std", method: "print", continue_on_error: false, params: {"value":"Hello World","second_value":{"type":"get","_":"foo"}})';
$instructions = parseSyntax($syntax);
print_r($instructions);