Receiver scripts

A .trb file can be loaded as the body of a receiver closure, type checked against a receiver type before it runs, and run by the sandboxed VM.

Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite

Draft. It runs in the VM: a program torb run runs loads and applies a script, and so does the toolchain for a project.trb; a native binary runs its scripts in the VM of the script host it links, and the value crosses into that VM and back through the derived Encode and Decode of the receiver (Receiver Scripts and the Sandbox).

A receiver closure is a value passed at a call site. A receiver script is the same idea with a whole file standing in for that value: project.trb is one already, and any .trb file can become one through Sandbox.load.

Example#

use Sandbox, SandboxError from "std/sandbox"

type ServerConfig {
  var host: String = "localhost"
  var port: Int = 8080
}

fn loadConfig(path: String): Result<ServerConfig, SandboxError> {
  const script = Sandbox.load<ServerConfig>(path)?
  var config = ServerConfig()
  script.apply(config)?
  config
}

Syntax#

Sandbox.load<Value>(path: String): Result<Script<Value>, SandboxError>
Script.apply(var value: Value): Result<Void, SandboxError>

Rules#

  1. Loading and running are two steps, and each has its own failure. Sandbox.load<Value>(path) reports what is wrong with the file itself: a syntax error, a type error against Value, or a module the script may not import. script.apply(value) reports what went wrong while the body ran, against a value of the caller's own.

  2. The type argument of load is the receiver, and it is checked as the body of (var self: Value) => Void. Value doubles as the whitelist of what the file can do: its members are what the script can name beyond the prelude.

  3. The path is relative to the directory of the project, not to the file that calls load. That is the opposite of use, whose relative path is relative to the importing file - a loaded script is a question about where the program runs, not about the source tree.

  4. A receiver script may hold top-level code, unlike an ordinary module: it is one of the four kinds of file top-level code allows, alongside an entry file, an unimported script and a *.test.trb file.

  5. Only the VM runs a receiver script. A program torb run runs in the VM loads and applies one, and so does a native binary that torb build --embed-vm built, which embeds the VM. A native binary torb build compiled runs it in the VM of the script host it links, and its receiver is then Encode & Decode, which the value crosses by. A literal path names a script the checker checked with the program; a path the program computes is checked against the type argument when the program loads it, by the torb that runs it. The toolchain runs one too: torb manifest evaluates a project.trb, while the commands that build and test still read it by parsing its literal top-level command calls.

What this is not#

A receiver script is not imported. use names a module and its exports; Sandbox.load names a file and checks it as a closure body, and the file itself declares nothing another file could import from it.

use Sandbox from "std/sandbox"

type ServerConfig {
  var host: String = "localhost"
}

fn describe(path: String) {
  print "loading {path} as ServerConfig"
}

describe "./config"
use Host from "./config"
  • The sandbox - the capabilities granted at the call site of Sandbox.load.
  • Receiver closures - the closure form a receiver script's body is checked as.
  • Top-level code - why a receiver script may hold statements a module cannot.
Esc