io7m-jparasol 0.11.3
Parasol Language 0.11.3 Specification
Notational Conventions
Unicode
The specification makes reference to the Unicode character set which, at the time of writing, is at version 6.2.0. The specification often references specific Unicode characters, and does so using the standard notation U+NNNN, where N represents a hexadecimal digit. For example, U+03BB corresponds to the lowercase lambda symbol λ.
EBNF
The specification gives grammar definitions in ISO/IEC 14977:1996 Extended Backus-Naur form.
Logic
The specification uses the following notation from propositional logic [0]. A summary of the notation used is as follows:
NotationDescription
∀x. P xUniversal quantification; for all x the proposition P holds for x
∃x. P xExistential quantification; there exists some x such that the proposition P holds for x
P ⇒ QImplication; P implies Q
P ⋀ QConjunction; P and Q
Sets
Where the specification refers to sets, it is referring to sets as defined in ZFC[1].
NotationDescription
e ∈ Ae is an element of the set A
e ∉ Ae is not an element of the set A
{ x₀, x₁, ... xₙ }A set consisting of values from x₀ to xₙ
{ e ∈ A | p(e) }A set consisting of the elements of A for which the proposition p holds
|A|The cardinality of the set A; a measure of the number of elements in A
The empty set
𝔹The booleans
The natural numbers
The real numbers
The integers
[a, b]A closed interval in a set (given separately or implicit from the types of a and b), from a to b, including a and b
(a, b]A closed interval in a set (given separately or implicit from the types of a and b), from a to b, excluding a but including b
[a, b)A closed interval in a set (given separately or implicit from the types of a and b), from a to b, including a but excluding b
(a, b)A closed interval in a set (given separately or implicit from the types of a and b), from a to b, excluding a and b
A ⊂ BA is a subset of, and is not equal to, B
A ⊆ BA is a subset of, or is equal to, B
Tuples
The notation tuples(S, N) denotes the set of n-tuples of length N of elements taken from the set S, where N is non-negative. For a given set S, the set of n-tuples may be defined inductively as follows:
  • tuples(S, 0) is the set of 0-tuples, containing one element denoted ().
  • tuples(S, N) is an ordered pair (x, tuples(S, N - 1)) where x ∈ S.
Some example sets of n-tuples are as follows:
S = { 1, 2, 3 }

tuples(S, 0) = { () }

tuples(S, 1) = {
  (1, ()),
  (2, ()),
  (3, ())
}

tuples(S, 2) = {
  (1, (1, ())),
  (2, (1, ())),
  (3, (1, ())),
  (1, (2, ())),
  (2, (2, ())),
  (3, (2, ())),
  (1, (3, ())),
  (2, (3, ())),
  (3, (3, ()))
}
Specific n-tuples are denoted P = (x₀, x₁, ..., xₙ), which is essentially shorthand for P = (x₀, (x₁, (... (xₙ, ())))), with the 0th element of P being x₀ and the nth element being xₙ.
Types
The specification (and the parasol language itself) uses notation and concepts taken from type theory [2]. A summary of the notation used is as follows:
NotationDescriptionExample
x : AThe term x is of type A23 : ℕ
A → BThe type of functions from values of type A to values of type Beven : ℕ → 𝔹
A × BThe type of products of A and B(23, true) : ℕ × 𝔹
Product types are typically used to encode the notion of n-ary functions, where (A × B × C) → D is the type of functions that take three arguments of types A, B, and C, respectively, and return values of type D.
Type rules
Declarative type rules describe the precise rules for assigning types to terms. If no type rule matches a term, then that term is considered ill-typed.
Type rules are given as zero or more premises, and a single conclusion, separated by a horizontal line. For a given rule, when all of the premises are true, then the conclusion is true. If a rule has no premises then the rule is taken as an axiom.
The gamma symbol Γ (U+0393) represents the current typing environment and can be thought of as a mapping from distinct variables to their types, with the set of variables in environment denoted by dom(Γ) (the domain of Γ). The notation Γ ⊢ P reads "Γ implies P" and is used in type rules to assign types to terms. The empty typing environment is represented by (U+2205). The diamond symbol (U+25C7) should be read as "is well-formed", so Γ ⊢ ◇ should be read as "the current typing environment is well-formed". The concept of well-formedness is often type-system-specific and is usually described when the rules are given. A summary of the notation is as follows:
NotationDescriptionExample
ΓThe current typing environmentΓ
The empty typing environment
Γ, xThe typing environment Γ extended with the variable x [3] Γ, x where x ∉ dom(Γ)
dom(Γ)The set of distinct variables in Γdom((∅, x, y)) = { x, y }
Γ ⊢ PThe environment Γ implies PΓ ⊢ 23 : ℕ (in the current typing environment, 23 is of type )
Γ ⊢ ◇The environment Γ is well-formed∅ ⊢ ◇ (the empty typing environment is well-formed)
An example of typing rules for natural number addition:
The empty rule states that the empty typing environment is well-formed. Because there are no premises above the horizontal line, this is taken as an axiom. The extension rule states that adding a term to the typing environment that is not already in that environment, results in a well-formed environment. The natural_intro rule states that, given a well-formed typing environment, any expression that is syntactically a natural number (represented by ) has type natural. The natural_plus rule states that, if variables m and n have type natural in the current typing environment, then m + n has type natural. When checking the type of the expression m + n, the rules are used to construct a derivation tree as follows:
Intuitively, a term only has a valid type if there is a sequence of rules from the empty environment that can assign a type to the term.
Operational Semantics
Operational semantics describe the precise rules for the evaluation of expressions in a given language. The rules make up the description of an abstract machine which passes through different states, one rule (or step) at a time, until the machine halts and produces a value.
Typically, operational semantics begin by first giving a set of values, indicating the final results of evaluation, and a set of identifiers syntactically identifying the set of evaluable expressions. The evaluation rules themselves are given in a style similar to type rules, where a rule applies if the premises above the horizontal line are true, and the conclusion indicates how the state of the abstract machine changes when the rule is applied.
As an example, assume a language of conditional expressions. An example expression in this language would be:
if true then
  if false then
    true
  else
    if
      if true then
        false
      else
        true
      end
    then
      false
    else
      true
    end
  end
else
  true
end
The values of this language are true and false. The expressions in this language include the values, and the form if e₀ then e₁ else e₂ end, where e₀, e₁ and e₂ are expressions. There are clearly multiple ways to evaluate expressions in this language, but in order to produce an algorithm that will execute on a computer, the evaluation rules should be:
  • Deterministic. That is, given a non-value expression e, there must be at most one evaluation rule that applies in order to work towards producing a value from e. If multiple rules apply, then evaluation is nondeterministic.
  • Complete. That is, given a non-value expression e, there must be at least one evaluation rule that applies in order to work towards producing a value from e. If no rule applies, then evaluation is said to be stuck.
The operational semantics for the language could be written as follows:
Expressions are assigned the identifiers e₀ to eₙ, so terms of those forms are assumed to be evaluable expressions when they appear in rules.
The notation e → e' should be read "e evaluates to e' in a single step".
The if_true rule states that if the condition of an if expression is exactly true, then the expression evaluates to the expression given in the left branch, in one step.
The if_false rule states that if the condition of an if expression is exactly false, then the expression evaluates to the expression given in the right branch, in one step.
The if_condition rule states that if the condition of an if expression is not a value, then the condition is evaluated first.
The example expression given earlier can now be evaluated completely and deterministically by following the given rules:
if true then
  if false then
    true
  else
    if
      if true then
        false
      else
        true
      end
    then
      false
    else
      true
    end
  end
else
  true
end

→ by if_true to:

if false then
  true
else
  if
    if true then
      false
    else
      true
    end
  then
    false
  else
    true
  end
end

→ by if_false to:

if
  if true then
    false
  else
    true
  end
then
  false
else
  true
end

→ by if_condition to:

if
  false
then
  false
else
  true
end

→ by if_false to:

true
The substitution notation e [x := y] denotes the expression e where all occurences (if any) of the variable x have been replaced with y. This is used, for example, to describe function evaluation:
The function_eval_0 and function_eval_1 rules state that expressions are evaluated from left-to-right when applying f to a pair of arguments.
The function_eval_2 rule states that when all of the expressions passed to f have been reduced to values, the expression as a whole evaluates to the body of f, called e, with occurrences of the arguments x and y in e substituted with their values.
OpenGL
As the parasol language is intended to be executed on programmable GPUs, familiarity with OpenGL and the OpenGL shading language is assumed.
Lexical Conventions
Units
The text of a parasol program is a combination of the texts of separate units, where a unit typically corresponds to a file in the operating system under which the compiler is running.
Units consist of a series of lexical tokens separated by whitespace. What constitutes a token is the subject of the following sections.
Character set
The character set used for parasol program source code is UTF-8. Inside of comments, any Unicode character is permitted. Outside of comments, the subset of UTF-8 permitted for use in programs is detailed in the following sections.
Whitespace
The following characters are considered to be whitespace:
CodepointName
U+0009Horizontal tab
U+000ALine feed
U+000CForm feed
U+000DCarriage return
U+0020Space
Lines of source are separated with line_separator, which may be any of the following:
Codepoint(s)Name
U+000ALine feed
U+000DCarriage return
U+000D U+000ACarriage return immediately followed by line feed
Comments
A comment starts with -- (two adjacent U+002D characters) and extends to the end of the line. Comments may appear on any line in a unit. The contents of comments have no effect whatsoever on the semantics of parasol programs.
Tokens
Integer literals
An integer_literal is a sequence of one or more digits, optionally preceded by a minus sign (U+002D), representing a decimal integer. The precise syntax is given by the following EBNF:
digit_nonzero =
  "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;

digit =
  "0" | digit_nonzero ;

integer =
  "0" | ( ["-"] , digit_nonzero , { digit } ) ;
Real literals
A real_literal consists of an integral and fractional part, separated by a dot (U+002E) and optionally preceded by a minus sign (U+002D), and represents a real number. The precise syntax is given by the following EBNF:
real =
  ["-"] , digit , { digit } , "." , digit , { digit } ;
Boolean literals
A boolean_literal is either true or false. The precise syntax is given by the following EBNF:
boolean_literal =
  "true" | "false" ;
Identifiers
Identifiers are sequences of uppercase letters ([U+0041, U+005A]), lowercase letters ([U+0061, U+007A]), and underscores (U+005F). An identifier is uppercase iff its first character is an uppercase letter, or lowercase iff its first character is a lowercase letter. Identifiers cannot begin with underscores or digits. The precise syntax is given by the following EBNF:
letter_lower =
  "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" |
  "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" |
  "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
  "y" | "z" ;

letter_upper =
  "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" |
  "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" |
  "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" |
  "Y" | "Z" ;

letter =
  letter_lower | letter_upper ;

name_lower =
  letter_lower , { letter | digit | "-" | "_" } ;

name_upper =
  letter_upper , { letter | digit | "-" | "_" } ;
Keywords
All of the following character sequences are reserved as keywords and cannot be used otherwise:
  • : (U+003A)
  • , (U+002C)
  • { (U+007B)
  • } (U+007D)
  • . (U+002E)
  • = (U+003D)
  • ( (U+0028)
  • ) (U+0029)
  • ; (U+003B)
  • [ (U+005B)
  • ] (U+005D)
  • as
  • column
  • depth
  • discard
  • else
  • end
  • false
  • fragment
  • function
  • if
  • import
  • in
  • is
  • let
  • module
  • new
  • out
  • package
  • parameter
  • program
  • record
  • shader
  • then
  • true
  • type
  • value
  • vertex
  • with
Declarations
Overview
The parasol language consists of terms, types, and shaders, which are organized into modules (which are further organized into packages).
Terms
Description
Terms are the computational elements of the parasol language. They consist of values, which effectively give names to expressions, and functions, which are named computational rules in the mathematical sense.
Declarations
A term_declaration may either be a value_declaration or a function_declaration.
Names
The names selected for terms must be unique with respect to other terms within the module in which they are defined. That is, there cannot be two terms with the same name in the same module. Terms do not share a name space with types or shaders.
The following restrictions apply when naming terms:
  • Term names must begin with a lowercase letter. This is directly implied by the grammar.
  • Names cannot contain two adjacent underscores (U+005F).
  • Names cannot end with underscores (U+005F).
  • Names cannot start with the character sequence "gl_" (U+0067, U+006C, U+005F).
  • Names cannot match any of the keywords or reserved words defined in any version of the OpenGL shading language. See the GLSL identifiers section for the complete list.
Recursion
No term_declaration can be recursive with respect to itself or any other term_declaration in the module in which it appears.
A term d₀ is said to refer statically to a term d₁ if the free variables of d₀ contain the name of d₁.
A term_declaration d is (mutually) recursive iff:
  • d refers statically to itself.
  • There is a sequence of terms t₀, t₁, ..., tₙ such that d refers statically to t₀, and for all m where 0 <= m < n, tₘ refers statically t₍ₘ₊₁₎, and tₙ refers statically to d.
Order of declarations
For the purposes of sorting term declarations based on their dependencies, terms can be partially ordered based on the terms to which they refer statically. That is, if a term t₀ refers statically to term t₁, then t₁ < t₀.
Terms do not have to be declared in any given order. That is, if a term t₀ refers statically to term t₁ in the same module, there is no requirement that t₁ be declared before t₀.
Terms are sorted topologically prior to any evaluation based on the given partial order relation, and the restrictions on recursion ensure that it is always possible to sort terms in the order of their dependencies.
Values
A value_declaration binds an expression to a name.
The type of the value_declaration will be inferred from the given expression, but the declaration can be optionally ascribed with the name of a type, in which case the type of the expression will be checked against the ascription and an error raised in the case of a mismatch.
Functions
A function_declaration binds an expression e to a name along with a given return type t and a set of formal parameters, where e contains zero or more variables bound by the formal parameters and which, when evaluated, will result in a value of type t.
Type rules
A value_declaration of type t introduces a term of type t into the environment:
A function_declaration with parameters of type (s₀ ✕ s₁ ✕ ... sₙ) that returns type u introduces a term of type (s₀ ✕ s₁ ✕ ... sₙ) → u into the environment:
Operational semantics
With terms sorted according to their partial order, evaluation of value_declarations in the current module proceeds from top-to-bottom, with the value of each evaluated value_declaration being substituted into the terms that follow it:
Syntax
The precise syntax of term_declarations is given by the following EBNF:
type_path =
    name_lower
  | name_upper , "." , name_lower ;

value_declaration =
  "value" , name_lower , [ ":" , type_path ] , "=" , expression ;

function_formal_parameter =
  name_lower , ":" , type_path ;

function_formal_parameters =
  "(" , function_formal_parameter, { "," , function_formal_parameter } , ")" ;

function_declaration =
  "function" , name_lower , function_formal_parameters , ":" , type_path , "=" , expression ;

term_declaration =
  value_declaration | function_declaration ;
Examples
value x = 23;

value y = I.plus x 24;

function identity (
  x : integer
) : integer = x;
Types
Declarations
A type_declaration declares a new record type. This section documents the declarations themselves, while the types section documents the actual type system itself.
Names
The names selected for types must be unique with respect to other types within the module in which they are defined. That is, there cannot be two types with the same name in the same module. Types do not share a name space with terms or shaders.
The following restrictions apply when naming types:
  • Type names must begin with a lowercase letter. This is directly implied by the grammar.
  • Names cannot contain two adjacent underscores (U+005F).
  • Names cannot end with underscores (U+005F).
  • Names cannot start with the character sequence "gl_" (U+0067, U+006C, U+005F).
  • Names cannot match any of the keywords or reserved words defined in any version of the OpenGL shading language. See the GLSL identifiers section for the complete list.
Recursion
No type_declaration can be recursive with respect to itself or any other type_declaration in the module in which it appears.
A type d₀ is said to refer statically to a type d₁ if d₀ appears anywhere in the definition of d₁.
A type_declaration d is (mutually) recursive iff:
  • d refers statically to itself.
  • There is a sequence of types t₀, t₁, ..., tₙ such that d refers statically to t₀, and for all m where 0 <= m < n, tₘ refers statically t₍ₘ₊₁₎, and tₙ refers statically to d.
Records
A type_declaration binds a record_type_expression to a name. A record_type_expression declares a set of named fields and types.
A record_type_expression cannot contain two fields with the same name, but two distinct record_type_expressions can have fields with the same names. To clarify, these are valid type_declarations:
type t is record
  x : integer,
  y : integer
end;

type u is record
  x : integer,
  y : integer
end;
However, this is not:
type t is record
  x : integer,
  x : integer
end;
As described in the types section, types have by-name equivalence and therefore two identical record_type_expressions bound to different names are not type-compatible.
Type rules
A type_declaration introduces a new record type into the typing environment. See the rules for record types for the effects that this has on typing rules.
Syntax
The precise syntax of type declarations is given by the following EBNF:
record_type_field =
  name_lower , ":" , type_path ;

record_type_expression =
  "record" , record_type_field , { "," , record_type_field } , "end" ;

type_declaration =
  "type" , name_lower , "is" , type_expression ;

type_expression =
  record_type_expression
  ;
        
Examples
type t is record
  x : integer,
  y : integer,
  z : integer
end;

type u is record
  v0 : t,
  v1 : t,
  v2 : t
end;
Shaders
Description
Shaders represent programs that will execute on the targeted graphics hardware. They are divided into vertex shaders, fragment shaders, and programs, (which essentially aggregate other shaders into usable programs).
Declarations
Names
The names selected for shaders must be unique with respect to other shaders within the module in which they are defined. That is, there cannot be two shaders with the same name in the same module. Shaders do not share a name space with terms or types.
The following restrictions apply when naming shaders:
  • Shader names must begin with a lowercase letter. This is directly implied by the grammar.
  • Names cannot contain two adjacent underscores (U+005F).
  • Names cannot end with underscores (U+005F).
  • Names cannot start with the character sequence "gl_" (U+0067, U+006C, U+005F).
  • Names cannot match any of the keywords or reserved words defined in any version of the OpenGL shading language. See the GLSL identifiers section for the complete list.
Inputs/Outputs/Parameters
Shaders consume data from inputs and parameters, and produce data on outputs.
Inputs are data sources that produce values that may change every time the shader is executed (once per vertex for vertex shaders and once per fragment for fragment shaders).
Parameters are data sources that produce values that may be constant over the entire lifetime of the program.
Outputs are data sinks that, when assigned values, pass those values to the next stage of the rendering pipeline. Typically, vertex shaders linearly interpolate values written to outputs and pass them on to the fragment shader, and fragment shaders send values written to outputs to the framebuffer for display.
The following restrictions apply when naming inputs, outputs, and parameters:
  • Names must begin with a lowercase letter. This is directly implied by the grammar.
  • Names cannot contain two adjacent underscores (U+005F).
  • Names cannot end with underscores (U+005F).
  • Names cannot start with the character sequence "gl_" (U+0067, U+006C, U+005F).
  • Names cannot match any of the keywords or reserved words defined in any version of the OpenGL shading language. See the GLSL identifiers section for the complete list.
Type rules
Only a subset of the available types are permitted for inputs, and outputs:
Parameters may be of any type [4].
Syntax
The precise syntax of shader declarations is given by the following EBNF:
shader_parameter_declaration =
  "parameter" , name_lower , ":" , type_path ;

shader_vertex_input_declaration =
  "in" , name_lower , ":" , type_path ;

shader_vertex_output_declaration =
  "out" , name_lower , ":" , type_path ;

shader_vertex_output_declaration =
  "out" , "vertex" , name_lower , ":" , type_path ;

shader_vertex_parameter =
    shader_parameter_declaration
  | shader_vertex_input_declaration
  | shader_vertex_output_declaration
  | shader_vertex_output_main_declaration ;

shader_vertex_parameters =
  { shader_vertex_parameter , ";" } ;

shader_vertex_output_assignment =
  "out" , name_lower , "=" , term_path ;

shader_vertex_output_assignments =
  shader_vertex_output_assignment , ";" , { shader_vertex_output_assignments } ;

shader_vertex_declaration =
  "vertex" , name_lower , "is" ,
  shader_vertex_parameters ,
  [ "with" , local_declarations ] ,
  "as" ,
  shader_vertex_output_assignments ,
  "end" ;

shader_fragment_input_declaration =
  "in" , name_lower , ":" , type_path ;

shader_fragment_output_declaration =
  "out" , name_lower , ":" , type_path , "as" , integer_literal ;

shader_fragment_parameter =
    shader_parameter_declaration
  | shader_fragment_input_declaration
  | shader_fragment_output_declaration ;

shader_fragment_parameters =
  { shader_fragment_parameter , ";" } ;

shader_fragment_discard_declaration =
  "discard" , "(" , expression , ")" ;

shader_fragment_local_declaration =
    local_declaration
  | shader_fragment_discard_declaration ;

shader_fragment_local_declarations =
  shader_fragment_local_declaration , ";" , { shader_fragment_local_declarations } ;

shader_fragment_output_assignment =
  "out" , name_lower , "=" , term_path ;

shader_fragment_output_assignments =
  shader_fragment_output_assignment , ";" , { shader_fragment_output_assignments } ;

shader_fragment_declaration =
  "fragment" , name_lower , "is" ,
  shader_fragment_parameters ,
  [ "with" , shader_fragment_local_declarations ] ,
  "as" ,
  shader_fragment_output_assignments ,
  "end" ;

shader_program_declaration =
  "program" , name_lower , "is" ,
  "vertex" , shader_path , ";" ,
  "fragment" , shader_path , ";" ,
  "end" ;

shader_declaration =
  "shader" , ( shader_vertex_declaration | shader_fragment_declaration | shader_program_declaration ) ;

shader_declarations =
  { shader_declaration , ";" } ;
Vertex shaders
Description
Vertex shaders are programs that process data on a per-vertex basis in OpenGL.
Declarations
A shader_vertex_declaration declares a set of inputs, outputs, and parameters, as well as a sequence of zero or more local_value_declarations, similar in semantics and typing to that found in a let expression, and a sequence of assignments of values to the declared outputs. A vertex shader must also define exactly one output of type vector_4f, indicated with the vertex keyword, to which must be assigned a value representing the current homogeneous vertex position.
It is required that there be exactly one shader_vertex_output_assignment for each shader_vertex_output_declaration.
Type rules
Each shader_parameter_declaration and shader_vertex_input_declaration introduces a new term of the given type into the environment, accessible only within the scope of the shader definition, as shown by the shader_vertex_inputs_parameters rule:
Each local_value_declaration introduces a new term of the given type into the environment, accessible in each successive local_value_declaration and in the shader_vertex_output_assignments, as shown by the shader_vertex_values rule:
Finally, each output assigned in a shader_vertex_output_assignment must be of the correct type, as shown by the shader_vertex_output_assignment rule:
Operational Semantics
The local_value_declarations are evaluated from top-to-bottom, in an identical manner to let expressions, as shown by the shader_vertex_values rule:
Examples
shader vertex v is
  parameter mm_modelview  : matrix_4x4f;
  parameter mm_projection : matrix_4x4f;
  parameter mm_normal     : matrix_3x3f;
  in position             : vector_4f;
  in normal               : vector_3f;
  out vertex r_position   : vector_4f;
  out r_normal            : vector_3f;
with
  value p_result = M4.multiply_vector (M4.multiply (mm_projection, mm_modelview), position);
  value n_result = M3.multiply_vector (mm_normal, normal);
as
  out r_position = p_result;
  out r_normal   = n_result;
end;
Fragment shaders
Description
Fragment shaders are programs that process data on a per-fragment basis in OpenGL.
Declarations
A shader_fragment_declaration declares a set of inputs, outputs, and parameters, as well as a sequence of zero or more shader_fragment_local_declaration, and a sequence of assignments of values to the declared outputs.
A fragment shader may optionally define at most one output of type float, indicated with the depth keyword, to which must be assigned a value representing the current desired fragment depth (overriding the depth value typically calculated by the graphics system's rasterizer).
A shader_fragment_local_declaration is either a local_value_declaration, similar in semantics and typing to that found in a let expression, or a shader_fragment_discard_declaration.
Discard
A shader_fragment_discard_declaration statement halts evaluation of the current fragment shader iff the given expression evaluates to true.
Type rules
Each shader_parameter_declaration and shader_fragment_input_declaration introduces a new term of the given type into the environment, accessible only within the scope of the shader definition, as shown by the shader_fragment_inputs_parameters rule:
Each local_value_declaration introduces a new term of the given type into the environment, accessible in each successive local_value_declaration and in the shader_fragment_output_assignments, as shown by the shader_fragment_values rule:
Each output assigned in a shader_fragment_output_assignment must be of the correct type, as shown by the shader_fragment_output_assignment rule:
Finally, the expression passed to a shader_fragment_discard_declaration must be of a boolean type as shown by the shader_fragment_discard rule [5]:
Operational Semantics
The shader_fragment_local_declaration are evaluated from top-to-bottom, in an identical manner to let expressions, as shown by the shader_fragment_values rule:
Evaluation halts immediately upon evaluating any shader_fragment_discard_declaration where the condition evaluates to true.
Examples
shader fragment f is
  parameter texture_0 : sampler_2d;
  in uv               : vector_2f;
  out out0            : vector_4f as 0;
with
  value rgba = T.texture (texture_0, uv);
as
  out out0 = rgba;
end;
Programs
Description
Programs aggregate vertex and fragment shaders into single entities that are executed on the targeted graphics hardware.
Declarations
A shader_program_declaration declares a dependency on a vertex shader and a fragment shader.
Compatibility
In order for a given vertex shader and fragment shader to be used in a program, the set of inputs I of the fragment shader must be compatible with the set of outputs O of the vertex shader. Specifically, for each 0 <= s <= |I| - 1, there must be some 0 <= t <= |O| - 1 such that the name and type of Iₛ matches that of Oₜ:
∀s. 0 <= s <= |I| - 1 ⇒
  ∃t. (0 <= t <= |O| - 1 ⋀ ((name(Iₛ) = name(Oₜ) ⋀  type(Iₛ) = type(Oₜ)))
The set of parameters P of the given given vertex shader, and the set of parameters Q of the given fragment shader must be type-compatible if they share any of the same names. That is, for each 0 <= s <= |P| - 1, if there is some 0 <= t <= |Q| - 1 such that the name of Pₛ equals that of Qₜ, then the type of Pₛ must equal that of Qₜ.
∀s. 0 <= s <= |P| - 1 ⇒
  ∃t. (0 <= t <= |Q| - 1 ⋀ name(Iₛ) = name(Oₜ)) ⇒
    type(Iₛ) = type(Oₜ)
Examples
shader program p is
  vertex p;
  fragment f;
end;
Modules
Description
A module is a organizational unit containing terms, types, and shaders. Modules exist solely to partition the namespace into separate sections to allow for ease of code re-use across projects.
Declarations
A module_declaration declares a new module.
Names
The names selected for modules must be unique with respect to other modules within the package in which they are defined. That is, there cannot be two modules with the same name in the same package.
The following restrictions apply when naming modules:
  • Module names must begin with an uppercase letter. This is directly implied by the grammar.
  • Names cannot contain two adjacent underscores (U+005F).
  • Names cannot end with underscores (U+005F).
  • Names cannot start with the character sequence "GL_" (U+0047, U+004C, U+005F).
  • Names cannot match any of the keywords or reserved words defined in any version of the OpenGL shading language. See the GLSL identifiers section for the complete list.
Imports
A module may import any number of modules via import_declarations. An import_declaration, given in module X, specifies a name of a module Y prefixed with the name of the package in which the module Y was defined. The terms, types, and shaders of Y are then accessible in X by qualifying their names with Y. As an example:
package com.example;

module Y is
  value k = 23;
end;

module X is
  import com.example.Y;
  value z = Y.k;
end;
The value of X.z is 23.
Because two modules defined in different packages can have the same names, it is possible for imports to collide: If a X imports both modules com.example_0.Y and com.example_1.Y, then the name Y will be introduced twice. An import_declaration may therefore provide an optional name to disambiguate imported modules:
package com.example;

module X is
  import com.example_0.Y;
  import com.example_1.Y as Z;

  value z = Y.k;
  value q = Z.p;
end;
If a module X imports a module Y as Z, the terms, types, and shaders of Y are then accessible in X by qualifying their names with Z.
The imported names of modules are not visible outside of the module in which they are imported. For example, if a module X imports a module Y, and Y imports a module Z, the module Z is not visible as Y.Z.
Recursion
No module_declaration can be recursive with respect to itself or any other module_declaration.
A module_declaration d is (mutually) recursive iff:
  • d imports itself.
  • There is a sequence of modules d₀, d₁, ..., dₙ such that d imports d₀, and for all m where 0 <= m < n, dₘ imports d₍ₘ₊₁₎, and dₙ imports d.
Syntax
The precise syntax of module declarations is given by the following EBNF:
package_path =
  name_lower , { "." , name_lower } ;

import_path =
  package_path , "." , name_upper ;

import_declaration =
  "import" , import_path , [ "as" , name_upper ] ;

import_declarations =
  { import_declaration , ";" } ;

module_level_declarations =
  { value_declarations | function_declarations | type_declarations | shader_declarations } ;

module_declaration =
  "module" , name_upper , "is" ,
  import_declarations ,
  module_level_declarations ,
  "end" ;

module_declarations =
  module_declaration , ";" , { module_declaration , ";" } ;
Packages
Description
Packages are to modules as modules are to terms, types, and shaders. Packages provide a non-hierarchical partitioned namespace that separates modules for ease of code re-use across projects.
Declarations
A package_declaration declares that the current unit will contain declarations that will be placed in the named package.
Multiple units can contain the same package_declaration, however a single unit must contain exactly one package_declaration.
Syntax
The precise syntax of package declarations is given by the following EBNF:
package_path =
  name_lower , { "." , name_lower } ;

package_declaration =
  "package" , package_path ;
Expressions
Description
Overview
An expression is a computation that evaluates to a value of a single type according to the rules given in the operational semantics for each expression form. Expressions may be one of the following forms:
Operational Semantics
The operational semantics for each expression form appears in the respective sections for each. The following syntactic forms are considered to be values:
Syntax
The precise syntax of expressions is given by the following EBNF:
term_path =
    name_lower
  | name_upper , "." , name_lower ;

type_path =
    name_lower
  | name_upper , "." , name_lower ;

variable_or_application_expression =
  term_path [ "(" , expression , { "," , expression } , ")" ]
  ;

new_parameters =
  "(" , expression , { "," , expression } , ")" ;

new_expression =
  "new" , type_path , new_parameters ;

record_expression_fields =
  "{" , name_lower , "=" , expression , { "," name_lower , "=" , expression } , "}" ;

record_expression =
  "record" , type_path , record_expression_fields ;

local_declaration =
  "value" , name_lower , [ ":" , type_path ] , "=" , expression ;

local_declarations =
  local_declaration , ";" , { local_declarations } ;

let_expression =
  "let" , local_declarations , "in" , expression , "end" ;

conditional_expression =
  "if" , expression , "then" , expression , "else" , expression , "end" ;

matrix_column_access_expression =
  "column" , expression , integer_literal ;

expression_pre =
    integer_literal
  | real_literal
  | boolean_literal
  | variable_or_application_expression
  | conditional_expression
  | matrix_column_access_expression
  | let_expression
  | new_expression
  | record_expression
  ;

expression_projection =
  "." , name_lower ;

expression_swizzle_names =
  "[" , name_lower , { "," , name_lower } , "]" ;

expression =
  expression_pre , { expression_swizzle | expression_projection } ;
Integer literal
Description
An integer_literal is a simple integer value.
Type rules
integer_literal expressions are of type integer, where represents the literal:
Operational semantics
integer_literal expressions are values by definition, and are equal to the value of the literal.
Real literal
Description
A real_literal is a simple real-number value.
Type rules
Real literal expressions are of type float, where represents the literal:
Operational semantics
real_literal expressions are values by definition, and are equal to the value of the literal.
Boolean literal
Description
A boolean_literal is a simple true or false value.
Operational semantics
Boolean literal expressions are values by definition, and are equal to the value of the literal.
Variable
Description
A variable expression is a simple reference to a variable in the current environment.
Type rules
For variable expressions, if a variable x is of type t in the current environment, then the result of evaluating the variable has type t:
Operational semantics
Variables are replaced with their values during the evaluation of functions, let expressions, and top-level value declarations.
Function application
Description
A function_application expression applies a given function f to an n-tuple of argument expressions.
Type rules
For function_application expressions, if a function f takes an n-tuple of values of types (t₀ ✕ ... ✕ tₙ) and returns a value of type u, then applying f to an n-tuple of expressions of the corresponding types, results in a value of type u:
Operational semantics
For a function_application expression e, expressions passed to the function are evaluated from left-to-right by rules function_application_left_0 and function_application_left_1, and when all of the arguments have evaluated to values, the variables in the body of the function are substituted with the values of the arguments and the expression as a whole is evaluated, by rule function_application_body.
Conditional
Description
A conditional expression evaluates to the expression in either of its defined branches based on a given condition.
Type rules
For conditional expressions, if the condition is of type boolean, and both branches are of type t, then evaluating the expression results in a value of type t:
Operational semantics
For a conditional expression e, the condition of the expression is evaluated, if it is not already a value, by rule if_condition. If the condition is true, e evaluates to the expression in the left branch, by rule if_true. If the condition is false, e evaluates to the expression in the right branch, by rule if_false.
Let
Description
A let_expression consists of a sequence of local_value_declarations and a body consisting of a single expression. The local_value_declarations differ from top-level value_declarations in that the order of declaration is significant, and it is legal for multiple local_value_declarations to have the same name, with each new declaration hiding any previous declarations (including top level declarations) with the same name.
The same rules apply for local_value_declarations with regards to recursion as any other term declaration.
Type rules
For let_expressions, if each successive local_value_declaration is well-typed (with respect to the environment and the preceding local_value_declarations), and the body of the expression (denoted y) is of type t, then evaluating the expression results in a value of type t:
Operational semantics
For a let expression e, the local_value_declarations are evaluated from top-to-bottom with each of the previously evaluated declarations being substituted into the current declaration before evaluation, by rule let_locals. When all of the declarations have been evaluated, the values of the declarations are subsituted into the body of the let and e evaluates to the resulting expression, by rule let_body.
Record Projection
Description
A record_projection expression returns the value of a field from an expression r, which is expected to be of a record type.
Type rules
For record_projection expressions, if an expression r is of type t, and t is a record type with fields 𝔽ₘ to 𝔽ₙ, of types tₘ to tₙ, then accessing field 𝔽ₖ of r, where k ∈ [m, n], results in a value of type tₖ:
Operational semantics
For a record_projection expression e, the left hand side of the expression is evaluated, by rule record_projection_pre, and then e evaluates to the value associated with the label k by rule record_projection_value.
Record
Description
A record_expression constructs a new value of a record type.
Type rules
If a record type t exists in the current environment, then a record expression creates a new value of type t. Note that the type rule implies that all fields of the corresponding record type must be specified exactly once.
Operational semantics
A record_expression is a set of distinct record field labels with associated expressions. The expressions associated with each field are evaluated in the order that they appear in the source code, by rules record_expression_0 and record_expression_1, resulting in a new value of type t.
Swizzle
Description
A swizzle_expression selects one or more named components of an expression of a vector type, returning either a scalar value, or another vector.
Type rules
For swizzle_expressions, special type rules apply as described in the Vectors section of the Types section.
Operational semantics
For a swizzle_expression e, the left hand side of the expression is evaluated, by rule swizzle_pre, and the resulting expression is a new vector or scalar value.
Matrix Column Access
Description
A matrix_column_access_expression retrieves exactly one column of a matrix type, returning a value of an appropriate vector type representing the column.
Type rules
For matrix_column_access_expressions, special type rules apply as described in the Matrices section of the Types section.
Operational semantics
For a matrix_column_access_expression e, the first subexpression is evaluated, by rule matrix_column_access_pre, and the resulting expression is a new vector value.
Note that the integer constant given as the index of the column to return is required by the type rules to be greater than zero and less than the number of columns in the type, and therefore evaluation cannot fail due to an out-of-range index.
New
Description
A new_expression creates a new value of a given type, initialized with the values of the given expressions.
Type rules
For new_expression, that construct values of types with the new keyword, special type rules apply as described in the Constructors section of the Types section.
Operational semantics
A new_expression is of the form new t x, where the x is an n-tuple of expressions given as constructor arguments. The expressions given are evaluated from left-to-right by rules new_left_0 and new_left_1, resulting in a new value of type t.
Types
Overview
A type classifies a set of values. Types in the parasol language have by-name equivalence (that is, two types are equal iff they have the same name) and the types of all terms are checked statically. All well-typed terms in the language have exactly one type.
The language defines a set of basic types, and allows the declaration of new types. The set of basic types is:
Values of the basic types may be introduced via certain literal expressions, or via the use of the new keyword, which invokes an appropriate constructor function for the given type. The constructor function chosen is based upon the n-tuple of arguments presented to the new keyword, and if no appropriate function exists, the expression is rejected as ill-typed.
Constructors
Each of the basic types have zero or more associated anonymous constructor functions (often abbreviated to constructors) which are responsible for introducing values of the types into the environment. An expression of the form new s (x₀ : t₀, ..., xₙ : tₙ) has type s iff there is a constructor for s of type (t₀ ✕ ... tₙ) → s.
The descriptions for each of the basic types describe the available constructors for each.
Integer
Description
The integer type represents a signed, 32 bit, two's-complement integer. Values outside of the range [-(2³¹), (2³¹) - 1] are silently wrapped to produce the low-order 32 bits of the result.
Constructors
The type has the following constructors:
  • integer → integer
  • boolean → integer
  • float → integer
The constructor taking a value v of type boolean results in an integer value 0 iff v = false and an integer value 1 iff v = true.
The constructor taking a value v of type float results in an integer value consisting of the integral part of v with the fractional part truncated.
The integer type is a scalar type. As with the other scalar types, the type rule for the construction of values of type integer is:
Float
Description
The float type represents a single-precision IEEE754 floating point number.
Constructors
The type has the following constructors:
  • integer → float
  • boolean → float
  • float → float
The constructor taking a value v of type boolean results in a value 0.0 iff v = false and a value 1.0 v = true.
The constructor taking a value v of type integer results in an value with an integral part as close as possible to the value of v dependent on the internal precision of the float type.
The float type is a scalar type. As with the other scalar types, the type rule for the construction of values of type float is:
Boolean
Description
The boolean type represents a true or false value.
Constructors
The type has the following constructors:
  • boolean → boolean
  • float → boolean
  • integer → boolean
The constructor taking a value v of type float results in a value false iff v = 0.0 and a value true otherwise.
The constructor taking a value v of type integer results in a value false iff v = 0 and a value true otherwise.
The boolean type is a scalar type. As with the other scalar types, the type rule for the construction of values of type boolean is:
Vectors
Description
The vector_NT types represent fixed-length vectors, where N ∈ [2, 4] and represents the number of components in the vector, and T ∈ {f, i} where f indicates that the components of the vector are of type float and i indicates that the components of the vector are of type integer.
The components of the vector_NT types are labelled, in order, from the n-tuple of labels K = (x, y, z, w). At most N labels are used for each type, so the first, second, third, and fourth elements of the vector_4T types are labelled x, y, z, and w, respectively. The vector_3T types lack a w component, and the vector_2T types lack both z and w components.
The values of the components of the vector_NT types are extracted via swizzle expressions. A swizzle expression consists of an n-tuple Sₚ of labels taken from the set S = tuples(L, M) of n-tuples, where M <= N, L is the first M - 1 elements of K, and 0 <= P <= |S|, and evaluates to a value of type vector_MT or, in the case that M = 1, a scalar value of type T, consisting of the values of the components named in Sₚ:
Algebraically, swizzling is analogous to multiplication of a vector v of size N by an NxN matrix p, where each row of p consists of N - 1 zeroes and exactly one 1:
Constructors
The constructors of the vector_NT types can be conceptually divided into primary and auxiliary constructors. Each vector_NT type has exactly one primary constructor which initializes the components of the resulting vector_NT value to the values of the expressions in the exact order given. There is no practical or visible difference between a primary and auxiliary constructor; the distinction is simply made for the purposes of describing the typing rules.
The types of the primary constructors for each type are:
TypeConstructor type
vector_2i(integer, integer) → vector_2i
vector_3i(integer, integer, integer) → vector_3i
vector_4i(integer, integer, integer, integer) → vector_4i
vector_2f(float, float) → vector_2f
vector_3f(float, float, float) → vector_3f
vector_4f(float, float, float, float) → vector_4f
Given the presence of primary constructors, the type rule for the construction of a vector using the new keyword is:
The auxiliary constructors for the vector_NT types are:
TypeConstructor type
vector_2ivector_2i → vector_2i
vector_3i(integer, vector_2i) → vector_3i
vector_3i(vector_2i, integer) → vector_3i
vector_3ivector_3i → vector_3i
vector_4i(vector_2i, integer, integer) → vector_4i
vector_4i(integer, vector_2i, integer) → vector_4i
vector_4i(vector_2i, vector_2i) → vector_4i
vector_4i(integer, integer, vector_2i) → vector_4i
vector_4i(vector_3i, integer) → vector_4i
vector_4i(integer, vector_3i) → vector_4i
vector_4ivector_4i → vector_4i
vector_2fvector_2f → vector_2f
vector_3f(float, vector_2f) → vector_3f
vector_3f(vector_2f, float) → vector_3f
vector_3fvector_3f → vector_3f
vector_4f(vector_2f, float, float) → vector_4f
vector_4f(float, vector_2f, float) → vector_4f
vector_4f(vector_2f, vector_2f) → vector_4f
vector_4f(float, float, vector_2f) → vector_4f
vector_4f(vector_3f, float) → vector_4f
vector_4f(float, vector_3f) → vector_4f
vector_4fvector_4f → vector_4f
For each auxiliary constructor for a given vector_NT type, the values of the scalars (if any), and the values of the components of the given vectors (if any), are concatenated together in the order given to produce an n-tuple of length N which is then passed directly to the primary constructor for the given type.
Matrices
Description
The matrix_NxNf types represent square matrices, where N ∈ [3, 4] and represents the number of rows/columns in the matrix. Only matrices with elements of type float are provided.
The columns of values of matrix_NxNf types are extracted via matrix_column_access expressions. A matrix_column_access expression consists of an expression e of a matrix_NxNf type, and an integer constant K where 0 <= K < N, and evaluates to a value of type vector_Nf representing the Kth column of e.
Constructors
Values of the matrix_NxNf types are constructed by providing exactly N column vectors of size N. This is reflected in the available constructors for the matrix_NxNf types:
TypeConstructor type
matrix_3x3f(vector_3f, vector_3f, vector_3f) → matrix_3x3f
matrix_4x4f(vector_4f, vector_4f, vector_4f, vector_4f) → matrix_4x4f
The type rule for the construction of matrices with the new keyword is:
Samplers
Description
The sampler_2d and sampler_cube types represent abstract handles to two-dimensional and cube textures, respectively.
Constructors
The types have no constructors and there is no way to create new values of the types in the language. They are intended for use as parameters to shaders.
Records
Description
Record types are composite types consisting of labelled fields. Values of record types are constructed with the new keyword, and values of fields are accessed with record_projection expressions.
Only a subset of the available types can be used as fields in records:
Constructors
Record types do not have constructors and values of record types can only be introduced with record expressions.
Compilation and Execution
Overview
This section attempts to informally document the typical life cycle of a parasol program, from its initial state as a set of source code, to its final state executing on a GPU, from a somewhat abstract and idealized viewpoint. The intention is to allow the reader to better understand how the given operational semantics fit together and how the language as structured results in a working program.
Lifecycle
First, the intended program is compiled:
  1. The user submits a series of units, in no particular order, to the parasol implementation compiler, along with the fully-qualified name of a program.
  2. The compiler combines the units, checks for module name collisions, checks the sanity of the submitted modules, resolves all names, and checks the types of all terms.
  3. The compiler then erases all module_declarations from the environment, renaming all terms to result in a set of terms with unique names.
  4. The compiler then determines all terms upon which the program depends, removing all unused terms and sorting the resulting terms topologically.
At this point, the environment contains a vertex shader V with an associated ordered set of terms T, and fragment shader F with an associated ordered set of terms U.
Execution then proceeds as follows:
  1. Execution of V begins.
  2. The terms in T are evaluated in the order given.
  3. The operational semantics imply that the values of the terms in T have been substituted into the local declarations of V. The local declarations defined in V are evaluated.
  4. The values resulting from evaluation are written to the outputs of V.
  5. Execution of F begins.
  6. The terms in U are evaluated in the order given.
  7. The operational semantics imply that the values of the terms in U have been substituted into the local declarations of F. The local declarations defined in F are evaluated.
  8. If F contains a discard declaration, and the condition evaluates to true, execution of F halts and no output is produced.
  9. The values resulting from evaluation are written to the outputs of F.
Standard Library Reference
Module com.io7m.parasol.Boolean
or
function or (x : boolean, y : boolean) : boolean
Return the true iff x == true or y == true.
Module com.io7m.parasol.Float
absolute
function absolute (x : float) : float
Return the absolute value of x.
add
function add (x : float, y : float) : float
Return x + y.
arc_cosine
function arc_cosine (x : float) : float
Return the arc cosine acos(x).
arc_sine
function arc_sine (x : float) : float
Return the arc sine asin(x).
arc_tangent
function arc_tangent (x : float) : float
Return the arc tangent atan(x).
ceiling
function ceiling (x : float) : float
Return a value equal to the nearest integer that is greater than or equal to x.
clamp
function clamp (x : float, min : float, max : float) : float
Return x constrainted to the range [min, max].
cosine
function cosine (x : float) : float
Return the cosine cos(x).
divide
function divide (x : float, y : float) : float
Return the division x / y. The result is only defined if y > 0.
equals
function equals (x : float, y : float) : boolean
Return true iff x == y.
floor
function floor (x : float) : float
Return a value equal to the nearest integer that is less than or equal to x.
greater
function greater (x : float, y : float) : boolean
Return true iff x > y.
greater_or_equal
function greater_or_equal (x : float, y : float) : boolean
Return true iff x >= y.
interpolate
function interpolate (x : float, y : float, a : float) : float
Return a linearly interpolated value in the range [x, y] given by (x ✕ (1 - a)) + (y ✕ a).
is_infinite
function is_infinite (x : float) : boolean
Return true iff x == infinity.
is_nan
function is_nan (x : float) : boolean
Return true iff x is not a number.
lesser
function lesser (x : float, y : float) : boolean
Return true iff x < y.
lesser_or_equal
function lesser_or_equal (x : float, y : float) : boolean
Return true iff x <= y.
log2
function log2 (x : float) : float
Return the base 2 logarithm of x.
maximum
function maximum (x : float, y : float) : float
Return the maximum value of x and y.
minimum
function minimum (x : float, y : float) : float
Return the minimum value of x and y.
modulo
function modulo (x : float, y : float) : float
Return the value of x mod y.
multiply
function multiply (x : float, y : float) : float
Return the value of x ✕ y.
negate
function negate (x : float) : float
Return the negation -x.
power
function power (x : float, n : float) : float
Return the value of xⁿ.
round
function round (x : float) : float
Return the value of the nearest integer to x.
sign
function sign (x : float) : float
Return 0.0 iff x == 0.0, 1.0 iff x > 0.0, and -1.0 otherwise.
sine
function sine (x : float) : float
Return the sine sin(x).
square_root
function square_root (x : float) : float
Return the square root of x.
subtract
function subtract (x : float, y : float) : float
Return the value of of x - y.
tangent
function tangent (x : float) : float
Return the tangent tan(x).
truncate
function truncate (x : float) : float
Return the nearest integer to x whose absolute value is not greater than the absolute value of x.
Module com.io7m.parasol.Integer
add
function add (x : integer, y : integer) : integer
Return x + y.
divide
function divide (x : integer, y : integer) : integer
Return the division x / y. The result is only defined if y > 0.
multiply
function multiply (x : integer, y : integer) : integer
Return x ✕ y.
subtract
function subtract (x : integer, y : integer) : integer
Return x - y.
Module com.io7m.parasol.Matrix3x3f
multiply
function multiply (m0 : matrix_3x3f, m1 : matrix_3x3f) : matrix_3x3f
Return m0 ✕ m1.
multiply_vector
function multiply_vector (m : matrix_3x3f, v : vector_3f) : vector_3f
Return m ✕ v.
Module com.io7m.parasol.Matrix4x4f
multiply
function multiply (m0 : matrix_4x4f, m1 : matrix_4x4f) : matrix_4x4f
Return m0 ✕ m1.
multiply_vector
function multiply_vector (m : matrix_4x4f, v : vector_4f) : vector_4f
Return m ✕ v.
Module com.io7m.parasol.Sampler2D
texture
function texture (t : sampler_2d, uv : vector_2f) : vector_4f
Retrieve a texel from the texture t from the texture coordinate uv.
texture_with_offset
function texture_with_offset (t : sampler_2d, uv : vector_2f, o : vector_2i) : vector_4f
Retrieve a texel from the texture t from the texture coordinate uv using texel offsets o.
This function is not available on GLSL versions <= 120, and GLSL ES version 100, and no software emulation is possible.
texture_with_lod
function texture_with_lod (t : sampler_2d, uv : vector_2f, lod : float) : vector_4f
Retrieve a texel from the texture t from the texture coordinate uv using an explicit level-of-detail lod.
This function is not available on GLSL versions <= 120, and GLSL ES version 100, and no software emulation is possible.
Module com.io7m.parasol.Vector2f
add
function add (v0 : vector_2f, v1 : vector_2f) : vector_2f
Return the component-wise addition v0 + v1.
add_scalar
function add_scalar (v : vector_2f, x : float) : vector_2f
Return the addition (v[x] + s, v[y] + s).
divide
function divide (v0 : vector_2f, v1 : vector_2f) : vector_2f
Return the component-wise division v0 / v1.
divide_scalar
function divide_scalar (v : vector_2f, x : float) : vector_2f
Return the division (v[x] / s, v[y] / s).
dot
function dot (v0 : vector_2f, v1 : vector_2f) : float
Return the dot product v0 ⋅ v1.
interpolate
function interpolate (v0 : vector_2f, v1 : vector_2f, t : float) : vector_2f
Return the linear interpolation (v1 ✕ t) + (v0 ✕ (1 - t)).
magnitude
function magnitude (v : vector_2f) : float
Return the magnitude of v.
multiply
function multiply (v0 : vector_2f, v1 : vector_2f) : vector_2f
Return the component-wise multiplication v0 ✕ v1.
multiply_scalar
function multiply_scalar (v0 : vector_2i, x : float) : vector_3f
Return the multiplication (v[x] ✕ s, v[y] ✕ s).
negate
function negate (v : vector_2f) : vector_2f
Return the component-wise negation -v.
normalize
function normalize (v : vector_2f) : vector_2f
Return a vector pointing in the same direction as v but with a magnitude of 1.
reflect
function reflect (i : vector_2f, n : vector_2f) : vector_2f
Return the reflection of the incident vector i with respect to the vector n, as given by i - 2.0 ✕ dot(n, i) ✕ n.
refract
function refract (i : vector_2f, n : vector_2f, e : float) : vector_2f
Return the refraction vector of the incident vector i with respect to the vector n, with the given ratio of indices of refraction e.
subtract
function subtract (v0 : vector_2f, v1 : vector_2f) : vector_2f
Return the component-wise subtraction v0 - v1.
Module com.io7m.parasol.Vector2i
add
function add (v0 : vector_2i, v1 : vector_2i) : vector_2i
Return the component-wise addition v0 + v1.
add_scalar
function add_scalar (v : vector_2i, x : integer) : vector_2i
Return the addition (v[x] + s, v[y] + s).
divide
function divide (v0 : vector_2i, v1 : vector_2i) : vector_2i
Return the component-wise division v0 / v1.
divide_scalar
function divide_scalar (v : vector_2i, x : integer) : vector_2i
Return the division (v[x] / s, v[y] / s).
dot
function dot (v0 : vector_2i, v1 : vector_2i) : integer
Return the dot product v0 ⋅ v1.
interpolate
function interpolate (v0 : vector_2i, v1 : vector_2i, t : float) : vector_2i
Return the linear interpolation (v1 ✕ t) + (v0 ✕ (1 - t)).
magnitude
function magnitude (v : vector_2i) : float
Return the magnitude of v.
multiply
function multiply (v0 : vector_2i, v1 : vector_2i) : vector_2i
Return the component-wise multiplication v0 ✕ v1.
multiply_scalar
function multiply_scalar (v0 : vector_2i, x : integer) : vector_3f
Return the multiplication (v[x] ✕ s, v[y] ✕ s).
negate
function negate (v : vector_2i) : vector_2i
Return the component-wise negation -v.
normalize
function normalize (v : vector_2i) : vector_2i
Return a vector pointing in the same direction as v but with a magnitude of 1.
reflect
function reflect (i : vector_2i, n : vector_2i) : vector_2i
Return the reflection of the incident vector i with respect to the vector n, as given by i - 2.0 ✕ dot(n, i) ✕ n.
refract
function refract (i : vector_2i, n : vector_2i, e : float) : vector_2i
Return the refraction vector of the incident vector i with respect to the vector n, with the given ratio of indices of refraction e.
subtract
function subtract (v0 : vector_2i, v1 : vector_2i) : vector_2i
Return the component-wise subtraction v0 - v1.
Module com.io7m.parasol.Vector3f
add
function add (v0 : vector_3f, v1 : vector_3f) : vector_3f
Return the component-wise addition v0 + v1.
add_scalar
function add_scalar (v : vector_3f, x : integer) : vector_3f
Return the addition (v[x] + s, v[y] + s, v[z] + s).
divide
function divide (v0 : vector_3f, v1 : vector_3f) : vector_3f
Return the component-wise division v0 / v1.
divide_scalar
function divide_scalar (v : vector_3f, x : integer) : vector_3f
Return the division (v[x] / s, v[y] / s, v[z] / s).
cross
function cross (v0 : vector_3f, v1 : vector_3f) : vector_3f
Return the cross product v0 ✕ v1.
dot
function dot (v0 : vector_3f, v1 : vector_3f) : integer
Return the dot product v0 ⋅ v1.
interpolate
function interpolate (v0 : vector_3f, v1 : vector_3f, t : float) : vector_3f
Return the linear interpolation (v1 ✕ t) + (v0 ✕ (1 - t)).
magnitude
function magnitude (v : vector_3f) : float
Return the magnitude of v.
multiply
function multiply (v0 : vector_3f, v1 : vector_3f) : vector_3f
Return the component-wise multiplication v0 ✕ v1.
multiply_scalar
function multiply_scalar (v0 : vector_3f, x : float) : vector_3f
Return the multiplication (v[x] ✕ s, v[y] ✕ s, v[z] ✕ s).
negate
function negate (v : vector_3f) : vector_3f
Return the component-wise negation -v.
normalize
function normalize (v : vector_3f) : vector_3f
Return a vector pointing in the same direction as v but with a magnitude of 1.
reflect
function reflect (i : vector_3f, n : vector_3f) : vector_3f
Return the reflection of the incident vector i with respect to the vector n, as given by i - 2.0 ✕ dot(n, i) ✕ n.
refract
function refract (i : vector_3f, n : vector_3f, e : float) : vector_3f
Return the refraction vector of the incident vector i with respect to the vector n, with the given ratio of indices of refraction e.
subtract
function subtract (v0 : vector_3f, v1 : vector_3f) : vector_3f
Return the component-wise subtraction v0 - v1.
Module com.io7m.parasol.Vector3i
add
function add (v0 : vector_3i, v1 : vector_3i) : vector_3i
Return the component-wise addition v0 + v1.
add_scalar
function add_scalar (v : vector_3i, x : integer) : vector_3i
Return the addition (v[x] + s, v[y] + s, v[z] + s).
divide
function divide (v0 : vector_3i, v1 : vector_3i) : vector_3i
Return the component-wise division v0 / v1.
divide_scalar
function divide_scalar (v : vector_3i, x : integer) : vector_3i
Return the division (v[x] / s, v[y] / s, v[z] / s).
dot
function dot (v0 : vector_3i, v1 : vector_3i) : integer
Return the dot product v0 ⋅ v1.
interpolate
function interpolate (v0 : vector_3i, v1 : vector_3i, t : float) : vector_3i
Return the linear interpolation (v1 ✕ t) + (v0 ✕ (1 - t)).
magnitude
function magnitude (v : vector_3i) : float
Return the magnitude of v.
multiply
function multiply (v0 : vector_3i, v1 : vector_3i) : vector_3i
Return the component-wise multiplication v0 ✕ v1.
multiply_scalar
function multiply_scalar (v0 : vector_3i, x : float) : vector_3i
Return the multiplication (v[x] ✕ s, v[y] ✕ s, v[z] ✕ s).
negate
function negate (v : vector_3i) : vector_3i
Return the component-wise negation -v.
normalize
function normalize (v : vector_3i) : vector_3i
Return a vector pointing in the same direction as v but with a magnitude of 1.
reflect
function reflect (i : vector_3i, n : vector_3i) : vector_3i
Return the reflection of the incident vector i with respect to the vector n, as given by i - 2.0 ✕ dot(n, i) ✕ n.
refract
function refract (i : vector_3i, n : vector_3i, e : float) : vector_3i
Return the refraction vector of the incident vector i with respect to the vector n, with the given ratio of indices of refraction e.
subtract
function subtract (v0 : vector_3i, v1 : vector_3i) : vector_3i
Return the component-wise subtraction v0 - v1.
Module com.io7m.parasol.Vector4f
add
function add (v0 : vector_4f, v1 : vector_4f) : vector_4f
Return the component-wise addition v0 + v1.
add_scalar
function add_scalar (v : vector_4f, x : integer) : vector_4f
Return the addition (v[x] + s, v[y] + s, v[z] + s, v[w] + s).
divide
function divide (v0 : vector_4f, v1 : vector_4f) : vector_4f
Return the component-wise division v0 / v1.
divide_scalar
function divide_scalar (v : vector_4f, x : integer) : vector_4f
Return the division (v[x] / s, v[y] / s, v[z] / s, v[w] / s).
dot
function dot (v0 : vector_4f, v1 : vector_4f) : integer
Return the dot product v0 ⋅ v1.
interpolate
function interpolate (v0 : vector_4f, v1 : vector_4f, t : float) : vector_4f
Return the linear interpolation (v1 ✕ t) + (v0 ✕ (1 - t)).
magnitude
function magnitude (v : vector_4f) : float
Return the magnitude of v.
multiply
function multiply (v0 : vector_4f, v1 : vector_4f) : vector_4f
Return the component-wise multiplication v0 ✕ v1.
multiply_scalar
function multiply_scalar (v0 : vector_4f, x : float) : vector_4f
Return the multiplication (v[x] ✕ s, v[y] ✕ s, v[z] ✕ s, v[w] ✕ s).
negate
function negate (v : vector_4f) : vector_4f
Return the component-wise negation -v.
normalize
function normalize (v : vector_4f) : vector_4f
Return a vector pointing in the same direction as v but with a magnitude of 1.
reflect
function reflect (i : vector_4f, n : vector_4f) : vector_4f
Return the reflection of the incident vector i with respect to the vector n, as given by i - 2.0 ✕ dot(n, i) ✕ n.
refract
function refract (i : vector_4f, n : vector_4f, e : float) : vector_4f
Return the refraction vector of the incident vector i with respect to the vector n, with the given ratio of indices of refraction e.
subtract
function subtract (v0 : vector_4f, v1 : vector_4f) : vector_4f
Return the component-wise subtraction v0 - v1.
Module com.io7m.parasol.Vector4i
add
function add (v0 : vector_4i, v1 : vector_4i) : vector_4i
Return the component-wise addition v0 + v1.
add_scalar
function add_scalar (v : vector_4i, x : integer) : vector_4i
Return the addition (v[x] + s, v[y] + s, v[z] + s, v[w] + s).
divide
function divide (v0 : vector_4i, v1 : vector_4i) : vector_4i
Return the component-wise division v0 / v1.
divide_scalar
function divide_scalar (v : vector_4i, x : integer) : vector_4i
Return the division (v[x] / s, v[y] / s, v[z] / s, v[w] / s).
dot
function dot (v0 : vector_4i, v1 : vector_4i) : integer
Return the dot product v0 ⋅ v1.
interpolate
function interpolate (v0 : vector_4i, v1 : vector_4i, t : float) : vector_4i
Return the linear interpolation (v1 ✕ t) + (v0 ✕ (1 - t)).
magnitude
function magnitude (v : vector_4i) : float
Return the magnitude of v.
multiply
function multiply (v0 : vector_4i, v1 : vector_4i) : vector_4i
Return the component-wise multiplication v0 ✕ v1.
multiply_scalar
function multiply_scalar (v0 : vector_4i, x : float) : vector_4i
Return the multiplication (v[x] ✕ s, v[y] ✕ s, v[z] ✕ s, v[w] ✕ s).
negate
function negate (v : vector_4i) : vector_4i
Return the component-wise negation -v.
normalize
function normalize (v : vector_4i) : vector_4i
Return a vector pointing in the same direction as v but with a magnitude of 1.
reflect
function reflect (i : vector_4i, n : vector_4i) : vector_4i
Return the reflection of the incident vector i with respect to the vector n, as given by i - 2.0 ✕ dot(n, i) ✕ n.
refract
function refract (i : vector_4i, n : vector_4i, e : float) : vector_4i
Return the refraction vector of the incident vector i with respect to the vector n, with the given ratio of indices of refraction e.
subtract
function subtract (v0 : vector_4i, v1 : vector_4i) : vector_4i
Return the component-wise subtraction v0 - v1.
Appendices
EBNF Grammar
The full EBNF grammar of the language is as follows:
(* Terminals *)

digit_nonzero =
  "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;

digit =
  "0" | digit_nonzero ;

letter_lower =
  "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" |
  "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" |
  "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
  "y" | "z" ;

letter_upper =
  "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" |
  "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" |
  "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" |
  "Y" | "Z" ;

letter =
  letter_lower | letter_upper ;

name_lower =
  letter_lower , { letter | digit | "-" | "_" } ;

name_upper =
  letter_upper , { letter | digit | "-" | "_" } ;

integer_literal =
  "0" | ( ["-"] , digit_nonzero , { digit } ) ;

real_literal =
  ["-"] , digit , { digit } , "." , digit , { digit } ;

boolean_literal =
  "true" | "false" ;

(* Non-terminals *)

package_path =
  name_lower , { "." , name_lower } ;

package_declaration =
  "package" , package_path ;

import_path =
  package_path , "." , name_upper ;

import_declaration =
  "import" , import_path , [ "as" , name_upper ] ;

import_declarations =
  { import_declaration , ";" } ;

type_path =
    name_lower
  | name_upper , "." , name_lower ;

term_path =
    name_lower
  | name_upper , "." , name_lower ;

shader_path =
    name_lower
  | name_upper , "." , name_lower ;

value_declaration =
  "value" , name_lower , [ ":" , type_path ] , "=" , expression ;

value_declarations =
  { value_declaration , ";" } ;

function_formal_parameter =
  name_lower , ":" , type_path ;

function_formal_parameters =
  "(" , function_formal_parameter, { "," , function_formal_parameter } , ")" ;

function_declaration =
  "function" , name_lower , function_formal_parameters , ":" , type_path , "=" , expression ;

term_declaration =
  value_declaration | function_declaration ;

record_type_field =
  name_lower , ":" , type_path ;

record_type_expression =
  "record" , record_type_field , { "," , record_type_field } , "end" ;

type_declaration =
  "type" , name_lower , "is" , type_expression ;

type_declarations =
  { type_declaration , ";" } ;

type_expression =
  record_type_expression
  ;

variable_or_application_expression =
  term_path [ "(" , expression , { "," , expression } , ")" ]
  ;

new_parameters =
  "(" , expression , { "," , expression } , ")" ;

new_expression =
  "new" , type_path , new_parameters ;

record_expression_fields =
  "{" , name_lower , "=" , expression , { "," name_lower , "=" , expression } , "}" ;

record_expression =
  "record" , type_path , record_expression_fields ;

local_declaration =
  "value" , name_lower , [ ":" , type_path ] , "=" , expression ;

local_declarations =
  local_declaration , ";" , { local_declarations } ;

let_expression =
  "let" , local_declarations , "in" , expression , "end" ;

conditional_expression =
  "if" , expression , "then" , expression , "else" , expression , "end" ;

matrix_column_access_expression =
  "column" , expression , integer_literal ;

expression_pre =
    integer_literal
  | real_literal
  | boolean_literal
  | variable_or_application_expression
  | conditional_expression
  | matrix_column_access_expression
  | let_expression
  | new_expression
  | record_expression
  ;

expression_projection =
  "." , name_lower ;

expression_swizzle_names =
  "[" , name_lower , { "," , name_lower } , "]" ;

expression =
  expression_pre , { expression_swizzle | expression_projection } ;

shader_parameter_declaration =
  "parameter" , name_lower , ":" , type_path ;

shader_vertex_input_declaration =
  "in" , name_lower , ":" , type_path ;

shader_vertex_output_declaration =
  "out" , name_lower , ":" , type_path ;

shader_vertex_output_main_declaration =
  "out" , "vertex" , name_lower , ":" , type_path ;

shader_vertex_parameter =
    shader_parameter_declaration
  | shader_vertex_input_declaration
  | shader_vertex_output_declaration
  | shader_vertex_output_main_declaration ;

shader_vertex_parameters =
  { shader_vertex_parameter , ";" } ;

shader_vertex_output_assignment =
  "out" , name_lower , "=" , term_path ;

shader_vertex_output_assignments =
  shader_vertex_output_assignment , ";" , { shader_vertex_output_assignments } ;

shader_vertex_declaration =
  "vertex" , name_lower , "is" ,
  shader_vertex_parameters ,
  [ "with" , local_declarations ] ,
  "as" ,
  shader_vertex_output_assignments ,
  "end" ;

shader_fragment_input_declaration =
  "in" , name_lower , ":" , type_path ;

shader_fragment_output_declaration =
  "out" , name_lower , ":" , type_path , "as" , integer_literal ;

shader_fragment_output_depth_declaration =
  "out" , "depth", name_lower , ":" , type_path ;

shader_fragment_parameter =
    shader_parameter_declaration
  | shader_fragment_input_declaration
  | shader_fragment_output_declaration
  | shader_fragment_output_depth_declaration ;

shader_fragment_parameters =
  { shader_fragment_parameter , ";" } ;

shader_fragment_discard_declaration =
  "discard" , "(" , expression , ")" ;

shader_fragment_local_declaration =
    local_declaration
  | shader_fragment_discard_declaration ;

shader_fragment_local_declarations =
  shader_fragment_local_declaration , ";" , { shader_fragment_local_declarations } ;

shader_fragment_output_assignment =
  "out" , name_lower , "=" , term_path ;

shader_fragment_output_assignments =
  shader_fragment_output_assignment , ";" , { shader_fragment_output_assignments } ;

shader_fragment_declaration =
  "fragment" , name_lower , "is" ,
  shader_fragment_parameters ,
  [ "with" , shader_fragment_local_declarations ] ,
  "as" ,
  shader_fragment_output_assignments ,
  "end" ;

shader_program_declaration =
  "program" , name_lower , "is" ,
  "vertex" , shader_path , ";" ,
  "fragment" , shader_path , ";" ,
  "end" ;

shader_declaration =
  "shader" , ( shader_vertex_declaration | shader_fragment_declaration | shader_program_declaration ) ;

shader_declarations =
  { shader_declaration , ";" } ;

module_level_declarations =
  { value_declarations | function_declarations | type_declarations | shader_declarations } ;

module_declaration =
  "module" , name_upper , "is" ,
  import_declarations ,
  module_level_declarations ,
  "end" ;

module_declarations =
  module_declaration , ";" , { module_declaration , ";" } ;

unit =
  package_declaration , ";" ,
  module_declarations ;

Type rules
The full type rules for the language are as follows:
Operational semantics
The full operational semantics for the language are as follows:
GLSL identifiers
The complete list of reserved words in the OpenGL shading language as of version 4.3 are:
  • active
  • asm
  • atomic_uint
  • attribute
  • bool
  • break
  • buffer
  • bvec2
  • bvec3
  • bvec4
  • case
  • cast
  • centroid
  • class
  • coherent
  • common
  • const
  • continue
  • default
  • discard
  • dmat2
  • dmat2x2
  • dmat2x3
  • dmat2x4
  • dmat3
  • dmat3x2
  • dmat3x3
  • dmat3x4
  • dmat4
  • dmat4x2
  • dmat4x3
  • dmat4x4
  • do
  • double
  • dvec2
  • dvec3
  • dvec4
  • else
  • enum
  • extern
  • external
  • false
  • filter
  • fixed
  • flat
  • float
  • for
  • fvec2
  • fvec3
  • fvec4
  • goto
  • half
  • highp
  • hvec2
  • hvec3
  • hvec4
  • if
  • iimage1D
  • iimage1DArray
  • iimage2D
  • iimage2DArray
  • iimage2DMS
  • iimage2DMSArray
  • iimage2DRect
  • iimage3D
  • iimageBuffer
  • iimageCube
  • iimageCubeArray
  • image1D
  • image1DArray
  • image2D
  • image2DArray
  • image2DMS
  • image2DMSArray
  • image2DRect
  • image3D
  • imageBuffer
  • imageCube
  • imageCubeArray
  • in
  • inline
  • inout
  • input
  • int
  • interface
  • invariant
  • isampler1D
  • isampler1DArray
  • isampler2D
  • isampler2DArray
  • isampler2DMS
  • isampler2DMSArray
  • isampler2DRect
  • isampler3D
  • isamplerBuffer
  • isamplerCube
  • isamplerCubeArray
  • ivec2
  • ivec3
  • ivec4
  • layout
  • long
  • lowp
  • mat2
  • mat2x2
  • mat2x3
  • mat2x4
  • mat3
  • mat3x2
  • mat3x3
  • mat3x4
  • mat4
  • mat4x2
  • mat4x3
  • mat4x4
  • mediump
  • namespace
  • noinline
  • noperspective
  • out
  • output
  • packed
  • partition
  • patch
  • precision
  • public
  • readonly
  • resource
  • restrict
  • return
  • row_major
  • sample
  • sampler1D
  • sampler1DArray
  • sampler1DArrayShadow
  • sampler1DShadow
  • sampler2D
  • sampler2DArray
  • sampler2DArrayShadow
  • sampler2DMS
  • sampler2DMSArray
  • sampler2DRect
  • sampler2DRectShadow
  • sampler2DShadow
  • sampler3D
  • sampler3DRect
  • samplerBuffer
  • samplerCube
  • samplerCubeArray
  • samplerCubeArrayShadow
  • samplerCubeShadow
  • shared
  • short
  • sizeof
  • smooth
  • static
  • struct
  • subroutine
  • superp
  • switch
  • template
  • this
  • true
  • typedef
  • uimage1D
  • uimage1DArray
  • uimage2D
  • uimage2DArray
  • uimage2DMS
  • uimage2DMSArray
  • uimage2DRect
  • uimage3D
  • uimageBuffer
  • uimageCube
  • uimageCubeArray
  • uint
  • uniform
  • union
  • unsigned
  • usampler1D
  • usampler1DArray
  • usampler2D
  • usampler2DArray
  • usampler2DMS
  • usampler2DMSArray
  • usampler2DRect
  • usampler3D
  • usamplerBuffer
  • usamplerCube
  • usamplerCubeArray
  • using
  • uvec2
  • uvec3
  • uvec4
  • varying
  • vec2
  • vec3
  • vec4
  • void
  • volatile
  • while
  • writeonly
Lists

[3]
Usually accompanied with a side condition that x does not appear in dom(Γ))
[4]
Notably, parameters are the only means by which values of the sampler types can be introduced into a program.
[5]
The shader_fragment_discard_declaration is described as having type boolean, but the result of the expression is effectively consumed by the parasol language runtime and so this is not observable in practice.