src/DirectInterpreter.php

Summary

Maintainability
A
0 mins
Test Coverage
B
81%
<?php

declare(strict_types=1);

namespace Smuuf\Primi;

use \Smuuf\StrictObject;
use \Smuuf\Primi\Ex\RuntimeError;
use \Smuuf\Primi\Ex\SystemException;
use \Smuuf\Primi\Ex\ControlFlowException;
use \Smuuf\Primi\Code\Ast;
use \Smuuf\Primi\Tasks\Emitters\PosixSignalTaskEmitter;
use \Smuuf\Primi\Values\AbstractValue;
use \Smuuf\Primi\Helpers\Wrappers\CatchPosixSignalsWrapper;
use \Smuuf\Primi\Handlers\HandlerFactory;
use \Smuuf\Primi\Structures\CallRetval;

/**
 * Primi's direct raw abstract syntax tree interpreter.
 *
 * Raw interpreter can be used to simply execute some Primi source code
 * within a given context.
 *
 * @see https://en.wikipedia.org/wiki/Interpreter_(computing)#Abstract_Syntax_Tree_interpreters
 */
abstract class DirectInterpreter {

    use StrictObject;

    /**
     * Execute source code within a given context.
     *
     * This is a bit lower-level approach to running some source code provided
     * as `Ast` object. This allows the client to specify a context object
     * to run the code (represented by `Ast` object) within. This in turn
     * allows, for example, the REPL to operate within some specific context
     * representing a runtime-in-progress (where it can access local variables).
     *
     * @param Ast $ast `Ast` object representing the AST to execute.
     */
    public static function execute(
        Ast $ast,
        Context $ctx
    ): AbstractValue {

        EnvInfo::bootCheck();

        // Register signal handling - maybe.
        if ($ctx->getConfig()->getEffectivePosixSignalHandling()) {
            PosixSignalTaskEmitter::catch(SIGINT);
            PosixSignalTaskEmitter::catch(SIGQUIT);
            PosixSignalTaskEmitter::catch(SIGTERM);
        }

        $wrapper = new CatchPosixSignalsWrapper($ctx->getTaskQueue());
        return $wrapper->wrap(function() use ($ast, $ctx) {

            try {

                $tree = $ast->getTree();
                $retval = HandlerFactory::runNode($tree, $ctx);

                if ($ctx->hasRetval()) {
                    $ctx->popRetval();
                    throw new RuntimeError("Cannot 'return' from global scope");
                }

                return $retval;

            } catch (ControlFlowException $e) {

                $what = $e::ID;
                throw new RuntimeError("Cannot '{$what}' from global scope");

            } finally {

                try {

                    // This is the end of a single runtime, so run any tasks
                    // that may be still left in the task queue (this means, for
                    // example, that all callbacks in the queue will still be
                    // executed).
                    $ctx->getTaskQueue()->deplete();

                } catch (SystemException $e) {
                    throw new RuntimeError($e->getMessage());
                }

            }

        });

    }

}