Module scrolls.interpreter
The interpreter implementation.
This module is the main point of interaction for most uses of Scrolls. The Interpreter
implements functions that
interpret Scrolls scripts. The InterpreterContext
class is responsible for tracking the state of the Scrolls
interpreter.
Using the Interpreter
Quickstart
Basic usage of the interpreter is straightforward. Instantiate Interpreter
, and configure it with the desired
features, either from scrolls.builtins
, or custom modules. Then, the interpreter may be used to run Scrolls scripts.
import scrolls
# Create an interpreter. Note that an interpreter created this
# way will not actually do anything. It's the responsibility of
# the user to configure with the desired language features.
interpreter = scrolls.Interpreter()
# Configure the interpreter with the base language.
# scrolls.base_config is provided to make this common task
# a bit easier.
scrolls.base_config.configure(interpreter)
# Configure with stdio commands like input, and print
interpreter.command_handlers.add(scrolls.StdIoCommandHandler())
# Run your script.
script = """
!repeat(4) {
print "Hello world!"
}
"""
interpreter.run(script)
# Note: This will print
# Hello world!
# Hello world!
# Hello world!
# Hello world!
scrolls.__main__
implements a bare minimum standalone Scrolls interpreter with this interface. Take a look if you
need a more concrete example.
Extensions
Of course, the main usage of Scrolls is not as a standalone language, but as an engine embedded in a parent application. Eventually, you'll most likely need to implement extensions to the language.
The primary method of extending Scrolls is through call handlers (CallHandler
).
Call Types
Calls come in three different types, however all calls are fundamentally function calls. Most of the time, they will
call into some python code, but support is offered for calls written in Scrolls code themselves (see !def
).
Command Calls
Command calls are what most languages would refer to as "statements".
print "Tell me your name:"
input name
print "Hello," $name
In the above example, print
and input
are command calls. Command calls do not return anything. In scrolls.interpreter
terms, command calls are calls for which CallHandler.handle_call()
returns None
. Command calls are counted as
statements. See Interpreter.interpret_statement()
.
Expansion Calls
Expansion calls are what most languages would refer to as "functions".
print "Here's a random choice:" $(select foo bar baz)
print "9 + 10 is:" $(+ 9 10)
In the above example, select
and +
are expansion calls. Expansion calls always return strings. In scrolls.interpreter
terms, expansion calls are calls for which CallHandler.handle_call()
returns str
. Expansion calls are not
counted as statements, and must be used in the context of either a command call, or control call.
Note that even arithmetic operations like + - * /
are implemented as expansion calls.
See ArithmeticExpansionHandler
.
Control Calls
Control calls implement control structures.
!repeat(4) {
print "Hello world!"
}
!if($true) print "This was a true statement."
In the above example, repeat
and if
are control calls. Control calls are unique in that they take a Scrolls
statement in addition to the normal call arguments. This can be used to implement common control structures like
if
, while
, etc. See BuiltinControlHandler
. Control calls do not return anything, like command calls.
Control calls are counted as statements.
Implementing Call Handlers
The bare minimum for implementing a call handler is to implement the CallHandler
protocol. Call handlers must implement
two functions:
handle_call
: Called when a call is invoked.__contains__
: Should returnTrue
if the call handler supports a call, i.e."call_name" in handler
.
Let's make a simple command call handler for a call named printargs
, which prints out all of the arguments passed to
it.
import scrolls
# Create interpreter
interpreter = scrolls.Interpreter()
# Create your handler
class PrintArgsHandler:
def handle_call(self, ctx: scrolls.InterpreterContext) -> None:
if ctx.call_name == "printargs":
for arg in ctx.args:
print(arg)
def __contains__(self, call_name: str) -> bool:
return call_name == "printargs"
# Add the handler to the interpreter
interpreter.command_handlers.add(PrintArgsHandler())
# Run a script containing your command:
interpreter.run("""
printargs foo bar "this is one argument" baz
""")
# NOTE - Prints
# foo
# bar
# this is one argument
# baz
Using CallbackCallHandler
The CallHandler
protocol is deliberately left simple for maximum flexibility, but it's also kind of awkward for most
uses. A basic call handler is provided to handle boilerplate, CallbackCallHandler
.
Let's implement printargs
with CallbackCallHandler
.
import scrolls
# Create interpreter
interpreter = scrolls.Interpreter()
# Create your handler
# Note that this may also use the shortcut: scrolls.CallbackCommandHandler,
# but it's the same thing.
class PrintArgsHandler(scrolls.CallbackCallHandler[None]):
def __init__(self):
super().__init__()
self.add_call("printargs", self.printargs)
def printargs(self, ctx: scrolls.InterpreterContext) -> None:
for arg in ctx.args:
print(arg)
# Add the handler to the interpreter
interpreter.command_handlers.add(PrintArgsHandler())
# Run a script containing your command:
interpreter.run("""
printargs foo bar "this is one argument" baz
""")
# NOTE - Prints
# foo
# bar
# this is one argument
# baz
CallbackCallHandler
may be considered the base call handler class for most purposes. All builtin language features
are implemented with CallbackCallHandler
. See scrolls.builtins
.
Expand source code
"""
The interpreter implementation.
.. include:: ./pdoc/interpreter.md
"""
from .callhandler import *
from .interpreter_errors import *
from .run import *
from .state import *
from .struct import *
Sub-modules
scrolls.interpreter.callhandler
-
Call handler protocols and implementations.
scrolls.interpreter.interpreter_errors
-
Interpreter-specific errors.
scrolls.interpreter.run
-
Code relating to the execution of
AST
objects. scrolls.interpreter.state
-
Implementation of interpreter state. Variables, open files, call stack, etc.
scrolls.interpreter.struct
-
Data structures used to implement the interpreter state.