Skip to content

DeltaScript

Jordan Bunke edited this page Jun 25, 2024 · 25 revisions

DeltaScript is an original interpreted scripting language designed to be used in Delta Time applications. Scripts are very concise: they consist of a nameless head function that acts as the entry point for script execution and optional named helper functions. I designed DeltaScript with three principles and priorities in mind: 1) ease of use, 2) expressivity, and 3) extensibility.

Ease of use and expressivity

It is no secret that I am a big advocate of Java. Delta Time is a Java framework, after all. However, it is even less of a secret that Java is an extremely verbose language, and that boilerplate is par for the course. That is tolerable to certain developers in certain contexts, but I believe that writing scripts for direct use in applications should be a sleek, streamlined process that encourages and facilitates rapid iteration. Thus, I wanted users writing DeltaScript to be able to accomplish a lot by writing very little code, and ensuring that code is still readable and type-safe. Here are some measures I took to achieve that goal.

Syntactical shorthands and shortcuts

Like Java, variables in DeltaScript can be declared as final if they are immutable. To make this more concise, users can type ~ instead of the final keyword; these are treated the same way by the interpreter.

Similarly, functions that return a value and consist of a single return statement can be written in a single line. The following functions are equivalent:

f(params? -> return_type) -> expr
f(params? -> return_type) {
    return expr;
}

Collection syntax

DeltaScript supports four data structures: 1) arrays T[], 2) lists T<>, 3) sets T{} and 4) maps/dictionaries { T key : T val }. Each collection type is associated with a different type of bracket, which allows for briefer code to be written and for syntax to more succinctly express meaning.

{ string : string{} } proBendingSquads = {
    "Fire Ferrets" : { "Korra", "Mako", "Bolin" }, 
    "White Falls Wolfbats" : { "Tahno", "Ming", "Shaozu" } 
};

This is a valid variable declaration and initialization statement in DeltaScript. It is a one-liner, divided into multiple lines for the sake of readability. It defines a map/dictionary that maps strings to sets of strings called proBendingSquads with two entries. The keys here are the names of the teams, and the values are sets with the names of all the teams' players.

Examples

The following script returns a random RGB color:

(-> color) -> rgb(rc(), rc(), rc())
rc(-> int) -> rand(0, 0x100)

Its helper function rc() returns a random color channel value by calling the native rand(int min, int maxEx) function. The values returned by rc() range from 0 to 255 (inclusive); every integer in that range has an equal probability of being returned. The head function calls the native rgb(int r, int g, int b) function to return an opaque (alpha channel value of 255) RGB color.

The script also makes use of the single-expression function body shorthand syntax.

The following script takes an array of strings and concatenates them together as words in a sentence. An empty array will return the empty string.

(~ string[] words -> string) {
    string sentence = "";

    for (int i = 0; i < #| words, i++) {
        sentence += words[i];

        sentence += i + 1 < #| words ? " " : ".";
    }

    return sentence;
}

This script notably makes use of the #| (length/size) operator. This unary operator takes as an operand any string, set {}, list <> or array [] expression, and returns its length or size.

Extensibility

The DeltaScript parser and interpreter facilitate three types of extensions: 1) types, 2) function call statements, and 3) function call expressions.

Example

The following is an example of an automation script written for Stipple Effect, my pixel art editor. Stipple Effect is built on Delta Time and uses its own extension dialect of DeltaScript to allow for user scripting. The dialect defines the types project and layer, and has numerous function extension bindings for application actions (functions called on the namespace $SE).

() {
    $SE.set_side_mask([2, 2, 2, 2, 2, 2, 2, 2]);                                                     // 1
    
    ~ project p = $SE.get_project();

    ~ int[]{} sel = p.get_selection();                                                               // 2
    ~ int[]{} ol = flip_coin($SE.outline(sel, $SE.get_side_mask()), $SE.single_outline(sel, 3));     // 3

    for (~ int[] coord in ol)
        sel.add(coord);                                                                              // 4
    
    p.set_selection(sel);                                                                            // 5
}

What this script does:

  1. Sets the system outline side mask to be 2 pixels in every cardinal and diagonal direction
  2. Retrieves the selection in the active project as a series of pixels
  3. Makes a 50/50 decision with the native function flip_coin(T heads, T tails) -> T to either outline the selection with the previously set system outline side mask, or to set the outline to a single outline (cardinal directions only) with a 3-pixel border
  4. Adds the outline to the selection set
  5. Sets the project selection to the combined selection set, which now consists of the previous selection and its outline

What this looks like in practice:

Outline

More

Learn more about extending DeltaScript here.

More on DeltaScript