Skip to content

HorseIR Language Specification

Overview

HorseIR is an array-based programming language inspired by APL and ELI. Designed as a non-SSA intermediate representation for compiler writers, it supports both scientific computation and relational database queries. This is achieved by combining a rich, SQL-inspired type system with structured control flow and typical operators found in most programming languages as a collection of built-in functions.

Lexical Elements

A HorseIR program is written in the ASCII character set, with the exception of string data (i.e. characters, strings, and symbols) which may contain Unicode characters. Whitespace outside of string literals is ignored.

letter           = 'a' ... 'z' | 'A' ... 'Z' ;
digit            = '0' ... '9' ;
nzdigit          = '1' ... '9' ;
digits           = digit { digit } ;
ascii_character  = /* All valid ASCII */ ;
escape_sequence  = "\a" | "\b" | "\f" | "\n" | "\r" | "\t" | "\v" ;

Comments

There are 2 styles of comments for programmer documentation, both of which are ignored. 1. Line comments start with // and ignore all text until the end of the line (or EOF) 2. Block comments start with /* and end with */ and ignore all text in-between. Block comments do not nest and must be terminated.

// Line comment

/* Block comments
 * may be multiline
 */

Identifiers

Identifiers define program elements (e.g. variables, modules, and functions) and consist of letters, digits and the underscore. The first character must not be a digit.

Identifier  = ( letter | '_' ) { letter | digit | '_' } ;

The sink identifier, _, may be used as a /dev/null of any type. It does not introduce a binding on declaration.

Keywords

Keywords are special character sequences which may not be used as identifiers (with exceptions, see below). Keywords define syntactic elements as well as types.

module      repeat      i32         month       ktable
import      var         i64         minute
global      return      f32         second
def         break       f64         time
kernel      continue    complex     func
check_cast  bool        str         list
if          char        sym         dict
else        i8          dt          enum
while       i16         date        table

Note

Type keywords may be used as identifiers if they are part of a function literal (e.g. @list).

Operators

There are no explicit operators for data manipulation. Instead, each operation is defined as a function, with built-in functions providing functionality commonly found in other languages.

Punctuation

Punctuation symbols give structure to programs and are part of the language syntax.

(     )     =     ,     /
[     ]     :     @
{     }     ?     .
<     >     ;     *

Values

Each basic type has an associated literal value given below.

Values        = ValueList ':' Type
ValueList     = Value | '(' Value { ',' Value } ')'
Value         = IntValue  | FloatValue  | BoolValue   | ComplexValue |
                CharValue | StringValue | SymbolValue | CalendarValue ;

Sign          = '+' | '-'
Integer       = '0' | nzdigit { digit } ;
Float         = Integer '.' [ digits ] | '.' digits ;

IntValue      = [ Sign ] Integer ;
FloatValue    = [ Sign ] Float ;
BoolValue     = '0' | '1' ;

CharValue     = "'" ascii_character "'" ;
StringValue   = '"' { ascii_character } '"' ;
SymbolValue   = '`' ( Identifier | StringValue ) ;
ComplexValue  = FloatValue [ Sign Float ] 'i' ;

CalendarValue = DateTimeValue | MonthValue  | DateValue |
                MinuteValue   | SecondValue | TimeValue ;

// Must correspond to valid dates within range, see the type declarations
MonthValue    = Integer '-' Integer ;
DateValue     = Integer '-' Integer '-' Integer ;

MinuteValue   = Integer ':' Integer ;
SecondValue   = Integer ':' Integer ':' Integer ;
TimeValue     = Integer ':' Integer ':' Integer '.' Integer ;

DateTimeValue = DateValue 'T' TimeValue ;

Examples

// bool
b0:bool = 0:bool;
b1:bool = (0):bool;
b2:bool = (0,1,1,0,1):bool;

// small
i8_0:i8 = 105:i8;
i8_1:i8 = (105):i8;
i8_2:i8 = (10, 100, 200):i8;

// short
i16_0:i16 = 1050:i16;
i16_1:i16 = (1050):i16;
i16_2:i16 = (100, 1000, 200):i16;

// int
i32_0:i32 = 10500:i32;
i32_1:i32 = (10500):i32;
i32_2:i32 = (1000, 10000, 20000):i32;

// long long
i64_0:i64 = 105000:i64;
i64_1:i64 = (105000):i64;
i64_2:i64 = (10000, 100000, 200000):i64;

// float
f32_0:f32 = 3.1415926:f32;
f32_1:f32 = (3.1415926):f32;
f32_2:f32 = (3.1415926, 1.1415926, 2):f32;

// double
f64_0:f64 = 3.1415926:f64;
f64_1:f64 = (3.1415926):f64;
f64_2:f64 = (3.1415926, 2, 2.0):f64;

// date
d0:d = 2010-09-01:d;     // 1 date
d1:d = (2010-09-01):d;   // 1 date
d2:d = (2010-09-01, 1010-01-31, 3019-12-29):d; // 3 dates

// month
m0:m = 2010-09:m;
m1:m = (2010-09):m;
m2:m = (2010-09, 2010-10, 2010-11):m;

//  minute
w0:w = 20:15:w;
w1:w = (20:15):w;
w2:w = (20:15, 21:00, 01:59):w;

// second
v0:v = 17:06:20:v;
v1:v = (17:06:20):v;
v2:v = (17:06:20, 12:10:01, 09:10:12):v;

// time
t0:t = 11:22:33.001:t;
t1:t = (11:22:33.001):t;
t2:t = (11:22:33.001, 22:33:11.999, 01:02:03.123):t;

// datetime
z0:z = 2019-01-02T17:10:21.001:z;
z1:z = (2019-01-02T17:10:21.001):z;
z2:z = (2019-01-02T17:10:21.001, 2019-12-02T17:01:21.001):z;

Modules

A program consists of 1-or-more modules which define a collection of imports, functions and global variables. Modules may be combined into a single program and compiled together.

Module          = "module" Identifier '{' ModuleContents '}' ;
ModuleContents  = { ImportDirective | FunctionDeclaration | GlobalDeclaration } ;

Built-in Modules

HorseIR provides a built-in module "Builtin" that implements the basic mathematical and database operations. It exists as a pseudo-module and its implementation is provided by the compiler. As there are no operators (see Operators), this forms the core of the language functionality. Defining the built-in set as a regular module provides shadowing behaviour consistent with user-defined code.

HorseIR provides a second built-in module "System" which defines system variables and their respective default values. For example, pp controls the precision of output, with default value 10. Importing the system module allows programs to customize their local system environment.

Import Directives

Modules may be composed into larger programs, either by importing all module contents (.*), a specific element (.Identifier), or a list of elements (.{Identifier, ...}). Imports may include functions or global variables, however, they are not transitive.

ImportDirective  = "import" Identifier '.' ImportList ';' ;
ImportList = '*' | Identifier | '{' Identifier { ',' Identifier } '}' ;

Function Declarations

Functions define a collection of statements with 0-or-more input parameters and 0-or-more return types (supporting multiple returns). Each input parameter defines its name and type.

FunctionDeclaration  = FunctionKind Identifier '(' Parameters ')'
                           [ ':' ReturnTypes ] Block ;

FunctionKind  = "def" | "kernel" ;

Parameters    = [ Parameter { ',' Parameter } ] ;
Parameter     = Identifier ':' Type ;

ReturnTypes   = Type { ',' Type } ;

If the function specifies a return type, then the body must return on all paths.

The function kind specifies its intended execution target. def indicates a generic function while kernel directs the runtime system to use connected GPUs.

Entry Function

Execution begins with an entry function main with optional input parameter args:List<?> and any return type. When invoking a program, the entry module to search must be specified.

Global Declarations

Global variables belong to modules, and may be shared through the import directive. Each global variable consists of a name and associated type.

GlobalDeclaration  = "global" Identifier ':' Type '=' Operand ';' ;

Scope Rules

There are following scopes in a program:

  1. Program scope: All modules in the compilation unit.
  2. Module scopes: Methods and global variables in a module. Contents may be declared in any order.
  3. Method scopes: Parameters and local variables in a function. Variables must be declared before use.
  4. Block scopes: Blocks defined as part of control-flow structures define new scopes.

While there may be multiple module and method scopes, there is only a single program scope.

Name Uniqueness

Declarations within a scope must be unique:

  • A module name in the program scope
  • A method name in a module
  • A global variable in a module

Name Resolution

To resolve the use of an identifier, the compiler checks:

  1. Block scopes (if any)
  2. Method scope
  3. Module scope
  4. Imported content

Local variables shadow global declarations, and global declarations shadow imported content.

Imported module content may optionally be used without the module name (i.e. as sum instead of Bultin.sum) if they have not been shadowed. In the case of shadowing, the fully qualified name is required. Both global variables and methods follow the same rules. Local variables cannot be imported.

module A {
    def x() : i32 { ... }
    def y() : i32 { ... }
}

module main {
    import A.*;

    def x() : i32 { ... }

    def main() {
        a:i32 = @x();    // Resolves to main.x
        b:i32 = @y();    // Resolves to A.y

        c:i32 = @A.x();  // Resolves to A.x
    }
}

When two imported modules contain an element of the same name, the last import shadows the earlier import.

Types

Type  = Wildcard | BasicTypes | ListType | DictType | EnumType | TableTypes ;

Wildcard Type

The wildcard type ? is used to represent either heterogeneous data or dynamically-typed data of any type (including compound types). The compiler must resolve all statically-determined wildcards prior to generating code and ensure type correctness.

Wildcard  = '?' ;

Declarations may either specify the exact type, or use the wildcard to use compile-time resolution.

t:? = @sum(a);

/* Equivalent to the following, assuming @sum returns i32 */
t:i32 = @sum(a);

Basic Types

The basic unit of data in HorseIR is a vector (i.e. an array), consisting of data of the same type.

Name Alias Byte Description
boolean bool 1* 0 (false) and 1 (true)
small i8 1 Half short integer or char
short i16 2 Short integer
int i32 4 Integer
long i64 8 Long integer (default, x64)
float f32 4 Single precision
double f64 8 Double precision
complex complex 8 Complex number (real+imaginary single precision floats)
char char 1 Half short integer or char
symbol sym 8 Symbol, but stored in integer
string str 8 String
month month 4 Month (YYYY.MM)
date date 4 Date (YYYY.MM.DD)
date time dt 8 Date time
minute minute 4 Minute (hh:mm)
second second 4 Second (hh:mm:ss)
time time 4 Time (hh:mm:ss.lll)
function N/A 8 Function literal (Starting with @)

Syntactically, the type alias is used to refer to the type. The short name is used internally.

BasicTypes  = "bool" | "i8"   | "i16"     | "i32"    | "i64"    |
              "f32"  | "f64"  | "complex" | "char"   | "str"    | "sym"  |
              "dt"   | "date" | "month"   | "minute" | "second" | "time" ;

Note

  • Vectors of function literals are currently not supported
  • Plan: store boolean values as 1-bit

TODO: A vector of function literals is necessary for join operations as join operators, for example, join_index(@lt@eq, columns_a, columns_b).

Compound Types

Compound types store more complex structures allowing heterogeneity and mappings.

Name Alias Short Description
list list G Collection of items
dictionary dict N Key-value mapping
enumeration enum Y Mapping (i.e. foreign key)
table table A Collection of columns
keyed table ktable K Two normal tables

List Type

A list defines a variable length container consisting of cells, each cell containing data of any type. Nested lists are permitted.

ListType  = "list" '<' Type { ',' Type } '>' ;

For a list type, either a single type may be specified for the entire list, or a type for each cell. In the case of a single type, the list has an unbounded number of cells, all with the same type. If more than one type is specified, the list corresponds to a tuple.

// Defines a list with 1+ cells of i32 type
list<i32>

// Defines a list with exactly 3 cells of explicit types
list<i32, i64, i32>
Wildcard Cell Type

List cell types may also be specified using the wilcard type according to the following rules:

  1. If there is only 1 cell type given, the wildcard may resolve to a type list with any number of elements of any type
  2. If there is more than 1 cell type given, the wildcard must resolve to a single type
// Equivalent to: list<i32>, list<i32, i64, i8>, etc.
t:list<?> = ...;

// Equivalent to: list<i32, i32>, list<i32, list<i32>>, etc.
// Error to: list<i32, i32, i32>
t:list<i32, ?> = ...;

Dictionary Type

A dictionary stores key-value pairs, mapping keys to values.

  • If key/value is a basic type, each element in the vector should be considered as a single key/value
  • If key/value is a list type, each cell in the list should be considered as a single key/value
  • If key/value is a compound type other than list, its entirety is considered as a single key/value
DictType  = "dict" '<' Type ',' Type '>' ;

Dictionaries are formed from collections of keys and values using built-in function @dict.

a:str = ("a", "b", "c"):str;
b:str = ("Montreal", "Toronto", "Vancouver"):str;
c:dict<str, str> = @dict(a, b);

The above creates mappings: a \rightarrow Montreal, b \rightarrow Toronto, and c \rightarrow Vancouver.

Enumeration Type

An enumeration represents the indexing relationship between two vectors: enum keys and enum values. For each value, the enumeration stores the index of the same element in the keys vector. If no corresponding element is found, the length of the enum keys is stored.

EnumType  = "enum" '<' Type '>' ;

An enumeration type specifies the key and value type.

// Defines an enumeration of i32 keys and values
enum<i32>

Enumerations are formed from vectors of keys and values using built-in function @enum.

a:i32 = (1, 2, 3):i32;
b:i32 = (3, 3, 1,2):i32;

// Forms an enumeration with internal index vector (2,2,0,1):i32
c:enum<i32> = @enum(a, b);

Note

An enumeration type is commonly found in key/fkey schemas in relational database systems.

Table Types

A table consists of a non-empty list of columns. Each column has a name and a vector of homogeneous values. There are two kinds of tables: table and ktable (keyed table).

TableTypes  = "table" | "ktable" ;
  1. Table: A normal table of columns
  2. Keyed table: A table with key columns

Tables are formed from a vector of column symbols and a list of column values. The number of symbols and list cells must be equal.

student_id:i32 = (1, 2, 3):i32;
student_age:i8 = (10, 11, 9):i8;
student_grade:i8 = (9, 9, 9):i8;

tab_cols:sym = (`id, `age, `grade):sym;
tab_vals:list<?> = @list(student_id, student_age, student_grade);

tab:table = @table(tab_cols, tab_vals);

The resulting table tab can be viewed as follows:

id age grade
1 10 9
2 11 9
3 9 9

A keyed table is similar to a normal table and consists of a set of key columns and a set of non-key columns. A key column, as in relational databases, must have unique non-null values. The function @ktable creates a keyed table from two tables with the same number of rows. The columns from the first table become the key columns, and columns from the second table become the non-key columns.

a:table = ...;
b:table = ...;
c:ktable = @ktable(a, b);
id (key) age grade
1 10 9
2 11 9
3 9 9

Note

  • An empty table has no rows, but must have at least one column.
  • A keyed table must have at least one key column.
  • A keyed table with multiple key columns has compound keys.
  • The conversion between tables and keyed tables uses two built-in functions:
    • @add_key: Designates columns as keys.
    • @remove_key: Removes columns from keys. If all keys are removed, a normal table is returned.

Value Ranges

Basic types have value ranges based on standard C conventions. Numeric types are always signed.

  • bool: 0 or 1
  • Numeric types depend on the number of bits
    • i8: -27 to 27-1
    • i16: -215 to 215-1
    • i32: -231 to 231-1
    • i64: -263 to 263-1
    • f32: 1.2E-38 to 3.4E+38 (precision: 6 decimal places)
    • f64: 2.3E-308 to 1.7E+308 (precision: 15 decimal places)
  • A complex number is the combination of two floating point numbers (f32)
  • Each date type has its own date-specific format (YYYY-MM-DD T hh:mm:ss.ll) and value range
    • YYYY (year): 1000 to 9999
    • MM (month): 01 to 12 (two digits required)
    • DD (day): 01 to 28/29/30/31
      • January - 31 days
      • February - 28 days (common year) or 29 days (leap year)
      • March - 31 days
      • April - 30 days
      • May - 31 days
      • June - 30 days
      • July - 31 days
      • August - 31 days
      • September - 30 days
      • October - 31 days
      • November - 30 days
      • December - 31 days
    • hh (hour): 00 to 23
    • mm (minute): 00 to 59
    • ss (second): 00 to 59
    • ll (millisecond): 000 to 999

Noted that an error occurs when a number exceeds the range of its type.

2:bool    // Error
999:i8    // Error

Type Conversions

Type conversions are performed with explicit casting. Only the following type conversions are permitted (any conversion not-listed is disallowed).

Integer

  • An integer of narrower width may be cast to an integer of wider width (e.g. i32 to i64).
  • An integer of wider width may not be cast to a integer of narrower width.

Integer and Float

  • An integer may be cast to a float. Loss of precision may occur (e.g. i64 to f32).
  • A float value may be cast to an integer by truncating it's decimal part.
    • Only f32 to i32/i64, and f64 to i64 is allowed.

Integer and Char

  • Integer and character values may not be cast to each other.

Float

  • A float with lower precision may be cast to a float of higher precision.
  • A float with higher precision may not be cast to a float of lower precision.

Boolean

  • A boolean value may be cast to an integer and vice-versa.
    • Zero is false, non-zero is true

String and Symbol

  • Strings may be cast to symbols and vice-versa.

Illustrations

Basic types

                     contiguous
                      /
+-------+-------+-------+
|  35   |  79   | ...   |   (integers, for example)
+-------+-------+-------+

List

        +------+
        | list |
        +------+
         /  |    ... 
    +----+ +----+
    | c0 | | c1 |  ...   (cells)
    +----+ +----+

Dictionary

+-----+  map  +-------------+
| "a" |  -->  | "Montreal"  |
+-----+       +-------------+
| "b" |  -->  | "Toronto"   |
+-----+       +-------------+
| "c" |  -->  | "Vancouver" |
+-----+       +-------------+
| ... |  -->  | ........... |   (key --> value)
+-----+       +-------------+

Enumeration

        +---+---+---+
key     | 7 | 3 | 6 |                                  key
        +---+---+---+                                     \ 
                                                            enum
                                                          / 
        +---+---+---+---+---+         +---+---+---+---+---+
value   | 3 | 3 | 6 | 6 | 7 |  ---->  | 1 | 1 | 2 | 2 | 0 |  (indices)
        +---+---+---+---+---+         +---+---+---+---+---+

Table

+----+-----+-------+
|"Id"|"Age"|"Grade"|
+----+-----+-------+
  |      |       |
+---+  +----+  +---+
| 1 |  | 10 |  | 9 |
+---+  +----+  +---+
| 2 |  | 11 |  | 9 |
+---+  +----+  +---+
| 3 |  | 9  |  | 9 |   (columnar store)
+---+  +----+  +---+

Keyed table

+-----------ktable-----------+
|  +----+   +-----+-------+  |
|  |"Id"|   |"Age"|"Grade"|  |
|  +----+   +-----+-------+  |
|    |        |       |      |
|  +---+    +----+  +---+    |
|  | 1 |    | 10 |  | 9 |    |
|  +---+    +----+  +---+    |
|  | 2 |    | 11 |  | 9 |    |
|  +---+    +----+  +---+    |
|  | 3 |    | 9  |  | 9 |    |
|  +---+    +----+  +---+    |
| /* key */  /* non-keys */  |
+----------------------------+

Shapes

HorseIR has two kinds of types: basic types and compound types, which correspond to different shapes.

Basic types

A basic type is a simple type representing a collection of items with a homogeneous type. Therefore, the data of a basic type is stored in sequential memory, called vector.

An example of an integer vector which contains three integers: (1,2,3):i64:

+-----+-----+-----+
|  1  |  2  |  3  |
+-----+-----+-----+

Compound Types

A compound type is a list-based type representing a collection of items which may have non-homogeneous types. A list consists of many cells. A cell can be either a vector or another list.

An example of a list with two items "abc":str and (1,2,3):i32 below:

+--------+--------+
| cell 0 | cell 1 |
+--------+--------+
  /         \
+-----+  +-----+-----+-----+
|"abc"|  |  1  |  2  |  3  |
+-----+  +-----+-----+-----+

The type of the list can be list<str,i32> or list<?>.

Shape Rules

Conventions

Name Alias Type Notes
scalar S vector length = 1
vector V vector length \neq 1
list L list two lists: L1, L2
primitive op operator builtin functions

Monadic Elementwise

op(S) => S
op(V) => V
  • Return the same length as the input operand

Reduction

op(S) => 1
op(V) => 1
  • Reduction functions, such as sum and min, always return a scalar.

Dyadic Elementwise

op(S, S) => S
op(S, V) => V
op(V, S) => V
op(V, V) => V  //length agree on both sides
  • Both operands operate on vector types

Dyadic List Based

Input with two lists: L1 and L2.

each_left (L1, L2) => L1
each_right(L1, L2) => L2
each_item (L1, L2) => L1  //length agree on both sides
  • If neither L1 nor L2 is list, the list-based operation is ignored;
  • If one side (left/right) is chosen and the corresponding operand is a list, the first-level items of the list are iterated in order;
  • If one side (left/right) is chosen and the corresponding operand is not a list, the operand is processed as a whole.

Shape Append

append(S, S) => V
append(S, V) => V
append(V, S) => V
append(V, V) => V
append(L, S) => L
append(L, V) => L
append(S, L) => L
append(V, L) => L
append(L, L) => L
  • append: concatenate two items

Shape Left

op(LeftShape, RightShape) => LeftShape
  • Return left shape

Shape Right

op(LeftShape, RightShape) => RightShape
  • Return right shape

Shape Left Value

op(LeftShape, RightShape) => ReturnShape
  • ReturnShape is determined by the value of the left parameter.

Statements

A function or control structure body consists of a potentially empty list of statements. There are 2 kinds of statements, and one statement modifier (labels).

Block         = '{' { Statement } '}'
ControlBlock  = Statement | Block

Statement     = AssignStmt | ControlStmt | ExpressionStmt | VarDecl ;

Assignments

Assign statements consist of left-hand side target variables and a right-hand side expression. In the case of multiple return values, more than one target variable must be present. Target variable may either be declared with their respective types or assigned.

AssignStmt  = VarList '=' Expression ';' ;

VarList     = Var { ',' Var } ;
Var         = Identifier [ ':' Type ] | Identifier '.' Identifier ;

Assignments copy the right-hand side if necessary (may be omitted if the expression is a function call).

Control Statements

Both structured an unstructured control-flow are supported for conditional execution and jumps.

ControlStmt  = IfStmt    | WhileStmt | RepeatStmt | ReturnStmt |
               BreakStmt | ContinueStmt ;

Condition    = Operand ;

The condition of a control-flow statement must be a scalar value (i.e. a vector of 1 element). For convenience, two built-in functions @any and @all reduce a vector to a single value for conditional execution:

  • @any returns true if any value in a vector is true
  • @all returns true only if all values in a vector are true

If-ElseIf-Else Statements

An if-elseif-else statement provides conditional execution of blocks of code. The condition must be a boolean scalar, and the else-if and else clauses are optional.

IfStmt = "if" '(' Condition ')' ControlBlock [ "else" ControlBlock ]

While and Repeat Statements

A while loop executes its body until its condition evaluates to false. The condition must be a boolean scalar. A repeat statement executes the body a fixed number of iterations. The condition must be an integer scalar.

WhileStmt   = "while" '(' Condition ')' ControlBlock ;
RepeatStmt  = "repeat" '(' Condition ')' ControlBlock ;

Return Statements

A return statement exits the function and optionally returns a list of values to its calling context.

ReturnStmt  = "return" [ { Operand { ',' Operand } ] ';' ;

Break and Continue Statements

A break statement exits a loop, whereas a continue statement jumps to the next iteration. Break and continue statements may optionally specify a label corresponding to a conditional statement.

BreakStmt     = "break" ';' ;
ContinueStmt  = "continue" ';' ;

Note

break or continue can only appear inside a loop body (i.e. while and repeat)

Expression Statements

An expression statement evaluates a non-value expression. Either a built-in or user-defined function that has return value.

ExpressionStmt = Expression ';'

Variable Declarations

A variable declaration binds variable names to their associated type without an initialization expression.

VarDecl = "var" Identifier [ { ',' Identifier } ] ':' Type ';' ;

Expressions

Expressions evaluate to a value and may either be function calls, literals or identifiers.

Expression = FunctionCall | Operand | Cast ;

Function Calls

Function calls consist of the function name (optionally fully qualified) and operand arguments. Function calls may not be nested.

FunctionCall  = FunctionId '(' [ Operands ] ')' ';'
FunctionId    = '@' Identifier [ '.' Identifier  ] ;

Function calls are pass-by-value and return by value.

Operands

Operands, used either as parameters or return values, may only be identifiers or literals.

Operands  = Operand { ',' Operand } ;
Operand   = Identifier [ '.' Identifier ] | Literal ;

Literals

Literals may either be function names, or vectors and consist of the value and associated type. Function literals may omit the declared type.

Literal  = FunctionLiteral | VectorLiteral ;

FunctionLiteral  = FunctionId [ ':' "func" ] ;
VectorLiteral    = Value ':' Type | '(' Value { ',' Value } ')' ':' Type ;

Values within a vector literal must correspond to the declared type (or be compatible, see type conversions).

Cast Expression

A type cast changes the type of the expression to the destination type. See type conversions for more details.

Cast = "check_cast" '(' Expression ',' Type ')' ;