--- permalink: /spec/lang/2019R1/ redirect_from: - /spec/lang/v2019R1/ ---
Primary contributors:
(Other contributors are listed in Appendix C.)
Copyright © 2018, 2019 WSO2
Licensed under the Creative Commons Attribution-NoDerivatives 4.0 International license
Language and document status
The design of the Ballerina language is approaching stability.
Some language features described by this specification are less stable than the rest of the language. These are marked with either as having either "preview" or "experimental" status. Preview status means that we expect the final design to be close enough to the current design that it will be straightforward to update code that makes uses the current design to the final design. Experimental status means that we believe that we want to have similar functionality, but we are not yet confident about how close the final design will be to the feature as currently described.
In addition, we know there are some areas where the specification needs to provide more details about the semantics of the language.
Comments on this document are welcome and should be made by creating an issue in
https://github.com/ballerina-platform/ballerina-spec
, which is the
GitHub repository where this specification is maintained. The design of the
language may also be discussed in the<EMAIL_ADDRESS>mailing list.
5. Values, types and variables
11. [Experimental] Transactions
B. Changes since previous versions
Ballerina is a programming language intended for network distributed applications. It is a statically typed, concurrent programming language with all functionality expected of a modern, general purpose programming language. But it also has several unusual aspects that make it particularly suitable for its intended purpose.
First, it provides language constructs specifically for consuming and providing network services. Future versions of Ballerina will add language constructs for other functionality often needed by network distributed applications such as security, stream processing, distributed transactions and reliable messaging.
Second, it is designed to take advantage of sequence diagrams as a way of describing the interactions within network distributed applications. There is a close correspondence between the function-level concurrency-related syntax and sequence diagrams; this syntax is in effect a syntax for writing sequence diagrams. This makes it possible to provide an editable graphical representation of a function as a sequence diagram.
Third, it has a type system that is more flexible and allows for looser coupling that traditional statically typed languages. The type system is structural: instead of requiring the program to explicitly say which types are compatible with each other, compatibility of types and values is determined automatically based on their structure; this is particularly useful when combining data from multiple, independently-designed systems. In addition, the type system provides union types and open records. This flexibility allows the type system to be used as a schema for the data that is exchanged in distributed applications. Ballerina's data types are designed to work particularly well with JSON; any JSON value has a direct, natural representation as a Ballerina value. Ballerina also provides support for XML and relational data.
Ballerina is not a research language. It is intended to be a pragmatic language suitable for mass-market commercial adoption. It tries to feel familiar to programmers who are used to popular, modern C-family languages, notably Java, C#, JavaScript. It also borrows ideas from many other existing programming languages including TypeScript, Go, Rust, D, Kotlin, TypeScript, Swift, Python and Perl.
Ballerina is designed for modern development practices with a module based development model with namespace management via module repositories, including a globally shared central repository. Module version management, dependency management, testing, documentation, building and sharing are part of the language platform design architecture and not left for later add-on tools.
Ballerina is a "batteries included" language: it comes with a standard library, which includes not only the usual low-level, general-purpose functionality, but also support for a wide variety of network protocols, interface standards, data formats and authentication/authorization standards, which make writing secure, resilient distributed applications significantly easier than with other languages. The standard library is not specified in this document.
Productions are written in the form:
symbol := rhs
where symbol is the name of a nonterminal, and rhs
is as follows:
0xX
means the single character whose Unicode code point is
denoted by the hexadecimal numeral X^x
means any single Unicode code point that does not match x
and is not a disallowed character;x..y
means any single Unicode character whose code point is
greater than or equal to that of x and less than or equal to that of ystr
means the characters str
literallysymbol
means a reference to production for the nonterminal
symbol
x|y
means x or yx&y
means x and y interleaved in any order[x]
means zero or one timesx?
means x zero or one timesx*
means x zero or more timesx+
means x one or more times(x)
means x (grouping)
The rhs of a symbol that starts with a lower-case letter implicitly allows white
space and comments, as defined by the production TokenWhiteSpace
,
between the terminals and nonterminals that it references.
A Ballerina program is divided into modules. A module has a source form and a binary form. The module is the unit of compilation; a Ballerina compiler translates the source form of a module into its binary form. A module may reference other modules. When a compiler translates a source module into a binary module, it needs access only to the binary form of other modules referenced from the source module.
A binary module can only be referenced if it is placed in a module store. There are two kinds of module store: a repository and a project. A module stored in a repository can be referenced from any other module. A module stored in a project can only be referenced from other modules stored in the same project.
A repository organizes binary modules into a 3-level hierarchy:
Organizations are identified by Unicode strings, and are unique within a repository. A module name is a Unicode string and is unique within a repository organization. A particular module name can have one or more versions each associated with a separate binary module. Versions are semantic, as described in the SemVer specification.
A project stores modules using a simpler single level hierarchy, in which the module is associated directly with the module name.
A binary module is a sequence of octets. Its format is specified in the Ballerina Platform Specification.
An abstract source module consists of:
An abstract source module can be stored in a variety of concrete forms. For
example, the Ballerina Platform Specification describes a method for storing an
abstract source module in a filesystem, where the source parts are files with a
.bal
extension stored in a directory, the module name comes from
the name of that directory, and the version and organization name comes from a
configuration file Ballerina.toml
in that directory.
The grammar in this document specifies how a sequence of Unicode code points is interpreted as part of the source of a Ballerina module. A Ballerina module part is a sequence of octets (8-bit bytes); this sequence of octets is interpreted as the UTF-8 encoding of a sequence of code points and must comply with the requirements of RFC 3629.
After the sequence of octets is decoded from UTF-8, the following two transformations must be performed before it is parsed using the grammar in this document:
The sequence of code points must not contain any of the following disallowed code points:
Note that the grammar notation ^X does not allow the above disallowed code points.
identifier := UndelimitedIdentifier | DelimitedIdentifier UndelimitedIdentifier := IdentifierInitialChar IdentifierFollowingChar* DelimitedIdentifier :=^"
StringChar+"
IdentifierInitialChar :=A
..Z
|a
..z
|_
| UnicodeIdentifierChar IdentifierFollowingChar := IdentifierInitialChar | Digit UnicodeIdentifierChar := ^ ( AsciiChar | UnicodeNonIdentifierChar ) AsciiChar := 0x0 .. 0x7F UnicodeNonIdentifierChar := UnicodePrivateUseChar | UnicodePatternWhiteSpaceChar | UnicodePatternSyntaxChar UnicodePrivateUseChar := 0xE000 .. 0xF8FF | 0xF0000 .. 0xFFFFD | 0x100000 .. 0x10FFFD UnicodePatternWhiteSpaceChar := 0x200E | 0x200F | 0x2028 | 0x2029 UnicodePatternSyntaxChar := character with Unicode property Pattern_Syntax=True Digit :=0
..9
Note that the set of characters allowed in identifiers follows the requirements of Unicode TR31 for immutable identifiers; the set of characters is immutable in the sense that it does not change between Unicode versions.
TokenWhiteSpace := (Comment | WhiteSpaceChar)*
Comment := //
AnyCharButNewline*
AnyCharButNewline := ^ 0xA
WhiteSpaceChar := 0x9 | 0xA | 0xD | 0x20
TokenWhiteSpace
is implicitly allowed on the right hand side of
productions for non-terminals whose names start with a lower-case letter.
Ballerina programs operate on a rich universe of values. This universe of values is partitioned into a number of basic types; every value belongs to exactly one basic type.
Values are of three kinds, each corresponding to a kind of basic type:
Values can be stored in variables or as members of structures. A simple value is stored directly in the variable or structure. However, for other types of value, what is stored in the variable or member is a reference to the value; the value itself has its own separate storage. Non-simple types (i.e. structured types and behavioral types) are thus collectively called reference types. A reference value has an identity determined by its storage location. References make it possible for distinct members of a structure to refer to values that are identical, in the sense that they are stored in the same location. Thus values in Ballerina represent not just trees but graphs.
Simple values are inherently immutable because they have no identity distinct from their value. All basic types of structural values, with the exception of the XML, are mutable, meaning the value referred to by a particular reference can be changed. Whether a behavioural value is mutable depends on its basic type: some of the behavioural basic types allow mutation, and some do not. Mutation cannot change the basic type of a value. Mutation makes it possible for the graphs of references between values to have cycles.
Ballerina programs use types to categorize values both at compile-time and runtime. Types deal with an abstraction of values, which does not consider storage location or mutability. This abstraction is called a shape. A type denotes a set of shapes. Subtyping in Ballerina is semantic: a type S is a subtype of type T if the set of shapes denoted by S is a subset of the set of shapes denoted by T. Every value has a corresponding shape. A shape is specific to a basic type: if two values have different basic types, then they have different shapes. Since shapes do not deal with storage location, they have no concept of identity; shapes therefore represent trees rather graphs. For simple values, there is no difference between a shape and a value, except for one case: positive and negative zero of a floating point basic type are distinct values but have the same shape. There are two important relations between a value and a type:
For an immutable value, looking like a type and belonging to a type are the same thing.
When a Ballerina program declares a variable to have a compile-time type, this means that the Ballerina compiler together with the runtime system will ensure that the variable will only ever contain a value that belongs to the type. Ballerina also provides mechanisms that take a value that looks like a type and use it to create a value that belongs to a type.
Ballerina provides a rich variety of type descriptors, which programs use to describe types. For example, there is a type descriptor for each simple basic type; there is a type descriptor that describes as type as a union of two types; there is a type descriptor that uses a single value to describe a type that contains a single shape. This means that values can look like and belong to arbitrarily many types, even though they look like or belong to exactly one basic type.
In addition to describing a type, a type descriptor may also include information used to construct a value of the type, as well as metadata. Whereas the type described by a type descriptor is known at compile time, this additional information may need to be resolved at runtime. The typedesc basic type represents a type descriptor that has been resolved.
Most basic types of structured values (along with one basic type of simple value) are iterable, meaning that a value of the type can be accessed as a sequence of simpler values.
The following table summarizes the type descriptors provided by Ballerina. Experimental features are not included.
| Kind | Name | Set of values denoted by type descriptor |
| basic, simple | nil | () |
| boolean | true, false | |
| int | 64-bit signed integers | |
| float | 64-bit IEEE 754-2008 binary floating point numbers | |
| decimal | 128-bit IEEE 754-2008 decimal floating point numbers | |
| string | sequences of Unicode code points | |
| basic, structured | array | an ordered list of values, optionally with a specific length, where a single type is specified for all members of the list, |
| tuple | an ordered list of values, where a type is specified separately for each member of the list | |
| map | a mapping from keys, which are strings, to values; specifies mappings in terms of a single type to which all keys are mapped | |
| record | a mapping from keys, which are strings, to values; specifies maps in terms of names of fields (required keys) and value for each field | |
| table | ||
| XML | a sequence of zero or more characters, XML elements, processing instructions or comments | |
| error | an indication that there has been an error, with a string identifying the reason for the error, and a mapping giving additional details about the error | |
| basic, behavioral | function | a function with 0 or more specified parameter types and a single return type |
| future | ||
| object | ||
| service | ||
| typedesc | a type descriptor | |
| other | singleton | a single value described by a literal |
| union | the union of the component types | |
| optional | the underlying type and () | |
| any | all values | |
| anydata | ||
| byte | int in the range 0 to 255 inclusive | |
| json | the union of (), int, float, decimal, string, and maps and arrays whose values are, recursively, json |
A simple value belongs to exactly one of the following basic types:
The type descriptor for each simple basic type contains all the values of the basic type.
simple-type-descriptor := nil-type-descriptor | boolean-type-descriptor | int-type-descriptor | floating-point-type-descriptor | string-type-descriptor
nil-type-descriptor :=(
)
nil-literal :=(
)
|null
The nil type contains a single value, called nil, which is used to represent the
absence of any other value. The nil value is written ()
. The nil
value can also be written null
, for compatibility with JSON; the
use of null should be restricted to JSON-related contexts.
The nil type is special, in that it is the only basic type that consists of a
single value. The type descriptor for the nil type is not written using a
keyword, but is instead written ()
like the value.
boolean-type-descriptor :=boolean
boolean-literal :=true
|false
The boolean type consists of the values true and false.
int-type-descriptor :=int
int-literal := DecimalNumber | HexIntLiteral DecimalNumber :=0
| NonZeroDigit Digit* HexIntLiteral := HexIndicator HexNumber HexNumber := HexDigit+ HexIndicator :=0x
|0X
HexDigit := Digit |a
..f
|A
..F
Digit :=0
..9
NonZeroDigit :=1
..9
The int type consists of integers between -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807 (i.e. signed integers than can fit into 64 bits using a two's complement representation)
floating-point-type-descriptor :=float
|decimal
floating-point-literal := DecimalFloatingPointNumber | HexFloatingPointLiteral DecimalFloatingPointNumber := DecimalNumber Exponent [FloatingPointTypeSuffix] | DottedDecimalNumber [Exponent] [FloatingPointTypeSuffix] | DecimalNumber FloatingPointTypeSuffix DottedDecimalNumber := DecimalNumber.
Digit* |.
Digit+ Exponent := ExponentIndicator [Sign] Digit+ ExponentIndicator :=e
|E
HexFloatingPointLiteral := HexIndicator HexFloatingPointNumber HexFloatingPointNumber := HexNumber HexExponent | DottedHexNumber [HexExponent] DottedHexNumber := HexDigit+.
HexDigit* |.
HexDigit+ HexExponent := HexExponentIndicator [Sign] Digit+ HexExponentIndicator :=p
|P
Sign :=+
|-
FloatingPointTypeSuffix := DecimalTypeSuffix | FloatTypeSuffix DecimalTypeSuffix :=d
|D
FloatTypeSuffix :=f
|F
There are two basic types for floating point numbers:
The multiple bit patterns that IEEE 754 treats as NaN are considered to be the same value in Ballerina. Positive and negative zero of a floating point basic type are distinct values, following IEEE 754, but are defined to have the same shape, so that they will usually be treated as being equal.
IEEE-defined operations on floating point values must be performed using a rounding-direction attribute of roundTiesToEven (which is the default IEEE rounding direction, sometimes called round to nearest). All floating point values, including the intermediate results of expressions, must use the value space defined for the float and decimal type; implementations must not use extended precision for intermediate results. This ensures that all implementations will produce identical results. (This is the same as what is required by strictfp in Java.)
string-type-descriptor :=string
string-literal := DoubleQuotedStringLiteral DoubleQuotedStringLiteral :="
(StringChar | StringEscape)*"
StringChar := ^ ( 0xA | 0xD |\
|"
) StringEscape := StringSingleEscape | StringNumericEscape StringSingleEscape :=\t
|\n
|\r
|\\
|\"
StringNumericEscape :=\u[
CodePoint]
CodePoint := HexDigit+
A string is an immutable sequences of zero or more Unicode code points. Any code point in the Unicode range of 0x0 to 0x10FFFF inclusive is allowed other than surrogates (0xD800 to 0xDFFF inclusive).
In a StringNumericEscape
, CodePoint
must valid Unicode
code point; more precisely, it must be a hexadecimal numeral denoting an
integer n where 0 <= n < 0xD800 or 0xDFFF < n <=
0x10FFFF.
A string is iterable as a sequence of its single code point substrings. String is the only simple type that is iterable.
There are five basic types of structured value. First, there are three container basic types: list, mapping and table. Second, there is the xml basic type and the error basic type, which are both special in different ways.
Values of the container basic types are containers for other values, which are called their members. Containers are mutable: the members contained in a particular container can be changed. However, a container value can also be frozen at runtime or compile time, which prevents any change to its members. A frozen container value can refer only to immutable values: either other frozen values or values of basic types that are always immutable. Once frozen, a container value remains frozen. Values of basic type xml can also be frozen as described below.
The shape of the members of a container value contribute to the shape of the container. Mutating a member of a container can thus cause the shape of the container to change.
A type descriptor for a container basic type describe the shape of the container in terms of the shapes of its members. A container has an inherent type, which is a type descriptor which is part of the container's runtime value. At runtime, the container prevents any mutation that might lead to the container having a shape that is not a member of its inherent type. Thus a container value belongs to a type if and only if that type is its inherent type or a subset of its inherent type.
A frozen container value belongs to a type if and only if the type contains the shape of the value. In other words, freezing a container narrows its inherent type to a type that consists of just its current shape.
structured-type-descriptor := list-type-descriptor | mapping-type-descriptor | table-type-descriptor | xml-type-descriptor | error-type-descriptor
The following table summarizes the type descriptors for structured types.
| Integer index | String key | |
| Basic type | list | mapping |
| Type descriptor with uniform member type | array | map |
| Type descriptor with separate member types | tuple | record |
A value is defined to be pure if it either
A shape is pure if it is the shape of a pure value. A type is pure if it contains only pure shapes.
A list value is a container that keeps its members in an ordered list. The number of members of the list is called the length of the list. A member of a list can be referenced by an integer index representing its position in the list. For a list of length n, the indices of the members of the list, from first to last, are 0,1,...,n - 1. The shape of a list value is an ordered list of the shapes of its members.
A list is iterable as a sequence of its members.
The type of list values can be described by two kinds of type descriptors.
list-type-descriptor := array-type-descriptor | tuple-type-descriptor
The inherent type of a list value must be a list-type-descriptor
.
The inherent type of a list value determines a type Ti for a
member with index i. The runtime system will enforce a constraint that
a value written to index i will belong to type Ti.
Note that the constraint is not merely that the value looks like
Ti.
Both kinds of type descriptor are covariant in the types of their members.
An array type-descriptor describes a type of list value by specifying the type that the value for all members must belong to, and optionally, a length.
array-type-descriptor := member-type-descriptor[
[ array-length ]]
member-type-descriptor := type-descriptor array-length := int-literal | constant-reference-expr | inferred-array-length inferred-array-length :=*
A type T[]
contains a list shape if all members of the list shape
are in T
. A type T[n]
contains a list shape if in
addition the length of the list shape is n.
A constant-reference-expr
in an array-length
must
evaluate to a non-negative integer. An array length of *
means that
the length of the array is to be inferred from the context; this is allowed only
within a type descriptor occurring in a context that is specified to be
inferable; its meaning is the same as if the length was specified explicitly.
Note also that T[n]
is a subtype of T[]
, and that if
S
is a subtype of T
, then S[]
is a
subtype of T[]
; this is a consequence of the definition of
subtyping in terms of subset inclusion of the corresponding sets of shapes.
An array T[]
is iterable as a sequence of values of type
T
.
A tuple type descriptor describes a type of list value by specifying a separate type for each member of the list.
tuple-type-descriptor :=[
tuple-member-type-descriptors]
tuple-member-type-descriptors := member-type-descriptor (,
member-type-descriptor)* [,
tuple-rest-descriptor] | [ tuple-rest-descriptor ] tuple-rest-descriptor := type-descriptor...
A tuple type descriptor T with m member type descriptors contains a list shape L of length n if and only if:
...
, and R contains the j-th member of L for each j from m + 1 to
n.
Note that a tuple type where all the member-type-descriptor
s are
the same and there is no tuple-rest-descriptor is equivalent to an
array-type-descriptor with a length.
A mapping value is a container where each member has a key, which is a string, that uniquely identifies within the mapping. We use the term field to mean the member together its key; the name of the field is the key, and the value of the field is that value of the member; no two fields in a mapping value can have the same name.
The shape of a mapping value is an unordered collection of field shapes one for each field. The field shape for a field f has a name, which is the same as the name of f, and a shape, which is the shape of the value of f.
A mapping is iterable as sequence of fields, where each field is represented by
a 2-tuple (s, val)
where s
is a string for the name of a field, and val
is the value of the field. The order of the fields in the sequence is
implementation-dependent, but implementations are encouraged to preserve and use
the order in which the fields were added.
The type of mapping values can be described by two kinds of type descriptors.
mapping-type-descriptor := map-type-descriptor | record-type-descriptor
The inherent type of a mapping value must be a
mapping-type-descriptor
. The inherent type of a mapping value
determines a type Tf for the value of the field with name
f. The runtime system will enforce a constraint that a value written to
field f will belong to type Tf. Note that the
constraint is not merely that the value looks like Tf.
Both kinds of type descriptor are covariant in the types of their members.
A map type-descriptor describes a type of mapping value by specifying the type that the value for all fields must belong to.
map-type-descriptor :=map
type-parameter type-parameter :=<
type-descriptor>
A type map<T>
contains a mapping shape m if every field
shape in m has a value shape that is in T
.
A value belonging to type map<T>
is iterable as a sequence of
values of type (string, T)
.
A record type descriptor describes a type of mapping value by specifying a type separately for the value of each field.
record-type-descriptor := inclusive-record-type-descriptor | exclusive-record-type-descriptor inclusive-record-type-descriptor :=record
{
field-descriptor*}
exclusive-record-type-descriptor :=record
{|
field-descriptor* [record-rest-descriptor]|}
field-descriptor := individual-field-descriptor | record-type-reference individual-field-descriptor := type-descriptor field-name [?
| default-value];
default-value :==
expression record-type-reference :=*
type-descriptor-reference;
record-rest-descriptor := type-descriptor...
;
Each individual-field-descriptor
specifies an additional constraint
that a mapping value shape must satisfy for it to be a member of the described
type. The constraint depends on whether ?
is present:
?
is not present, then the constraint is that the mapping
value shape must have a field shape with the specified field-name and with a
value shape that is a member of the specified type-descriptor; this is called a
required field;?
is present, then the constraint is that if the mapping
value shape has a field shape with the specified field-name, then its value
shape must be a member of the specified type-descriptor; this is called an
optional field.
The order of the individual-field-descriptor
s within a
record-type-descriptor
is not significant. Note that the delimited
identifier syntax allows the field name to be any non-empty string.
An exclusive-record-type-descriptor, which uses the {|
and
|}
delimiters, allows exclusively the fields described. More
precisely, for a mapping value shape and a record-type-descriptor, let the extra
field shapes be the field shapes of the mapping value shapes whose names are not
the same as field-name of any individual-field-descriptor; a mapping value shape
is a member of the type described by an exclusive-record-type-descriptor only if
either:
T...
, and the value shape of
every extra field shape is a member of T
.
An inclusive-record-type-descriptor, which uses the {
and
}
delimiters, allows any mapping value that includes the fields
described. More precisely, a type descriptor record { F };
is
equivalent to record {| F; T...; |}
, where T is the type that
contains all pure shapes (which can be written as anydata|error
).
A record type descriptor that either is an inclusive-record-type-descriptor or is an exclusive-record-type-descriptor with a record-rest-descriptor is called open; a record type descriptor that is not open is called closed.
A default-value
specifies a default value for the field, which is
used when the record type descriptor is used to construct a mapping value but no
value is specified explicitly for the field. The type descriptor contains a
0-argument function closure for each default value. The closure is created from
the expression when the type descriptor is resolved. The closure is evaluated to
create a field value each time the default is used in the construction of a
mapping value. The default value does not affect the type described by the type
descriptor.
A record-type-reference
pulls in fields from a named record type.
The type-descriptor-reference
must reference a type defined by a
record-type-descriptor
. The field-descriptor
s and any
record-rest-descriptor
are copied into the type being defined; the
meaning is the same as if they had been specified explicitly. For default
values, the closure rather than the expression is copied in. A
record-rest-descriptor
in the referencing type overrides any
record-rest-descriptor
in the referenced type. For the purposes of
resolving a record-type-reference
, a referenced or referencing type
that is an inclusive-record-type-descriptor
is treated as if it
were the equivalent exclusive-record-type-descriptor
with an
explicit record-rest-descriptor
.
A table is intended to be similar to the table of relational database table. A table value contains an immutable set of column names and a mutable bag of rows. Each column name is a string; each row is a mapping that associates a value with every column name; a bag of rows is a collection of rows that is unordered and allows duplicates.
A table value also contains a boolean flag for each column name saying whether that column is a primary key for the table; this flag is immutable. If no columns have this flag, then the table does not have a primary key. Otherwise the value for all primary keys together must uniquely identify each row in the table; in other words, a table cannot have two rows where for every column marked as a primary key, that value of that column in both rows is the same.
table-type-descriptor :=table
{
column-type-descriptor+}
column-type-descriptor := individual-column-type-descriptor | column-record-type-reference individual-column-type-descriptor := [key
] type-descriptor column-name;
column-record-type-reference :=*
type-reference [key-specifier (,
key-specifier)*];
key-specifier :=key
column-name column-name := identifier
A table type descriptor has a descriptor for each column, which specifies the name of the column, whether that column is part of a primary key and the type that values in that column must belong to. The type descriptor for the column must be a pure type. If a column is part of a primary key, then the type descriptor for the column must also allow only non-nil simple values.
Note that a table type T' will be a subtype of a table type T if and only if:
A table is iterable as a sequence of mapping values, one for each row, where each mapping value has a field for each column, with the column as the field name and the value of that column in that row as the field value. The mapping values will belong to a closed record type.
Issues:
xml-type-descriptor := xml
An XML value represents an immutable sequence of zero or more of the items that can occur inside an XML element, specifically:
The attributes of an element are represented by a map<string>. The content of each element in the sequence is itself a distinct XML value. Although the sequence is immutable, an element can be mutated to change its content to be another XML value.
An XML value is iterable as a sequence of its items, where each character item is represented by a string with a single code point and other items are represented by a singleton XML value. A single XML item, such as an element, is represented by a sequence consisting of just that item.
error-type-descriptor :=error
[error-type-params] error-type-params :=<
(explicit-error-type-params | inferred-error-type-param)>
explicit-error-type-params := reason-type-descriptor [,
detail-type-descriptor] reason-type-descriptor := type-descriptor detail-type-descriptor := type-descriptor inferred-error-type-param :=*
An error value belongs to the error basic type, which is a basic type which is distinct from other structured types and is used only for representing errors. An error value contains the following information:
An error value is immutable, since the detail is frozen and the stack trace information cannot be changed.
The shape of an error value consists of the shape of the reason and the shape of
the detail; the stack trace is not part of the shape. A type descriptor
error<r, d> contains an error shape if r
contains the shape's reason, and d, if present, contains the shape's
detail. The bare type error contains all error shapes. The
reason-type-descriptor must be a subtype of string; the detail-type-descriptor
must be a subtype of record { }
(which is equivalent to
map<anydata|error>
).
A type of error<*>
means that the type is a subtype of error,
where the precise subtype is to be inferred from the context. This is allowed
only within type descriptors occurring in a context that is specified to be
inferable.
Error is the only structured basic type that is not iterable.
behavioral-type-descriptor := function-type-descriptor | object-type-descriptor | future-type-descriptor | service-type-descriptor | stream-type-descriptor | typedesc-type-descriptor
function-type-descriptor :=function
function-signature function-signature :=(
param-list)
return-type-descriptor return-type-descriptor := [returns
annots type-descriptor ] param-list := required-params [,
defaultable-params] [,
rest-param] | defaultable-params [,
rest-param] | [rest-param] required-params := required-param (,
required-param)* required-param := annots type-descriptor param-name defaultable-params := defaultable-param (,
defaultable-param)* defaultable-param := annots type-descriptor param-name default-value rest-param := annots type-descriptor...
[param-name]
A function is a part of a program that can be explicitly executed. In Ballerina, a function is also a value, implying that it can be stored in variables, and passed to or returned from functions. When a function is executed, it is passed an argument list as input and returns a value as output.
When the execution of a function returns to its caller, it returns exactly one
value. A function that would in other programming languages not return a value
is represented in Ballerina by a function returning ()
. Note that
the function definition does not have to explicitly return ()
; a
return statement or falling off the end of the function body will implicitly
return ()
.
The argument list passed to a function consists of zero or arguments in order; each argument is a value, but the argument list itself is not passed as a value. The argument list must conform to the param-list as described in this section. Usually, the compiler's type checking will ensure that this is the case; if not, the function will panic.
It is convenient to consider the complete param-list as having a type. This type is described by a tuple-type-descriptor that has a member-type-descriptor for each required-param and defaultable-param, and has a tuple-rest-descriptor if and only if there is a rest-param. The i-th member-type-descriptor of the tuple type descriptor is the same as the type-descriptor of the i-th member of the param-list; the type-descriptor of the tuple-rest-descriptor, if present, is the same as the type-descriptor of the rest-param.
An argument list consisting of values v1,..., vn conforms to a param-list that has type P, if and only if for each i with 1 <= i <= n, vi belongs to Ti, where Ti is defined to be the type that contains a shape s if and only if P contains a list shape whose i-th member is s.
When an argument list is passed to a function, the non-rest parameters are initialized from the arguments in the argument list in order. The conformance of the argument list to the param-list declared for the function ensures that each parameter will be initialized to a value that belongs to the declared type of the parameter. If there is a rest-param, then that is a initialized to a newly created lists containing the remaining arguments in the argument-list; the inherent type of this list will be T[] where T is the type of the rest-param. The conformance of the argument list ensures that the members of this list will belong to type T.
A defaultable-param is a parameter for which a default value is specified. The expression specifying the default value may refer to previous parameters by name. For each defaultable parameter, the function's type descriptor includes a closure that computes the default value for the parameter using the values of previous parameters. The caller of the function uses the closures in the function's type descriptor to compute default values for any defaultable arguments that were not specified explicitly. These default values are included in the argument list passed to the function. Whether a parameter is defaultable, and what its default is, do not affect the shape of the function and thus do not affect typing. The closures computing the defaultable parameters are created when the type descriptor is resolved; the default value is computed by calling the closure each time the function is called and the corresponding parameter is not specified. Whether a parameter is defaultable is used at compile time, but the closure that computes the default value is only used at runtime.
The name of each parameter is included in the function's type descriptor. A caller of the function may specify parameters by name. In this case, the caller uses the parameter name at compile time in conjunction with the type descriptor to create the argument list. The parameter names do not affect the shape of the function and thus do not affect typing.
The process by which the function caller creates an argument list, which may make use of arguments specified both by position and by name, is described in more detail in the section on function calls.
Function types are covariant in their return types and contravariant in the type of their parameter lists. More precisely, a function type with return type R and parameter list type P is a subtype of a function type with return type R' and parameter list type P' if and only if R is a subtype of R' and P' is a subtype of P. A function value f belongs to a function type T if the declared type of f is a subtype of T.
Objects are a combination of fields along with a set of associated functions, called methods, which can be used to manipulate them. An object's methods are associated with the object when the object is constructed and cannot be changed thereafter. The fields and methods of an object are in separate namespaces, so it is possible for an object to have a field and a method with the same name.
An object type descriptor, in addition to describing the object type, also defines a way to construct an object of this type, in particular it provides the method definitions that are associated with the object when it is constructed.
It is also possible to have an object type descriptor that only describes an object type and cannot be used to construct an object; this is called an abstract object type descriptor.
object-type-descriptor := object-type-qualsobject
{
object-member-descriptor*}
object-type-quals := [abstract
] [client
] | [client
]abstract
object-member-descriptor := object-field-descriptor | object-method | object-type-reference
If object-type-quals
contains the keyword abstract
,
then the object type descriptor is an abstract object type descriptor. An
abstract object type descriptor must not have an object-ctor-function.
If object-type-quals
contains the keyword client
, then
the object type is a client object type. A client object type may have remote
methods; other objects types must not.
object-field-descriptor :=
object-visibility-qual type-descriptor field-name [default-value];
An object-field-descriptor
specifies a field of the object. The
names of all the fields of an object must be distinct.
Methods are functions that are associated to the object and are called via a
value of that type using a method-call-expr
.
object-method := method-decl | method-defn method-decl := metadata object-visibility-qual [remote
]function
method-name function-signature;
method-defn := metadata method-defn-qualsfunction
method-name function-signature method-body method-defn-quals := object-visibility-qual [remote
] method-name := identifier method-body := function-body
The names of all the methods of an object must be distinct: there is no method overloading. Method names beginning with two underscores are reserved for use by built-in abstract object types.
Within a method-body
, the fields and methods of the object are not
implicitly in-scope; instead the keyword self
is bound to the
object and can be used to access fields and methods of the object.
All the methods of an object type must be either declared or defined in the
object. A method-decl
declares a method without defining it; a
method-defn
also supplies a definition. An abstract object type
descriptor must not contain a method-defn
.
A method that is declared or defined with the remote qualifier is a remote method. A remote method is allowed only in a client object. A remote method is invoked using a different syntax from a non-remote method.
Any method that is declared but not defined within an object that is not abstract must be defined outside the object at the top-level of the module:
outside-method-defn := annots method-defn-qualsfunction
qual-method-name function-signature function-body qual-method-name := object-type-name.
method-name
The method-defn-quals
on an outside-method-defn must contain the
same qualifiers (both remote
and visibility qualifiers) as the
declaration of the method within the object.
object-visibility-qual := [explicit-visibility-qual] explicit-visibility-qual :=public
|private
Each field and method of an object type is visible within and can be accessed from a specific region of code, which is specified by its object-visibility-qual as follows:
private
, then the visibility
region consists of the methods defined by this object type descriptor;public
, then the visibility region
contains all modules.
The visibility of a method or field of an abstract object type cannot be
private
.
The shape of an object consists of an unordered collection of object field shapes and an unordered collection of object method shapes. An object field shape or object method shape is a triple consisting of the name of the field or method, the visibility region, and a shape for the value of the field or for the method's function.
An object type is inclusive, in a similar way to an
inclusive-record-type-descriptor: an object shape belongs to an object type if
it has at least the fields and methods described in the object-type-descriptor.
Thus all object values belong to the type object { }
.
An object-type-descriptor that has a field with name f, visibility region R and type T contains an object shape only if the object shape contains an object field shape that has name f, visibility region R and a value shape that is contained in T. An object-type-descriptor that has a method with name m, visibility region R and function type T contains an object shape only if the object shape contains an object method shape that has name m, visibility region R and a function value that belongs to type T.
Thus an object type T' is a subtype of an object type T only if for each field or method f of T there is a corresponding field or method f' of T such that the type of f' in T' is a subtype of the type of f in T and the visibility region of f' in T' is the same as the visibility region of f in T.
This implies that:
A non-abstract object type provides a way to initialize an object of the type. An object is initialized by:
__init
method, if there is one
The return type of the __init
method must be a subtype of the union
of error and nil, and must contain nil; if __init
returns an error,
it means that initialization of the object failed. The __init
method can declare parameters in the same way as any other method.
At any point in the body of a __init
method, the compiler
determines which fields are potentially uninitialized. A field is potentially
uninitialized at some point if that field does not have a default value and it
is not definitely assigned at that point. It is a compile error if a
__init
method:
self
variable other than to access or modify the value of a field.An object must have a __init method unless all its fields have a default value. An object without a __init method behaves as it had a __init method with no parameters and an empty body (which will always return nil).
The visibility of the __init
method cannot be private
.
object-type-reference :=*
type-descriptor-reference;
The type-descriptor-reference
in an
object-type-reference
must reference an abstract object type. The
object-member-descriptors from the referenced type are copied into the type
being defined; the meaning is the same as if they had been specified explicitly.
If a non-abstract object type To has a type reference to an abstract
object type Ta, then each method declared in Ta must be
defined in To using either a method-defn
or an
outside-method-defn
with the same visibility. If Ta has
a method or field with module-level visibility, the To must be in the
same module.
future-type-descriptor := future
[type-parameter]
A future value represents a value to be returned by a named worker. A future
value belongs to a type future<T>
if the value to be returned
belongs to T.
A value belongs to a type future
(without the type-parameter)
if it has basic type future.
service-type-descriptor := service
A service value contains resources and methods. A service method is similar to an object method. A resource is a special kind of method, with associated configuration data, that is invoked in response to network input received by a Listener.
All service values belong to the type service
.
It is planned that a future version of Ballerina will provide a mechanism that allows more precise typing of services. In the meantime, implementations can use annotations on type definitions to support this.
stream-type-descriptor := stream
[type-parameter]
A value of type stream<T>
is a distributor for values of type
T
: when a value v of type T is put into the stream, a function will
be called for each subscriber to the stream with v as an argument. T must be a
pure type.
A value belongs to a type stream
(without the type-parameter)
if it has basic type stream.
typedesc-type-descriptor := typedesc
[type-parameter]
A type descriptor value is an immutable value representing a resolved type
descriptor. The type typedesc contains all values with basic type typedesc. A
typedesc value t belongs to a type typedesc<T> if
and only if the type described by t
is a subtype of T.
The typedesc type is thus covariant with its type parameter.
Referencing an identifier defined by a type definition in an expression context will result in a type descriptor value.
type-descriptor := simple-type-descriptor | structured-type-descriptor | behavioral-type-descriptor | singleton-type-descriptor | union-type-descriptor | optional-type-descriptor | any-type-descriptor | anydata-type-descriptor | byte-type-descriptor | json-type-descriptor | type-descriptor-reference |(
type-descriptor)
type-descriptor-reference := qualified-identifier
It is important to understand that the type descriptors specified in this section do not add to the universe of values. They are just adding new ways to describe subsets of this universe.
singleton-type-descriptor := simple-const-expr
A singleton type is a type containing a single shape. A singleton type is described using an compile-time constant expression for a single value: the type contains the shape of that value. Note that it is possible for the variable-reference within the simple-const-expr to reference a structured value.
union-type-descriptor := type-descriptor |
type-descriptor
The value space of a union type T1|T2
is the union of
T1
and T2
.
optional-type-descriptor := type-descriptor ?
A type T?
means T optionally, and is exactly equivalent to
T|()
.
any-type-descriptor := any
The type descriptor any
describes the type consisting of all values
other than errors. A value belongs to the any type if and only if its basic type
is not error. Thus all values belong to the type any|error
. Note
that a structure with members that are errors belongs to the any
type.
anydata-type-descriptor := anydata
The type descriptor anydata
describes the type of all pure values
other than errors. The type anydata
contains a shape if and only if
the shape is pure and is not the shape of an error value.
Thus the type anydata|error
is the supertype of all pure types. The
type anydata
is equivalent to the union
() | boolean | int | float | decimal | string | (anydata|error)[] | map<anydata|error> | xml | table
byte-type-descriptor := byte
The byte type is an abbreviation for a union of the int values in the range 0 to 255 inclusive.
json-type-descriptor := json json means () | boolean | int | float | decimal | string | json[] | map<json>
There are several abstract object types that are built-in in the sense that the language treats objects with these types specially. Note that it is only the types that are built-in; the names of these types are not built-in.
Note It is likely that a future version of this specification will provide generic types, so that a library can provide definitions of these built-in types.
A value that is iterable as a sequence of values of type T provides a way of creating an iterator object that matches the type
abstract object { public next() returns record {| T value; |}?; }
In this specification, we refer to this type as Iterator<T>.
Conceptually an iterator represents a position between members of the sequence. Possible positions are at the beginning (immediately before the first member if any), between members and at the end (immediately after the last member if any). A newly created iterator is at the beginning position. For an empty sequence, there is only one possible position which is both at the beginning and at the end.
The next()
method behaves as follows:
{ value: v }
where v is the member of the
sequence between the previous position and the new position
Note that it is not possible for the next()
method simply to return
a member of the sequence, since a nil member would be indistinguishable from the
return value for the end position.
An object can make itself be iterable as a sequence of values of type T by
providing a method named __iterator
which returns a value that is a
subtype of Iterator<T>. In this specification, we refer to this type as
Iterable<T>.
An object can declare itself to be a collection of values of type V indexed by
keys of type K, but defining a __get(K k)
method returning a value
of type V, that returns the value associated with key k. If the collection is
mutable, then the object can also declare a __put(K k, V v)
method
that changes the value associated with key k to to value v. In this
specification, we refer to these types as ImmutableCollection<T> and
MutableCollection<T>.
The Listener type is defined as follows.
abstract object { function __attach (service s, string? name = ()) returns error?; function __start () returns error?; function __stop () returns error?; }
Note that if an implementation does precise service typing using annotations on
type definitions, it will need to treat Listener as being parameterized in the
precise service type that is used to the first argument to
__attach
.
These section specifies a number of operations that can be performed on values. These operations are for internal use by the specification. These operations are named in CamelCase to distinguish them from built-in methods.
Freeze(v) is defined for any pure value v. The result of Freeze(v) is always v. It recursively freezes any structural values and their members, thus maintaining the invariant that frozen values can only refer to immutable values. Freeze(v) for a value v is already frozen or is of an immutable type (i.e. a simple type of error) just returns v without modifying it. Freeze(v) must terminate for any pure value v, even if v has cycles.
Clone(v) is defined for any pure value v. It performs a deep copy, recursively copying all structural values and their members. Clone(v) for a value v that is frozen or of an immutable type (ie a simple type or error) returns v. If v is a container, Clone(v) has the same inherent type as v. The graph of references of Clone(v) must have the same structure as that of v. This implies that the number of distinct references reachable from Clone(v) must be the same as the number of distinct references reachable from v. Clone(v) must terminate for any pure value v, even if v has cycles.
Clone(v) cannot be implemented simply by recursively calling Clone on all members of v. Rather Clone must maintain a map that records the result of cloning each reference value. When a Clone operation starts, this map as empty. When cloning a reference value, it must use the result recorded in the map if there is one.
UnfrozenClone works the same as Clone except for its treatment of frozen values. Whereas for a frozen value v, Clone(v) is v, UnfrozenClone(v) returns a new value that is not frozen and that has the same shape as v. UnfrozenClone handles values of immutable types (simple types and errors) in the same way as Clone. Like Clone, UnfrozenClone preserves graph structure.
SameShape(v1, v2) is defined for any pure values v1, v2. It returns true or false depending of whether v1 and v2 have the same shape. SameShape(v1, v2) must terminate for any pure values v1 and v2, even if v1 or v2 have cycles. SameShape(v1, v2) returns true if v1 and v2 have the same shape, even if the graphs of references of v1 and v2 have different structures. If two values v1 and v2 have different basic types, then SameShape(v1, v2) will be false.
The possibility of cycles means that SameShape cannot be implemented simply by calling SameShape recursively on members. Rather SameShape must maintaom a mapping that records for each pair of references whether it is already in process of comparing those references. When a SameShape operation starts, this map is empty. Whenever it starts to compare two references, it should see whether it has already recorded that pair (in either order), and, if it has, proceed on the assumption that they compare equal.
SameShape(Clone(x), x) is guaranteed to be true for any pure value.
NumericConvert(t, v) is defined if t is the typedesc for float, decimal or int, and v is a numeric value. It converts v to a value in t, or returns an error, according to the following table.
| from \ to | float | decimal | int |
| float | unchanged | closest math value | round, error for NaN or out of int range |
| decimal | closest math value | unchanged | |
| int | same math value | same math value | unchanged |
Binding patterns are used to support destructuring, which allows different parts of a single structured value each to be assigned to separate variables at the same time.
binding-pattern := capture-binding-pattern | wildcard-binding-pattern | list-binding-pattern | mapping-binding-pattern | error-binding-pattern capture-binding-pattern := variable-name variable-name := identifier wildcard-binding-pattern :=_
list-binding-pattern :=[
list-member-binding-patterns]
list-member-binding-patterns := binding-pattern (,
binding-pattern)* [,
rest-binding-pattern] | [ rest-binding-pattern ] mapping-binding-pattern :={
field-binding-patterns}
field-binding-patterns := field-binding-pattern (,
field-binding-pattern)* [,
rest-binding-pattern] | [ rest-binding-pattern ] field-binding-pattern := field-name:
binding-pattern | variable-name rest-binding-pattern :=...
variable-name error-binding-pattern :=error
(
error-arg-list-binding-pattern)
error-arg-list-binding-pattern := simple-binding-pattern (,
named-arg-binding-pattern)* [,
rest-binding-pattern] | named-arg-binding-pattern (,
named-arg-binding-pattern)* [,
rest-binding-pattern] | [ rest-binding-pattern ] simple-binding-pattern := capture-binding-pattern | wildcard-binding-pattern named-arg-binding-pattern := arg-name=
binding-pattern
A binding pattern may succeed or fail in matching a value. A successful match causes values to be assigned to all the variables occurring the binding-pattern.
A binding pattern matches a value in any of the following cases.
...v
, then a successful match causes a new
list value consisting of all members of the matched list except for the the
first m values to be assigned to v
;...v
then a successful match causes a new mapping value
consisting of all the other fields to be assigned to v
; a
field-binding-pattern consisting of just a variable-name x
is
equivalent to a field-binding-pattern x: x
;
...v
then a successful match causes a new mapping value
consisting of all fields other than f1, f2, ... ,
fn to be assigned to v
;All the variables in a binding-pattern must be distinct e.g. (x, x) is not allowed.
Given a type descriptor for every variable in a binding-pattern, there is a type descriptor for the binding-pattern that will contain a value just in case that the binding pattern successfully matches the value causing each variable to be assigned a value belonging to the type descriptor for that variable.
typed-binding-pattern := inferable-type-descriptor binding-pattern
inferable-type-descriptor := type-descriptor | var
A typed-binding-pattern combines a type-descriptor and a binding-pattern, and is
used to create the variables occurring in the binding-pattern. If var is used
instead of a type-descriptor, it means the type is inferred. How the type is
inferred depends on the context of the typed-binding-pattern. An
inferable-type-descriptor is an inferable context for a type descriptor, which
means that *
can be used with the type descriptor to infer certain
parts of it.
The simplest and most common form of a typed-binding-pattern is for the binding pattern to consist of just a variable name. In this case, the variable is constrained to contain only values matching the type descriptor.
When the binding pattern is more complicated, the binding pattern must be consistent with the type-descriptor, so that the type-descriptor unambiguously determines a type for each variable occurring in the binding pattern. A binding pattern occurring in a typed-binding-pattern must also be irrefutable with respect to the type of value against which it is to be matched. In other words, the compiler will ensure that matching such a binding pattern against a value will never fail at runtime.
For every variable, there is place in the program that creates it. Variables are lexically scoped: every variable has a scope which determines the region of the program within which the variable can be referenced.
There are two kinds of scope: module scope and block scope. A variable with module scope can be referenced anywhere within a module. Identifiers with module scope are used to identify not only variables but other module-level entities such as functions.
A variable with block scope can be referenced only within a particular block (always delimited with curly braces). Block-scope variables are created by a variety of different constructs, many of which use a typed-binding-pattern. Parameters are treated as read-only variables with block scope.
A variable with block scope can have the same name as a variable with module scope; the former variable will hide the latter variable while the former variable is in scope. However, it is a compile error if a variable with block scope has the same name as another variable with block scope and the two scopes overlap.
expression := literal | list-constructor-expr | mapping-constructor-expr | table-constructor-expr | service-constructor-expr | string-template-expr | xml-expr | new-expr | variable-reference-expr | field-access-expr | annot-access-expr | index-expr | xml-attributes-expr | function-call-expr | method-call-expr | error-constructor-expr | anonymous-function-expr | arrow-function-expr | type-cast-expr | typeof-expr | unary-expr | multiplicative-expr | additive-expr | shift-expr | range-expr | numeric-comparison-expr | is-expr | equality-expr | binary-bitwise-expr | logical-expr | conditional-expr | check-expr | trap-expr | table-query-expr |(
expression)
For simplicity, the expression grammar is ambiguous. The following table shows the various types of expression in decreasing order of precedence, together with associativity.
| Operator | Associativity |
x.k
x.@a
f(x)
x.f(y)
x[y]
new T(x)
|
|
+x
-x
~x
!x
<T> x
typeof x
|
|
x * y
x / y
x % y
|
left |
x + y
x - y
|
left |
x << y
x >> y
>>>
|
left |
x ... y
x ..< y
|
non |
x < y
x > y
x <= y
x >= y
x is y
|
non |
x == y
x != y
x === y
x !== y
|
left |
x & y |
left |
x ^ y |
left |
x | y
|
left |
x && y |
left |
x || y |
left |
x ?: y |
right |
x ? y : z |
right |
(x) => y |
right |
When the evaluation of an expression completes normally, it produces a result, which is a value. The evaluation of an expression may also complete abruptly. There are two kinds of abrupt completion: check-fail and panic. With both kinds of abrupt completion there is an associated value, which always has basic type error.
The following sections describes how each kind expression is evaluated, assuming that evaluation of subexpressions complete normally. Except where explicitly stated to the contrary, expressions handle abrupt completion of subexpressions as follows. If in the course of evaluating an expression E, the evaluation of some subexpression E1 completes abruptly, then then evaluation of E also completes abruptly in the same way as E1.
A type is computed for every expression at compile type; this is called the static type of the expression. The compiler and runtime together guarantee that if the evaluation of an expression at runtime completes normally, then the resulting value will belong to the static type. A type is also computed for check-fail abrupt completion, which will be a (possibly empty) subtype of error; however, for panic abrupt completion, no type is computed.
The detailed rules for the static typing of expressions are quite elaborate and are not specified completely in this document. This document only mentions some key points that programmers might need to be aware of.
For a context in which an expression occurs, there may be a type descriptor that describes the static type that the expression is expected to have. This is called the contextually expected type. For example, if a variable is declared by a type descriptor TD, then TD will be the contextually expected type for the expression initializing the variable. A type descriptor must be resolved before it can be used to provide a contextually expected type.
Many kinds of expression that construct values use the contextually expected type to determine the type of value constructed, rather than requiring the type to be specified explicitly. For each such kind of expression, there is a set of basic types (most often consisting of a single basic type) that the value constructed by that kind of expression will always belong to. In this case, the contextually expected type is narrowed by intersecting it with this set of basic types; this narrowed type is called the applicable contextually expected type. The narrowing is performed on the type descriptor by first normalizing the type descriptor into a union, where each member of the union is not a union and describes shapes from a single basic type, and then eliminating any members of the union with the wrong basic type; if this leaves no members, then it is a compile-time error; if it leaves a single member of the union, then the the applicable contextually expected type is this single member, otherwise it is a union of the remaining members.
Note the language provides a way to say that the type of a variable is to be inferred from the static type of the expression used to initialize the variable. In this case, there is no contextually expected type for the evaluation of the expression. Not having a contextually expected type is different from having a contextually expected type that allows all values.
There is an additional complexity relating to inferring types. Expressions in fact have two static types, a precise type and a broad type. Usually, the precise type is used. However, in a few situations, using the precise type would be inconvenient, and so Ballerina uses the broad type. In particular, the broad type is used for inferring the type of an implicitly typed non-final variable. Similarly, the broad type is used when it is necessary to infer the member type of the inherent type of a container.
In most cases, the precise type and the broad type of an expression are the same. For a compound expression, the broad type of an expression is computed from the broad type of the sub-expressions in the same way as the precise type of the expression is computed from the precise type of sub-expressions. Therefore in most cases, there is no need to mention the distinction between precise and broad types.
The most important case where the precise type and the broad type are different
is literals. The precise type is a singleton type containing just the shape of
the value that the literal represents, whereas the broad type is the precise
type widened to contain the entire basic type of which it is a subtype. For
example, the precise type of the string literal "X"
is the
singleton type "X"
, but the broad type is string
.
For a type-cast-expr, the precise type and the broad type are the type specified in the cast.
Ballerina makes a sharp distinction between type conversion and type casting.
Casting a value does not change the value. Any value always belongs to multiple types. Casting means taking a value that is statically known to be of one type, and using it in a context that requires another type; casting checks that the value is of that other type, but does not change the value.
Conversion is a process that takes as input a value of one type and produces as output a possibly distinct value of another type. Note that conversion does not mutate the input value.
Ballerina always requires programmers to make conversions explicit, even between different types of number; there are no implicit conversions.
const-expr := literal | list-constructor-expr | mapping-constructor-expr | table-constructor-expr | string-template-expr | xml-expr | constant-reference-expr | type-cast-expr | unary-expr | multiplicative-expr | additive-expr | shift-expr | range-expr | numeric-comparison-expr | is-expr | equality-expr | binary-bitwise-expr | logical-expr | conditional-expr |(
const-expr)
Within a const-expr
, any nested expression must also be a
const-expr.
constant-reference-expr := variable-reference-expr
A constant-reference-expr
must reference a constant defined with
module-const-decl
.
A const-expr
is evaluated at compile-time. The result of a
const-expr
is automatically frozen. Note that the syntax of
const-expr does not allow for the construction of error values.
simple-const-expr := nil-literal | boolean | [Sign] int-literal | [Sign] floating-point-literal | string-literal | const-reference-expr
A simple-const-expr is a restricted form of const-expr used in contexts where various forms of constructor expression would not make sense. Its semantics are the same as a const-expr.
literal := nil-literal | boolean-literal | numeric-literal | string-literal | byte-array-literal numeric-literal := int-literal | floating-point-literal
A numeric-literal represents a value belonging to one of the basic types int, float or decimal. The basic type to which the value belongs is determined as follows:
FloatTypeSuffix
, then the
basic type is float;DecimalTypeSuffix
, then the
basic type is decimal;HexFloatingPointLiteral
, then the
basic type is float;The precise type of a numeric-literal is the singleton type containing just the shape of the value that the numeric-literal represents. The broad type is the basic type of which the precise type is a subset.
byte-array-literal := Base16Literal | Base64Literal Base16Literal :=base16
WS`
HexGroup* WS`
HexGroup := WS HexDigit WS HexDigit Base64Literal :=base64
WS`
Base64Group* [PaddedBase64Group] WS`
Base64Group := WS Base64Char WS Base64Char WS Base64Char WS Base64Char PaddedBase64Group := WS Base64Char WS Base64Char WS Base64Char WS PaddingChar | WS Base64Char WS Base64Char WS PaddingChar WS PaddingChar Base64Char :=A
..Z
|a
..z
|0
..9
|+
|/
PaddingChar :==
WS := WhiteSpaceChar*
The static type of byte-array-literal is byte[N]
, where N is the
number of bytes encoded by the Base16Literal or Base64Literal. The inherent type
of the array value created is also byte[N]
.
list-constructor-expr :=[
[ expr-list ]]
expr-list = expression (,
expression)*
Creates a new list value. The members of the list come from evaluating each expression in the expr-list in order.
The inherent type of the newly created list is derived from the applicable
contextually expected type. It is an error if there is no contextually expected
type. If the applicable contextually expected type is a list type descriptor,
then that used as the inherent type. If the applicable contextually expected
type is a union type descriptor, then any members of the union that do not
contain list shapes of length N will be ignored, where N is the number of
expressions in the expr-list
; it is a compile-time error if this
does not leave a single list type descriptor, which is then used as the inherent
type. The static type of the list-constructor-expr will be the same as the
inherent type.
The type that the inherent type requires for each list member provides the contextually expected type for the expression for the member.
Some members of a list can be filled in automatically. A member can be filled in automatically if its type descriptor, as determined by the inherent type of the list, provides a way to create a filler value. The following table specifies the filler value for each kind of type descriptor that provides one.
| Type descriptor | Filler value | When provided |
|---|---|---|
() |
() |
|
boolean |
false |
|
int |
0 |
|
float |
+0.0f |
|
decimal |
+0.0d |
|
string |
"" |
|
| list type descriptor | [] |
if that is a valid constructor for the type |
| mapping type descriptor | { } |
if that is a valid constructor for the type |
| table | empty table (with no rows) | |
| object | new T() |
if this is valid and its static type does not include error, where
T is the object type descriptor (an abstract object type will not
have a filler value) |
xml |
xml`` |
|
stream<T> |
new stream<T> |
|
| singleton | the single value used to specify the type | |
| union | () |
if () is a member of the union |
| the filler value for basic type B | if all members of the union belong to a single basic type B, and the filler value for B also belongs to the union | |
T? |
() |
|
any |
() |
|
anydata |
() |
|
byte |
0 |
|
json |
() |
The inherent type of a list establishes either a fixed length for the list or just a minimum length for the list, which may be zero. In the latter case, for each j greater than m, where m is the minimum length of the list, the inherent type of the list must be such that j-th member can be filled in automatically. In either case, a list constructor may specify only the first k members, provided that for each i from k + 1 up to the fixed length of the list, the i-th member can be filled in automatically.
mapping-constructor-expr :={
[field (,
field)*]}
field := (literal-field-name | computed-field-name):
value-expr literal-field-name := field-name | string-literal computed-field-name :=[
expression]
value-expr := expression
A mapping-constructor-expr creates a new mapping value. An expression can be used to specify the name of a field by enclosing the expression in square brackets.
The inherent type of the newly created mapping is derived from the applicable contextually expected type. It is an error if there is no contextually expected type. If the applicable contextually expected type is a mapping type descriptor, then that used as the inherent type. If the applicable contextually expected type is a union type descriptor, then any members of the union that are inconsistent with the field names specified as a literal-field-name in the mapping-constructor-expr will be ignored; it is a compile-time error if this does not leave a single mapping type descriptor, which is then used as the inherent type. The static type of the mapping-constructor-expr will be the same as the inherent type.
If the inherent type descriptor is a record type descriptor, a field will be added to the constructed value using the default value from the type descriptor for any field that is not specified explicitly in the mapping constructor and that has a default value.
The type that the inherent type requires for each field provides the contextually expected type for the value-expr for that field. The contextually expected type for a computed-field-name is string.
table-constructor-expr :=table
{
[column-descriptors [,
table-rows]]}
column-descriptors :={
column-descriptor (,
column-descriptor)*}
column-descriptor := column-constraint* column-name column-constraint :=key
|unique
|auto
auto-kind auto-kind := auto-kind-increment auto-kind-increment :=increment
[(
seed,
increment)
] seed := integer increment := integer table-rows :=[
table-row (,
table-row)*]
table-row :={
expression (,
expression)*}
The contextually expected type of the table-constructor-expr determines the inherent type of the constructed value.
For example,
table { { key firstName, key lastName, position }, [ {"Sanjiva", "Weerawarana", "lead" }, {"James", "Clark", "design co-lead" } ] }
service-constructor-expr := annotsservice
service-body-block service-body-block :={
service-method-defn*}
service-method-defn := metadata service-method-defn-quals function identifier function-signature method-body service-method-defn-quals := [explicit-visibility-qual |resource
]
A service-constructor-expr
constructs a service value. The result
of evaluating a service-constructor-expr is a value of type service. If a
service-method-defn contains a resource qualifier, then it defines a resource,
otherwise it defines a method. The self variable can be used in a method-body of
a service-method-defn in the same way as for objects.
Each service value has a distinct type descriptor. (Evaluating a service constructor thus has an effect analogous to defining an anonymous object type and then creating a value of that type.)
The return type of a resource must be a subtype of error?
and must contain
nil.
string-template-expr :=string
BacktickString BacktickString :=`
BacktickItem* Dollar*`
BacktickItem := BacktickSafeChar | BacktickDollarsSafeChar | Dollar* interpolation interpolation :=${
expression}
BacktickSafeChar := ^ (`
|$
) BacktickDollarsSafeChar :=$
+ ^ ({
|`
|$
) Dollar :=$
A string-template-expr
interpolates the results of evaluating
expressions into a literal string. The static type of the expression in each
interpolation must be a simple type and must not be nil. Within a
BacktickString
, every character that is not part of an
interpolation
is interpreted as a literal character. A
string-template-expr is evaluated by evaluating the expression in each
interpolation in the order in which they occur, and converting the result of the
each evaluation to a string as if using by <string>
. The result
of evaluating the string-template-expr
is a string comprising the
literal characters and the results of evaluating and converting the
interpolations, in the order in which they occur in the
BacktickString
.
A literal `
can be included in string template by using an
interpolation ${"`"}
.
xml-expr := xml
BacktickString
An XML expression creates an XML value as follows:
content
in the W3C XML
Recommendation. For the purposes of parsing as XML, each interpolated expression
is interpreted as if it were an additional character allowed by the CharData and
AttValue productions but no other. The result of this step is an XML Infoset
consisting of an ordered list of information items such as could occur as the
[children] property of an element information item, except that interpolated
expressions may occur as Character Information Item or in the [normalized value]
of an Attribute Information Item. Interpolated expressions are not allowed in
the value of a namespace attribute.BacktickString
, and
converted to strings if necessary. A new copy is made of the XML value and the
result of the expression evaluations are inserted into the corresponding
position in the newly created XML value. This XML value is the result of the
evaluation.new-expr := explicit-new-expr | implicit-new-expr explicit-new-expr := new type-descriptor(
arg-list)
A new-expr constructs a new object.
An explicit-new-expr allocates storage for the object and initializes it,
passing the supplied arg-list to the object's __init
method. It is
a compile error if the type-descriptor does not specify an object type or if the
arg-list does not match the signature of the object type's __init
method. If the result of calling the __init
method is an error
value e, then the result of evaluating the explicit-new-expr is e; otherwise the
result is the newly initialized object.
An explicit-type-expr specifying a type descriptor T has static type T, except
that if the type of the __init
method is E?, where E is a subtype
of error, then it has static type T|E.
implicit-new-expr := new [(
arg-list)
]
An implicit-new-expr is equivalent to an explicit-new-expr that specifies the
applicable contextually expected type as the type descriptor. An
implicit-new-expr consisting of just new
is equivalent to
new()
.
variable-reference-expr := variable-reference variable-reference := qualified-identifier
A variable-reference can refer to a variable, a parameter, a constant (defined with a module constant declaration) or a type (defined with a type definition).
If the variable-reference references a type defined with a type definition, then the result of evaluating the variable-reference-expr is a typedesc value for that type.
field-access-expr := expression .
field-name
{MISSING}
annot-access-expr := expression @.
annot-tag-reference
The annot-tag-reference must refer to an annotation tag declared with an
annotation declaration. The static type of expression must be a subtype of
typedesc
.
An annot-access-expr
is evaluated by first evaluating
expression
resulting in a typedesc value t. If t
has an annotation with the tag referenced by annot-tag-reference
,
then the result of the annot-access-expr
is the value of that
annotation; otherwise, the result is nil.
The static type of the annot-access-expr
is T? where T is the type
of the annotation tag.
index-expr := expression[
expression]
{MISSING}
Evaluation of an index-expr that attempts to accessing a list with an out of bounds index will complete abruptly with a panic.
xml-attributes-expr := expression @
Returns the attributes map of a singleton xml value, or nil if the operand is
not a singleton xml value. The result type is map<string>?
.
function-call-expr := [start
] function-reference(
arg-list)
function-reference := variable-reference arg-list := positional-args [,
other-args] | [other-args] positional-args := positional-arg (,
positional-arg)* positional-arg := expression other-args := named-args | rest-arg named-args := named-arg (,
named-arg)* named-arg := arg-name=
expression arg-name := identifier rest-arg :=...
expression
A function-call-expr is evaluated by constructing an argument list and passing the argument list to the function referred to by the variable-name. If the function terminates normally, then the result of the function-call-expr is the return value of the function; otherwise the function-call-expr completes abruptly with a panic. The static type of the function-call-expr is the return type of the function type.
However, if start
is specified, then the called function is
executed on a new strand, and the result of the function-call-expr
is a value of type future<T>, where T is the return type of the function
called.
The variable-reference must refer to a variable with function type. The type descriptor of that function type is used to construct an argument list from the specified arg-list. Note that it is the type descriptor of the declared type of the variable that is used for this purpose, rather than the runtime type descriptor of the referenced function value.
The expressions occurring in the arg-list are evaluated in the order in which they occur in the arg-list. The result of evaluating each positional-arg is added to the argument list in order. The contextually expected type for the expression in the i-th positional-arg is the type of the i-th parameter.
If there is a rest-arg, then it is evaluated. The result must be a list value. Each member of the list value is added to the argument in the order that it occurs. The static type of the list value must be such as to guarantee that the resulting argument list will conform to the function's declared param-list. The rest-arg is not restricted to supplying the part of the argument list that will be bound to a rest-param, and its static type is not restricted to being an array type. If there is rest-arg, then no parameter defaults are added.
If there is no rest-arg, then each non-rest parameter that was not supplied by positional argument is added in order from a named parameter, if there is one, and otherwise using the parameter default. The contextually expected type for the expression specifying a named argument is the type declared for the corresponding parameter. A default parameter is computed by calling the closure in the type descriptor, passing it the previous arguments in the argument list. It is a compile-time error if there is a non-rest parameter for which there was no positional argument and no named argument and which is not defaultable. It is also an error if there is a named argument for a parameter that was supplied by a positional argument.
When a function to be called results from the evaluation of an expression that
is not merely a variable reference, the function can be called either by first
storing the value of the expression in a variable or using the call
built-in method on functions.
method-call-expr := [start
] expression.
method-name(
arg-list)
The basic type of the value that results from evaluating expression determines how the method-name is used to lookup the method to call. If the basic type is service or object, then the method is looked up in the object's methods; otherwise, the method is looked up in the built-in methods of that basic type. A method-call-expr cannot be used to call a remote method; it can only be called by a remote-method-call-action. A method-call-expr cannot be used to invoke a resource.
If start
is specified, then the called method is executed on a new
strand, and the result of the method-call-expr
is a value of type
future<T>, where T is the return type of the method called.
error-constructor-expr :=error
(
error-arg-list)
error-arg-list := positional-arg (,
named-arg)* | [named-arg (,
named-arg)*]
An error-constructor-expr constructs a new error value. The positional-arg, if present, specifies the error reason string. Each named-arg specifies a field of the error detail mapping. The static type of positional-arg must be a subtype of string; the static type of each named-arg must be a pure type. The stack trace describes the stack at the point where the error constructor was evaluated.
If there is a contextually expected type and the type of the error reason of the applicable contextually expected error type is a singleton string type, then the positional-arg can be omitted and the error reason will be the single string allowed by the type.
If there is a contextually expected type and the applicable contextually expected type is an error type (rather than a union) with detail type d, then the error detail mapping will also have a field for any defaultable fields of d for which no named-arg was specified.
Evaluating the error-constructor-expr creates a new detail record. The result of evaluating each named-arg and every defaultable arg is cloned using the Clone abstract operation, then frozen using the Freeze abstract operation, before being added to the detail record. The detail record is itself frozen after all fields have been added.
anonymous-function-expr :=
annots function
function-signature function-body-block
Evaluating an anonymous-function-expr creates a closure, whose basic type is function. If function-body-block refers to a block-scope variable defined outside of the function-body-block, the closure will capture a reference to that variable; the captured reference will refer to the same storage as the original reference not a copy.
arrow-function-expr := arrow-param-list=>
expression arrow-param-list := identifier |(
[identifier (,
identifier)*])
Arrow functions provide a convenient alternative to anonymous function
expressions that can be used for many simple cases. An arrow function can only
be used in a context where a function type is expected. The types of the
parameters are inferred from the expected function type. The scope of the
parameters is expression
. The static type of the arrow function
expression will be a function type whose return type is the static type of
expression
. If the contextually expected type for the
arrow-function-expr
is a function type with return type T, then the
contextually expected type for expression
is T.
type-cast-expr :=<
type-descriptor>
expression
A type-cast-expr
casts the value resulting from evaluating
expression
to the type described by the type-descriptor, performing
a numeric conversion if required.
A type-cast-expr is evaluated by first evaluating expression
resulting in a value v. Let T be the type described by
type-descriptor
. If v belongs T, then the result of the
type-cast-expr is v. Otherwise, if T includes shapes from exactly one basic
numeric type N and v is belongs to another basic numeric type, then let n be
NumericConvert(N, v); if n is not an error and n belongs to T, then the result
of the type-cast-expr is n. Otherwise, the evaluation of the type-cast-expr
completes abruptly with a panic.
Let T be the static type described by type-descriptor
, and let TE
be the static type of expression
. Then the static type of the
type-cast-expr
is the intersection of T and TE', where TE' is TE
with its numeric shapes transformed to take account to the possibility of the
numeric conversion specified in the previous paragraph.
The type-descriptor
provides the contextually expected type for
expression
.
typeof-expr := typeof
expression
The result of a typeof-expr
is a typedesc value for the runtime
type of v, where v is the result of evaluating expression
.
The runtime type of v is the narrowest type to which v belongs.
typeof
with a simple
value produces a new typedesc value.typeof
with an
identical reference value produces an identical typedesc value. The type
descriptor resulting from typeof
will be identical to the type
descriptor used to construct the value. For containers, this is the same as the
inherent type, except in the case where the inherent type of a structured value
has been changed by freeze or stamp methods. For behavioural values, this is the
same as the type descriptor used with new. Any annotations that were attached to
the type descriptor used to construct the value will this be available on the
constructed value.
The static type of typeof-expr
is typedesc<T>, where T is the
static type of expression
.
unary-expr :=+
expression |-
expression |~
expression |!
expression
The unary -
operator performs negation. The static type of the
operand must be a number; the static type of the result is the same basic type
as the static type of the operand. The semantics for each basic type are as
follows:
The unary + operator returns the value of its operand expression. The static type of the operand must be a number, and the static type of the result is the same as the static type of the operand expression.
The ~ operator inverts the bits of its operand expression. The static type of the operand must be int, and the static type of the result is an int.
The !
operator performs logical negation. The static type of the
operand expression must be boolean. The !
operator returns true if
its operand is false and false if its operand is true.
multiplicative-expr := expression*
expression | expression/
expression | expression%
expression
The *
operator performs multiplication; the /
operator
performs division; the %
operator performs remainder.
The static type of both operand expressions is required to be the same basic type; this basic type will be the static type of the result. The following basic types are allowed:
*
performs the multiplication operation with the destination
format being the same as the source format, as defined by IEEE 754-2008; no
exceptions are thrown/
performs the division operation with the destination format
being the same as the source format, as defined by IEEE 754-2008; no exceptions
are thrown%
performs a remainder operation; the remainder is not the
IEEE-defined remainder operation but is instead a remainder operation analogous
to integer remainder; more precisely,
additive-expr := expression+
expression | expression-
expression
The +
operator is used for both addition and concatenation; the
-
operator is used for subtraction.
It is required that either
The semantics for each basic type is as follows:
shift-expr := expression<<
expression expression>>
expression expression>>>
expression
A shift-expr performs a bitwise shift. Both operand expressions must have static type that is a subtype of int. The left hand operand is the value to be shifted; the right hand value is the shift amount; all except the bottom 6 bits of the shift amount are masked out (as if by x & 0x3F). Then a bitwise shift is performed depending on the operator:
<<
performs a left shift, the bits shifted in on the
right are zero>>
performs a signed right shift; the bits shifted in on the
left are the same as the most significant bit>>>
performs a unsigned right shift, the bits shifted in on the
left are zeroIf the operator is >> or >>> and the left hand operand is a subtype of byte, then the static type of the result is byte; otherwise the static type of the result is int.
range-expr := expression...
expression | expression..<
expression
A range-expr results in a new array of integers in increasing order including all integers n such that
...
, less than or equal to the value of the
second expression..<
, less than the value of the second
expressionIt is a compile error if the static type of either expression is not a subtype of int.
A range-expr is designed to be used in a foreach statement, but it can be used anywhere.
numerical-comparison-expr := expression<
expression | expression>
expression | expression<=
expression | expression>=
expression
A numerical-comparison-expr compares two numbers.
The static type of both operands must be of the same basic type, which must be int, float or decimal. The static type of the result is boolean.
Floating point comparisons follow IEEE, 754-2008, so
is-expr :=
expression is
type-descriptor
An is-expr tests where a value belongs to a type.
An is-expr is evaluated by evaluating the expression yielding a result v. If v belongs to the type denoted by type-descriptor, then the result of the is-expr is true; otherwise the result of the is-expr is false.
equality-expr := expression==
expression | expression!=
expression | expression===
expression | expression!==
expression
An equality-expr tests whether two values are equal. For all four operators, it is a compile time error if the intersection of the static types of the operands is empty.
The === operator tests for exact equality. The !== operator results in the negation of the result of the === operator. Two values are exactly equal if they have the same basic type T and
The == operator tests for deep equality. The != operator results in the negation of the result of the == operator. For both == and !=, both operands must have a static type that is pure. Two values v1, v2 are deeply equal if SameShape(v1, v2) is true.
Note that === and == are the same for simple values except as regards floating point zero: == treats positive and negative zero from the same basic type as equal whereas === treats them as unequal. Both == and === treat two NaN values from the same basic type as equal. This means that none of these operators correspond to operations defined by IEEE 754-2008, because they do not treat NaN in the special way defined for those operations.
binary-bitwise-expr := bitwise-and-expr | bitwise-xor-expr | bitwise-or expr bitwise-and-expr = expression&
expression bitwise-xor-expr = expression^
expression bitwise-or-expr = expression|
expression
A binary-bitwise-expr does a bitwise AND, XOR, or OR operation on its operands.
The static type of both operands must be a subtype of int. The static type of the result is as follows:
logical-expr := expression&&
expression | expression||
expression
conditional-expr := expression?
expression:
expression | expression?:
expression
L ?:
R is evaluated as follows:
check-expression := check
expression
Evaluates expression resulting in value v. If v has basic type error, then check-expression completes abruptly with a check-fail with associated value v. Otherwise evaluation completes normally with result v.
If the static type of expression e is T|E, where E is a subtype of error, then the static type of check e is T.
The trap expression stops a panic and gives access to the error value associated with the panic.
trap-expr := trap
expression
Semantics are:
expression
resulting in value v
expr
is T, then type of trap expr
is
T|error.action := remote-method-call-action | worker-receive-action | wait-action | flush-action | synchronous-send-action | check-action | trap-action |(
action)
action-or-expr := action | expression check-action :=check
action trap-action :=trap
action
Actions are an intermediate syntactic category between expressions and statements. Actions are similar to expressions, in that they yield a value. However, an action cannot be nested inside an expression; it can only occur as part of a statement or nested inside other actions. This is because actions are shown in the sequence diagram in the graphical syntax.
An action is evaluated in the same way as an expression. Static typing for actions is the same as for expressions.
A check-action
and trap-actio
n is evaluated in the
same way as a check-exp
r and trap-expr
respectively.
function-body-block :={
[default-worker-init, named-worker-decl+] default-worker}
default-worker-init := sequence-stmt default-worker := sequence-stmt named-worker-decl :=worker
worker-name return-type-descriptor{
sequence-stmt}
worker-name := identifier
A worker represents a thread of control running within a function. A statement is always executed in the context of a current worker. A worker is in one of three states: running, suspended or terminated. When a worker is in the terminated state, it has a termination value. A worker terminates either normally or abnormally. An abnormal termination results from a panic, and in this case the termination value is always an error. If the termination value of a normal termination is an error, then the worker is said to have terminated with failure; otherwise the worker is said to have terminated with success. Note that an error termination value resulting from a normal termination is distinguished from an error termination value resulting from an abnormal termination.
A function always has a single default worker, which is unnamed. When a function is called, the current worker is suspended, and a default worker for the called function is started. When the default worker terminates, the function returns to its caller, and the caller's worker is resumed. If the default worker terminates normally, then its termination value provides the return value of the function. If the default worker terminates abnormally, then the evaluation of the function call expression completes abruptly with a panic and the default worker's termination value provides the associated value for the abrupt completion of the function call. The function's return type is the same as the return type of the function's default worker.
The default worker of a function and the worker that called the function belong to the same thread of control, which is called a strand. Only one worker in a strand is running at any given time.
A function also has zero or more named workers. Each named worker runs on its own new strand. The termination of a function is independent of the termination of its named workers. The termination of a named worker does not automatically result in the termination of its function. When a default worker terminates causing the function to terminate, the function does not automatically wait for the termination of its named workers. There is a wait-action that allows one worker to explicitly wait for the termination of another worker.
A named worker has a return type. If the worker terminates normally, the
termination value will belong to the return type. If the return type of a worker
is not specified, it defaults to nil as for functions. A return-type-descriptor
in a named-worker-decl is an inferable context for a type descriptor, which
means that *
can be used to infer parts of the type descriptor; in
particular, it is convenient to use error<*>
to specify the
error type.
When a function has named workers, the default worker executes in three stages, as follows:
Variables declared in default-worker-init are in scope within named workers, whereas variables declared in default-worker are not.
The execution of a worker's sequence-stmt may result in the execution of a
statement that causes the worker to terminate. For example, a return statement
causes the worker to terminate. If this does not happen, then the worker
terminates as soon as it has finished executing its sequence-stmt. In this case,
the worker terminates normally, and the termination value is nil. In other
words, falling off the end of a worker is equivalent to return;
,
which is in turn equivalent to return ();
.
The parameters declared for a function are in scope in the function-body-block. They are implicitly final: they can be read but not modified. They are in scope for named workers as well as for the default worker.
The name of a worker is in-scope as a final local variable. The scope is the function-body-block with the exception of the default-worker-init. When the worker name is accessed using a variable-reference-expr, it has type future<T>, where T is the return type of the worker.
In the above, function includes method, and function call includes method call.
statement := action-stmt | block-stmt | local-var-decl-stmt | local-type-defn-stmt | xmlns-decl-stmt | assignment-stmt | compound-assignment-stmt | destructuring-assignment-stmt | call-stmt | if-else-stmt | match-stmt | foreach-stmt | while-stmt | break-stmt | continue-stmt | fork-stmt | panic-stmt | lock-stmt | async-send-stmt | return-stmt | transaction-stmt | transaction-control-stmt
The execution of any statement may involve the evaluation of actions and expressions, and the execution of substatements. The following sections describes how each kind of statement is evaluated, assuming that the evaluation of those expressions and actions completes normally, and assuming that the execution of any substatements does not cause termination of the current worker. Except where explicitly stated to the contrary, statements handle abrupt completion of the evaluation of expressions and actions as follows. If in the course of executing a statement, the evaluation of some expression or action completes abruptly with associated value e, then the current worker is terminated with termination value e; if the abrupt termination is a check-fail, then the termination is normal, otherwise the termination is abnormal. If the execution of a substatement causes termination of the current worker, then the execution of the statement terminates at that point.
sequence-stmt := statement* block-stmt :={
sequence-stmt}
A sequence-stmt
executes its statements sequentially. A
block-stmt
is executed by executing its sequence-stmt
.
fork-stmt :=fork
{
named-worker-decl+}
The fork statement starts one or more named workers, which run in parallel with each other, each in its own new strand.
Variables and parameters in scope for the fork-stmt remain in scope within the workers (similar to the situation with parameters and workers in a function body).
The scope of the worker name declared in a named-worker-decl includes both other workers in the same fork-stmt and the block containing the fork-stmt starting from the point immediately after the fork-stmt. When a worker-name is in scope it can be accessed using a variable-reference-expr, resulting in a value of type future<T>, where T is the return type of that worker.
A wait-action waits for one or more workers to terminate, and gives access to their termination values.
wait-action := single-wait-waction | multiple-wait-action | alternate-wait-action wait-future-expr := expression but not mapping-constructor-expr
A wait-future-expr is used by a wait-action to refer to the worker to be waited for. Its static type must be future<T> for some T. It can use a variable reference to refer to an in-scope named-worker-decl, which will be treated as a reference to a variable of type future<T> where T is the return value of the worker.
Note that it is only possible to wait for a named worker, which will start its own strand. It is not possible to wait for a default worker, which runs on an existing strand.
A mapping-constructor-expr is not recognized as a wait-future-expr (it would not type-check in any case).
single-wait-action := wait
wait-future-expr
A single-wait-action waits for a single future.
A single-wait-action is evaluated by evaluating wait-future-expr resulting in a value f, which must be of basic type future. It then waits until the strand of the future has terminated. If the strand terminates normally, the single-wait-action completes normally with the termination value of the strand as the result. Otherwise, the single-wait-action completes abruptly with a panic, with the associated value being the termination value of the strand, which will be an error.
If the static type of the wait-future-expr is future<T> , then the static type of the single-wait-action is then T.
multiple-wait-action :=wait
{
wait-field (,
wait-field)*}
wait-field := variable-name | field-name:
wait-future-expr
A multiple-wait-action waits for multiple futures, returning the result as a record.
A wait-field that is a variable-name v
is equivalent to a
wait-field v: v
, where v
must be the name of an
in-scope named worker.
A multiple-wait-action is evaluated by evaluating all of the wait-future-exprs resulting in a value of type future for each wait-field. It then waits for all of these futures. If all the futures complete normally, then it constructs a record with a field for each wait-field, whose name is the field-name and whose value is the completion value of the strand.
alternate-wait-action :=wait
wait-future-expr (|
wait-future-expr)+
An alternate-wait-action waits for one of multiple futures to terminate.
An alternate-wait-action is evaluated by first evaluating all of the wait-future-exprs, resulting in a set of future values. It then starts waiting for all of the futures. As soon as one of the futures completes normally with a non-error value v, the alternate-wait-action completes normally with result v. If all of the futures complete normally with an error, then it completes normally with result e, where e is the termination value of the last future to complete.
If the static type of the wait-future-exprs is future<T1>, future<T2>, ..., future<Tn>, then the static type of the alternative-wait action is T1|T1|. . .Tn
Messages can be sent between workers.
Sends and receives are matched up at compile-time. This allows the connection between the send and the receive to be shown in the sequence diagram. It is also guarantees that any sent message will be received, provided that neither the sending nor the receiving worker terminate abnormally or with an error.
Messages can only be sent between workers that are peers of each other. The default worker and the named workers in a function are peers of each other. The workers created in a fork-stmt are also peers of each other. The workers created in a fork-stmt are not peers of the default worker and named workers created by a function.
peer-worker := worker-name | default
A worker-name refers to a worker named in a named-worker-decl, which must be in scope; default refers to the default worker. The referenced worker must be a peer worker.
Each worker maintains a separate logical queue for each peer worker to which it sends messages; a sending worker sends a message by adding it to the queue; a receiving worker receives a message by removing it from the sending worker's queue for that worker; messages are removed in the order in which they were added to the queue.
sync-send-action := expression->>
peer-worker async-send-stmt := expression->
peer-worker;
The sync-send-action and async-send-stmt send a message to another worker. In both cases, the message is the result of applying the Clone abstract operation to the result of evaluating expr. The message is sent to the worker identified by peer-worker.
In both cases, the message is added to the message queue maintained by the sending worker for messages to be sent to the sending worker. Conceptually, the message is added to the queue even if the receiving worker has already terminated.
For each async-send-stmt and sync-send-action S, the compiler determines a unique corresponding receive-action R, such that a message sent by S will be received by R, unless R's worker has terminated abnormally or with failure. It is a compile-time error if this cannot be done. The compiler determines a failure type for the corresponding receive-action. If the receive-action was not executed and its worker terminated normally, then the termination value of the worker will belong to the failure type. The failure type will be a (possibly empty) subtype of error.
The execution of the async-send-stmt completes as soon as the message is added to the queue. A subsequent flush action can be used to check whether the message was received.
The sync-send-action is evaluated by waiting until the receiving worker either executes a receive action that receives the queued message or terminates. The evaluation of sync-send-action completes as follows:
The static type of the sync-send-action is F|() where F is the failure type of the corresponding receive action. If F is empty, then this static type will be equivalent to ().
The contextually expected type used to interpret expression
is the
contextually expected type from the corresponding receive-action.
If the receive-action corresponding to an async-send-stmt has a non-empty failure type, then it is a compile-time error unless it can be determined that a sync-send-action or a flush-action will be executed before the sending worker terminates with success.
If a worker W is about to terminate normally and there are messages still to be sent in a queue (which must be the result of executing an async-send-stmt), then the worker waits until the messages have been received or some receiving worker terminates. If a receiving worker R terminates without the message being received, R must have terminated abnormally, because the rule in the preceding paragraph. In this case, W terminates abnormally and W will use R's termination value as its termination value.
receive-action := single-receive-action | multiple-receive-action
single-receive-action := <-
peer-worker
A single-receive-action receives a message from a single worker.
For each single-receive-action R receiving from worker W, the compiler determines a corresponding send set. The corresponding send set S is a set of sync-send-actions and async-send-stmts in W, such that
The compiler terminates a failure type for the corresponding send set. If no member of the corresponding send set was executed/evaluated and the sending worker terminated normally, then the termination value of the sending worker will belong to the failure type. The failure type will be a (possibly empty) subtype of error.
A single-receive-action is evaluated by waiting until there is a message available in the queue or the sending worker terminates. The evaluation of single-receive-action completes as follows:
The static type of the single-receive-action is T|F where T is the union of the static type of the expressions in the corresponding send set and F is the failure type of the corresponding send set.
multiple-receive-action :=<-
{
receive-field (,
receive-field)*}
receive-field := peer-worker | field-name:
peer-worker
A multiple-receive-action receives a message from multiple workers.
A peer-worker can occur at most once in a multiple-receive-action.
A receive-field consisting of a peer-worker W
is equivalent to a
field W:W
.
The compiler determines a corresponding send set for each receive field, in the same way as for a single-receive-action. A multiple-receive-action is evaluated by waiting until there is a message available in the queue for every peer-worker. If any of the peer workers W terminate before a message becomes available, then the evaluation of the multiple-receive-action completes as follows
Otherwise, the result of the evaluation of multiple-receive-action completes by removing the first message from each queue and constructing a record with one field for each receive-field, where the value of the record is the message received.
The contextually expected typed for the multiple-receive-action determines a contextually expected type for each receive-field, in the same way as for a mapping constructor. The contextually expected type for each receive-field provides the contextually expected type for the expression in each member of the corresponding send set.
The static type of multiple-receive-action is R|F where
flush-action := flush
[peer-worker]
If peer-worker is specified, then flush waits until the queue of messages to be received by peer-worker is empty or until the peer-worker terminates.
Send-receive correspondence for async-send-stmt implies that the queue will eventually become empty, unless the peer-worker terminates abnormally or with failure. The evaluation of flush-action completes as follows:
If the flush-action has a preceding async-send-stmt without any intervening sync-send-action or other flush-action, then the static type of the flush-action is F|(), where F is the failure type of the receive-action corresponding to that async-send-stmt. Otherwise, the static type of the flush-action is nil.
If peer-worker is omitted, then the flush-action flushes the queues to all other workers. In this case, the static type will be the union of the static type of flush on each worker separately.
This section provides further details about how compile-time correspondence is established between sends and receive. This is based on the concept of the index of a message in its queue: a message has index n in its queue if it is the nth message added to the queue during the current execution of the worker.
local-var-decl-stmt := local-init-var-decl-stmt | local-no-init-var-decl-stmt local-init-var-decl-stmt := annots [final
] typed-binding-pattern=
action-or-expr;
A local-var-decl-stmt
is used to declare variables with a scope
that is local to the block in which they occur.
The scope of variables declared in a local-var-decl-stmt
starts
immediately after the statement and continues to the end of the block statement
in which it occurs.
A local-init-var-decl-stmt is executed by evaluating the action-or-expr
resulting in a value, and then matching the typed-binding-pattern to the value,
causing assignments to the variables occurring in the typed-binding-pattern. The
typed-binding-pattern is used unconditionally, meaning that it is a compile
error if the static types do not guarantee the success of the match. If the
typed-binding-pattern uses var
, then the type of the variable is
inferred from the static type of the action-or-expr; if the
local-init-var-decl-stmt includes final, the precise type is used, and otherwise
the broad type is used. If the typed-binding-pattern specifies a
type-descriptor, then that type-descriptor provides the contextually expected
type for action-or-expr.
If final
is specified, then the variables declared must not be
assigned to outside the local-init-var-decl-stmt.
local-no-init-var-decl-stmt := annots [final
] type-descriptor variable-name;
A local variable declared local-no-init-var-decl-stmt
must be
definitely assigned at each point that the variable is referenced. This means
that the compiler must be able to verify that the local variable will have been
assigned before that point. If final
is specified, then the
variable must not be assigned more than once.
Usually the type of a reference to a variable is determined by the variable's declaration, either explicitly specified by a type descriptor or inferred from the static type of the initializer.
In addition, this section defines cases where a variable is used in certain kinds of boolean expression in a conditional context, and it can be proved at compile time that the value stored in local variable or parameter will, within a particular region of code, always belong to a type that is narrower that the static type of the variable. In these cases, references to the variable within particular regions of code will have a static type that is narrower that the variable type.
Given an expression E with static type boolean, and a variable x with static type Tx, we define how to determine
based on the syntactic form of E as follows.
Narrowed types apply to regions of code as follows:
local-type-defn-stmt := annotstype
identifier type-descriptor;
A local-type-defn-stmt binds the identifier to a type descriptor within the scope of the current block. The type-descriptor is resolved when the statement is executed.
xmlns-decl-stmt :=xmlns
xml-namespace [as
identifier ];
xml-namespace := simple-const-expr
The xml-decl-stmt is used to declare a XML namespace. If the identifier is omitted then the default namespace is defined. Once a default namespace is defined, it is applicable for all XML values in the current scope. If an identifier is provided then that identifier is the namespace prefix used to qualify elements and/or attribute names. The static type of the simple-const-expr must be a subtype of string.
assignment-stmt := lhs=
action-or-expr;
lhs := variable-reference | lhs.
field-name | lhs[
expression]
| lhs@
An lhs
evaluates to a storage location, into which a value can be
stored and from which a value can be fetched.
When a list value has an inherent type that is not fixed length. then attempting to write a member of a list at an index i that is greater than or equal to the current length of the list will first increase the length of the list to i + 1, with the newly added members of the array being filled in automatically.
The type of lhs provides the contextually expected type for action-or-expr.
compound-assignment-stmt := lhs CompoundAssignmentOperator action-or-expr;
CompoundAssignmentOperator := BinaryOperator=
BinaryOperator :=+
|-
|*
|/
|&
||
|^
|<<
|>>
|>>>
These statements update the value of the LHS variable to the value that results from applying the corresponding binary operator to the value of the variable and the value of the expression.
destructure-assignment-stmt := binding-pattern-not-variable-reference=
action-or-expr;
binding-pattern-not-variable-reference := wildcard-binding-pattern | list-binding-pattern | mapping-binding-pattern | error-binding-pattern
A destructuring assignment is executed by evaluating the action-or-expr resulting in a value v, and then matching the binding pattern to v, causing assignments to the variables occurring in the binding pattern.
The binding-pattern has a static type implied by the static type of the variables occurring in it. The static type of action-or-expr must be a subtype of this type.
action-stmt := action ;
An action-stmt is a statement that is executed by evaluating an action and discarding the resulting value, which must be nil. It is a compile-time error if the static type of an action in an action-stmt is not nil.
call-stmt := call-expr;
call-expr := function-call-expr | method-call-expr |check
call-expr |trap
call-expr
A call-stmt is executed by evaluating call-expr as an expression and discarding the resulting value, which must be nil. It is a compile-time error if the static type of the call-expr in an call-stmt is not nil.
remote-method-call-action := [start
] expression->
method-name(
arg-list)
Calls a remote method. This works the same as a method call expression, except
that it is used only for a method with the remote
modifier.
If start
is specified, then the called method is executed on a new
strand, and the result of the remote-method-call-expr
is a value of type
future<T>, where T is the return type of the method called.
if-else-stmt :=if
expression block-stmt [else
if
expression block-stmt ]* [else
block-stmt ]
The if-else statement is used for conditional execution.
The static type of the expression following if
must be boolean.
When an expression is true then the corresponding block statement is executed
and the if statement completes. If no expression is true then, if the else block
is present, the corresponding block statement is executed.
match-stmt :=match
action-or-expr{
match-clause+}
match-clause := match-pattern-list [match-guard]=>
block-stmt match-guard :=if
expression
A match statement selects a block statement to execute based on which patterns a value matches.
A match-stmt is executed as follows:
The scope of any variables created in a match-pattern-list of a match-clause is both the match-guard, if any, and the block-stmt in that match-clause. The static type of the expression in match-guard must be a subtype of boolean.
match-pattern-list :=
match-pattern (|
match-pattern)*
A match-pattern-list can be matched against a value. An attempted match can succeed or fail. A match-pattern-list is matched against a value by attempting to match each match-pattern until a match succeeds.
All the match-patterns in a given match-pattern-list must bind the same set of variables.
match-pattern :=
var
binding-pattern
| wildcard-match-pattern
| const-pattern
| list-match-pattern
| mapping-match-pattern
| error-match-pattern
A match-pattern combines the destructuring done by a binding-pattern with the ability to match a constant value.
Note that an identifier can be interpreted in two different ways within a match-pattern:
var
, an identifier names a variable which is
to be bound to a part of the matched value when a pattern match succeeds;A match-pattern must be linear: a variable that is to be bound cannot occur more than once in a match-pattern.
const-pattern := simple-const-expr
A const-pattern denotes a single value. Matching a const-pattern against a value succeeds if the value has the same shape as the value denoted by the const-pattern. A variable-reference in a const-pattern must refer to a constant. Successfully matching a const-pattern does not cause any variables to be created.
Matching a wildcard-match-pattern against a value succeeds if the value belongs to type any, in other words if the basic type of the value is not error.
wildcard-match-pattern :=_
list-match-pattern :=[
list-member-match-patterns]
list-member-match-patterns := match-pattern (,
match-pattern)* [,
rest-match-pattern] | [ rest-match-pattern ] mapping-match-pattern :={
field-match-patterns}
field-match-patterns := field-match-pattern (,
field-match-pattern)* [,
rest-match-pattern] | [ rest-match-pattern ] field-match-pattern := field-name:
match-pattern rest-match-pattern :=...
var
variable-name error-match-pattern :=error
(
error-arg-list-match-pattern)
error-arg-list-match-pattern := simple-match-pattern (,
named-arg-match-pattern)* [,
rest-match-pattern] | named-arg-match-pattern (,
named-arg-match-pattern)* [,
rest-match-pattern] | [ rest-match-pattern] simple-match-pattern := wildcard-match-pattern | const-pattern |var
variable-name named-arg-match-pattern := arg-name=
match-pattern
Matching a mapping-match-pattern
against a mapping value succeeds
if and only every field-match-pattern
matches against a field of
the value. The variable in the rest-match-pattern
, if specified, is
bound to a new mapping that contains just the fields for which that did not
match a field-match-pattern
.
foreach-stmt :=foreach
typed-binding-patternin
action-or-expr block-stmt
A foreach statement iterates over a sequence, executing a block statement once for each member of the sequence.
The scope of any variables created in typed-binding-pattern is block-stmt. These variables are implicitly final.
In more detail, a foreach statement executes as follows:
In step 2, the compiler will give an error if the static type of expression is not suitable for 2a or 2b.
In step 5, the typed-binding-pattern is used unconditionally, and the compiler
will check that the static types guarantee that the match will succeed. If the
typed-binding-pattern uses var, then the type will be inferred from the type of
expression
.
while-stmt := while
expression block-stmt
A while statement repeatedly executes a block statement so long as a boolean-valued expression evaluates to true.
In more detail, a while statement executes as follows:
The static type of expression
must be a subtype of boolean.
continue-stmt :=continue
;
A continue statement is only allowed if it is lexically enclosed within a while-stmt or a foreach-stmt. Executing a continue statement causes execution of the nearest enclosing while-stmt or foreach-stmt to jump to the end of the outermost block-stmt in the while-stmt or foreach-stmt.
break-stmt :=break
;
A break statement is only allowed if it is lexically enclosed within a while-stmt or a foreach-stmt. Executing a break statement causes the nearest enclosing while-stmt or foreach-stmt to terminate.
lock-stmt := lock
block-stmt
A lock statement is used to execute a series of assignment statements in a serialized manner. For each variable that is used as an L-value within the block statement, this statement attempts to first acquire a lock and the entire statement executes only after acquiring all the locks. If a lock acquisition fails after some have already been acquired then all acquired locks are released and the process starts again.
Note The design of shared data access is likely to change in a future version.
panic-stmt :=panic
expression;
A panic statement terminates the current worker abnormally. The result of
evaluating expression
provides the termination value of the worker.
The static type of expression
must be a subtype of error.
return-stmt :=return
[ action-or-expr ];
A return statement terminates the current worker normally.The result of evaluating the action-or-expr provides the termination value of the worker. If action-or-expr is omitted, then the termination value is nil.
The following built-in methods are available.
The following methods are provided on a variety of different basic types, with a consistent semantics.
The length method returns an integer representing the number of items that a value contains, where the meaning of item depends on the basic type of value. The following list specifies the basic types for which it is provided, and the meaning on each basic type:
The iterator method is provided for all iterable basic types. It returns a new object belonging to the Iterator<T> abstract type, where T depends on the basic type as follows:
The freeze, clone and unfrozenClone built-in methods work in a similar way. They are provided on all structural types other than error, and all basic types other than nil. When applied to a value v, the method starts by checking whether v is a pure value; if it is not, the method returns an error. (If v belongs to type anydata, then it is guaranteed that v is a pure value.) Otherwise, the method returns Freeze(v), Clone(v) or UnfrozenClone(v).
The static type of the return type of applying one of these methods to an expression of type T is T if T is a subtype of anydata, and otherwise is T|error.
The isFrozen method is provided on all structural types other than error. When applied to a value v, it returns true if the value is frozen and false otherwise.
The following built-in methods are provided on both the decimal and float basic types. They all return boolean.
The following built-in methods are provided on the error basic type:
The following built-in method is provided on the function basic type:
f.call(args)
means the same as f(args)
; this
allows f to be an arbitrary expressionThe stamp and convert methods are provided on typedesc values. They both take a single parameter of type anydata, and return a value of type T|error where T is the type denoted by the typedesc value.
The stamp method takes a single argument of type anydata.
t.stamp(v) works as follows
The convert method takes a single argument of type anydata. It is similar to stamp, but it does not modify its argument.
t.convert(v) works as follows:
Each source part in a Ballerina module must match the production
module-part
.
The import declarations must come before other declarations; apart from this, the order of the definitions and declarations at the top-level of a module is not significant.
module-part := import-decl* other-decl* other-decl := metadata other-decl-item other-decl-item := module-type-defn | module-const-decl | module-var-decl | listener-decl | function-defn | outside-method-defn | service-decl | xmlns-decl-stmt | annotation-decl
Creating a Ballerina program from a collection of modules requires that one module is identified as the root module. The modules comprising the program consist of the root module together with transitive closure of its imported modules.
If the root module of the program has a function called main
, then
it must be public and its return type must both be a subtype of
error?
and contain nil. Any parameters must have a type that is a
subtype of anydata; they are not restricted to strings.
If the root module does not have a function called main
, then there
must be at least one service declared in at least one of the program's modules.