SceneLang

Contents

Description

SceneLang is a Domain-Specific Language (DSL) used to describe a 3D scene that can be rendered by Raytracer.

Being a DSL, SceneLang lacks some of the basic features of general purpose languages: there are no functions or custom types or even flexible arithmetic operations. SceneLang is made only to construct scenes to be rendered.

To render the content of a scenelang script just invoke the cli tool command:

julia raytracer_cli.jl render myscript.sl

where .sl is the suffix for a SceneLang script.

Each SceneLang script is parsed through the interpreter present in module Raytracer.Interpreter, which interprets the script by first interpreting the stream of charachters associated with the script as a list of token and then evaluating how these tokens relate to each other as a syntax.

Let's start by looking at what tokens are in a SceneLang script.

Script tokenization

In a SceneLang script there are mainly five classes of tokens:

Commands

Commands are all-uppercase words. Their role is to indicate that an action will be performed on arguments that follow.

Warning

Commands are a finite group of predetermined words, therefore the lexer throws an exception when it finds an all-uppercase word that is not a listed command.

Examples
SET  # valid command token
DROP # invalid command token, not in list
Set  # not a command token, lowercase letters are present in the word

Types

Types are words starting with an uppercase letter and at least one lowercase letter in the rest of the word. Their role is to specify the type of a value when it is constructed.

Warning

Types, as commands, are a finite group of predetermined words, therefore the lexer throws an exception when it finds an uppercase-starting word that is not a listed type.

Examples
Pigment  # valid type token
PIgment  # invalid type token, not in list
pigment  # not a type token, first letter not uppercase

Keywords

Keywords are words preceded by a dot (.) marker. They are used either as a specifier to a type or command or as an attribute name when constructing a value.

Warning

Keywords are not a global scope defined list of names, but they have meaning only in certain semantic contexts, but the lexer is unaware of this so any word following a dot will be marked as a keyword even if it is syntactically invalid.

Examples
.Uniform         # valid keyword token
.transformation  # valid keyword token, there is no rule on capitalization
transformation   # not a keyword token, word not preceded by a dot

Identifiers

Identifiers are words, starting with a lowercase letter, that are intended to be paired with an instance of a type and can be used in place of that value.

Examples
identifier        # valid identifier token
_IDENTIFIER       # valid identifier token, no capitalization rule after the first letter
a_long_identifier # valid identifier token in snake case
Not_an_identifier # not an identifier, first letter must be lowercase or an underscore
Tip

For ease of visual parsability we suggest to avoid the second style above and to prefer snake case for long identifiers and keep everything lowercase.

Literals

Literals are every other token in the script: numbers, strings, and mathematical expressions.

6                      # this is a LiteralNumber token
"this is a string"     # this is a LiteralString token
$ 1 + 2id ^ 4 - 1e-2 $ # this is a MathExpression token

MathExpression tokens are a peculiar feature of SceneLang and will be explored deeper in its own chapter.

Syntax structure

The syntax of SceneLang is composed of only three entities:

Variables

Variables in SceneLang are composed only of an identifier token, so the two terms could be used interchangeably, since the difference stands only in the fact that "variables" is their name when analyzing the syntax while "identifiers" is when talking about lexicon.

The value stored into a variable cannot be altered or copied into other variables. Values only serve to label a particular instance of an object construted at variable set-up and stored in a dictionary. They behave exactly as if that object were contructed in-place. The only places where a varible is explicitly requested to be is when they are set up and when they are destroyed. In all other instances they could be as substituted with appropriate constructors, at the cost of readability and time to parse the script.

Constructors

Constructors are a syntactical construct that constructs an object into memory.

There are three types of constructors symbolical constructors, named constructors, and command constructors.

Symbolical constructors determine the output type by the symbols present around the arguments, while named constructors have the arguments surrounded by parenthesis an preceded by the type name (and sometimes a type specificator keyword).

<1, 0, 0>      # a symbolic constructor for a color
Color(1, 0, 0) # the same color but in a named constructor form

Named constructors have an additional characteristic: they can have default values and can take keyword arguments:

Color(1)       # the same color as in the previous example but the second
               # and third arguments are defaulted to 0
Color(.R 1)    # as above but with a keyword argument
Tip

Keyword arguments may seem verbose at first but come in very handy both when trying to understand what the code is doing and when one wants to list arguments in a different order than standard.

Warning

Mixed positional and keyword argument types are allowed as long as the positional arguments precede the keyword ones and as long as the keyword arguments do not redefine positional arguments:

Color(1, .B 0.5, .G 0.75) # valid mixed argument constructor
Color(.R 1, 0.75, 0.5)    # throws InvalidKeyword exception: positional after keyword
Color(1, .R 0.5)          # throws InvalidKeyword exception: keyword tries to redefine
                          # the first positional argument

Command constructors exist either to make the code more readable and/or to simplify notation. They are composed of a command and one or more arguments. If more than one argument may be required these must be surrounded by curved parenthesis. A clear example are the commands to create rotation matrices without requiring the user to manually type the transformation matrix.

For example the following two constructors return the same transformation:

Transformation([1.0,  0.0,        0.0,       0.0,
                0.0,  0.707107,  -0.707107,  0.0,
                0.0,  0.707107,   0.707107,  0.0,
                0.0,  0.0,        0.0,       1.0])
ROTATE(.X 45)

Numbers and strings can be considered primitive types and thus require a dedicated tokenization. Therefore a named or command constructor for them would be needlessy redundant and verbose as their constructor would take only one argument of the same type of the one produced and there is no risk of ambiguity.

Examples
9          # since a number is a primitive type it can be
           # constructed by simply typing the number itself
"a string" # a string must surround the desired text with double quotes

Now we'll list all constructors for all the different types supported by SceneLang.

Numbers

As stated above, numbers are primitive types and have a symbolic constructor which is a simple numeric token.

This constructor supports integer notation 1, dotted notations 1.0 and 1., and the scientific notations 1e2, 1e+2, 1e-2 and equivalent notation using E.

Warning

The notation .1 is not supported, the parser would interpret it as a keyword, use +.1, -.1, or 0.1 instead.

TIME command

A special constructor command can also be used: TIME. This constructor returns the value of the time setting, which is set at the cli level (see render image and render animation). This is needed in animations to change values between a frame and the other.

Strings

Being a primitive type, strings only have a symbolic constructor.

Strings are surrounded by double quotes (") and support the usual set of escaped characters (e.g. \n, \t, ...). SceneLang strings do not support formatting or interpolations.

Colors

Colors have a named constructor with signature

Color(.R red::number = 0, .G green::number = 0, .B blue::number = 0)

and a symbolic constructor with signature

<red::number, green::number, blue::number>

Note that these colors are pixels of an HDR image, therefore have no upper bound.

Points

Points have a named constructor with signature

Point(.X x::number = 0, .Y y::number = 0, .Z z::number = 0)

and a symbolic constructor with signature

{x::number, y::number, z::number}

Lists

Lists have a named constructor with signature

List(element::number, ::number...)

and a symbolic constructor with signature

[element::number, ::number...]

Transformations

Transformations have a named constructor with signature

Transformation(matrix::list.lenght16)

Transformations can be concatenated using the symbol * which behaves like a matrix multiplication operation.

Transformations also have the following four command constructors.

SCALE command

The SCALE command constructs a scaling transformation and has two different signatures:

SCALE(.X x::number = 1, .Y y::number = 1, .Z z::number = 1) # scales by different factors on the given axes
SCALE factor::number # scales uniformly on all axes
TRANSLATE command

The TRANSLATE command constructs a translation transformation. It has the signature

TRANSLATE(.X x::number = 0, .Y y::number = 0, .Z z::number = 0)
ROTATE command

The ROTATE command constructs a rotation transformation combining rotations in different axis. In the scene's euclidean 3D space scaling and translation transformations are commutable along different axes, so the order of application of the transformation does not matter: this is not the case for rotations.

Therefore the ROTATE command has a peculiar syntax for its arguments: after the command a series of keywords indicating the rotation axis are followed by the rotation angles in degrees in a way similar to the following example.

ROTATE(.X 45 * .Z 30 * .X 20 * .Y 15)

You can clearly see that in this syntax keywords can be repeated and are, therefore, non-optional. Furthermore, the order in which they are written matters as the result of the construction will be the concatenation of all the individual rotations. This is the reason why the arguments are not separated by commas but by concatenation symbols *.

Images

Images have two name constructors with signature

Image(file_path::string) # loads an image from a file at the given path if the format is valid
Image(width::number.integer, height::number.integer) # constructs a black image of size `width`x`height`

and a command constructor with signature

LOAD file_path::string # loads an image from a file at the given path if the format is valid

Valid argument of USING.

Pigments

Pigments have named constructors associated with each of their subtype specifiers. Their signatures are:

Pigment.Uniform(.color color::color = <1, 1, 1>)
Pigment.Checkered(.N::number.integer = 2 , .color_on color_on::color = <1, 1, 1>, .color_off::color = <0, 0, 0>)
Pigment.Image(.image image::image = Image(1, 1))

BRDFs

BRDFs have named constructors associated with each of their subtype specifiers. Their signatures are:

Brdf.Diffuse(.pigment pigment::pigment = Pigment.Uniform())
Brdf.Specular(.pigment pigment::pigment = Pigment.uniform(), .threshold_angle_rad angle::number = 0.0017453294)

Materials

Materials have a named constructor with signature

Material(.brdf brdf::brdf = Brdf.Diffuse(), .emitted_radiance radiance::pigment = Pigment.Uniform())

Shapes

Shapes have named constructors associated with each of their subtype specifiers. Their signatures are:

Shapes.Sphere  (.material material::material = Material(),
                .transformation transformation::transformation = SCALE())
Shapes.Plane   (.material material::material = Material(),
                .transformation transformation::transformation = SCALE())
Shapes.Cube    (.material material::material = Material(),
                .transformation transformation::transformation = SCALE())
Shapes.Cylinder(.material material::material = Material(),
                .transformation transformation::transformation = SCALE())

Furthermore, shapes have also command constructors for Constructive Solid Geometries (CSG). Their signatures are:

UNITE(shape::shape, ::shape...)
INTERSECT(shape::shape, ::shape...)
DIFF(shape::shape, ::shape...)
FUSE(shape::shape, ::shape...)

Valid argument of SPAWN.

Lights

Lights have a named constructor with signature

Light(.position position::point = {0,0,0},
      .color color::color = <1,1,1>,
      .linear_radius radius::number = 0)

Valid argument of SPAWN.

PCGs

PCGs have a named constructor with signature

Pcg(.state state::number.integer = 42,
    .inc   inc::number.integer = 54)

Camera

Cameras have named constructors associated with each of their subtype specifiers. Their signatures are:

Camera.Perspective(.aspect_ratio ratio::number = 1.,
                   .transformation transformation::transformation = SCALE 1.,
                   .screen_distance distance::number = 1.)
Camera.Orthogonal(.aspect_ratio ratio::number = 1.,
                  .transformation transformation::transformation = SCALE 1.)

Valid argument of USING.

Renderers

Renderers have named constructors associated with each of their subtype specifiers. Their signatures are:

Renderer.OnOff(.on_color on::color = <1,1,1>,
               .off_color off::color = <0,0,0>)
Renderer.Flat(.background_color background::color = <0,0,0>)
Renderer.PointLight(.background_color background::color = <0,0,0>,
                    .ambient_color ambient::color = <1e-3,1e-3,1e-3>)
Renderer.PathTracer(.background_color color::color = <0,0,0>,
                    .rng              rng::pcg = Pcg()
                    .n                n::number.integer = 10,
                    .max_depth        max::number.integer = 2,
                    .roulette_depth   roulette::number.integer = 3)

Valid argument of USING.

Tracers

Tracers have a named constructor with signature

Tracer(.samples_per_side samples::number = 1, .rng rng::pcg = Pcg())

Valid argument of USING.

MathExpressions

Numbers, points, and colors can also be constructed via a MathExpression token. Math expressions are sections of code surrounded by dollar signs $. They can contain only mathematical operations, numbers and identifiers storing numbers.

These expressions are first checked for validity at lexing time, where it is ensured that they only contain numbers, identifiers, and valid operations and that these operations have the right amount of arguments to them. The valid operations are:

op symbol# of argsaction
+1+add
-1 or 2change sign or subtract
*1+multiply
/2float divide
div2integer divide
%2modulo
^2raise to the power of
floor1approximate to integer by defect
ceil1approximate to integer by excess
round1approximate to nearest integer
exp1natural base exponential
exp21binary base exponential
exp101decimal base exponential
log1natural base logarithm
log21binary base logarithm
log101decimal base logarithm
log1p1natural base logarithm of arg + 1
sin1sine function (argument in radians)
cos1cos function (argument in radians)
tan1tan function (argument in radians)
asin1arcsine function
acos1arccos function
atan1 or 2arctan function
Point3Julia Point constructor
RGB3Julia RGB constructor

After successful tokenization the expression is evaluated. Starting with the innermost expression all the identifiers are substituted with their value (if they are defined, otherwise an exception will be thrown) and then the result of the expression is calculated. If the result is a finite number the second innermost expression is evaluated and so on until the outermost expression is evaluated. If the result is either infinite or NaN an exception will be thrown.

The tokenization and evaluation processes make use of the Meta.parse and eval functions provided by the Julia language. Therefore every valid Julia syntax for mathematical expressions is considered valid as long as it respects the restrictions discussed previously in this section.

Examples
SET a 9                   # set the `a` variable to be equal to 9
SET res1 $1 + 2a$         # this will set `res1` to be equal to 19
# SET res2 $1 + 2b$       # this would throw an `UndefinedIdentifier` exception
# SET res3 $div(1, 2, 3)$ # this would throw an `InvalidExpression` exception

Instructions

SceneLang scripts are a series of instructions parsed by the interpreter.

Each instruction starts with an instruction command and ends when all the possible arguments are consumed.

Arguments can only be variables or constructors.

Info

In the following command signatures we will use:

  • enclosing angular brackets<> to isolate single variable elements of the signature;
  • a pipe | between two elements indicates that one or the other can be present at that position;
  • enclosing squared brackets [] to indicate optional arguments;
  • appended dots ... to indicate that the previous element may be repeated an indefinite amount of times.

SET

Assign to a variable a constructed variable.

SET <<identifier> <constructor>>...
Note

All variables are constants in Scenelang and they exist and can't be overwritten until they are UNSET.

Note

You cannot set an identifier to be equal to the value stored in another identifier as it is not needed in a program where the lifetime of the value is the lifetime of the variable.

Examples
SET my_number 6 # sets `my_number` to be equal to 6

# <identifier> <value> pairs can be chained after a SET statement
SET
    sphere Shape.Sphere()
    cube   Shape.Cube()
# as soon as the next token in the series is
# not an identifier the SET statement is interrupted
Tip

Since SceneLang is not sensitive to spaces and newlines chained commands can have any layout you prefer, we still suggest, for easier visual parsing, the style we use in our examples of separating every element by a newline and a tabulation.

UNSET

Destroy a variable and the assigned value.

UNSET <identifier>...
Examples
UNSET my_number # my_number is now not assigned to any value and
                # cannot be called any more unless it is SET again

# <identifier>s can be chained after an UNSET statement
UNSET
    sphere
    cube
# as soon as the next token in the series is
# not an identifier the UNSET statement is interrupted

SPAWN

Spawns a shape or a light into the rendered world.

SPAWN <<shape_identifier>|<shape_constructor>>...
Examples
SET my_number 10
SET my_sphere Shape.Sphere()

# <identifier>s or <constructor>s can be chained after a SPAWN statement
SPAWN
    my_sphere    # my sphere is spawned into the world
#   my_number    # this would throw a `WrongValueType` exception
    Shape.Cube() # spawned shapes can also be constructed in-place
    Light()      # lights can also be spawned
# as soon as the next token in the series is
# not an identifier the SPAWN statement is interrupted

USING

Sets a rendering settings to a given value.

USING <<camera_identifier>  |<camera_constructor>  |
       <image_identifier>   |<image_constructor>   |
       <renderer_identifier>|<renderer_constructor>|
       <tracer_identifier>  |<tracer_constructor>
      >...

The rendering settings that can be set are:

  • the camera to be used and its properties;
  • the image to be impressed;
  • the renderer to be used and its properties;
  • the tracer to be used and its properties.
Warning

USING instruction must be used once and only once per setting within a script. if a setting is not defined an UndefinedSetting exception will be thrown, else, if a definition occurs more than once, a SettingRedefinition exception will be thrown.

Examples
USING Camera.Orthogonal() # sets the camera setting to
                          # be the default orthogonal camera

SET p_camera Camera.Perspective()
SET image Image(1920, 1080)

# <identifier>s or <constructor>s can be chained after a USING statement
USING
    Renderer.PointLight() # Usable objects can
    Tracer()              # be constructed in-place
    image                 # or they could be used by identifier
#   p_camera			  # this would throw a `SettingRedefinition` exception
# as soon as the next token in the series is
# not an identifier the USING statement is interrupted

DUMP

Prints to stdout the specified content. Used mainy as a debug tool.

# prints the value associated with the specified field of the scene
DUMP.variables
DUMP.world
DUMP.lights
DUMP.image
DUMP.camera
DUMP.renderer
DUMP.tracer

DUMP.ALL # prints all of the above