Skip to content
Neil Tallim edited this page Jul 14, 2015 · 2 revisions

Introduction

Prismscript is meant to be very respectful of your coding style and practises: it won't impose any requirements on how you build or design things.

Walkthrough

You can get an interpreter embedded and running functions with a small amount of code:

import prismscript.processor.interpreter as prismscript_interpreter
import prismscript.stdlib
import prismscript.discover_functions

script = """
test_node{
    exit 'hello!';
}

test_function(x){
    return math.pow(x, 2);
}
"""

interpreter = prismscript_interpreter.Interpreter(script)
interpreter.register_scoped_functions(prismscript.discover_functions.scan(prismscript.stdlib, ''))

node = interpreter.execute_node('test_node')
try:
    prompt = node.send(None) #Start execution of the coroutine; may yield a value
    while True: #The loop could be external, allowing a threadpool to make a single pass at the prompt before waiting for user input or something
       #Act on `prompt` to decide what to send back
       data = None
       prompt = node.send(data) #Send the message back in and get the next yielded value for processing
except prismscript_interpreter.StatementExit as e:
    #Guaranteed to occur, barring another exception.
    print("Exited with value %(value)r" % {
     'value': e.value,
    })


function = interpreter.execute_function('test_function', {'x': 4,})
try:
    prompt = function.send(None)
    while True:
       prompt = node.send(None)
except prismscript_interpreter.StatementExit as e:
    #Occurs if an `exit` statement is encountered.
    print("Exited with value %(value)r" % {
     'value': e.value,
    })
except prismscript_interpreter.StatementReturn as e:
    #Guaranteed to occur, barring another exception or an `exit`.
    print("Returned %(value)r" % {
     'value': e.value,
    })

This code defined a node and a function, then executed each one. That's pretty much all there is to using the interpreter in practise.

Exceptions

Prismscript exposes some important exceptions, documented here.

  • Error(Exception): Every error Prismscript raises is an instance of this.
  • ExecutionError(Error): Raised when Prismscript is evaluating a statement. It exposes the useful attributes location_path, which can be used to determine where the statement resides in a script, and base_exception, which is the external exception encountered, like a KeyError from Python; if None, the implication is that the exception originated within Prismscript and is likely syntax-related.
  • NamespaceLookupError(Error): The requested namespace element is not defined in any searchable context.
  • NodeNotFoundError(NamespaceLookupError): A request was made to process a node that does not exist.
  • FunctionNotFoundError(NamespaceLookupError): A request was made to process a function that does not exist.
  • ScopedFunctionNotFoundError(FunctionNotFoundError): A request was made to process a function that was not reflected into the interpreter's namespace.
  • VariableNotFoundError(NamespaceLookupError): An undeclared variable was requested.
  • ScopedVariableNotFoundError(VariableNotFoundError): A reference was made to a variable not reflected into the interpreter's namespace.
  • StatementReturn(FlowControl): A return statement was encountered at the top-level of execution. (Raised only when executing function directly; this is implicitly converted into an 'exit' in nodes)
  • StatementExit(FlowControl): An exit statement was encountered. (May be raised when executing a function directly, if the function uses exit to halt operation)

Runtime extension

If your application's logic is such that the scripting namespace may need to grow while live, you can call Interpreter.extend_namespace(script), which accepts a script, like the one defined in the walkthrough, and overlays its functions and nodes onto the existing namespace.

In practise, its usage is identical to passing a script to the interpreter on initialisation (in fact, you could initialise the interpreter with an empty string and just composite a script after the fact, if you wanted to), with all the same processing mechanics: if your new script contains structural errors, exceptions will be raised, though the interpreter's state won't be affected.

Special operations

Disabling threading support

There may be cases where, for whatever reason, you don't want to have threads in the scripts your interpreters run. A simple, effective solution to this is to pass threading=False when instantiating the interpreter, which will cleanly omit types.Thread and types.Lock from the interperter's namespace, preventing them from being accessible to scripts.

Sanitising lock-states

Interpreter.release_locks(current_thread_is_dead=True)

In (hopefully) exceedingly rare cases, the interpreter may be made to execute poorly written code, wherein locks are acquired, but not cleanly released by threads. Invoking the method described above, as stated, before re-entering an interpreter that was previously used, is a low-overhead way of ensuring that any abandoned locks won't lead to deadlocks. Any offending thread instances are returned, providing a means of investigating the problem by correlating the instance to the section of code that instantiated it, through reflection and logging. Locks that are currently held by active threads are not released by this method, making it safe (if a little expensive) to use without awareness of the interpreter's state.

Clone this wiki locally