Primary contributors:
(Other contributors are listed in Appendix D.)
Copyright © 2018, 2019, 2020 WSO2
Licensed under the Creative Commons Attribution-NoDerivatives 4.0 International license
Language and document status
This is a draft document, previewing the next release of the specification, code-named Swan Lake.
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 ballerina-dev@googlegroups.com
mailing list.
Ballerina is a statically typed, concurrent programming language, focusing on network interaction and structured data. It is intended to be the core of a language-centric middleware platform. It has all the general-purpose functionality expected of a modern 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 middleware functionality such as event stream processing and reliable messaging; this is described in more detail in Appendix C.
Second, its abstractions and syntax for concurrency and network interaction have been designed so that there is a close correspondence with sequence diagrams. This enables a bidirectional mapping for any Ballerina function between its textual representation in the syntax described in this specification and its graphical representation as a sequence diagram, such that the sequence diagram fully shows the aspects of the behavior of that function that relate to concurrency and network interaction.
Third, it has a type system that is more flexible and allows for looser coupling than 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 tabular 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# and JavaScript. It also gets ideas and inspiration from many other existing programming languages including TypeScript, Go, Rust, D, Kotlin, Swift, Python and Perl.
The Ballerina language has been designed in conjunction with the Ballerina platform, which provides comprehensive support for a module-based software development model, including versioning, dependency management, testing, documentation, building and sharing. Modules are organized into repositories; there is a globally-shared, central repository, but repositories can also be local.
The Ballerina language includes a small library, the lang library, which provides fundamental operations on the data types defined by the language; the lang library is defined by this specification. The Ballerina platform includes an extensive 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 source form of a module consists of an ordered collection of one or more source parts; each source part is a sequence of bytes that is the UTF-8 encoding of part of the source code for the module. The format of a source part is defined by this specification. The format of a binary module is specified by the Ballerina platform.
A source module can reference other modules. Each source module can be
separately compiled into a binary module: compilation of a source module needs
access only to the binary form of other modules referenced from the source
module. A source module identifies each module that it references using an
organization name and a module name, which is divided into one or more parts.
Both the organization name and each part of the module name are Unicode strings.
Any organization name starting with the string ballerina
is
reserved for use by the Ballerina platform.
The Ballerina platform defines a packaging system for Ballerina modules, which allows one or more modules to be combined into a package. The packaging system treats the first part of each module's name as being a package name. All the modules combined into a package share the same package name. Packages have both a source and a binary format. The source format stores the source form of a package's modules in a hierarchical filesystem. The binary format stores the binary form of a package's module as a sequence of bytes.
Binary packages can be stored in a package repository. Packages are versioned; versions are semantic, as described in the SemVer specification. A package repository can store multiple versions of the same package. Thus, within a repository, binary packages are organized into a three-level hierarchy:
The source format of a package includes a Ballerina.toml
file that
allows control over the package versions used for referenced modules.
The packaging system also allows control over which modules are exported from a package; modules that are not exported from a package are visible only to modules within the package.
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 := UnquotedIdentifier | QuotedIdentifier UnquotedIdentifier := (IdentifierInitialChar | IdentifierEscape) (IdentifierFollowingChar | IdentifierEscape)* QuotedIdentifier :='
(IdentifierFollowingChar | IdentifierEscape)+ IdentifierInitialChar := AsciiLetter |_
| UnicodeIdentifierChar IdentifierFollowingChar := IdentifierInitialChar | Digit IdentifierEscape := IdentifierSingleEscape | NumericEscape IdentifierSingleEscape :=\
^ ( AsciiLetter | 0x9 | 0xA | 0xD | UnicodePatternWhiteSpaceChar ) NumericEscape :=\u{
CodePoint}
CodePoint := HexDigit+ AsciiLetter :=A
..Z
|a
..z
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.
The QuotedIdentifier
syntax allows a reserved keyword
K
can be used as an identifier by preceding it with a
single quote i.e. 'K
. The IdentifierEscape
syntax allows an arbitrary non-empty string to be treated as an identifier. In a
NumericEscape
, 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.
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 four kinds, each corresponding to a kind of basic type:
There is a fundamental distinction between values that have a storage identity and values that do not. A value that has storage identity has an identity that comes from the location where the value is stored. All structural and behavioural values have a storage identity, whereas all simple values do not. Storage identity for sequence values is more complicated and will be explained in the section on sequence values.
Values can be stored in variables or as members of structures or in constituents of sequences. When a value has no storage identity, it can be stored directly in the variable, structure or sequence. However, when a value has storage identity, what is stored in the variable, structure or sequence is a reference to the location where the value is stored rather than the value itself. Storage identity allows values in Ballerina to represent not just trees but graphs.
Ballerina provides the ability to test whether two values have the same storage identity, but does not expose the specific storage location of a value. For values with storage identity, there is the concept of creating a new value: this means creating a value that has a storage identity that is different from any existing value. For values with storage identity, there is also the concept of copying: it means to create a value that is the same, except for having a new storage identity. The concept of having storage identity is similar to the concept of a reference type in some other programming languages, but also accomodates the concept of a sequence value.
Ballerina programs use types to categorize values both at compile-time and runtime. Types deal with an abstraction of values that does not consider storage identity. 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. The shape of the values contained in a structured value are part of the shape of the structured value. Since shapes do not deal with storage identity, they represent trees rather graphs. For simple values, there is no difference between a shape and a value, with the exception of floating point values where the shape does not consider representation details that do not affect the mathematical value being represented.
A value is plain data if it is a simple value, a sequence value or a structured value that does not contain a behavioral value at any depth. More precisely, a value is defined to be plain data if it is
Plain data values can in general contain cycles of references, but in some contexts are restricted to be acyclic. Plain data values, including values with cycles, can be compared for equality.
The two most important kinds of behavioural values are functions and objects. A function value can be executed by calling it; when a function is called, it is passed values and arguments and returns a value. An object encapsulates data with functions that operate on the data: an object's members are divided into fields, which hold the data, and methods, which are the functions that operate on the data.
There are two kinds of things that can be mutated in Ballerina: variables and values. Mutation of values is tied to storage identity: mutation is only possible for values with storage identity. When a value stored in some storage location is mutated, the change will be visible through all variables referring to the value in that location. But not all values with storage identity can be mutated: a value may not support mutation even though it has a storage identity.
The possibility of mutation gives rise to two relations between a value and a type:
If a value cannot be mutated, 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 hold 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.
Every value has a read-only bit. If the read-only bit is on, it means that the value is immutable. A value's read-only bit is fixed when the value is constructed, and cannot be changed thereafter. Ballerina maintains the invariant that immutability is deep: any value reachable from a value with its read-only bit set is guaranteed to have its read-only bit set. Here reachable means reachable through read operations: every member of a structure value is reachable from the structure value; every constituent of a sequence value is reachable from the sequence value; every member of an object value is reachable from the object value. Reachability is transitive: if t is reachable from s, and s is reachable from r, then t is reachable from r. Reachability is also considered reflexive: a value is reachable from itself.
Some basic types are inherently immutable: the read-only bit is always on for a value that belongs to an inherently immutable basic type. All simple types are inherently immutable as are functions. Some basic types are selectively immutable: a type is selectively immutable if it is possible to construct both values of the type that have the read-only bit on and values that do not have the read-only bit on. All structured types are selectively immutable as are objects. Finally, some basic types are inherently mutable: the read-only bit is never on for a value belonging to an inherently mutable basic type.
It is also possible to limit the mutability of variables, by making them final. This means that the value that a variable holds cannot be changed after the variable has been initialized. Unlike immutability of variables, this is not deep. A final variable can hold a mutable value.
There are three possible operations on storage: read, write and execute. The concept of immutability relates to reading and writing storage, and provides limited information about execution: execution cannot lead to mutation of an immutable value. For functions and objects, it is useful to have more information about how execution may lead to mutation. Ballerina has a concept of isolation that provides this.
In addition to defining when a value is reachable from a value, we can define when a value is reachable from a variable: a value is reachable from a variable if the value is reachable from the value that the variable holds. We can also define when mutable state is reachable from a value or variable: the mutable state of a value v is reachable from a value or variable, if v is reachable from the value or variable; the mutable state of a variable v is reachable only from the variable v.
Objects have an isolated bit in addition to a read-only bit; an object value is isolated if its isolated bit is set. Mutable state is defined to be freely reachable from a value or variable if it is reachable without following a reference to an isolated object. A variable or value is an isolated root if its mutable state is isolated from the rest of the program's mutable state: any mutable state that is freely reachable from the isolated root is reachable from outside only through the isolated root. More precisely, if some mutable state s is freely reachable from an isolated root value r, then s is not freely reachable from a variable or value that is not reachable from r except by following a reference through r; similarly, if some mutable state s is freely reachable from an isolated root variable r, then s is not freely reachable from a value that is not reachable from r and is not freely reachable from any variable other than r. A variable can also be declared to be isolated. Ballerina maintains the invariant that isolated objects and isolated variables are isolated roots. Ballerina also guarantees that any mutable state freely reachable from an isolated object or isolated variable is accessed only within the scope of a lock statement, which ensures that there is no data race in accessing that mutable state. This implies that there will be no data race accessing any mutable state that is reachable (not just freely reachable) from an isolated object or isolated variable.
Functions and methods have an isolated bit in addition to a read-only bit; a function or method is isolated if its isolated bit is set. Ballerina guarantees that a call an isolated method or function will only result in access to mutable state if at least one of the following conditions applies:
The caller of a function can thus ensure that a function call will not lead to a data race by ensuring that no data race is possible for the mutable state freely reachable from the arguments that it passes to the function, for example by passing only immutable values or isolated objects.
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 a 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.
The following table summarizes the type descriptors provided by Ballerina.
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 | decimal floating point numbers | |
basic, sequence | string | a sequence of Unicode scalar values |
XML | a sequence of zero or more elements, processing instructions, comments or text items | |
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 | a ordered collection of mappings, where a mapping is uniquely identified within the table by a key derived from the mapping | |
basic, behavioral | 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 |
function | a function with 0 or more specified parameter types and a single return type | |
future | a value to be returned by a function execution | |
object | a combination of named fields and named methods | |
typedesc | a type descriptor | |
handle | reference to externally managed storage | |
stream | a sequence of values that can be generated lazily | |
other | singleton | a single value described by a literal |
readonly | any value whose read-only bit is on | |
any | any value other than an error | |
never | no value | |
optional | a value that is either () or belongs to a type | |
union | a value that belongs to at least one of a number of types | |
intersection | a value that belongs to all of a number of types | |
distinct | ||
anydata | plain data (a simple value, sequence value or structured value that does not contain behavioral members at any depth) | |
json | the union of (), int, float, decimal, string, and maps and arrays whose values are, recursively, json | |
byte | int in the range 0 to 255 inclusive |
A shape is divided into two aspects: the primary aspect and the read-only aspect. A value's read-only bit is a part of the read-only aspect of the value's shape. The read-only bit of values contained in a structured value is part of the read-only aspect of those values and of the read-only aspect of the structured value. Everything about a shape except the read-only bits constitutes the primary aspect of the shape. If two plain data values compare equal, then the primary aspect of their shapes is the same, but there may be differences in the read-only aspect.
Type descriptors other than readonly
describes types using only the
primary aspect of shape: whether a value belongs to the type is not affected by
the read-only aspect of the value's shape. The readonly
type uses
only the read-only aspect: whether a value belongs to the readonly
type depends only on the read-only aspect of the value's shape.
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.
type-descriptor := simple-type-descriptor | sequence-type-descriptor | structured-type-descriptor | behavioral-type-descriptor | other-type-descriptor
For simplicity, the type-descriptor grammar is ambiguous. The following table shows the various types of type descriptor in decreasing order of precedence, together with associativity.
Operator | Associativity |
distinct T |
|
T[] |
|
T1 & T2 |
left |
T1 | T2 |
left |
function(args) returns T |
right |
Ballerina has a feature, called distinct types, which provides functionality similar to that provided by nominal types, but which works within Ballerina's structural type system. Distinct types are similar to the branded types found in some other structurally typed languages, such as Modula-3.
The semantics of distinct types are based on type-ids. These are similar to the
brands used by branded types. A distinct type is created by the use of the
distinct
keyword in either a distinct-type-descriptor or the
class-type-quals
of a module-class-defn
. Each such
occurrence of the distinct
keyword has a distinct type-id, which
uniquely identifies it within a Ballerina program. A type-id has three parts:
distinct
within the module; this takes one of two forms:
distinct
keyword is part of a distinct-type-descriptor
that is the only distinct-type-descriptor occurring within a module-type-defn,
then the local id is the name of the type defined by the module-type-defn;distinct
keyword is part of the class-type-quals
of a module-class-defn, then the local id is the name of the class defined
by the module-class-defn;Distinct types can be used with only the object or error basic types. An object value or error value has a set of type-ids. These type-ids are fixed at the time of construction and are immutable thereafter. A value's set of type-ids may be empty. The type-ids of a value are part of the value's shape and so can affect when an object belongs to a type. The set of type-ids of an object or error value are divided into primary type-ids and secondary type-ids: the secondary type-ids could be inferred from the primary type-ids using the program's source.
An object or error value is always constructed using a specific type descriptor. A type descriptor for objects and errors thus performs a dual role: it denotes a type and it defines a mechanism to construct a value of the type. A type descriptor is definite if it induces a specific set of type-ids. The set of type-ids of an object or error value are those induced by the type-descriptor used to construct it; such a type descriptor must therefore be definite. A type descriptor that denotes a type that does not allow object or error values induces an empty set of type-ids and so is vacuously definite. For other type descriptors, the section that specifies that type descriptor will say when it is definite, the set of type-ids that it induces when it is, and which of those are primary.
Values of some basic types are iterable. An iterable value supports an iteration operation, which treats the iterable value as consisting of a sequence of zero or more simpler values, which are in some sense a part of the iterable value; the iteration operation provides the values in the sequence, one after another. The sequence of values that an iteration operation on a value provides is the iteration sequence of the value. Each iterable basic type defines the iteration sequence for a value of that basic type. There is also a value associated with the completion of the iteration operation, which is nil if the iteration completed successfully and an error otherwise. The iteration operation thus determines two associated types for an iterable type: the value type, which is the type of the values in the iteration sequence, and the completion type, which is the type of the iteration completion value.
The following tables summarizes the iterable basic types.
Basic type | Iteration sequence | Type descriptor | Value type | Completion type |
---|---|---|---|---|
string | length 1 substrings | string |
string:Char |
() |
xml | singleton xml values | xml<T> |
T |
() |
list | members in order | T[] |
T |
() |
mapping | members | map<T> |
T |
() |
table | members in order | table<T> |
T |
() |
stream | items | stream<T,C> |
T |
C |
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.
All simple basic types are inherently immutable.
simple-type-descriptor := nil-type-descriptor | boolean-type-descriptor | int-type-descriptor | floating-point-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
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).
The byte
type is a subtype of
int
. The lang.int
lang library module are also
provides built-in subtypes for signed and
unsigned integers representable in 8, 16 and 32 bits.
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
The float type corresponds to IEEE 754-2008 64-bit binary (radix 2) floating point numbers. A float value can be represented by either a DecimalFloatingPointNumber with an optional FloatTypeSuffix, or by a HexFloatingPointLiteral.
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 float values must be performed using a rounding-direction attribute of roundTiesToEven (which is the default IEEE rounding direction, sometimes called round to nearest). All float values, including the intermediate results of expressions, must use the value space defined for the float 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.)
The decimal type corresponds to a subset of IEEE 754-2008 128-bit decimal (radix 10) floating point numbers. Any decimal value can be represented by a DecimalFloatingPointNumber with an optional DecimalTypeSuffix.
A decimal value is a triple (s, c, e) where
representing the mathematical value -1s × c × 10e. The range for the exponent e is implementation dependent, but must be at least the range supported by the IEEE 754-2008 decimal128 format (which is -6176 to 6111 inclusive).
The decimal type corresponds to the ANSI X3.274 subset of IEEE 754-2008, which has the following simplifications:
Operations on the decimal type use the roundTiesToEven rounding mode, like the float type.
The shape of a decimal value is its mathematical value. Thus two decimal values have the same shape if they represent the same mathematical value, even if they do so using different exponents.
sequence-type-descriptor := string-type-descriptor | xml-type-descriptor
A sequence value belongs to one of the following two basic types:
A sequence value consists of an ordered sequence of zero or more constituent items, where the constituent items belong to the same basic type as the sequence value itself. The length of a sequence value is the number of its constituent items. Each constituent of a sequence value has an integer index ≥ 0 and < length. A sequence value is a singleton if its length is 1. For each sequence basic type, there is an empty value, which has length 0. As with other basic types, the sequence basic types are disjoint with themselves and with other basic types. Thus the empty value for string is distinct from the empty value for xml, and these are both distinct from nil.
The values belonging to a sequence basic type B can be defined in terms of its singleton values and a concatenation operation, by the following rules:
The concatenation of any value v belonging to B with the empty sequence of B in either order is v.
Note that for a sequence consisting of a single item v is the same thing as v. A single item is a sequence. The type of the constituent items of a sequence of basic type B is thus a subtype of B. This is a fundamental difference between sequences and lists.
Only singleton values of a sequence type can have storage identity. When a constituent of a sequence value has storage identity, what is stored in the sequence value is a reference to the location where the constituent value is stored rather than the constituent value itself.
A sequence value is iterable: the iteration sequence consists of the singleton items of the sequence value in order and the iteration completion value is always nil.
string-type-descriptor :=string
string-literal := DoubleQuotedStringLiteral DoubleQuotedStringLiteral :="
(StringChar | StringEscape)*"
StringChar := ^ ( 0xA | 0xD |\
|"
) StringEscape := StringSingleEscape | NumericEscape StringSingleEscape :=\t
|\n
|\r
|\\
|\"
A string is an sequence of zero or more Unicode characters. More precisely, it is a sequence whose singleton values represent Unicode scalar values, where a Unicode scalar value is any code point in the Unicode range of 0x0 to 0x10FFFF inclusive, other than surrogate code points, which are 0xD800 to 0xDFFF inclusive. Note that a string may include Unicode noncharacters, such as 0xFFFE and 0xFFFF.
String values do not have storage identity and so the string basic type is inherently immutable.
There is a built-in subtype
string:Char
for single character strings.
An xml value is a sequence representing parsed XML, such as occurs in the content of an XML element. The singleton values are of the following types:
The element, processing instruction and comment singletons correspond directly
to information items in the XML Information Set. A text singleton corresponds to
one or more character information items. When an xml value is constructed,
consecutive text singletons are merged, so that an xml value never contains
consecutive text singletons. There are built-in
subtypes xml:Element
, xml:ProcessingInstruction
,
xml:Comment
and xml:Text
corresponding to the above
singletons; xml:Text
also allows the empty xml value.
xml-type-descriptor :=xml
[type-parameter] type-parameter :=<
type-descriptor>
A shape belongs to type xml
if its basic type is xml
.
A type parameter of an xml-type-descriptor must be a subtype of
xml
. A shape belongs to type xml<T>
if all of
its constituent items belong to T
. So, for example,
xml<xml:Element>
is the type for xml values containing
only elements. Note that xml<xml<T>>
is the
same as xml<T>
and that
xml<xml:Text>
is the same as xml:Text
.
The name of an element is represented by a string. The attributes of an element
are represented by a value of type map<string>
. The children
of an element is represented by a value of type xml
.
Singleton element, processing instruction and comment values have storage identity. Other xml values do not.
The xml:Text
type is inherently immutable. This implies that both
text singletons and empty xml values always have their read-only bits on. The
xml:Element
, xml:ProcessingInstruction
and
xml:Comment
types are selectively immutable. The read-only bit of a
xml value with length greater than one is on if and only if the read-only bit of
all its constituent items is on. The attributes and children of an element are
reachable from the element. Thus, if the read-only bit of an
xml:Element
is on, then the read-only bits of the mapping
representing its attributes and of the xml value representing its children are
also on.
Note that although the mutable constituents of mutable xml value can be mutated, the number and the storage identity of the constituents of a xml value are fixed when the value is constructed. The storage identity of the attributes map of an element are also fixed when the element is constructed.
The name of an element or attribute, which in the XML Information Set is represented by a combination of the [namespace name] and [local name] properties of an element information item (EII) or attribute information item (AII), is represented by a single expanded name string. If the [namespace name] property has no value, then the expanded name consists of just the value of the [local name] property; otherwise, the expanded name is of the form:
{namespace-uri}local-name
where namespace-uri
and
local-name
are the values of the [namespace name] and
[local name] properties respectively.
The attributes map for an element includes not only an entry for each AII in the
[attributes] property of the EII, but also an entry for each attribute in the
[namespace attributes] property. The key of the entry is the string representing
the name of the attribute, constructed from the AII item as described in the
previous paragraph. The name of every namespace attribute will thus start with
the string {http://www.w3.org/2000/xmlns/}
.
The attributes map can also contain entries representing namespace attributes
synthesized from the [in-scope namespaces] property. There will be a synthesized
namespace attribute for every prefix other than xml
that occurs as
a prefix of the EII or of an AII in the element's [attributes] property and for
which there is no declaration in the [namespace attributes] property. No
namespace attribute will be synthesized for the default namespace. (The
synthesized namespace attributes ensure that namespace prefixes will not be lost
if the element is extracted into a new context.)
An xml value can be converted to an XML information set for serialization. This is done in the context of a set of namespace declarations that are in-scope from the xml value's parent element, if any. The process of converting an xml element singleton into an EII has the following stages.
http://www.w3.org/XML/1998/namespace
, then use a prefix of
xml
. If there is already a namespace declaration in the [namespace
attributes] that declares a prefix with that namespace name, then that prefix is
used. Otherwise if there is a namespace declaration in the in-scope namespaces
that declares a prefix with that namespace and it is not redeclared or
undeclared by the [namespace attributes], then that prefix is used. Otherwise
generate a prefix and add an AII to the [namespace attributes] to declare
it.xmlns=""
AII is added to the
[namespace attributes] property to undeclare the default namespace.Structured values are containers for other values, which are called their members. There are three basic types of structured value: list, mapping and table.
Structured values are usually mutable. Mutating a structured value changes which values it contains. Structured values can also be constructed as immutable. Immutability is deep: immutable structured values cannot contain mutable structured values; if the read-only bit of a structured value is on, then the read-only bit of each of its members is on.
The shape of the members of a structured value contributes to the shape of the structured value. A structured type descriptor describe the shape of the structured value in terms of the shapes of its members. Mutating a member of a structured value can cause the shape of the structured value to change. A structured value has an inherent type, which is a type descriptor which is part of the structured value's runtime value. At runtime, the structured value prevents any mutation that might lead to the structured value having a shape that is not a member of its inherent type. Thus a structured value belongs to a type if and only if its inherent type is a subtype of that type.
The inherent type of an immutable structured value is a singleton type with the structured value's shape as its single member. Thus, an immutable structured value belongs to a type if and only if the type contains the shape of the value.
Every structured value has a length, which is the number of its members. All structured values are iterable: the iteration sequence consists of the members of the structure and the completion type is always nil.
A structured value provides random access to its members using a key that uniquely identifies each member within the structure. A key can be out-of-line, meaning it is independent of the member, or in-line, meaning it is part of the member. The member type for a key type K in a structured type T consists of all shapes v such that there is a shape in T with key in K and shape v. A type K is an optional key type for T if there is a shape v in T and a key k in K such that v does not have a member k; a type that is not an optional key type is a required key type.
structured-type-descriptor := list-type-descriptor | mapping-type-descriptor | table-type-descriptor
The following table summarizes the type descriptors for structured types.
Structured type | list | mapping | table |
Key source | out-of-line | out-of-line | in-line |
Key type | integer | string | anydata |
Type descriptor with uniform member type | array | map | table |
Type descriptor with separate member types | tuple | record | - |
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. The key for a member of a list is the integer index representing its position in the list, with the index of the first member being 0. 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: the iteration sequence consists of the members of the list in order and the iteration completion value is always nil.
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.
The type of the values in the iteration sequence of a value belonging
T[]
is 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.
Each field also has a read-only bit. This is in addition to the read-only bit of the mapping value. If a mapping value's field has its read-only bit on, then that field cannot be assigned to nor removed. If the mapping value's read-only bit is on, then the read-only bit of every field is also on. A field's read-only bit is fixed when the mapping value is constructed, and cannot be changed thereafter. If a field's read-only bit is on, the read-only bit of the value of the field is also on. The read-only bit of a field is part of the read-only aspect of the mapping value's shape.
A mapping is iterable: the iteration sequence consists of the members of the mapping and the iteration completion value is always nil. The order of the iteration 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
A type map<T>
contains a mapping shape m if every field
shape in m has a value shape that is in T
.
The type of the values in the iteration sequence of a value belonging
map<T>
is T
.
If a type descriptor T has lax static typing,
then the type map<T>
also has lax static typing.
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-inclusion individual-field-descriptor := metadata [readonly
] type-descriptor field-name [?
| (=
default-expression)];
field-name := identifier default-expression := expression record-type-inclusion :=*
type-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.
If an individual-field-descriptor
specifies readonly
,
then there is also a constraint that the field has its read-only bit set.
Furthermore, the type of the field is the intersection of readonly
and the type specified by the type-descriptor.
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, provided that the values of all other fields are plain data. A type
descriptor record { F };
is thus equivalent to record {| F;
anydata...; |}
, where anydata
is defined below as the type descriptor for plain data.
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.
If a record type descriptor is closed and every individual-type-descriptor
specifies readonly
, then it describes a type that is a subtype of
readonly
: a shape belongs to the type only if its read-only bit is
set.
A default-expression
is an expression that 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 expression must meet the requirements for an isolated function. 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-inclusion
includes fields from a named record type.
The type-reference
must reference a type described by a
record-type-descriptor
. The field-descriptor
s and any
record-rest-descriptor
are included 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. An
individual-field-descriptor
in a
record-type-descriptor
can override an
individual-field-descriptor
of the same name in an included
record-type-descriptor
, provided the type declared for the field in
the overriding field descriptor is a subtype of the type declared in the
overridden field descriptor. It is an error for an
record-type-descriptor
to directly or indirectly include itself. A
record-rest-descriptor
in the including type overrides any
record-rest-descriptor
in the included type. For the purposes of
resolving a record-type-reference
, a including or included 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 a structural value, whose members are mapping values. A table provides access to its members using a key that comes from the read-only fields of the member. It keeps its members in order, but does not provide random access to a member using its position in this order.
Every table value has, in addition to its members, a key sequence, which is used to provide keyed access to its members. The key sequence is an ordered sequence of field names. The key sequence of a table is fixed when a table is constructed and cannot be changed thereafter. For each field name in the key sequence, every member of the table must have a read-only field with that name and the value of the field must be acyclic plain data. A table's key sequence determines a key value for each member of the table: if the key sequence consists of a single field name f, then the key value of a member is that value of field f of that member. If the key sequence consists of multiple field names f1,f2,...,fn where n is ≥ 2, then the key value for a member r is a tuple with members v1,v2,...,vn where vi is the value of field fi of r. A table constrains its membership so that a key value uniquely identifies a member within the table. More precisely, for every two rows ri and rj in a table with i not equal to j, the key value for ri must not be equal to the key value for rj. Key values are compared for equality using the DeepEquals abstract operation. This constraint is enforced by the table both when the table is constructed and when the table is mutated. As a special case, a table's key sequence may be empty; this represents a keyless table, whose members are not uniquely identified by a key.
The shape of a table value is a triple consisting of
table-type-descriptor :=table
row-type-parameter [key-constraint] row-type-parameter := type-parameter key-constraint := key-specifier | key-type-constraint key-specifier :=key
(
[ field-name (,
field-name)* ])
key-type-constraint :=key
type-parameter
The row-type-parameter specifies the shape of the table's members. A table type
table<R> KC
contains a table shape if
and only if every table member shape belongs to R
and
the table shape satisfies the key constraint KC
. The
type specified by a row-type-parameter must be a subtype of
map<any|error>
In a table-type-descriptor table<R>
key(ks)
, R
and
ks
must be consistent in the following sense: for each
field name fi in ks
, fi must be a
required, read-only field of R
with a type that is a
subtype of anydata
. A table shape satisfies a key-constraint
key(ks)
if and only if its key sequence is
ks
. A table shape satisfies a key-constraint
key<K>
if and and only if its set of key value shapes are a
subset of K
. The shape of a keyless table satisfies the
key-constraint key<never>
.
As with other structured values, a table value has an inherent type. The inherent type of a table value must be a table-type-descriptor with a key-constraint that is a key-specifier.
A table is iterable: the iteration sequence consists of the members of the table in order and the iteration completion value is always nil.
behavioral-type-descriptor := error-type-descriptor | function-type-descriptor | object-type-descriptor | future-type-descriptor | typedesc-type-descriptor | handle-type-descriptor | stream-type-descriptor
error-type-descriptor :=error
[error-type-param] error-type-param :=<
(detail-type-descriptor | inferred-type-descriptor)>
detail-type-descriptor := type-descriptor inferred-type-descriptor :=*
An error value provides information about an error that has occurred. Error values belong to a separate basic type; this makes it possible for language constructs to handle errors differently from other values.
The error type is inherently immutable. An error value contains the following information:
The detail mapping must be a subtype of map<Cloneable>
.
The shapes of the message, cause and detail record are part of the shape of the
error; the stack trace is not part of the shape. A type descriptor
error<T>
contains an error shape if
T
contains the shape of its detail. The type
error
contains a shape if its basic type is 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.
An error-type-descriptor is always definite and induces an empty set of type-ids. An intersection type can be used to describe an error type that induces a non-empty set of type-ids.
function-type-descriptor := function-qualsfunction
function-signature | [isolated-qual]function
function-quals := transactional-qual [isolated-qual] | [isolated-qual] [transactional-qual] isolated-qual :=isolated
transactional-qual :=transactional
function-signature :=(
param-list)
return-type-descriptor
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.
param-list := required-params [,
defaultable-params] [,
included-record-params] [,
rest-param] | defaultable-params [,
included-record-params] [,
rest-param] | included-record-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-expression | inferred-typedesc-default) included-record-params := included-record-param (,
included-record-param)* included-record-param := [annots]*
type-reference [param-name] rest-param := [annots] type-descriptor...
[param-name] param-name := identifier inferred-typedesc-default :=<
>
A param-name can be omitted from a required-param, defaultable-param, included-record-param or rest-param only when occuring in the function-signature of a function-type-descriptor.
The argument list passed to a function consists of zero or more 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, defaultable-param and included-record-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; for an included-record-param, the type-descriptor is the type-reference; 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.
A defaultable-param is a parameter for which a default value is specified. An
expression can be used to specify the default value; this expression may refer
to previous parameters. Each such expression is turned into a closure that
computes the default value for the parameter using the values of previous
parameters, and this closure is part of the type descriptor for the function. It
is also possible for the default to be specified as <>
; this
means that the default value is a typedesc
value that is to be
inferred by the caller from the contextually expected type of the function call.
It is allowed only when the parameter name to which the default value applies is
referenced in the return-type-descriptor, as described below, and is allowed for
at most one parameter in a function. The caller of the function uses 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. If the
function-type-descriptor includes an isolated-qual, then an expression used as a
default-expression must meet the requirements for an isolated function.
The name of each parameter is included in the function's type descriptor. A caller of the function may specify the name of the parameter that an argument is supplying. 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 type-reference in an included-record-param must refer to a type defined by a
record-type-descriptor. Specifying an included-record-param *T
p
is similar to specifying a required-param T
p
. The difference is that the caller of the function may
specify the value for a field of an included-record-param using the field's name
as if it had been declared as a parameter. Any field of the
included-record-param not so specified is defaulted using the
record-type-descriptor. A field-descriptor is a parameter field
descriptor
of a function if it is a field descriptor of a
record-type-descriptor referenced by an included-record-param of the function.
The field names of all the parameter field descriptors whose type is not
never
must be distinct from each other and from the names of the
parameters. Whether a parameter is an included-record-param is part of the
function's type-descriptor, but does not affect the shape of the function and
thus does not affect whether a function value belongs to a function type.
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.
return-type-descriptor := [ returns
[annots] type-descriptor ]
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 value returned by a function will belong to the type specified in the
return-type-descriptor. An empty return-type-descriptor is equivalent to
returns ()
.
A return-type-descriptor may be depend on the param-list in the following way. A
type-reference occurring in a return-type-descriptor can refer to a parameter
name if the type of the parameter is a subtype of typedesc
. If p is
such a parameter, then a reference to p in the return-type-descriptor denotes
the type that is the value of p. The return-type-descriptor thus denotes a
distinct set of shapes for each invocation of the function. A function-signature
with a return-type-descriptor that uses a type-reference to refer to a parameter
name in this way is said to be dependently-typed. Functions with
dependently-types signatures can be declared and used within Ballerina, but
Ballerina does not yet provide a mechanism to define such functions.
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.
If a function-type-descriptor includes an isolated-qual
, then a
function value belongs to the type only if the value's isolated bit is set; if
the function type does not include an isolated-qual
, then whether a
function value belongs to the type is not affected by the value's isolated bit.
All function values belongs to a function-type-descriptor function
;
a function value belongs to the function-type-descriptor isolated
function
if and only if it is an isolated function.
In addition to a readonly bit and an isolated bit, a function value also has a
transactional bit. A function value with its transactional bit set can only be
called in a transactional scope; this is enforced by
the type system. A function value with its transactional bit not set belongs to
the type described by a function-type-descriptor with a function-signature only
if the function-type-descriptor does not include a transactional
qualifier. This means that a function-type-descriptor with a function-signature
but without a transactional
qualifier is a subtype of the
corresponding function-type-descriptor with a transactional
qualifier. Note that the subtyping relationship for transactional
is in the opposite direction from isolated
. A function value's
transactional bit does not affect whether it belongs to the type described by a
function-type-descriptor without a function-signature.
An object value encapsulates data with functions that operate on the data. An
object consists of named members, where each member is either a field or a
method. A field of an object stores a value. A method of an object is a function
that can be invoked on the object using a method-call-expr
; when a
method is invoked on an object, the function can access the object using the
self
variable. An object's methods are associated with the object
when the object is constructed and cannot be changed thereafter. Fields and
methods have names that uniquely identify them within their object; fields and
methods share a single symbol space; it is not possible for an object to have a
field and a method with the same name.
The type of object values can be described using an
object-type-descriptor
, as specified in this section. It can also
be described using a class. A class both describes an object type and
provides a way to construct an object belonging to the type; in particular, it
provides the method definitions that are associated with the object when it is
constructed. A class is defined using a module-level class definition.
The object basic type is selectively immutable. An object value has a read-only
bit, which is fixed when the object is constructed. An
object-type-descriptor
does not describe whether the object's
read-only bit is set: whether a value belongs to the type described by an
object-type-descriptor
is not affected by the object's read-only
bit. Whether an object is read-only can be described separately using a
readonly
type descriptor, which can be combined with an
object-type-descriptor
using the type intersection operator.
object-type-descriptor := object-type-qualsobject
{
object-member-descriptor*}
object-type-quals := isolated-qual [object-network-qual] | [object-network-qual [isolated-qual]] object-member-descriptor := object-field-descriptor | method-decl | remote-method-decl | object-type-inclusion
If an isolated-qual is specified, then an object belongs to the described type only if the object's isolated bit is set. If an isolated-qual is not specified, then an object's isolated bit does not affect whether it belongs to the described type.
Objects are the basis for Ballerina's network interaction primitives. There are two special kinds of object for network interaction: client objects and service objects. A client object supports network interaction with a remote system that is originated by the Ballerina program. A service object supports network interaction with a remote system that is originated by the remote system. An object may be a client object or a service object or neither; it cannot be both a client object and a service object. An object that is a client object or a service object is a network interaction object.
object-network-qual :=client
|service
If the object-network-qual
is client
, then an object
belongs to the type only if it is a client object. If the
object-network-qual
is service
, then an object belongs
to the type only if it is a service object. If object-network-qual
is not present, then an object belongs to the type regardless of whether it is a
client object or service object or neither. A object-network-qual
is not allowed in conjunction with public
.
Network interaction objects provide two additional features: remote methods and resources. Remote methods are supported by both client and service objects. The remote methods of an object are in a separate symbol space from the other methods of the object. Resources are supported only by service objects. Object type descriptors do not provide a way to describe the resources of a service object: whether a service object belongs to an object type is not affected by the service object's resources. Resources are described in detail in the section on object constructors.
object-field-descriptor := metadata [public
] type-descriptor field-name;
An object-field-descriptor
describes a field of the object. The
field-name
of an object-field-descriptor
must be
distinct from the field-name
of every other
object-field-descriptor
in the object-type-descriptor
and from the method-name
of every method-decl
in the
object-type-descriptor
.
If an object's read-only bit is set, then the read-only bit of the value of every field must also be set and the value of a field cannot be changed after the object has been constructed.
method-decl := metadata [public
] method-qualsfunction
method-name function-signature;
method-quals := function-quals method-name := identifier
A method-decl
describes a method of the object. The
method-name
of a method-decl
must be distinct from the
method-name
of every other method-decl
in the
object-type-descriptor
and from the field-name
of
every object-field-descriptor
in the
object-type-descriptor
. Note that here is no method overloading.
The method-name
in a method-decl
must not be
init
.
The isolated-qual
together with the function-signature
give the function type of the method. An isolated method has the same access to
the object on which the method is invoked that it has to parameters.
remote-method-decl := metadata remote-method-qualsfunction
method-name function-signature;
remote-method-quals := remote-qual function-quals | isolated-qual [transactional-qual] remote-qual | transactional-qual [isolated-qual] remote-qual | isolated-qual remote-qual transactional-qual | transactional-qual remote-qual isolated-qual remote-qual :=remote
A remote-method-decl
declares a remote method. An object can
include a remote-method-decl
only if it is a network interaction
object. A remote method is a method for network interaction: a remote method of
a client object is for outbound network interaction; a remote method of a
service object is for inbound network interaction. The method-name
of a remote-method-decl
must be distinct from the
method-name
of every other remote-method-decl
in the
object-type-descriptor
. However, a method-decl
and a
remote-method-decl
can have the same name. A remote method cannot
be called using the normal method call syntax. A remote method of a client
object can only be invoked by a client-remote-method-call-action
. A
remote method of a service object is invoked by a listener object provided by a
library module; Ballerina does not yet define a mechanism to allow such a
library module to be implemented completely in Ballerina.
Each field and method of an object has a visibility region, which is
the region of code within which the field or method is visible and can be
accessed. The visibility region of a remote-method-decl
or of
object-field-descriptor
or method-decl
that includes
public
consists of all the modules of the program. Otherwise the
visibility region is the module containing this object type descriptor; this is
called module-level visibility.
The shape of an object consists of a read-only bit, an isolated bit, a network interaction kind and an unordered collection of object field shapes and object method shapes. A network interaction kind has one of three values: service, client or empty. An object field shape or object method shape is a 4-tuple consisting of the name of the field or method, the visibility region, a remote flag 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, remote qualifier 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, a remote flag that matches 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 if an object type descriptor T has fields or methods with module-level visibility, then it is possible to define another object type descriptor that is a subtype of T only within the same module as T.
object-type-inclusion :=*
type-reference;
Every type descriptor referenced directly or indirectly by a
type-reference
in an object-type-inclusion
must be an
object-type-descriptor
, distinct-type-descriptor
,
intersection-type-descriptor
or
a class without a readonly
qualifier; the
referenced type descriptor will thus necessarily be definite and a subtype of
object. If a referenced object-type-descriptor or class has an
isolated
, service
or client
qualifier
Q, then the referencing object-type-descriptor must also have the
qualifier Q. The object-field-descriptor
s and
method-decl
s from the referenced type are included in the type
being defined. A type-reference to a class is treated as a reference to the
class's type; only the types of the methods and fields are copied from the
referenced type. An object-field-descriptor
or
method-decl
in a object-type-descriptor
can override
respectively an object-field-descriptor
or method-decl
of the same name in an included object-type-descriptor
, provided
the type declared for the field or method in the overriding descriptor is a
subtype of the type declared in the overridden descriptor. An
object-field-descriptor
cannot override a method-decl
,
nor can a method-decl
override an
object-field-descriptor
.
An object-type-descriptor is always definite. The set of type-ids induced by an object-type-descriptor is the union of the set of type-ids induced by the type descriptors that it includes. An induced type-id is primary in an object-type-descriptor if and only if it is primary in any of the included type descriptors. It is an error if the induced set of type-ids includes a non-public type-id from another module.
It is an error for an object-type-descriptor
to directly or
indirectly include itself.
future-type-descriptor := future
[type-parameter]
A future value refers to a named worker, which will return a value. A future
value belongs to a type future<T>
if the return type of the
named worker is a subtype of T.
A value belongs to a type future
(without the type-parameter)
if it has basic type future.
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.
handle-type-descriptor := handle
A handle value is a reference to storage managed externally to a Ballerina program. Handle values are useful only in conjunction with functions that have external function bodies; in particular, a new handle value can be created only by a function with an external function body. Handle values are inherently immutable.
A value belongs to a type handle
if it has a basic type of handle.
stream-type-descriptor :=stream
[stream-type-parameters] stream-type-parameters :=<
type-descriptor [,
type-descriptor]>
A stream is an object-like value that can generate a sequence of values. There
is also a value associated with the completion of the generation of the
sequence, which is either nil, indicating the generation of the sequence completed
successfully, or an error. A stream belongs to type
stream<T,C>
if the values in the generated sequence all
belong to T and if the completion value belongs to C. The type
stream<T>
is equivalent to stream<T,()>
. A
value belongs to a type stream
(without the type-parameter) if it
has basic type stream. A type stream<T,C>
where C does not
include nil describes an unbounded stream.
A stream supports two primitive operations: a next operation and a close
operation. The next operation has the same semantics as the next method on the
Iterator object type. The close operation informs the stream that there will be
no more next operations and thus allows the stream to release resources used by
the stream; the close operation on a stream<T,C>
has a result
of type C?
, where nil means that the close operation was
successful. The close operation is idempotent: performing the close operation on
a stream on which the close operation has already been performed has no effect.
When the generation of the sequence is complete, the stream automatically
performs the close operation to release resources used by the stream.
The type descriptor stream<T,C>
works like a class, in that
it can be used with new
to construct stream values that belong to
the type. The normal implementation of a stream<T,C>
is a
wrapper around an object belonging to object type
StreamImplementor<T,C>
. The next and close operations on
the stream delegate to the next and close methods on the StreamImplementor.
The stream module of the lang library provides additional operations on stream that can be implemented in terms of the primitive next and close operations.
A stream is iterable. A stream of type stream<T,C>
has value
type T
and completion type C
. Calling the next method
on the iterator created for an iteration has the same effect as performing the
next operation on the stream. The stream does not keep a copy of the sequence of
values returned by the next operation. Any subsequent iteration operation on the
same stream will not generate further values, so the iteration sequence for
iterations other than the first will be the empty sequence.
Note that in this version of Ballerina the stream type is not a class because Ballerina does not yet support parameterized classes.
other-type-descriptor := | type-reference | singleton-type-descriptor | any-type-descriptor | never-type-descriptor | readonly-type-descriptor | distinct-type-descriptor | union-type-descriptor | intersection-type-descriptor | optional-type-descriptor | anydata-type-descriptor | json-type-descriptor | byte-type-descriptor |(
type-descriptor)
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.
type-reference := identifier | qualified-identifier
A type descriptor can use a type-reference to refer to a type definition in the same module or another module.
A type-reference referring to a type descriptor T is definite if and only if T. If it is, the type-ids induced by are the same as those induced by T and a type-id is primary in t if and only if it is primary in T. It is an error if the induced set of type-ids includes a non-public type-id from another module.
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; in this case, the value will have its read-only bit set; a value without its read-only bit set will thus not belong to any singleton type.
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.
The any-type-descriptor is not definite.
never-type-descriptor := never
The type descriptor never
describes the type that does not contain
any shapes. No value ever belongs to the never
.
This can be useful to describe for the return type of a function, if the
function never returns. It can also be useful as a type parameter. For example,
xml<never>
describes the an xml type that has no
constituents, i.e. the empty xml value.
Note that for anytype T
, the type T|never
is the same
as T
.
readonly-type-descriptor := readonly
A shape belongs to the type readonly
if its read-only bit is on.
A value belonging to an inherently immutable basic type will always have its read-only bit on. These basic types are:
A value belonging to a selectively immutable basic type may have its read-only bit on. These basic types are:
distinct-type-descriptor := distinct
type-descriptor
Each occurrence of a distinct-type-descriptor describes a type that is distinct from any other occurrence of a distinct-type-descriptor. Only object and error values can belong to a type described by a distinct-type-descriptor.
The type denoted by a distinct-type-descriptor D
, where
D
is distinct T
, and
d is the type-id of this occurrence of D
,
contains a shape s of an object or error value if and only if both
T
contains s and the set of type-ids of
s contains d. The set of type-ids induced by
D
consists of d as the primary type-id and
the type-ids induced by T
as the secondary type-ids. The
type T
must be definite and must be a subtype of object
or a subtype of error. The type D
is always definite.
Note that D
is always a proper subtype of
T
.
union-type-descriptor := type-descriptor |
type-descriptor
The set of shapes denoted by an union type T1|T2
is the union of
the set of shapes denoted by T1
and the set of shapes denoted by
T2
. Thus, the type T1|T2
contains a shape if and only
if either the type denoted by T1
contains the shape or the type
denoted by T2
contains the shape. A type descriptor
T1|T2
is not a class and cannot be used with new
,
even if one or both of T1 and T2 are classes.
A union type T1|T2
is definite if and only if both T1
and T2
are definite and the set of type-ids induced by
T1
and T2
are the same. If it is definite, then it
induces the same set of type-ids as T1
and T2
, and a
type-id is primary if it is primary in either T1
or
T2
.
intersection-type-descriptor := type-descriptor &
type-descriptor
The set of shapes denoted by an intersection type is the
intersection of the set of shapes denoted by T1
and the set of
shapes denoted by T2
. Thus, the type T1&T2
contains a shape if and only if the types denoted by T1
and
T2
both contain the shape. It is a error to have an intersection
type that denotes an empty set of shapes. In an intersection type
T1&T2
, it is an error if T1
and T2
are both function types unless there is a single function-type-descriptor that
denotes a type that is a subtype of both T1
and T2
.
A type descriptor T1&T2
is not a class and cannot be used
with new
, even if one or both of T1 and T2 are classes.
An intersection type T1&T2
is definite if and only if both
T1
and T2
are definite. If it is definite, then it
induces the union of the set of type-ids induced by T1
and
T2
, and a type-id is primary if it is primary in either
T1
or T2
.
Intersection types are particularly useful in conjunction with
readonly
. A set of readonly shapes can be described by
readonly&T
, where T
describes the primary aspect
of the shape.
optional-type-descriptor := type-descriptor ?
A type T?
means the union of T
and ()
. It is completely equivalent to
T|()
.
anydata-type-descriptor := anydata
The type descriptor anydata
describes the type of plain data. The
type anydata
contains a shape if and only if it is the shape of a
value that is plain data. The anydata
type can thus be defined as:
() | boolean | int | float | decimal | string | xml | anydata[] | map<anydata> | table<map<anydata>>
json-type-descriptor := json
The json
type is designed for processing data expression in JSON
format. It is a built-in name for a union defined as follows:
type json = () | boolean | int | float | decimal | string | json[] | map<json>;
In addition, the json
type is defined to have lax static typing.
byte-type-descriptor := byte
The byte type is a predefined name for a union of the int values in the range 0
to 255 inclusive. It is equivalent to the built-in
subtype int:Unsigned8
.
There are several object types that are built-in in the sense that the language treats objects with these types specially. There are two kinds of object type:
A value of iterable type with iteration value type T and iteration completion type C provides a way of creating an iterator object that belongs to the object type
object { public next() returns record {| T value; |}|C; }
In this specification, we refer to this type as Iterator<T,C>.
Conceptually an iterator is at a position between members of the iteration 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
Mutation of a structured value during iteration is handled as follows. A call to
next()
must panic if there has been any mutation to the structured
value since the iterator was created other than the following:
In the latter case, next()
must return the value associated with
the key at the point when next()
is called.
Note that it is not possible for the next()
method simply to return
the values in the iteration sequence, since there would be no way to distinguish
a nil or error value that is part of the iteration sequence from a nil or error
value that represents the result of the iteration.
An object belongs to the object type Iterable<T,C> if it has a
method named iterator
with no arguments and a return type that is
subtype of Iterator<T,C> and it belongs to the distinct type
Iterable
defined in lang.object
. An object that
belongs to Iterable<T,C> is iterable: the object returned by the
iterator
method determines the iteration sequence and iteration
completion value.
An object belongs to the object type StreamImplementor<T,C> if it
belongs to Iterator<T,C> and also optionally has a method
close()
with return value C?
. This is equivalent to
belonging to the following type.
object { public isolated function next() returns record {| T value; |}|C; } | object { public isolated function next() returns record {| T value; |}|C; public isolated function close() returns C?; }
The close method says that there will be no more calls to the next method. Any
call to a next method after the close method has been called must result in a
panic. A missing close method behaves like a close method that puts the object
into a closed state, in which calls to next will result in a panic, and then
immediately returns ()
.
The object type Listener<T,A>, where T is a a subtype of service
object {}
and A is a subtype of string[]|string|()
, is
described by the following object type descriptor:
object { public function attach(T svc, A attachPoint) returns error?; public function detach(T svc) returns error?; public function start() returns error?; public function gracefulStop() returns error?; public function immediateStop() returns error?; }
The above methods all return an error value if an error occurred, and return nil otherwise.
A type is a listener object type if it is a subtype of the object type
Listener<T,A>, for some type T that is a subtype of service object
{}
and some type A that is a subtype of string[]|string|()
.
The RawTemplate type describes the type of object constructed by a raw template
expression. The type is defined in lang.object
as follows.
distinct object { public (readonly & string[]) strings; public (any|error)[] insertions; }
Classes of type RetryManager are defined by the application to allow control
over retries performed by the retry statement and retry transaction statement
The object type RetryManager<E>, where E is a a subtype of
error
, is described by the following object type descriptor:
object { public function shouldRetry(E e) returns boolean; }
Note that RetryManager<E> is contravariant in E i.e. RetryManager<E> is a subtype of RetryManager<E'> if and only if E' is a subtype of E.
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 with an initial upper-case letter to distinguish them from functions in the lang library.
The FillMember(s, k) operation is defined for a structured value s and an out-of-line key value k. It can be performed when s does not have a member with key k; if it succeeds, it will result in a member with key k being added to s. It will succeed if the inherent type of s allows the addition of a member with key k and there is a way to construct a filler value for the type descriptor that the inherent type of s requires for member k. The following table specifies when and how a filler value can be constructed for a type descriptor.
Type descriptor | Filler value | When available |
---|---|---|
() |
() |
|
boolean |
false |
|
int |
0 |
|
float |
+0.0f |
|
decimal |
+0.0d |
|
string |
"" |
|
array or tuple type descriptor | [] |
if that is a valid constructor for the type |
map or record type descriptor | { } |
if that is a valid constructor for the type |
table | empty table (with no rows) | |
object | new T() |
if T is a class, where T is the type descriptor for the object, and the static type of T's init method allows no arguments and does not include error |
stream | empty stream | |
xml |
xml`` |
|
built-in subtype of xml
|
xml`` |
if this belongs to the subtype, i.e. if the subtype is
xml:Text
|
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 |
|
built-in subtype of int
|
0 |
|
json |
() |
There are two cloning operations. Both of these operate on values belonging to the Cloneable type defined as follows:
public type Cloneable readonly|xml|Cloneable[]|map<Cloneable>|table<map<Cloneable>>;
This type is defined in the lang.value module of the lang library. In this document,
the type will be referred to as value:Cloneable
.
Clone(v) is defined for any value v that belongs to the type
value:Cloneable
. It performs a deep copy, recursively copying all
structural values and their members and recursively copying all sequence values
and their constituents. Clone(v) for an immutable value v returns v. If v is of
a basic type that has an inherent type, 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 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.
The Clone operation is exposed by the clone
function in the
lang.value module of the lang library.
ImmutableClone(v) is defined for any value v that belongs to the type
value:Cloneable
. It performs a deep copy of v similar to Clone(v),
except that newly constructed values will be constructed as immutable and so
have their read-only bit on. Any immutable value is not copied. So the result
of Immutable always has its read-only bit on.
Like Clone, ImmutableClone must preserve graph structure, including cycles. Conceptually the whole graph is constructed before being made immutable.
The ImmutableClone operation is exposed by the cloneReadOnly
function in the lang.value module of the lang library.
DeepEquals(v1, v2) is defined for any values v1, v2 that belong to type anydata. It returns true or false depending of whether the primary aspect of the shape v1 and of v2 are the same. In other words, DeepEquals returns true if and only if the values are the same ignoring whether read-only bits are on or off. DeepEquals(v1, v2) must terminate for any values v1 and v2 of type anydata, even if v1 or v2 have cycles. DeepEquals(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 DeepEquals(v1, v2) will be false.
The possibility of cycles means that DeepEquals cannot be implemented simply by calling DeepEquals recursively on members. Rather DeepEquals must maintain a mapping that records for each pair of references whether it is already in process of comparing those references. When a DeepEquals 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.
DeepEquals(Clone(x), x) is guaranteed to be true for any value of type anydata.
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 |
ToString(v,style) converts a value to a string in one of three styles. The conversion can be performed in one of three styles, specified by the style parameter, which are as follows.
The following table summarizes the result of ToString(v,style) for each basic type.
Basic type of v | style | ||
expression | informal | direct | |
nil |
()
|
null
|
|
true |
true
|
||
int |
2
|
||
float |
1.0
|
||
float:NaN
|
NaN
|
||
float:Infinity
|
Infinity
|
||
decimal |
1d
|
1
|
|
1.20d
|
1.20
|
||
string |
"xyz"
|
xyz
|
|
xml |
xml`<d>t</d>`
|
`<d>t</d>`
|
<d>t</d>
|
array |
[X,Y]
|
||
X, Y expression style | X,Y informal style | ||
map |
{"x":X,"y":Y}
|
||
X, Y expression style | X,Y informal style | ||
table |
table key(k) [R1,R2]
|
[R1,R2]
|
|
R1,R2 expression style | R1,R2 informal style | ||
error |
error D1&D2 (M,C,F1=V1,F2=V2)
|
||
V1, V2 expression style, D1,D2 in the form {module-id}local-id , M expression style, C expression style
|
V1, V2 informal style, just local id of D1,D2, M informal style, C informal style | ||
D1,D2 are type-ids, M is the error message, C is the error cause, F1=V1,F2=V2 are field names and values of the error detail | |||
object w/o toString method |
object D1&D2 U where U is an identifier that uniquely identifies the object (objects will have the same identifier only if they are === )
|
||
object with toString method |
object S
|
S
|
|
S in direct style where S is the result of calling the toString method |
|||
cycle detected |
...[N]
|
...
|
|
N is 0-based index down path from root object to object where cycle is detected |
There are three abstract operations related to ordering: Compare(x, y), CompareA(x, y), CompareD(x, y). CompareA(x, y) and CompareD(x, y), which are used to define sorting, return one of the values LT, EQ, GT representing the cases where x is less than, equal to or greater than y. Compare(x, y), which is used to define the relational operators, can also return the value UN to represent the case where x is unordered with respect to y; this is used to deal with NaN.
The three operations are related as follows: Compare(x, y) is the same as CompareA(x, y) and CompareD(x, y) unless Compare(x, y) is UN.
A type is ordered if all the values that belong to the type can be compared to each other using the above three abstract operations. Thus each of the abstract operations are defined when there is an ordered type to which both arguments belong.
If C is any of the three abstract operations, and there is an ordered type to which both x and y belong, then:
The most straightforward ordered types are basic types where all the values of the type can be put into an order. These types are boolean, int, string. For each of these three types, for any pair of values x, y belonging to the type
The basic type decimal is ordered. The abstract operations are defined the same as for int, except that Compare(x, y) is EQ if x and y have the same shape. In other words, the compare operations ignore precision.
The basic type float is also ordered. Compare(x,y) is as defined by IEEE 754-2008 clause 5.11. In particular:
When x and y belong to type float, CompareA(x, y) and CompareD(x, y) differ from Compare(x, y) as follows:
If type T is ordered, then type T? Is also ordered. Comparison operations involving nil are defined as follows:
The CompareA operation is used for sorting in ascending order; the CompareD operation is used in reverse for sorting in descending order. The net effect of the above rules is thus that both () and NaN will appear at the end of a sorted list, with NaN before (), in both ascending and descending order.
The following rules determine when a subtype of list is ordered:
Each of the three abstract operations C are extended to apply to lists as follows:
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-type-reference](
error-arg-list-binding-pattern)
error-type-reference := type-reference error-arg-list-binding-pattern := error-message-binding-pattern [,
error-cause-binding-pattern] [,
error-field-binding-patterns] | [error-field-binding-patterns] error-message-binding-pattern := simple-binding-pattern error-cause-binding-pattern := simple-binding-pattern | error-binding-pattern simple-binding-pattern := capture-binding-pattern | wildcard-binding-pattern error-field-binding-patterns := named-arg-binding-pattern (,
named-arg-binding-pattern)* [,
rest-binding-pattern] | rest-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 of the detail record 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 declares it. Variables are lexically scoped: every variable declaration 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; if declared
public
, it can also be referenced from outside the module.
Identifiers with module-scope are used to identify not only variables but other module-level entities such as functions. Within module-scope, identifiers are separated into three symbol spaces:
The prefix symbol space is special in that it is associated with a source part rather than a module.
Block-scope is divided into symbol spaces in the same way as module-scope, except that block-scope does not have a symbol space for annotation tags, since annotation tags cannot be declared with block-scope.
An identifier declared 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.
It is not an error if an identifier is declared with block-scope and there is already a declaration of the same identifier in the same symbol space with module-scope. In this case, the block-scope declaration will hide the module-scope declaration for the region where the block-scope declaration is in scope. However, it is a compile error if an identifier is declared with block-scope and its scope overlaps with the scope of another declaration of the same identifier in the same symbol space also with block-scope.
expression := literal | string-template-expr | xml-template-expr | raw-template-expr | structural-constructor-expr | object-constructor-expr | new-expr | variable-reference-expr | field-access-expr | optional-field-access-expr | xml-attribute-access-expr | annot-access-expr | member-access-expr | function-call-expr | method-call-expr | error-constructor-expr | anonymous-function-expr | let-expr | type-cast-expr | typeof-expr | unary-expr | multiplicative-expr | additive-expr | shift-expr | range-expr | relational-expr | is-expr | equality-expr | binary-bitwise-expr | logical-expr | conditional-expr | checking-expr | trap-expr | query-expr | xml-navigate-expr | transactional-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 |
m:x
x.k
x.@a
f(x)
x.f(y)
x[y]
new T(x)
|
|
+x
-x
~x
!x
<T> x
typeof x
check x
checkpanic x
trap x
|
|
x * y
x / y
x % y
|
left |
x + y
x - y
|
left |
x << y
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
let x = y in z
from x in y select z
|
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 time; 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 yet specified completely in this document.
In some situations it is convenient for static typing to be less strict than
normal. One such situation is when processing data in Ballerina using a static
type that is less precise than the type that the data is in fact expected to
belong to. For example, when the Ballerina json
type is used for
the processing of data in JSON format, the Ballerina static type will not
capture the constraints of the particular JSON format that is been processed.
Ballerina supports this situation through the concept of lax static typing, which has two parts: the first part is that a type descriptor can be classified as lax; the second part is that particular kinds of expression can have less strict static typing rules when the static type of a subexpression is described by a lax type descriptor. With these less strict rules, a potential type error that would have been a compile-time error according to the normal strict static typing rules would instead be allowed at compile-time and result in an error value at runtime; the effect is thus that some static type-checking is instead done dynamically.
In this version of Ballerina, only the first step has been taken towards
supporting this concept. There is a fixed set of type descriptors that are
classified as lax: specifically json
is lax, and
map<T>
is lax if T is lax. The only kinds of expression for
which lax typing rules are specified are field-access-expr,
optional-field-access-expr and checking-expr.
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 structured value.
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
.
Ballerina defines requirements for when an expression is isolated. If an expression meets the requirements, then it is compile-time guaranteed that the value of the expression will be an isolated root and will not be aliased.
If the static type of an expression is immutable, i.e. a subtype of
readonly
, or an isolated object, i.e. a subtype of isolated
object {}
, then the expression is isolated.
Also, an expression of one of the following syntactic kinds is isolated if all its subexpressions are isolated (regardless of its static type):
list-constructor-expr
table-constructor-expr
mapping-constructor-expr
xml-template-expr
raw-template-expr
type-cast-expr
checking-expr
trap-expr
conditional-expr
The section for each kind of expression will describe any alternative conditions under which that kind of expression is also isolated.
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 is only one exception: a value of type
xml:Text
can be implicitly converted to a string. If a value of
type xml:Text
is the empty xml value, then it is implicitly
converted to the empty string; otherwise the value is implicitly converted to a
string that has a constituent character for each singleton item in the
xml:Text
sequence.
const-expr := literal | string-template-expr | xml-template-expr | raw-template-expr | structural-constructor-expr | error-constructor-expr | constant-reference-expr | type-cast-expr | unary-expr | multiplicative-expr | additive-expr | shift-expr | range-expr | relational-expr | is-expr | equality-expr | binary-bitwise-expr | logical-expr | conditional-expr |(
const-expr)
A value resulting from the evaluation of a const-expr
always has
its read-only bit on.
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. Constructors called
within a const-expr
construct their values as immutable. Note that
the syntax of const-expr does not allow for the construction of error values.
The result of a const-expr
is always immutable.
simple-const-expr := nil-literal | boolean-literal | [Sign] int-literal | [Sign] floating-point-literal | string-literal | constant-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]
.
Template expressions make use of strings enclosed in backticks with interpolated expressions.
BacktickString :=`
BacktickItem* Dollar*`
BacktickItem := BacktickSafeChar | BacktickDollarsSafeChar | Dollar* interpolation interpolation :=${
expression}
BacktickSafeChar := ^ (`
|$
) BacktickDollarsSafeChar :=$
+ ^ ({
|`
|$
) Dollar :=$
string-template-expr := string
BacktickString
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 using the ToString abstract
operation with the direct style. 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-template-expr := xml
BacktickString
An XML template expression constructs 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.An xml-template-expr occurring within a const-expr will construct an xml value that has its read-only bit on.
raw-template-expr := BacktickString
A raw-template-expr constructs an object belonging to the abstract RawTemplate object type.
A raw-template-expr is evaluated by
insertions
field with value v
strings
field consisting of a read-only array of strings
containing the characters in BacktickString outside of interpolations, split at
the interpolation points; the length of the this array is one more than the
length of v.The result of the raw-template-expr is the newly constructed object.
For each raw-template-expr there is a separate class, which
is a subtype of the RawTemplate type. The objects constructed by a
raw-template-expr belong to this class. The type of the
insertions
field is determined from the contextually expected type
or the static type of the expressions in the BacktickString in the same way as
with a list-constructor-expr. The strings
and
insertions
fields will be read-only if required by the contextually
expected type. The value of the strings
field is constructed once
by the class. The value of the insertions
is
constructed once for each evaluation of the raw-template-expr.
Each basic type of structure has its own expression syntax for constructing a value of the type.
structural-constructor-expr := list-constructor-expr | table-constructor-expr | mapping-constructor-expr
An structural-constructor-expr occurring within a const-expr will construct a structural value that has its read-only bit on.
list-constructor-expr :=[
[ expr-list ]]
expr-list := expression (,
expression)*
A list-constructor-expr creates a new list value. The members of the list come from evaluating each expression in the expr-list in order.
If there is a contextually expected type, then the inherent type of the newly
created list is derived from the applicable 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.
If there is no contextually expected type, then the inherent type will be a tuple-type-descriptor with a member-type-descriptor for each expression in the expr-list; the type of each member-type-descriptor will be the broad type of the corresponding expression in the expr-list.
If there is a contextually expected type, then the type that the inherent type requires for each list member provides the contextually expected type for the expression for the member; otherwise there is no contextually expected type for the expressions for members.
A member of a list can be filled in automatically if the FillMember abstract operation would succeed on it. 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 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 := specific-field | computed-name-field | spread-field specific-field := [readonly
] (field-name | string-literal):
value-expr | [readonly
] [variable-name] value-expr := expression computed-name-field :=[
field-name-expr]
:
value-expr field-name-expr := expression spread-field :=...
expression
A mapping-constructor-expr creates a new mapping value.
A specific-field specifies a single field, where the field name is known at
compile-time. A specific-field that consists of just a variable name
x
is equivalent to a field x:
x
.
The static type of the expression in a spread-field must allow only mapping
values, i.e. must be a subtype of map<any|error>
. All the
fields of the mapping value that results from evaluating that expression are
included in the mapping value being constructed. It is a compile-time error if
the static type of the expression in a spread-field allows a field that
duplicates a specific-field or that could also occur in another spread-field.
Note that a spread-field with an inclusive record type of record { never
x?; }
cannot duplicate a specific field for x
.
If there is a contextually expected type, then the inherent type of the newly created mapping is derived from the applicable 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 in a specific-field 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 there is no contextually expected type, then the inherent type will be an
exclusive-record-type-descriptor with an individual-field-descriptor for each
specific-field; the type of each field-descriptor will be the broad type of the
value-expr in the field, unless the field is read-only in which case the type of
the field-descriptor will be the precise type. The static type of the expression
in every spread-field will also be added to the inherent type. If there are
fields specified as a computed-name-field, then there will also be a
record-rest-descriptor T...
, where T
is the union of
the broad types of the value-expr in all such fields.
If a specific-field does not use a string-literal for the name of the field and the inherent type descriptor is a record type descriptor, then the record type descriptor must include an individual-type-descriptor for that field.
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.
If there is a contextually expected type, then the type that the inherent type requires for each field provides the contextually expected type for the value-expr in a field; otherwise there is no contextually expected type for the value-expr for fields. If there is a contextually expected type, the contextually expected type for the expression in a spread-field is map<T>, where the T is the smallest type such that the inherent type is a subtype of map<T>. The contextually expected type for a field-name-expr is string.
A computed-name-field specifies a single field, where the name of the field is specified by an expression enclosed in square brackets. A mapping-constructor-expr first constructs a mapping value without considering any computed-name-field. The effect of a computed-name-field is to modify the member of the mapping with the specified name after the mapping has been constructed. If the modification is incompatible with the inherent type, then the mapping-constructor-expr will panic. The modifications are performed in the order in which the computed-name-fields occur in the mapping-constructor-expr.
If the applicable contextually expected type is a subtype of readonly, then the
mapping will be constructed with its read-only bit on. If the inherent type
makes a specific field readonly, then that field will be constructed with its
read-only bit on. A specific-field that starts with readonly
will
also be constructed with its read-only bit on.
table-constructor-expr :=table
[key-specifier][
[row-list]]
row-list := mapping-constructor-expr (,
mapping-constructor-expr)*
A table-constructor-expr creates a new table value. The members of the table come from evaluating each mapping-constructor-expr in the row-list in order.
For example,
table key(email) [ { email: "sanjiva@weerawarana.org", firstName: "Sanjiva", lastName: "Weerawarana" }, { email: "jjc@jclark.com", firstName: "James", lastName: "Clark" } ]
The inherent type of the constructed table is a table type descriptor including
a key-specifier table<T> key(ks)
, where
T
is the member type and ks
is
the key sequence. The inherent type is determined from the contextually expected
type togther with the table-constructor-expr. The static type of the
table-constructor-expr will be the same as this inherent type.
If there is a contextually expected type, then the member type of inherent type of the newly created table is derived from the applicable contextually expected type, which must be a table-type-descriptor. If there is no contextually expected type, then the member type of the inherent type is derived from the the static type of the expressions for the members: the member type will be the smallest record type that is a supertype of the static types of all the expressions in the row-list. It is an error if there is no contextually expected type and the row-list is empty.
The key sequence of the inherent type comes from the key-specifier of applicable contextually expected type or the key-specifier in the table-constructor-expr. If both of these are present, then they must be the same. If neither of them are present, then the key sequence is empty. The key sequence of the table value is the same as that of its inherent type. The key sequence and member type must meet the same consistency requirements as if they were specified together in a table type descriptor. For every field-name in the key sequence of the inherent type every mapping-constructor-expr in the row-list must include a specific-field that has a value-expr that is a const-expr. It is a compile-time error if two or more rows of the table have the same key value.
If there is a contextually expected type, then the type that the inherent type requires for each table member provides the contextually expected type for the expressions in the expr-list; otherwise there is no contextually expected type for these expressions.
There are two kinds of expression that can be used to construct an object: with an object constructor expression, the methods and fields of the constructed object are defined within the expression; with a new expression, the methods and fields come from a separate, named class definition.
object-constructor-expr := [annots] object-type-qualsobject
[type-reference] object-constructor-block object-constructor-block :={
object-member*}
object-member := object-field | method-defn | remote-method-defn | resource-method-defn
The resulting of evaluating an object-constructor-expr
is an
object. The object-constructor-expr
must specify
client
if any method-defn
includes a
remote-qual
.
The annotations applying to the object-constructor-expr
and its
members are evaluated when the object-constructor-expr
is
evaluated. This means that every object resulting from the evaluation of an
object-constructor-expr
has its own type descriptor.
If there is a type-reference
, then the referenced type is included
in the object's type in the same was as an object-type-inclusion
,
and the type-ids of the constructed object are type-ids of the referenced type.
If there is no type-reference
, then the applicable contextually
expected type T, if any, must be definite, and the type-ids of the constructed
object are the type-ids, if any, induced by T.
The object-constructor-expr
is implicitly read-only if the
applicable contextually expected type is a subtype of readonly or if the
type-reference
is present and references a type that is a subtype
of readonly. If the object-constructor-expr
is implicitly
read-only, then the object will be constructed with its read-only bit on;
furthermore, in this case an object-type-inclusion
is allowed to
directly or indirectly reference a readonly class.
The object-constructor-expr
is explicitly isolated if
object-type-quals
includes isolated
or if the
type-reference is present and references an isolated object type. An
object-constructor-expr
that is explicitly isolated will construct
an object with its isolated bit set.
object-visibility-qual :=public
|private
A visibility qualifier of private
can be used within an
object-constructor-expr
expression; this means that the visibility
region consists of every method-defn-body in the object-constructor-expr.
object-field := metadata [object-visibility-qual] [final
] type-descriptor field-name [=
field-initializer];
field-initializer := expression
An object-field
declares and initializes a field of the object.
If a field does not specify a field-initializer, then there must be an
init
method and the init
method must initialize the
field. The field-initializer must meet the requirements for an isolated function
unless the init
method is present and not declared as
isolated
. If the object-constructor-expr
is implicitly
read-only, then the contextually expected type for a field-initializer will be
the intersection of readonly and the type specified in the type-descriptor of
the object-field.
If final
is present, then the field must be assigned to exactly
once, either by its initializer or in the init
method.
An object-constructor-expr
will construct an object with its
read-only bit set if every object-field is declared as final
and
has a type-descriptor that is a subtype of readonly
. In this case,
the static type of the object-constructor-expr
will be intersected
with readonly
.
An object field is isolated if it is declared as final
and
has a type-descriptor that is a subtype of readonly
or
isolated object {}
, or if the object-constructor-expr
is implicitly read-only. An object-constructor-expr
is
implicitly isolated if it is implicitly readonly or if every object
field is isolated. An object-constructor-expr
that is implicitly
isolated will construct an object with its isolated bit set. In this case, the
static type of the object-constructor-expr
will be intersected with
isolated object {}
. If the object-constructor-expr
is
explicitly isolated, then every field-initializer
must be an
isolated expression and any field that is not isolated must be declared as
private.
method-defn :=
metadata object-visibility-qual method-quals
function
method-name function-signature method-defn-body
A method-defn
defines a method of an object.
remote-method-defn :=
metadata remote-method-quals
function
method-name function-signature method-defn-body
A remote-method-defn
defines a remote method. This is allowed only
when an object-network-qual
is present in the
object-constructor-expr
.
method-defn-body := function-defn-body
Within a method-defn-body
, the fields and methods of the object are
not implicitly in-scope; instead the variable self
is bound to the
object and can be used to access fields and methods of the object. If the
object-constructor-expr
is explicitly isolated, then the
self
variable must be accessed only within the scope of a lock
statement, except when self
is part of a
field-access-expr
of the form self.f
, where
f
is the name of an isolated field.
If isolated-qual
is present, then the method has its isolated bit
set and its method-defn-body
must satisfy the requirements for an
isolated function.
In addition to remote methods, a service object can use resources to
support network interaction. Resources support a more data-oriented style of
network interaction, which complements the RPC style supported by remote
methods. A service object's resources are arranged in a tree. Each resource
exposes one or more named methods; by convention the method name
get
is used for retrieving the resource. A listener thus identifies
a resource method to be called using the combination of a hierarchical path,
which identifies the resource, and the resource method name. A resource method
can also specify that a segment of the path provided by the listener should be
treated as a parameter. A resource method can only be invoked by a listener
object.
resource-method-defn := metadata resource-method-qualsfunction
resource-method-name resource-path function-signature method-defn-body resource-method-quals := resource-qual function-quals | isolated-qual [transactional-qual] resource-qual | transactional-qual [isolated-qual] resource-qual | isolated-qual resource-qual transactional-qual | transactional-qual resource-qual isolated-qual resource-qual :=resource
resource-method-name := identifier resource-path := dot-resource-path | resource-path-segment (/
resource-path-segment)* [/
resource-path-rest-param] | resource-path-rest-param dot-resource-path :=.
resource-path-segment := resource-path-segment-name | resource-path-segment-param resource-path-segment-name := identifier resource-path-segment-param :=[
[annots] type-descriptor param-name]
resource-path-rest-param :=[
[annots] type-descriptor...
param-name]
A resource-method-defn
defines a resource method. This is allowed
only when an object-network-qual
of service
is present
in the object-constructor-expr
. The resource-path
specifies the node in the service object's resource tree to which the method is
attached and the resource-method-name
specifies the method name. It
is an error to have two resource-method-defn
s that both have the
same name and have a path that refers to the same node. A
dot-resource-path
specifies the root of the service object's
resource tree. In a service object's resource tree, each edge from a parent node
to a child node has a label, which uniquely identifies the child relative to its
siblings. A label is either a string or one of two special values that indicate
that the corresponding path segment should be treated as a parameter. It is an
error for a parent node to have more than one edge to a child node labelled with
one of the two special values. A resource-path-segment-name
specifies a label that is a string. The two special label values come from a
resource-path-segment-param
and a
resource-path-rest-param
respectively. A parameter in a
resource-path-segment-param
will be bound to a value representing a
single path segment; a parameter in a resource-path-rest-param
will
be bound to a list representing zero or more path segments, with each member
representing a single path segment. The type of a path segment depends on the
listener, but the listener must be able to convert it into a string. For
example, in HTTP a path segment is natively a sequence of bytes, and an HTTP
listener could convert it into a string by decoding with UTF-8. It also depends
on the listener which types other than string it is able to convert a path
segment to.
The return type of a resource method must not allow values belonging to the
function basic type. A resource method with a name of get
that can
return a service object represents a subservice. It is subject to the following
restrictions:
resource-path-rest-param
;The semantics of hierarchical resources are defined by a lookup function, which takes four parameters:
It returns an error or a tuple [r, v], where
The function lookup(m, p, r, v) is defined as follows:
get
method that can return a service
object, then call the get
method of r using v
to bind the path parameters; if the result is an error, then return that error;
otherwise, the result must be a service object. Let r' be the root of
its resource tree. Return lookup(m, p, r',
[]).The syntax allows only the last of a resource method's path parameters to be a rest parameter. When a list of path segments is bound to the path parameters of a resource method, one path segment is bound to each path parameter that is not a rest parameter and a list of the remaining path segments is bound to the rest parameter, if any.
A method-defn
with a method-name
of init
is used to initialize the object and is treated specially. 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 parameter list of an init
method within an
object-constructor-expr
must be empty.
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 an initializer 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.
If the object-constructor-expr
is explicitly isolated, then a field
can only be assigned to by an assignment-stmt
with a right hand
side that is an isolated expression. The requirement to access self
within a lock statement does not apply to the init
method.
The visibility of the init
method cannot be private
.
Any init
method is not part of the shape of an object, and so does
not affect when an object value belongs to a type. The init
method
can be called in a method-call-expr
only when the expression
preceding the .
is self
.
A missing init
method is equivalent to an isolated
init
method with no parameters and an empty body (which will always
return nil).
new-expr := explicit-new-expr | implicit-new-expr explicit-new-expr :=new
class-descriptor(
arg-list)
class-descriptor := identifier | qualified-identifier | stream-type-descriptor
A new-expr constructs a new object or stream. The class-descriptor in an explicit-new-expr must refer to a class or a stream type.
When the class-descriptor refers to a class, the explicit-new-expr allocates
storage for an object of the type defined by the class and initializes it by
passing the supplied arg-list to the init
method defined by the
class. It is a compile error if the type-descriptor if the arg-list does not
match the signature of the class'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. The explicit-new-expr is isolated if the type of the
init
is isolated and the expression for every argument in the
arg-list is isolated.
When the class-reference refers to a stream type stream<T,E>
,
the arg-list must either be empty or be a single argument belonging to object
type StreamImplementor<T,E?>. When the arg-list is empty, the result will
be an empty stream (i.e. a stream whose next method returns nil). When the
arg-list evaluates to a StreamImplementor object, the result will be a stream
that wraps that object. The explicit-new-expr is isolated if the the arg-list is
empty or if the expression for the single argument is isolated.
An explicit-type-expr specifying a class-descriptor T has static type T, except
that if T is an class type and 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 class-descriptor. An
implicit-new-expr consisting of just new
is equivalent to
new()
. It is an error if the applicable contextually expected type
is not a class or stream type.
variable-reference-expr := variable-reference
variable-reference := identifier | qualified-identifier | xml-qualified-name
xml-qualified-name := xml-namespace-prefix :
identifier
A variable-reference can refer to a variable, a parameter, a constant (defined with a module constant declaration), a function, a type (defined with a module type definition) or a class (defined with a module class definition).
When the variable reference has a prefix and the prefix has been declared using an xmlns-decl rather than an import-decl, then the result of evaluating the variable-reference-expr is a string of the form:
{namespace-uri}local-name
where the namespace-uri comes from xml-namespace-uri specified in the xmlns-decl, and the local-name comes from the identifier following the colon.
If the variable-reference references a type defined with a module type definition or a class defined with a module class definition, then the result of evaluating the variable-reference-expr is a typedesc value for that type or class.
A variable-reference-expr is isolated if it refers to an identifier bound by a let-expr to an expression that is isolated.
A variable-reference-expr that refers to a variable declared by a
module-var-decl that includes isolated
is only allowed within a
lock-stmt.
field-access-expr := expression .
field-name
A field-access-expr is typically used to access a field of an object or a field of a record. More generally, it can be used to access a member of an object or a member of a mapping. The semantics depends on the static type T of expression. A field-access-expr where T is a subtype of xml is interpreted as an xml-required-attribute-access-expr.
If T is a subtype of the object basic type, then T must have a member with name
field-name. The field-access-expr is evaluated by first evaluating the
expression to get a value obj. If the member is a field, then the
result of the expression if the value of that field of obj and the
static type of the field-access-expr is the type of that field of T. If that
member is a method, then the result of the expression is a new function value
that when called will call the method with self
bound to
obj; the isolated bit of the new function value is set if and only if
the isolated bits of both obj and the method are set; the type
descriptor of the new function value will have the same annotations as the type
descriptor of the method. The rest of this subsection applies when T is not a
subtype of the object basic type.
Let T' be the intersection of T and basic type mapping, let K be the singleton type containing just the string field-name, and let M be the member type for K in T'. The compile-time requirements on the field-access-expr depend on whether the type descriptor describing T is lax:
The static type of field-access-expr is M|E, where E is empty if K is a required key type and T' is a subtype of T, and error otherwise (E can only be error in the lax case.) In the lax case, if M is lax, then the static type of the field-access-expr is lax even if E is an error.
A field-access-expr is evaluated as follows:
optional-field-access-expr := expression ?.
field-name
An optional-field-access-expr accesses a possibly undefined mapping member,
returning ()
if the member does not exist.
An optional-field-access-expr where the static type of expression
is
a subtype of xml is interpreted as an xml-optional-attribute-access-expr.
Let T be the static type of expression, let T' be the intersection of T and basic type mapping, let K be the singleton type containing just the string field-name and let M be the member type of K in T'. The compile-time requirements on the optional-field-access-expr depend on whether the type descriptor describing T is lax:
()
and the mapping basic type, and the type descriptor for T must
include field-name
as an individual-type-descriptor (if the type
descriptor is a union, then this requirement must be satisfied by at least one
member of the union).The static type of the optional-field-access-expr is M|N|E where
()
if ()
is a subtype of T or K is not a
required key type for T', and empty otherwise;error
if T' is not a subtype of T?, and empty otherwise (E
can only be error in the lax case).An optional-field-access-expr is evaluated as follows:
()
, the result is ()
()
xml-attribute-access-expr := xml-required-attribute-access-expr | xml-optional-attribute-access-expr xml-required-attribute-access-expr := expression.
xml-attribute-name xml-optional-attribute-access-expr := expression?.
xml-attribute-name xml-attribute-name := xml-qualified-name | qualified-identifier | identifier
An XML attribute access expression provides convenient access to an attribute of an XML element. It is a compile-time requirement that the static type of the expression is a subtype of xml.
A string representing the name of the attribute is computed at compile-time from the xml-attribute-name. When the xml-attribute-name is an identifier without a prefix, the attribute name string is the identifier. When the xml-attribute-name has a prefix, normally the xml-attribute-name is an xml-qualified-name, in which the prefix is an xml-namespace-prefix declared using an xmlns-decl. In this case, the xml-qualified-name is expanded at compile-time into an attribute name string of the form
{namespace-uri}local-name
where the namespace-uri comes from xml-namespace-uri specified in the xmlns-decl, and the local-name comes from the identifier following the colon.
It is also allowed for the xml-attribute-name to be specified as a
qualified-identifier, in which the prefix is a module-prefix declared using an
import-decl. In this case the qualified-identifier must refer to a
module-const-decl of type string, and the attribute name string is the value of
the referenced constant. This allows e.g. xml:lang
to work.
An xml-optional-attribute-access-expr is evaluated as follows. The expression is
evaluated resulting in an xml value v. If v is an empty
xml value, the result is ()
. Otherwise, if v is not a
singleton element, the result is an error. Otherwise, let m be that
element's attribute map and let k be the attribute name string
computed at compile-time from the xml-attribute-name. If m has a
member s with key k, the the result is s.
Otherwise, the result is ()
.
An xml-required-attribute-access-expr is evaluated the same as an
xml-optional-attribute-expr, except that for cases where the result of the
xml-optional-attribute-expr would be ()
, the result of the
xml-required-attribute-access-expr is an error.
The static type of an xml-required-attribute-access-expr is
string|error
The static type of an
xml-optional-attribute-access-expr is string|error|()
.
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.
member-access-expr := container-expression[
(key-expression | multi-key-expression)]
container-expression := expression key-expression := expression multi-key-expression := expression (,
expression)+
A member-access-expr accesses a member of a structured value using its key, or a constituent of a sequence value using its index.
The requirements on the static type of container-expression and key-expression are as follows:
K
and
the static type of key-expression must be a subtype of
K
, where the static type of container-expression is
table<R> key<K>
;
A multi-key-expression is allowed only when the static type of a
container-expression is a subtype of table. A multi-key-expression
E1, E2,..., En
is equivalent to a
list-constructor-expr [E1, E2,...,
En]
.
A member-access-expr is evaluated as follows:
()
; otherwise, the result is the member
of c with key k.()
or c
does not contain a member with key k, the result is ()
;
otherwise, the result is the member of c with key k.
Let T the static type of container-expression. If T is a subtype of string, then
the static type of the member-access-expr is string:Char
, that is the subtype of
strings containing strings of length 1. If T is a subtype of xml<M>, then
the static type of the member-access-expr is M|E, where E is the type of the
empty xml value. If T is a subtype of table<R>, then the static type of
member-access-expr is R?. Otherwise, let K be the static type of key-expression
and let M be the member type of K in T; if T contains nil, or T is a subtype of
mapping and K is an optional key type for T, then the static type of the
member-access-expr is M?, otherwise the static type is M.
function-call-expr := function-reference(
arg-list)
function-reference := variable-reference arg-list := positional-args [,
other-args] | [other-args] other-args := named-args | rest-arg
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. The argument list will be used to bind the parameters of the function before the body of the function is executed. 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.
The function-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 contextually expected type for the expression comes from the static type required for the expression as specified below.
positional-args := positional-arg (,
positional-arg)*
positional-arg := expression
The result of evaluating the i-th positional-arg becomes the i-th member of the argument list. The static type of the expression for the i-th positional-arg must be a subtype of the type declared for the i-th parameter that is not a rest-param, if there is such a parameter; otherwise, there must be a rest-param and the static type must be a subtype of the type declared for the rest-param.
named-args := named-arg (,
named-arg)* named-arg := arg-name=
expression arg-name := identifier
The arg-name of every named-arg must be distinct. Each named-arg must correspond to either a parameter that is not a rest-param or to a field of an included-record-param. In the latter case, there are two possibilities.
never
.never
, the
record-type-descriptor must include an individual-field-descriptor that has type
never
and is optional, and so disallows a field with that
name;It is an error if there is a named-arg and a positional-arg that correspond to the same parameter. If there is a named-arg or positional-arg corresponding to an included-record-param, it is an error for a named-arg to specify a field of that included-record-param. The static type of the expression of the named-arg must be a subtype of the type declared for the corresponding parameter or field-descriptor.
rest-arg := ...
expression
The static type of the expression in a rest-arg must be either a list type or a mapping type. If it is a list type, then the rest-arg is equivalent to specifying each member of the list as a positional-arg. If it is a mapping type, then the rest-arg is equivalent to specifying each field of the mapping as a named-arg, with the name and value of the named-arg coming from the name and value of the field. In either case, the static type of the expression must be such as to ensure that the equivalent named-args or positional-args would be valid.
If there is neither a named-arg nor a positional-arg for a parameter that is not a rest-param, then a default value is computed for the parameter, if possible; otherwise, it is an error. Default values are computed in parameter declaration order.
<>
, then the value for the parameter will be a typedesc value
determined at compile-time from the contextually expected type of the
function-call-expr as follows. Let the name of the parameter for which a default
of <>
was specified be t. Let the type of
t be typedesc<T>. Let the return type descriptor for
the function be R, which will have references to t. Let
the contextually expected type for the function-call-expr be C. Then
the default value is found by unifying C and R: the
default value is a typedesc value representing a type descriptor S
that is a subtype of T, such that R with S
substituted for t is equivalent to C.
A function-call-expr is isolated if the type of the function being called is
isolated and the expression for every argument is isolated. In addition, a call
to the clone
or cloneReadOnly
functions defined by the
lang.value module of the lang library is always isolated.
When there is a need to call a function value resulting from the evaluation of an expression, the result of the expression evaluation can be assigned to a variable and then the function called by using a reference to the variable.
method-call-expr := expression.
method-name(
arg-list)
A method-call-expr
either calls an object's method, calls an
object's field where the type of the field is a function or calls a function in
the lang library. The evaluation of the method-call-expr starts by evaluating
expression
resulting in some value v. There is no
contextually expected type for expression
.
If the static type of expression
is a subtype of object, and the
object type includes a method named method-name
, then the
method-call-expr
is executed by calling that method on
v. The arg-list
is used to construct an argument list
that is passed to the method in the same way as with a
function-call-expr
. A method-call-expr
cannot be used
to call a remote method. A remote method of a client object can be called by a
client-remote-method-call-action
. In this case, a method-call-expr
is isolated if the expression preceding .
is isolated, the
expression for each argument in the arg-list is isolated and the type of the
method being called is isolated.
Otherwise, if the static type of expression
is a subtype of object,
and the object type includes a field named method-name
with a
function type, then the method-call-expr
is executed by calling the
value of that field. The arg-list
is used to construct an argument
list that is passed to the method in the same way as with a
function-call-expr
. In this case, a method-call-expr is isolated if
the type of the function is isolated and the expression for each argument in the
arg-list is isolated.
Otherwise, the method-call-expr
will be turned into a call to a
function in the lang library m:method-name(expression, arg-list)
,
where m is an automatically created module prefix for a module lang.M of the lang
library, where M is selected as follows.
expression
is a subtype of some basic
type with identifier B, and the module lang.B contains a function
method-name
then M is B. The identifier for a basic type is the
reserved identifier used in type descriptors for subtypes of that basic type, as
listed in the Lang library section.expression
is
xml:Text
and the module lang.string contains a function
method-name
, then M is string
, and the result of
evaluating expression
is implicitly converted to a string before
the function is called.value
.
It is a compile-time error if the resulting function call does not satisfy all
the constraints that would apply if it has been written explicitly as a
function-call-expr
. The method-call-expr
expression is
isolated under the same conditions that the corresponding
function-call-expr
would be isolated.
error-constructor-expr :=error
[error-type-reference](
error-arg-list)
error-arg-list := positional-arg [,
positional-arg] (,
named-arg)*
An error constructor constructs a new error value. If an error-type-reference is
specified, it must denote a type that is a subtype of error; the effect is the
same as making the contextually expected type be that specified by the
error-type-reference. If there is no applicable contextually expected type, then
it is the same as if there were a contextually expected type of
error
.
The applicable contextually expected type E must be definite. The type-ids of the constructed error value are those induced by E. Note that it is allowed for E to refer to an intersection-type-descriptor.
The first positional-arg is of type string and specifies the error message; the
second positional-arg, if present, is of type error?
, with a
default of nil, and specifies the cause.
Evaluating the error-constructor-expr constructs a new detail mapping. Each
named-arg specifies a field of the error detail mapping; the static type of each
named-arg must be a subtype of value:Cloneable
. The type descriptor
E implies a type descriptor D for the detail mapping. The arg-name of every
named-arg must be specified as the field-name of an individual-field-descriptor
occurring in D, unless there are no are no such field names. Fields with default
values will also be added to the detail record based on D in the same way as the
mapping-constructor-expr adds fields with default values based on the
contextually expected type. The contextually expected type for each named-arg is
determined from D in the same way as for a mapping-constructor-expr. The detail
mapping is constructed as immutable, with its members being the result of
appplying the ImmutableClone abstract operation to the result of evaluating each
named-arg and every defaultable arg.
The stack trace in the constructed error value describes the execution stack at the point where the error constructor was evaluated.
anonymous-function-expr := explicit-anonymous-function-expr | infer-anonymous-function-expr
explicit-anonymous-function-expr := [annots] function-quals function
function-signature (block-function-body|expr-function-body)
Evaluating an anonymous-function-expr creates a closure, whose basic type is function. With an explicit-anonymous-function-expr, the type of the function is specified explicitly as usual with a function-signature. With an infer-anonymous-function-expr, the type of the function is inferred.
If block-function-body refers to a block-scope variable defined outside of the block-function-body, 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.
infer-anonymous-function-expr := infer-param-list expr-function-body infer-param-list := identifier |(
[identifier (,
identifier)*])
An infer-anonymous-function-expr can only be used in a context where a function
type is expected. Both the types of the parameters and whether the function type
is isolated
are inferred from the expected function type. The
function type will be inferred to be transactional
if the
expr-function-body calls any functions with a transactional type.
The scope of the parameters is expr-function-body
.
The static type of the infer-anonymous-function-expr will be a function type
whose return type is the static type of the expression
in
expr-function-body
. If the contextually expected type for the
anonymous-function-expr
is a function type with return type T, then
the contextually expected type for expression
in
expr-function-body
is T.
let-expr :=let
let-var-decl [,
let-var-decl]*in
expression let-var-decl := [annots] typed-binding-pattern=
expression
A let-expr binds variables and then evaluates an expression with those variables in scope.
A let-expr let T B = E1 in E2
is evaluated as
follows. E1 is evaluated resulting in a value v. The typed binding
pattern T B is matched to v, causing assignments to the variables occuring in B.
Then E2 is evaluated with those variables in scope; the resulting
value is the result of the let-expr.
A let-expr let D1, D2,...,Dn in E
is transformed into let D1 in let D2 in ... let
Dn in E
.
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 precise static type of the expression following
=
.
Since expressions cannot modify variables, the variables bound in a let-var-decl
are implicitly final. A let-expr is isolated if the expression following
in
is isolated; a reference in that expression to a variable bound
by a let-var-decl will be treated as isolated if the expression initializing the
variable in the let-var-decl is isolated.
type-cast-expr :=<
type-cast-param>
expression type-cast-param := [annots] type-descriptor | annots
A type-cast-expr
casts the value resulting from evaluating
expression
to the type described by the type-descriptor, performing
an implicit conversion or numeric conversion if required.
Normally, the parameter for a type-cast-expr includes a type-descriptor. However, it is also allowed for the parameter to consist only of annotations; in this case, the only effect of the type cast is for the contextually expected type for expression to be augmented with the specified annotations. The rest of this subsection describes the normal case, where the type-cast-expr includes a type-descriptor.
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 v belongs to xml:Text
and T
includes shapes from basic type string, then let s be the result of implicitly
converting v to a string; if s belongs to T, then the result of the
type-cast-expr is s. 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 xml shapes and numeric shapes transformed to take account to the
possibility of the implicit conversion and numeric conversion specified in the
previous paragraph. It is a compile-time error to attempt to use a type cast to
cast away error: if the intersection of TE and error
is
non-empty, then the intersection of T and error
must also be
non-empty. A checkpanic
expression can be used instead to assert
that the result of an expression will never be an error.
The contextually expected type for expression
is the intersection
of the contextually expected type of the type-cast-expr
and the
type described by the type-descriptor
.
Note that a type-cast-expr
of <readonly>
can be
used both to cause constructors withing the expression
to construct
values with the read-only bit on and to verify that the value resulting from
the evaluation of expression
has its read-only bit on.
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 the same as the type
descriptor used to construct the value. For containers, this is the same as the
inherent type; when the container is immutable, it will be a singleton type. For
an object, this is the same as the type descriptor used with new. For an error,
this is the same as the type descriptor used in the functional-constructor-expr.
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:
If the contextually expected type for a -
expression is T, then the
contextualy expected type for the operand expressions is T', where a value v is
in T' if it belongs to int, decimal or float, and T contains a value with the
same basic type as v.
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 a subtype of the same basic type; this basic type will be the static type of the result. The following basic types are allowed:
*
performs integer multiplication; a panic will occur on overflow/
performs integer division, with any fractional part discarded
ie with truncation towards zero; a panic will occur on division by zero or
overflow, which happens if the first operand is -263 and the second
operand is -1%
performs integer remainder consistent with integer division,
so that if x/y
does not result in a panic,
then (x/y)*y +
(x%y)
is equal to x
; a
panic will occur if the second operand is zero; if the first operand is
-263 and the second operand is -1, then the result is 0*
performs the multiplication operation with the destination
format being the same as the source format, as defined by IEEE 754-2008; this
never causes a panic to occur/
performs the division operation with the destination format
being the same as the source format, as defined by IEEE 754-2008; this never
causes a panic to occur%
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,
x
is NaN or y
is NaN or
x
is an infinity or y
is a zero,
then x % y
is NaNx
, and infinite y
, x %
y
is x
x
and finite non-zero
y
, x % y
is equal to
the result of rounding x - (y × n)
to the nearest representable value using the roundTiesToEven rounding mode,
where n
is the integer that is nearest to the
mathematical quotient of x
and y
without exceeding it in magnitude; if the result is zero, then its sign is that
of x
If the contextually expected type for a multiplicative-expr is T, then the contextualy expected type for both operand expressions is T', where a value v is in T' if it belongs to int, decimal or float, and T contains a value with the same basic type as v.
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:
+
performs integer addition; a panic will occur on
overflow-
performs integer subtraction; a panic will occur on
overflow+
performs the addition operation with the destination format
being the same as the source format, as defined by IEEE 754-2008; this never
causes a panic to occur-
performs the subtraction operation with the destination
format being the same as the source format, as defined by IEEE 754-2008; this
never causes a panic to occurIf the contextually expected type for a multiplicative-expr is T, then the contextualy expected type for both operand expressions is T', where a value v is in T' if it belongs to int, decimal, float, string or xml and T contains a value with the same basic type as v.
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 zero
If the operator is >> or >>> and the left hand operand is a
subtype of int:UnsignedK
when K
is 8, 16 or 32, then the static type of the result is
int:UnsignedN
where N
is the
smallest such K
; otherwise, the static type of the
result is int
.
range-expr := expression...
expression | expression..<
expression
The result of a range-expr is a new object belonging to the object type Iterable<int,()> that will iterate over a sequence of integers in increasing order, where the sequence includes 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 elsewhere.
A range-expr is always isolated.
relational-expr := expression<
expression | expression>
expression | expression<=
expression | expression>=
expression
A relational-expr tests the relative order of two values. There must be an ordered type to which the static type of both operands belong. The static type of the result is boolean.
A relational-expr is evaluated by first evaluating the two expressions, resulting in values x and y. The result of relational-expr is true if and only if the result of the Compare(x,y) operation is
<
>
<=
>=
and otherwise false.
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 v1 and v2 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 a subtype of anydata. Two values v1, v2 are deeply equal if DeepEquals(v1, v2) is true.
Note that === and == are the same for simple values except for floating point types.
For the float type, the operators differ 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 neither 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.
For the decimal type, the operators differ in whether they consider the
precision of the value. For example, 1.0 == 1.00
is true but
1.0 === 1.00
is false.
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:
int:UnsignedK
when K
is 8, 16 or
32, then the static type of the result is int:UnsignedN
where N
is the smallest such K
;
otherwise, the static type of the result is int
;int:UnsignedK
when K
is 8, 16 or 32, then the static type of the result is
int:UnsignedN
where N
is the
smallest such K
; otherwise, the static type of the result
is int
.logical-expr := logical-and-expr | logical-or-expr logical-and-expr := expression&&
expression logical-or-expr := expression||
expression
The static type of each expression in a logical-expr must be a subtype of boolean.
A logical-and-expr is evaluated as follows:
false
, then the result of the
logical-and-expr is x, and the right-hand expression is not
evaluated;A logical-or-expr is evaluated as follows:
true
, then the result of the
logical-or-expr is x, and the right-hand expression is not
evaluated;conditional-expr := expression?
expression:
expression | expression?:
expression
L ?:
R is evaluated as follows:
checking-expr := checking-keyword expression checking-keyword :=check
|checkpanic
Evaluates expression resulting in value v. If v has basic type error, then
check
, then the check-expression
completes abruptly with a check-fail with associated value v;checkpanic
, then the check-expression
completes abruptly with a panic with associated value v.
If the static type of expression Expr
is T|E, where E is
a subtype of error, then the static type of check Expr
or checkpanic Expr
is T. However, if:
check
,Expr
is lax,json
, and()|int|float|decimal|string
,
then instead check Expr
is treated as check
val:ensureType(Expr, s)
, where
s
is a typedesc value representing S and
val
refers to the value
module in langlib.
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.A query expression provides a language-integrated query feature using SQL-like syntax. In this version of Ballerina, the functionality is similar to a list comprehensions; future versions will provided richer functionality.
query-expr := [query-construct-type] query-pipeline select-clause [on-conflict-clause] query-construct-type :=table
key-specifier |stream
query-pipeline := from-clause intermediate-clause* intermediate-clause := from-clause | where-clause | let-clause | join-clause | order-by-clause | limit-clause
A query expression consists of a sequence of clauses. The semantics of clauses
is specified in terms of transforming a sequence of frames, where a frame is a
binding of variables to values. The input to each clause is a sequence of
frames. As each clause is executed, it iterates over its input frames and emits
output: the final clause, which is a select
clause, emits output
values; the other clauses emit output frames. When a
query-expr is evaluated, its clauses are executed in a pipeline by making the
sequence of frames emitted by one clause be the input to the next clause. Each
clause in the pipeline is executed lazily, pulling input from its preceding
clause. The input to the first clause is a single empty frame.
The execution of a clause may complete early with an error value, in which case this error value is the result of the query, except when constructing a stream. Otherwise, the result of evaluating a query expression is a single value, which is constructed from the sequence of values emitted by the last clause. The result must be one of the following basic types:
A query-expr that constructs a stream must start with the stream
keyword and is evaluated differently from a query-expr that constructs a value
of other basic types. The clauses in the query-expr are executed lazily: clauses
in the query-expr becomes closures, which are called as a result of next
operations being performed on the stream. If a next operation causes the
execution of a clause that completes early with an error value, then the error
value is returned by the next operation; the evaluation of the query-expr will
already have resulted in a stream value. If the next operation results in the
evaluation of an expression within the query-expr completing abruptly with a
check-fail, the associated error value will be returned as the result of the
next operation. If the next operation results in the evaluation of an expression
within the query-expr completely abruptly with a panic, then the next operation
will complete abruptly with a panic.
If the query-expr starts with table
, then the query-expr will
construct a table; the key-specifier specifies the key sequence of the
constructed table in the same way as with a table-constructor-expr. Otherwise,
the applicable contextually expected type determines the basic type of the value
constructed. If there is no contextually expected type, then the basic type of
the value constructed is the basic type of expression following in
;
it is a compile-time error if the static type of this expression is not a
subtype of one of the basic types that a query expression can construct.
When during the construction of a table, an emitted value is added as a new
member, it replaces any existing member with the same key value; when a new
member replaces an existing member, it will have the same position in the order
of members as the existing member. This behavior may be controlled by an
on conflict
clause.
Variables bound by the clauses of a query-pipeline are implicitly final, and cannot be modified.
from-clause :=from
typed-binding-patternin
expression
A from
clause is executed as follows:
in
expression with f in scope resulting
in an iterable value c;i.next()
resulting in a value r;()
, stop loop L;r.value
;where-clause := where
expression
A where
clause is executed as follows:
expression
with f in scope resulting in
value b;let-clause :=let
let-var-decl [,
let-var-decl]*
A let
clause consisting of a single let-var-decl is executed as follows:
A let
clause with more than one let-var-decl
is
transformed into multiple let
clauses: let x1 =
E1, x2 = E2
is transformed into
let x1 = E1 let x2 =
E2
.
join-clause := [outer
]join
typed-binding-patternin
expression join-on-condition join-on-condition :=on
expressionequals
expression
A join clause performs an inner or left outer equijoin.
A join
clause is executed as follows:
in
resulting in an iterable
value c;i.next()
resulting in a value r;()
, stop loop L;r.value
;equals
with f
in scope resulting in a value k;equals
with frame f
in scope resulting in a value k;outer
was specified and fs is empty, then emit a
single frame consisting of f augmented with a frame that binds every
variable occurring in typed-binding-pattern to ()
.
In the above, if c evaluates to a table, and the result of evaluating
the expression to the right of equals
for a member of the table
will always be the key value for that member of the table, then the above can be
optimized by using the table instead of creating a new mapping.
Variables bound by previous clauses are not in scope for the expression
following in
, nor for the expression on the right of
equals
. Variables bound by the typed-binding-pattern are not in
scope for the expression following in
, nor for the expression on
the left of equals
.
When outer
is specified, the typed-binding-pattern is treated
specially: the inferable-type-descriptor in the typed-binding-pattern must be
var
, and for each variable occurring in the typed-binding-pattern,
if the type that would be inferred usually for the variable would be
T, then the type inferred in this case will be
T?
.
order-by-clause :=order
by
order-key [,
order-key]* order-key := expression [order-direction] order-direction :=ascending
|descending
The static type of the expression in an order-key must be an ordered type. The order-direction is ascending
if not explicitly specified.
An order-by
clause is executed by constructing a list
of entries, where each entry consists of:
In order to define the correct ordering of the list, we first define a CompareK operation that compares two keys with a specified direction: CompareK(x,y,d) is CompareA(x,y) if d is ascending and otherwise (d is descending) is Reverse(CompareD(x,y)), where Reverse applied to LT, EQ, GT is GT, EQ, LT respectively. Now define an operation CompareKL(x,y,d) applying to two lists of keys and a list of directions as follows. Let x be [x1,x2,...,xn], y be [y1,y2,...,yn], and d be [d1, d2,...,dn]. Then let r be CompareK(x1,y1,d1). Then the result of CompareKL(x,y,d) is:
Then in a correct ordering of the list of entries when the list of directions is d, an entry with keys with keys x and index i is before an entry with keys y and index j if CompareKL(x,y,d) is LT or if CompareKL(x,y,d) is EQ and i is less than j.
An order-by
clause is executed as follows:
limit-clause := limit
expression
A limit
clause limits the number of frames emitted by a query pipeline.
A limit
clause is executed as follows:
The static type of expression must be a subtype of int
.
select-clause := select
expression
A select
clause is executed as follows:
on-conflict-clause :=on
conflict
expression
An on conflict
clause is allowed only for a query expression that
constructs a table with a key sequence. The expression is evaluated when the
select
clause emits a value that conflicts with a previous value,
in the sense that both values have the same key value in the table. The
expression is evaluated with the same frame in scope as the select clause that
emitted the value that conflicts with the previous value. The static type of the
expression must be a subtype of error?
. If the result of evaluating
the expression is an error e, then the result of evaluating the
query-expr is e. Otherwise, the result must be nil, and the earlier
new value replaces the earlier conflicting value, in the same way as if there
not been an on conflict
clause.
Note that the expression may have side-effects; for example, it may call a function that logs an error.
XML navigation expressions allow for convenient navigation of XML element structure, in a similar way to XPath.
xml-filter-expr | xml-step-expr:=
xml-name-pattern := xml-atomic-name-pattern [|
xml-atomic-name-pattern]* xml-atomic-name-pattern :=*
| identifier | xml-namespace-prefix:
identifier | xml-namespace-prefix:
*
An XML name pattern matches a string specifying the name of an XML element.
An xml-atomic-name-pattern that is *
matches any name.
An xml-namespace-prefix in an xml-atomic-name-pattern must be declared by an xmlns-decl. If there is an in-scope default namespace (declared by an xmlns-decl), an xml-atomic-pattern that is just an identifier specifies a name in that default namespace.
xml-filter-expr := expression.<
xml-name-pattern>
An xml-filter-expr selects constituents of a sequence that are elements with a
name matching a specified name pattern. The static type of the expression must
be a subtype of xml. The static type of the xml-filter-expr is
xml<xml:Element>
.
An xml-step-expr provides access to the children or descendants of an element, similar to a location path in XPath.
xml-step-expr := expression xml-step-start xml-step-extend* xml-step-start := xml-all-children-step | xml-element-children-step | xml-element-descendants-step xml-all-children-step :=/*
xml-element-children-step :=/<
xml-name-pattern>
xml-element-descendants-step :=/**/<
xml-name-pattern>
xml-step-extend :=.<
xml-name-pattern>
|[
expression]
|.
method-name(
arg-list)
The static type of the expression must be a subtype of xml.
An xml-step-expr that starts with an xml-all-children-step
E /* X
is equivalent to
xml:map(xml:elements(E), v => xml:getChildren(v) X)
where v
is a variable name not used in X
.
An xml-step-expr that starts with an xml-element-children-step
E /< NP > X
is equivalent to
xml:map(xml:elements(E), v => xml:getChildren(v) .<NP> X)
where v
is a variable name not used in X
.
An xml-step-expr that starts with an xml-element-descendants-step
E /**/< NP > X
is equivalent to
xml:map(xml:elements(E), v => xml:getDescendants(v) .<NP> X)
where v
is a variable name not used in X
.
transactional-expr := transactional
A transactional-expr
evaluates to true if the expression is being
evaluated in transaction mode and false otherwise.
action := start-action | wait-action | send-action | receive-action | flush-action | client-remote-method-call-action | query-action | type-cast-action | checking-action | trap-action | commit-action |(
action)
action-or-expr := action | expression type-cast-action :=<
type-cast-param>
action checking-action := checking-keyword 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.
The syntax for actions is defined by the above grammar and precedence rules. The precedence rules are that a client-remote-method-call-action has higher precedence than a checking-action or a trap-action.
An action is evaluated in the same way as an expression. Static typing for actions is the same as for expressions.
A type-cast-action
, checking-action
or
trap-action
is evaluated in the same way as a
type-cast-expr
, checking-expr
or
trap-expr
respectively.
Ballerina's concurrency model supports both threads and coroutines. A Ballerina program is executed on one or more threads. A thread may run on a separate core simultaneously with other threads, or may be pre-emptively multitasked with other threads onto a single core.
Each thread is divided into one or more strands. No two strands belonging to the same thread can run simultaneously. Instead, all the strands belonging to a particular thread are cooperatively multitasked. Strands within the same thread thus behave as coroutines relative to each other. A strand enables cooperative multitasking by yielding. When a strand yields, the runtime scheduler may suspend execution of the strand, and switch its thread to executing another strand. The following actions cause a strand to yield:
In addition, any function with an external-function-body can potentially yield; it should only do so if it performs a system call that would block. or calls a Ballerina function that itself yields. Functions in the lang library do not themselves yield, although if they call a function passed as an argument, that function may result in yielding.
There are two language constructs whose execution causes the creation of new strands: named-worker-decl and start-action. These constructs may use annotations to indicate that the newly created strand should be in a separate thread from the current strand. In the absence of such annotations, the new strand must be part of the same thread as the current strand.
Before the block-function-body of a function-defn is executed, the parameters declared in the function-signature of the function-defn are initialized from the argument list that was constructed by the function-call-expr. 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 list 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.
The parameters are in scope for the block-function-body. They are implicitly final: they can be read but not modified.
block-function-body :={
[default-worker-init named-worker-decl+] default-worker}
default-worker-init := statement* default-worker := statement* named-worker-decl := [annots] [transactional-qual]worker
worker-name return-type-descriptor statement-block worker-name := identifier
A worker represents a single strand of a function invocation. A statement is always executed in the context of a current worker. A worker is in one of three states: active, inactive 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. The strand for the default worker is the same as the strand of the worker on which function was called. When a function is called, the current worker becomes inactive, 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 reactivated. Thus only one worker in each strand is active at any given time. 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.
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.
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.
A named-worker is a transactional scope only if it is declared as
transactional
. This is allowed only if the block-function-body is a
transactional scope, i.e. if it is the body of a function declared as
transactional
.
When a function has named workers, the default worker executes in three stages, as follows:
Parameters and 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 statements 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 statements. 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 scope of a worker-name in a named-worker-decl that is part of a block-function-body is the entire block-function-body with the exception of the default-worker-init. When a worker-name is in scope, it can be used in a variable-reference-expr. The result of evaluating such a variable reference is a future value that refers to the value to be returned by that named worker. The static type of the result is future<T>, where T is the return type of the worker.
A strand can initiate a wait on another strand by using a wait-action with a value of type future. Only one wait on a strand can succeed; this wait receives the value returned by the strand. Any other waits on that strand fail. It is a compile-time error if for any named worker it is possible for the name of that worker to be evaluated as a variable-reference more than once for any execution of that worker. This ensures that wait operations that use just a worker-name to identify the strand to wait on cannot fail at runtime.
In the above, function includes method, and function call includes method call.
expr-function-body := =>
expression
An expr-function-body is used for an expression-bodied function, that is a
function whose body is specified by an expression. An expr-function-body of the
form => E
is short for a block-function-body {
return E}
.
external-function-body :==
[annots]external
An external-function-body
means that the implementation of the
function is not provided in the Ballerina source module. A function-defn-body
with a function-signature that is dependently-typed must be an
external-function-body.
The body of a function or method declared as isolated
must meet the
following requirements.
final
but does not include isolated
, or is declared by
a listener-decl or a service-decl, andreadonly
or of
isolated object {}
.isolated
.readonly|isolated object {}
.
If an isolated function has an external-function-body
, then the
above requirements also apply to the external implementation as regards its
access to the mutable state of the Ballerina program, in the sense that it must
only access the mutable state that is reachable from the parameters passed to
the function. In addition, the function should have well-defined behaviour if it
is called concurrently on multiple strands.
statement := action-stmt | local-var-decl-stmt | xmlns-decl-stmt | assignment-stmt | compound-assignment-stmt | destructuring-assignment-stmt | call-stmt | if-else-stmt | regular-compound-stmt | continue-stmt | break-stmt | stmt-with-on-fail | fail-stmt | retry-stmt | transaction-stmt | retry-transaction-stmt | rollback-stmt | panic-stmt | return-stmt | fork-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. There are two possibilities.
fail-stmt
with an expression
that evaluates to e.panic-stmt
with an expression that
evaluates to e.If the execution of a substatement causes termination of the current worker, then the execution of the statement terminates at that point.
regular-compound-stmt := do-stmt | match-stmt | foreach-stmt | while-stmt | lock-stmt
A regular-compound-stmt
can be combined with an
on-fail-clause
to make a stmt-with-on-fail
.
statement-block :={
statement*}
A statement-block
is a syntactic grouping of statements, which are
executed sequentially.
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 in a named-worker-decl that is part of a fork-stmt consists of both other workers in the same fork-stmt and the block containing the fork-stmt starting from the point immediately after the fork-stmt. Within its scope, the worker-name can be used in a variable-reference-expr in the same way as the worker-name of a named-worker-decl that is part of a block-function-body.
start-action := [annots] start
(function-call-expr|method-call-expr|client-remote-method-call-action)
The keyword start
causes the following function or method
invocation to be executed on a new strand. The arguments for the function or
method call are evaluation on the current strand. A start-action returns a value
of basic type future immediately. If the static type of the call expression or
action C
is T, then the static type of start
C
is future<T>.
A wait-action waits for one or more strands to terminate, and gives access to their termination values.
wait-action := single-wait-action | 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. As elsewhere, a wait-future-expr can use an in-scope worker-name in a variable-reference-expr to refer to named worker.
Evaluation of a wait-action performs a wait operation on the future value that results from evaluating a wait-future-expr. This wait operation may complete normally or abruptly. The wait operation initiates a wait for the strand that the future refers to. If the wait fails, then the wait operation completes normally and the result is an error. If the wait succeeds, and the strand completed normally, then the wait operation completes normally, and the result is the termination value of the strand. If the wait succeeds, but the strand completed abnormally, then the wait operation completes abruptly with a panic and the associated value is the termination value of the strand.
In addition to a static type, a wait-future-expr has a compile-time eventual type. If a wait-future-expr is a variable-reference-expr referring to the worker-name of a named worker with return type T, then the eventual type of the wait-future-expr is T. Otherwise, the eventual of a wait-future-expr with static type future<T> is T|error. The result of a wait operation that completes normally will belong to the eventual type of the wait-future-expr, since the compiler ensures that a wait for a wait-future-expr that is a variable reference to a named worker cannot fail.
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 first evaluating wait-future-expr resulting in a value f of basic type future; the single-wait-action then performs a wait operation on f.
The static type of the single-wait-action is the eventual type of the wait-future-expr.
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 each wait-future-expr resulting in a value of type future for each wait-field. The multiple-wait-action then performs a wait operation on all of these futures. If all the wait operations 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 result of the wait operation. If any of the wait operations complete abruptly, then the multiple-wait-action completes abruptly.
The static type of the multiple-wait-action is a record where the static type of each field is the eventual type of the corresponding wait-future-expr.
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 each wait-future-expr, resulting in a set of future values. The alternate-wait-action then performs a wait operation on all of these members of this set. As soon as one of the wait operations completes normally with a non-error value v, the alternate-wait-action completes normally with result v. If all of the wait operations complete normally with an error, then it completes normally with result e, where e is the result of the last wait operation to complete. If any of the wait operations completely abruptly before the alternate-wait-action completes, then the alternate-wait-action completes abruptly.
The static type of the alternate-wait-action is the union of the eventual type of all of its wait-future-exprs.
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.
send-action := sync-send-action | async-send-action sync-send-action := expression->>
peer-worker async-send-action := expression->
peer-worker;
A send-action sends a message to the worker that is identified by peer-worker. A
send-action starts by evaluating the expression, resulting in a value
v
; the Clone abstract operation is then applied to
v
resulting in value c
. This
value c
is added to the message queue maintained by the
sending worker for messages to be sent to the sending worker;this queue
continues to exist even if the receiving worker has already terminated.
For each 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 difference between async-send-action and sync-send-action is in what happens
after the message is added to the queue. The evaluation of async-send-action
completes immediately after this, and the result is always ()
. A
subsequent flush action can be used to check whether the message was received.
With sync-send-action, evaluation waits 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 static type of the expression
must be a subtype of
value:Cloneable
. 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-action 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-action), 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 send actions in W, such that
The compiler terminates a failure type for the corresponding send set. If no member of the corresponding send set was 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-action 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-action 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-action. 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.
A client object is a stub that allows a worker to send network messages to a remote system according to some protocol. A local variable declared with client object type will be depicted as a lifeline in the function's sequence diagram. The remote methods of the client object correspond to distinct network messages defined by the protocol for the role played by the client object. The return value of a remote method represents the protocol's response. A client-remote-method-call-action is depicted as a horizontal arrow from the worker lifeline to the client object lifeline.
client-remote-method-call-action := expression->
method-name(
arg-list)
Calls a remote method of a client object. This works the same as a method call
expression, except that it is used for a client object method with the
remote
qualifier.
query-action := query-pipeline do-clause
do-clause := do
statement-block
The clauses in the query-pipeline of query-action are executed in the same way as the clauses in the query-pipeline of a query-expr.
The query-action is executed as follows. For each input frame f emitted by the query-pipeline, execute the statement-block with f in scope. If a clause in the query-pipeline completes early with error e, the result of the query-action is e. Otherwise, the result of the query-action is nil.
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 statement block
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. However, the language also recognizes two kinds of case where the way a local variable or parameter is used means that is known at compile-time that within a particular region of code the value of the variable will belong to a type that is narrower than the declared type. In these cases, references to the variable within particular regions of code will have a static type that is narrower that the variable type. One kind of case, which is described in this section, is when a variable is used in a boolean expression in a conditional context. The other kind of case is when a variable is used in a match statement; this is described in the Match statement section.
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:
xmlns-decl-stmt := xmlns-decl xmlns-decl :=xmlns
xml-namespace-uri [as
xml-namespace-prefix ];
xml-namespace-uri := simple-const-expr xml-namespace-prefix := identifier
An xmlns-decl
is used to declare a XML namespace. If there is
an xml-namespace-prefix
, then the in-scope namespaces that are used
to perform namespace processing on an xml-template-expr
will include a
binding of that prefix to the specified xml-namespace-uri
;
otherwise the in-scope namespaces will include a default namespace with the
specified xml-namespace-uri
.
An xml-namespace-prefix
declared by an xmlns-decl
is in the same symbol space as a module-prefix
declared by an
import-decl
. This symbol space is distinct from a module's main
symbol space used by other declarations. An xmlns-decl
in a
xmlns-decl-stmt
declares the prefix within the scope of the current
block.
The prefix xmlns
is predeclared as
http://www.w3.org/2000/xmlns/
and cannot be redeclared.
The static type of the simple-const-expr
must be a subtype of
string.
There are three kinds of assignment statement:
The first two of these build on the concept of an lvalue, whereas the last one builds on the concept of a binding pattern.
An lvalue is what the left hand side of an assignment evaluates to. An lvalue refers to a storage location which is one of the following:
Note that an lvalue cannot refer to a member of a table.
An lvalue that is both defined and initialized refers to a storage location that holds a value:
init
method returnslvexpr := variable-reference-lvexpr | field-access-lvexpr | member-access-lvexpr
The left hand side of an assignment is syntactically a restricted type of expression, called an lvexpr. When the evaluation of an lvexpr completes normally, its result is an lvalue. The evaluation of an lvexpr can also complete abruptly, with a panic or check-fail, just as with the evaluation of an expression.
The compiler determines a static type for each lvexpr just as it does for expressions, but the meaning is slightly different. For an lvexpr L to have static type T means that if the runtime evaluation of L completes normally resulting in an lvalue x, then if x is defined and initialized, it refers to a storage location that holds a value belonging to type T. In addition to a type, the compiler determines for each lvexpr whether it is potentially undefined and whether it is potentially uninitialized.
An lvalue supports three operations: store, read and filling-read.
The fundamental operation on an lvalue is to store a value in the storage location it refers to. This operation does not required the lvalue to be defined or initialized; a successful store operation on an undefined lvalue will result in the addition of a member to the structured value; a store on an uninitialized lvalue will initialize it. When an lvalue refers to a variable, it is possible to determine at compile-time whether the store of a value is permissible based on the declaration of the variable and the static type of the value to be stored. However, when the lvalue refers to a member of a structured value, this is not in general possible for three reasons.
The first of these reasons also applies to lvalues that refer to fields of objects. Accordingly, stores to lvalues other than lvalues that refer to variables must be verified at runtime to ensure that they are not impermissible for any of the above three reasons. An attempt to perform an impermissible store results in a panic at runtime.
List values maintain the invariant that there is a unique integer n, the length of the list, such that a member k of the list is defined if and only if k is a non-negative integer less than n. When a store is performed on an lvalue referring to a member k of a list value and the length of the list is n and k is > n, then each member with index i for each ≤ i < k is filled in, by using the FillMember abstract operation. The FillMember abstract operation may fail; in particular it will fail if the list is a fixed-length array. If the FillMember operation fails, then the attempt to store will panic.
An lvalue also allows a read operation, which is used by the compound assignment statement. Unlike a store operation, a read operation cannot result in a runtime panic. A read operation cannot be performed on an lvalue that is undefined or uninitialized.
Finally, a lvalue supports a filling-read operation, which is used to support chained assignment. A filling-read is performed only an lvalue with a static type that is a structured type. It differs from a read operation only when it is performed on a potentially undefined lvalue. If the lvalue is undefined at runtime, then the filling-read will use the FillMember abstract operation on the member that the lvalue refers to. If the FillMember operation fails, then the filling-read panics. Unlike the read operation, the filling-read operation can be performed on an undefined lvalue; it cannot, however, be performed on an uninitialized lvalue.
The evaluation of an lvexpr is specified in terms of the evaluation of its subexpressions, the evaluation of its sub-lvexprs, and store and filling-read operations on lvalues. If any of these result in a panic, then the evaluation of the lvexpr completes abruptly with a panic.
variable-reference-lvexpr := variable-reference
The result of evaluating variable-reference-lvexpr is an lvalue referring to a variable. Its static type is the declared or inferred type of the variable. The lvexpr is potentially uninitialized if it is possible for execution to have reached this point without initializing the referenced variable.
A variable-reference-lvexpr that refers to a variable declared by a
module-var-decl that includes isolated
is only allowed within a
lock-stmt.
field-access-lvexpr := lvexpr .
field-name
The static type of lvexpr must be either a subtype of the mapping basic type or a subtype of the object basic type.
In the case where the static type of lvexpr is a subtype of the object basic type, the object type must have a field with the specified name, and the resulting lvalue refers to that object field.
In the case where the static type of lvexpr is a subtype of the mapping basic type, the semantics are as follows.
field-name
as an individual-type-descriptor (if
lvexpr is a union, then this requirement applies to every member of the union);
lvexpr must not be potentially uninitialized, but may be potentially
undefined.member-access-lvexpr := lvexpr[
expression]
The static type of lvexpr must be either a subtype of the mapping basic type or a subtype of the list basic type. In the former case, the contextually expected type of expression must be string and it is an error if the static type of expression is not string; in the latter case, the contextually expected type of expression must be int and it is an error if the static type of expression is not int.
It is evaluated as follows:
The static type of the member-access-lvexpr is the member type for the key type K in T, where T is the static type of the lvexpr and K is the static type type of expression; the member-access-lvexpr is potentially undefined if K is an optional key type for T.
assignment-stmt := lvexpr=
action-or-expr;
The static type of action-or-expr must be a subtype of the static type of lvexpr. The static type of lvexpr provides the contextually expected type for action-or-expr. It is not an error for lvexpr to be potentially undefined or potentially uninitialized.
It is executed at as follows:
compound-assignment-stmt := lvexpr CompoundAssignmentOperator action-or-expr;
CompoundAssignmentOperator := BinaryOperator=
BinaryOperator :=+
|-
|*
|/
|&
||
|^
|<<
|>>
|>>>
It is a compile error if lvexpr is potentially undefined unless the static type of lvexpr is a subtype of the list basic type. It is a compile error if lvexpr is potentially uninitialized.
Let T1 be the static type of lvexpr, and let T2 be the static type of action-expr. Then let T3 be the static type of an expression E1 BinaryOp E2 where E1 has type T1 and E2 has type T2. It is a compile error if T3 is not a subtype of T1.
It is executed as follows:
destructuring-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.
A binding pattern within a destructuring assignment must not refer to a variable
declared by a module-var-decl that includes isolated
.
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
| checking-keyword 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 a subtype of nil. Note that
never
is a subtype of nil.
if-else-stmt :=if
expression statement-block [else
if
expression statement-block ]* [else
statement-block ]
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 statement block is executed
and the if statement completes. If no expression is true then, if the else block
is present, the corresponding statement block is executed.
do-stmt := do
statement-block
A do-stmt
is executed by executing its
statement-block
.
match-stmt :=match
action-or-expr{
match-clause+}
match-clause := match-pattern-list [match-guard]=>
statement-block match-guard :=if
expression
A match statement selects a statement block 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 statement-block 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 denoting a value p against a value v succeeds if DeepEquals(p,v) is true. The static type of the simple-const-expr in a const-pattern must be a subtype of anydata. 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-type-reference](
error-arg-list-match-pattern)
error-arg-list-match-pattern := error-message-match-pattern [,
error-cause-match-pattern] [,
error-field-match-patterns] | [error-field-match-patterns] error-message-match-pattern := simple-match-pattern error-cause-match-pattern := simple-match-pattern | error-match-pattern simple-match-pattern := wildcard-match-pattern | const-pattern |var
variable-name error-field-match-patterns := named-arg-match-pattern (,
named-arg-match-pattern)* [,
rest-match-pattern] | rest-match-pattern 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
.
For every match pattern, there is a set of shapes that the pattern matches. The
type corresponding to the match pattern is the type containing these shapes. The
value matches the pattern if and only if it looks like the type. A mutable value
thus can match the pattern without belonging to the corresponding type. However,
an immutable value that matches the pattern will always belong to the type.
In particular, for the match of an error-match-pattern
with an
error-type-reference
against an error value to succeed, the
error value must belong to the referenced error type.
A variable is the matched variable of a match-statement
if
the action-or-expression
following match
is a
variable-reference
that references this variable. The type of a
matched variable is subject to narrowing within regions of the
match-statement
as follows. The matched variable has a narrowed
type at the start of each match-clause, which is determined by previous
match-clause
s; the type for the first match-clause
is
the normal static type of the variable. Narrowing is applied to each
match-clause
as follows:
match-pattern
in the match-pattern-list
of the
match-clause
;readonly
, then the narrowed
type for the match-guard
, if any, and statement-block
of the match-clause
is N; otherwise it is
T;readonly
and there is no
match-guard
, then the narrowed type for the next
match-clause
is R; otherwise, it is T.foreach-stmt :=foreach
typed-binding-patternin
action-or-expr statement-block
A foreach statement iterates over an iterable value, executing a statement block once for each value in the iterable value's iteration sequence. The static type of action-or-expr must be an iterable type with an iteration completion type of nil.
The scope of any variables created in typed-binding-pattern is statement-block. 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
action-or-expr
.
while-stmt := while
expression statement-block
A while statement repeatedly executes a statement block 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 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 statement-block in the while-stmt or foreach-stmt.
break-stmt :=break
;
A break statement is only allowed if it is lexically 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
statement-block
A lock-stmt executing in the context of some strand must execute its statement-block in such a way that the effect on the state of the program is consistent with the execution of the statement-block not being interleaved with the execution of a lock-stmt on any other strand.
A naive implementation can simply acquire a single, program-wide, recursive mutex before executing a lock statement, and release the mutex after completing the execution of the lock statement. A more sophisticated implementation can perform compile-time analysis to infer a more fine-grained locking strategy that will have the same effects as the naive implementation.
It is not allowed to start a new strand within a lock. More precisely, when a strand has started but not yet completed the execution of a lock-stmt, the execution of a named-worker-decl or a start-action on that strand will result in a panic. It is a compile-time error for a named-worker-decl or start-action to occur lexically within a lock-stmt. The compiler may also give a compile-time error if a function definition contains a named-worker-decl or start-action, and there is a function call lexically within the lock-stmt that might result directly or indirectly in a call to that defined function.
There are two cases where a variable is restricted to be used only within a lock statement:
self
within an isolated object, unless
self
is being used only to read an isolated field; since
non-isolated fields are required to be private, this restriction applies only to
the object's methods.When a lock statement contains one of the above two cases of restricted variable usage, the lock statement is subject to the following additional requirements, which maintain the invariant that the restricted variable is an isolated root.
The execution of a statement can fail either as the result of check
expression or action, or from an explicit fail
statement. When
execution of a statement fails, control flow transfers to the nearest lexically
enclosing failure-handling statement. The failure handling statements are
stmt-with-on-fail, retry-stmt, transaction-stmt and retry-transaction-stmt. If
there is no such statement, it terminates the current worker normally. Note that
failure is completely distinct from panic.
When execution fails there is an associated error value, which is passed to the failure-handling statement if there is one. If there is no failure-handling statement, then the error value becomes the return value of the worker. The possible failure control flows are known at compile-time, as are the types of the associated error values. For every statement, the type of the error value associated with a failure causing control to transfer out of the block is determined at compile-time. This is in contrast with panics, for which the type of associated error value is not known at compile-time.
fail-stmt :=fail
expression;
Executing a fail-stmt will cause control flow to transfer to the nearest lexically enclosing failure handling statement.
The static type of the expression must be a subtype of error. Execution of the fail-stmt evaluates the expression; the resulting error value is passed to the failure handling statement or becomes the termination value of the current worker.
stmt-with-on-fail := regular-compound-stmt on-fail-clause on-fail-clause :=on
fail
typed-binding-pattern statement-block
A stmt-with-on-fail
is executed by executing the
regular-compound-stmt
. A check
expression or action,
or a fail
statement may cause control to transfer to the
on-fail-cause
. In this case, the typed-binding-pattern
is matched to the associated error value, binding the variables occurring within
the typed-binding-pattern
; the statement-block
is then
executed with these bindings in scope. Otherwise, the
on-fail-clause
is not executed.
The static type of the associated error value is determined from the static type
of the relevant check
expressions and actions, and
fail
statements. It is a compile error unless the match of the
typed-binding-pattern
with the associated error value is guaranteed
to succeed by the static type of the associated error value.
retry-stmt :=retry
retry-spec statement-block retry-spec := [type-parameter] [(
arg-list)
]
The type-parameter in a retry-spec specifies a class, which must be a subtype of
RetryManager>F<, where F is the failure type of the statement-block; this
implies that the parameter type of the class's shouldRetry method must be a
supertype of F. The arg-list in a retry-spec specifies parameters to be passed
to the class's init
method; as with new
, the arg-list
can be omitted if the class's init
method has no required
arguments.
A retry-stmt is executed as follows:
new T(A)
,
where T and A are the type-descriptor in the type-parameter and the arg-list
respectively.r.shouldRetry(e)
. If the result is true, go back to the previous
step. If the result is false, then continue with the fail.
If the type-parameter is omitted, it defaults to
DefaultRetryManager
defined in langlib error module.
Ballerina provides language support for distributed transactions. A global transaction consists of one or more transaction branches, where a branch corresponds to work done by a single strand of a Ballerina program that is part of the global transaction. Every transaction branch is uniquely identified by an XID, which consists of an identifier of the global transaction and an identifier of a branch with the global transaction.
At runtime, there is a mapping from each strand to a transaction stack, which is a stack of transaction branches. A strand is in transaction mode, if its transaction stack is non-empty; the top of a strand's transaction stack is the current transaction branch for that strand.
Static checking is based on the idea of a transactional scope: this is a lexical
region of the program where it is known that at runtime the strand executing the
region will always be in transactional mode. A function with the
transactional
qualifier can only be called in a transactional
scope; this includes function calls using start
.
A running instance of a Ballerina program includes a transaction manager. This may run in the same process as the Ballerina program or in a separate process. It should not be connected over an unreliable network. The transaction manager of a program is responsible for managing the transaction branches of all the program's strands.
When a global transaction involves multiple Ballerina programs, there must be a
network protocol that allows that transaction managers for each Ballerina
program to communicate. The protocol must also allow a remote method or resource
method on a service object to be informed of the global transaction, if any, to
which it belongs. When a listener object determines that a remote method or
resource method of a service object is to be a part of a global transaction
manager, then it will create a new transaction branch, join it to the global
transaction and push that on the transaction stack for the strand on which the
method is called; if the method is declared as transactional
, the
listener will not call the method unless it can do this. Similarly, the protocol
must allow a remote method on a client object that is called in transaction mode
to send information about the global transaction of which it is part; if the
method is declared as transactional
, then as usual the method can
only be called in transaction mode; it also implies that the client object
implementation is transaction-aware, and that the recipient of the message sent
by the remote method must join the global transaction. A global transaction can
combine Ballerina programs with programs written in other programming languages
provided they agree on this network protocol.
A strand in transaction mode can register commit or rollback handlers with the transaction manager; the handlers are functions which will be called by the transaction manager when the decision has been made whether to commit or rollback the global transaction to which the current transaction branch belongs.
Note that internal storage in Ballerina is not transactional. Rolling back a transaction does not automatically restore the mutable values are variables to their state when the transaction started. It is up to the programmer to do this using commit or rollback handlers.
When a new strand is created by a named-worker-decl that includes a
transactional
qualifier or by using start
to call a
function with a transactional
qualifier, a new transaction branch
will be created and joined to the global transaction; this transaction branch
will be pushed onto transaction stack for the newly created strand.
Ballerina also has the concept that a transaction may be part of a sequence of retries, where the last transaction can complete with either a rollback or commit, and the preceding transactions all completed with a rollback.
The semantics of Ballerina's transactional language features are defined in terms of the following abstract operations on the transaction manager. These operations cover only the interface between the application and the transaction manager, not the interface between the resource manager and the application. These operations are all performed by a Ballerina program in the context of a strand. The transaction manager supports additional operations, which are defined in the langlib transaction module.
The Begin operation starts a global transaction and pushes a transaction branch for the new global transaction onto the current strand's transaction stack. This transaction branch is the initiating branch of the global transaction. The current strand will be in transaction mode.
The Begin operation has an optional parameter that contains information about the previous transactions in a sequence of retries.
The Commit operation is only called in transaction mode, and when the current transaction branch is the initiating branch of a global transaction. The Commit operation always removes the topmost transaction branch from the strand's transaction stack. The Commit operation initiates a commit of the global transaction that the current transaction branch belongs to.
When the commit is initiated, the transaction manager runs a two-phase commit protocol if necessary. First, it requests each participant to prepare to commit and waits for the participants to respond. There are two possibilities. If it receives a response from each participant that it is prepared to commit, then the transaction manager will make a decision to commit. If it receives a response from any participant that it cannot commit or it gives up waiting for a response from any participant, then the transaction manager will make a decision to rollback; in this case, it will create an error value for the cause of the rollback.
Once the transaction manager has made a decision whether to commit, it calls the handlers that were registered with it for for the branches of the global transaction that it is managing; the commit handlers are called if the decision was to commit and the rollback handlers are called if the decision was to rollback. The error value for the cause of the rollback will be passed as an argument to the call of each rollback handler. At this point, the Commit operation can return. The transaction manager will also communicate its decision to the transaction's participants, which will result in them running commit or rollback handlers for their branches.
The Commit operation has an optional parameter which is a RetryManager object or nil, which defaults to nil. If this parameter is non-nil and the decision is to rollback, then the transaction manager will call the RetryManager object's shouldRetry method, record the result and then pass it as the willRetry parameter to any rollback handlers.
If the transaction manager decided to rollback, then the Commit operation returns the error value for the reason for the rollback. Otherwise, it returns nil.
The Rollback operation is only called in transaction mode, and when the current transaction branch is the initiating branch of a global transaction. The Rollback operation always removes the topmost transaction from the strand's transaction stack. The Rollback operations initiates a commit of the global transaction that the current transaction branch belongs to. The transaction manager calls the rollback handlers that were registered with it for the branches of the global transaction that it is managing. The Rollback operation has a parameter which is either an error value or nil; this is passed as an argument to any rollback handlers.
The Rollback operation also has an optional parameter which is a RetryManager object or nil, which defaults to nil. If this parameter is non-nil, then the transaction manager will call the RetryManager object's shouldRetry method, record the result and then pass it as the willRetry parameter to any rollback handlers.
A transaction is performed using a transaction statement. The semantics of the transaction statement guarantees that every transaction manager Begin operation will be paired with a Rollback or Commit operation.
transaction-stmt := transaction
statement-block
The statement-block must contain at least one commit-action. In addition, the compiler must be able to verify that any exit out of the statement-block that is neither a panic nor a fail will have executed a commit-action or rollback-statement.
A transaction-stmt is executed as follows:
The statement-block of a transaction-stmt is a transactional scope, except for any statements that could potentially be executed after the execution of a commit-action or rollback-stmt.
It is an error for a transactional-stmt to appear in a transactional scope. However, it is not an error if the current strand is in transaction mode when a transaction-stmt is executed: in this case, the Begin() operation performed by the transaction-stmt will, as usual, start a new transaction, which will be completely independent of any existing transactions.
retry-transaction-stmt := retry
retry-spec transaction-stmt
A retry-transaction-stmt is the same as a retry-stmt containing a transaction-stmt, except as follows.
commit-action := commit
A commit-action
is only allowed if it is lexically within the
statement-block
of a transaction-stmt.
Evaluating the commit-action
performs the transaction manager
Commit() operation; the result of this operation is the result of the
commit-action
. The type of the result is
transaction:Error?
. Note that the commit action does not alter the
flow of control.
rollback-stmt :=rollback
[expression];
A rollback-stmt
is only allowed if it is lexically within the
statement-block
of a transaction-stmt.
The rollback-stmt
performs the transaction manager Rollback(e)
operation, where e is the result of executing the expression
, if
present, and nil otherwise. Note that like the commit action, the rollback
statement does not alter the flow of control.
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.
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 constrained.
module-part := import-decl* other-decl* other-decl := listener-decl | service-decl | function-defn | module-type-defn | module-class-defn | module-var-decl | module-const-decl | module-enum-decl | module-xmlns-decl | annotation-decl
import-decl :=import
[org-name/
] module-name [as
import-prefix];
import-prefix := module-prefix |_
module-prefix := identifier | predeclared-prefix org-name := identifier module-name := identifier (.
identifier)* qualified-identifier := module-prefix:
identifier
If org-name is omitted, it is defaulted from the organization of the importing module.
A module-prefix
is a name that is used locally within the source of
a module to refer to another module. A module-prefix
in a
qualified-identifier must refer to a module-prefix
specified in an
import-declaration in the same source part.
An import-prefix of _
causes the module to be imported without
making its symbols available via a module-prefix. In this case, the effect of
importing the module will be just to cause the module to be included in the
program and initialized. It is an error for a source-part to import a module
using a module-prefix and then not to use that module-prefix.
A module-prefix
declared by an import-decl
is in the
same symbol space as a xmlns-namespace-prefix
declared by an
xmlns-decl
. This symbol space is distinct from a module's main
symbol space used by other declarations.
It is an error for a module to directly or indirectly import itself. In other words, the directed graph of module imports must be acyclic.
predeclared-prefix :=boolean
|decimal
|error
|float
|future
|int
|map
|object
|stream
|string
|table
|transaction
|typedesc
|xml
A reserved keyword t
that is a predeclared-prefix is
predeclared as referring to the lang.t
lang library
module, but this can be overridden by an import-decl. A predeclared-prefix can
be used as a module-prefix without using a QuotedIdentifier.
A Ballerina program consists of one or more modules; one of these modules is distinguished as the root module. The source code for a module uses import declarations to identify the modules on which it depends directly. At compile-time, a root module is specified, and the modules comprising the program are inferred to be those that the root module imports directly or indirectly. The directed graph of module imports must be acyclic.
Program execution may terminate successfully or unsuccessfully. Unsuccessful program termination returns an error value. Program execution consists of two consecutive phases: an initialization phase and a listening phase.
Module initialization is performed by calling an initialization function, which is synthesized by the compiler for each module. Module initialization can fail, in which case the initialization function returns an error value. The initialization phase of program execution consists of initializing each of the program's modules. If the initialization of a module is unsuccessful, then program execution immediately terminates unsuccessfully, returning the error value returned by the initialization function.
The initialization of a program's modules is ordered so that a module will not be initialized until all of the modules on which it depends have been initialized. (Such an ordering will always be possible, since the graph of module imports is required to be acyclic.) The order in which modules are initialized follows the order in which modules are imported so far as is consistent with the previous constraint.
A module's initialization function performs expression evaluation so as to
initialize the identifiers declared in the module's declarations; if evaluation
of an expression completes abruptly, then the module initialization function
immediately returns the error value associated with the abrupt completion. If a
module defines a function named init
, then a module's
initialization function will end by calling this function; if it terminates
abruptly or returns an error, then the module's initialization function will
return an error value. Note that the init
function of the root
module will be the last function called during a program's initialization phase.
This specification does not define any mechanism for processing the program
command-line arguments typically provided by an operating system. The Ballerina
standard library provides a function to retrieve these command-line arguments.
In addition, the Ballerina platform provides a convenient mechanism for
processing these arguments. This works by generating a new command-line
processing module from the specified root module. The init
function of the generated module retrieves the command-line arguments, parses
them, and calls a public function of the specified root module (typically the
main
function). The parsing of the command-line arguments is
controlled by the declared parameter types, annotations and names of the public
functions. The generated module, which imports the specified root module,
becomes the new root module.
A configuration is supplied as input to program execution. A
configuration consists of mapping from names of configurable
module-level variables to values. The values in a configuration always belong to
the type anydata&readonly
. The value for a variable in the
configuration is used during module initialization to initialize the variable
instead of the value specified in the module. A configurable module-level
variable may require that a configuration include a value for it. Except for
this requirement, a configuration may be empty. Before initializing any module,
the initialization phase must check that there is a value of the correct type
supplied for every configurable module-level variable that requires
configuration. If not, the module initialization phase terminates unsuccessfull.
If the initialization phase of program execution completes successfully, then execution proceeds to the listening phase, which is described in the next section. The termination of the listening phase, which may be successful or unsuccessful, terminates the program execution.
Service objects support network interaction using remote methods and resource methods. Listeners provide the interface between the network and service objects. A listener object receives network messages from a remote process according to some protocol and translates the received messages into calls on the remote and resource methods of service objects that have been attached to the listener object. It is up to the listener object to determine how this translation happens; the type of the listener object constrains the type of a service that can be attached to the listener. (This constraint cannot yet be fully expressed by Ballerina's type system.)
A service object's remote or resource method uses its return value to indicate to the listener what further handling of the network message is needed. An error return value is used to indicate that the method encountered some sort of error in handling the network message. When a service object is returned, it means that the listener should use that service object to further handle the network message. When no further handling is needed, the return value should be nil.
The return value can also be used to supply a response to the network message. This has the limitation that the method cannot control what happens if there is an error in sending a response. It also has the limitation that it cannot handle complex message exchange patterns, although returning multiple responses to a single request can be modelled by returning a stream. A listener object can avoid these limitations by passing a client object as a parameter to the service object's remote or resource method; the service object then makes calls on the remote methods of the client object in order to send a response back to the client.
The methods defined by the Listener object type allow for the management of the lifecycle of a listener object and its attached services. A listener declaration registers a listener object with a module, so that it can be managed by the module. The runtime state of each module includes a list of listener objects that have been registered with the module. A listener object that has been registered with a module is called a module listener
If at the start of the listening phase of program execution there are no module
listeners, then the listening phase immediately terminates successfully.
Otherwise, the start
method of each module listener is called; if
any of these calls returns an error value, then the listening phase terminates
unsuccessfully with this error value as its return value.
The listening phase of program execution continues until either the program
explicitly exits, by calling a standard library function, or the user explicitly
requests the termination of the program using an implementation-dependent
operating system facility (such as a signal on a POSIX system). In the latter
case, the gracefulStop
or immediateStop
method of
each registered listener will be called before termination.
listener-decl := metadata [public
]listener
[type-descriptor] variable-name=
expression;
A listener-decl
declares a module listener. A module listener
declares a variable in a similar way to a final variable declaration, but the
type of the variable is always a subtype of the Listener object type,
and it has the additional semantic of registering the variable's value with the
module as a listener. As with a variable declared in a final variable
declaration, the variable can be referenced by a variable-reference, but cannot
be assigned to. A module may have multiple multiple listeners.
If the type-descriptor is present it specifies the static type of the variable;
if it is not present, the the static type is the static type of
expression
. In both cases, the static type is constrained to be a
listener object type.
When a listener-decl is initialized as part of module initialization, its expression is evaluated. If expression evaluation completes abruptly, then module initialization fails. Otherwise the variable is initialized with the result of the evaluation.
service-decl := metadata [isolated-qual]service
[type-descriptor] [attach-point]on
expression-list object-constructor-block attach-point := absolute-resource-path | string-literal absolute-resource-path := root-resource-path | (/
resource-path-segment-name)+ root-resource-path :=/
expression-list := expression (,
expression)*
A service-decl
creates a service object and attaches it to one or more
listeners.
The static type S for the constructed service object is specified by the
type-descriptor if present, and otherwise is the union of a type inferred from
each expression in the expression list as follows. Each expression in the
expression-list must have a type Listener<T,A>|E where T is a subtype of
service object {}
, A is a subtype of
string[]|string|()
and E is a subtype of error
; the
inferred type is T. The object-constructor-block has the same semantics as in an
object-constructor-expr with a service
qualifier and a
type-reference that refers to S. If the service-decl includes
isolated
, then it is equivalent to an object-constructor-expr with
an isolated
qualifier as well as a service
qualifier.
The attach-point
determines the second argument passed to the
attach
method: if the attach-point
is absent, then the
argument is nil; if it is a string-literal
, then the argument is a
string; otherwise, it is an absolute-resource-path
and the argument
is an array of strings, with one string for each
resource-path-segment
. Attaching service objects
s1,...,sn with absolute
resource paths p1,...,pn
is equivalent to attaching a single service object with n
get
resource methods to /
, where the i-th
resource method has a path pi (an
absolute-resource-path
is turned into a
relative-resource-path
with the same
resource-path-segment
s) and returns
si.
A service-decl is initialized as part of module initialization as follows. The
object-constructor-block is evaluated as in an object-constructor-expr resulting
in a service object s
. Then for each expression in
expression-list:
obj
belonging to a listener object type;obj
is registered as a module listener
(registering the same object multiple times is the same as registering it
once);s
is then attached to obj
using obj
's attach
method;attach
fails, then module initialization
fails.A listener object can use the isolated bit of a service object and of a service object's remote method to determine whether it is safe to make concurrent calls to the remote object or the remote method.
The compiler may infer that a service object or a service object's remote method
is isolated even it is not explicitly declared as such. In doing so, it analyzes
a module to determine whether there is a set of declarations and definitions in
the module, where each of them is a service-decl
,
module-var-decl
, module-class-defn
function-defn
or method-defn
, and none of them
explicitly specified an isolated
qualifier, such that if all them
explicitly specified an isolated
qualifier, then they all would
meet the requirements for isolated functions
and isolated objects. If so, then the isolated bit of values resulting from them
is set in the same way as if isolated
qualifiers had been
explicitly specified.
This inference is purely an optimization to improve service concurrency and is
subject to two constraints. First, any inferences do not affect the static types
visible to other modules. Second, anything explicitly declared as
isolated
must satisfy the requirements of this specification
without without relying on inference.
function-defn := metadata [public
] function-qualsfunction
identifier function-signature function-defn-body function-defn-body := block-function-body | expr-function-body;
| external-function-body;
If a module has a function-defn with an identifier of init
, it is
called called automatically by the system at the end of the initialization of
that module; if this call returns an error, then initialization of the module
fails. The following special requirements apply to the init
function of a module: it must not be declared public
; its return
type must both be a subtype of error?
and contain ()
;
it must have no parameters.
If the function-quals includes transactional
, then
expr-function-body and the block-function-body are transactional scopes.
module-type-defn := metadata [public
]type
identifier type-descriptor;
A module-type-defn binds the identifier to the specified type descriptor. The binding is in the main symbol space. The type-descriptor is resolved as part of module initialization.
A class is a type descriptor that in addition to describing an object type also defines a way to construct an object belonging to the type; in particular, it provides the method definitions that are associated with the object when it is constructed.
module-class-defn := metadata [public
] class-type-qualsclass
identifier{
class-member*}
class-type-quals := (distinct
|readonly
| isolated-qual | object-network-qual)* class-member := object-field | method-defn | remote-method-defn | resource-method-defn | object-type-inclusion
It is an error for a keyword to appear more than once in
class-type-quals
.
As in an object constructor expression, a visibility qualifier of
private
can be used within a class definition; this means that the
visibility region consists of all method definitions in the class definition. If
class has private fields or methods, then it is not possible to define another
object type descriptor that is a subtype of the class's type.
If class-type-quals
contains readonly
, then an object
constructed using the class will have its read-only bit set; the effective type
of each field is thus the intersection of the specified type and
readonly
; furthermore, an object shape belongs to the class's type
only if its read-only bit is set.
If class-type-quals
contains isolated
, then the
defined object type is an isolated type, and an object constructed using the
type will have its isolated bit set. An object-field
or
method-defn
occurring as a class-member
in an isolated
class is subject to same requirements as when it occurs as an
object-member
in an object-constructor-expr
that is
explicitly isolated.
If an object-type-inclusion
references a type-descriptor that is a
class, then only the type of the class is included: the definitions of the
methods and any initializers for the fields are not included. If a class uses an
object-type-inclusion to include an object type T, then each method declared in
T must be defined in the class using a method-defn
with the same
visibility. If T has a method or field with module-level visibility, then C must
be in the same module. If the class is readonly (i.e.
class-type-quals
includes readonly
), then an
object-type-inclusion
in the class is allowed to directly or
indirectly reference a readonly class.
If class-type-quals
contains distinct
, then the
primary type-id of the class will the type-id of that occurrence of
distinct
, and the secondary type-ids will be the union of the
type-ids of the included object types. If class-type-quals
does not
contain distinct
, then the type-ids of the class come from the
included object types in the same way as with an object type descriptor.
An object of a class is initialized by:
init
method, if there is one
The parameter list of an init
method within an
module-class-defn
is not required to be empty, unlike within an
object-constructor-expr
.
module-var-decl := module-init-var-decl | module-no-init-var-decl
A module-var-decl declares a variable. The scope of variables declared in a
module-var-decl is the entire module. Module variables are not allowed to be
public. The variable may be initialized in the declaration or within the
module's init
function. If final
is specified, then it
is not allowed to assign to the variable after it is initialized.
module-init-var-decl := metadata [public
] module-init-var-quals typed-binding-pattern=
module-var-init;
module-init-var-quals := (final
| isolated-qual |configurable
)* module-var-init := expression |?
A module-init-var-decl declares and initializes a variable. It is an error for a
keyword to appear more than once in module-init-var-quals
. If the
typed-binding-pattern uses var
, then the type of the variable is
inferred from the static type of expression
; if the module-var-decl
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 a module-init-var-decl includes an isolated-qual, then the variable declared
is isolated. In this case, public
must not be specified, the
binding-pattern in the typed-binding-pattern must be just a variable-name, and
the expression must be an isolated expression. A variable declared as an
isolated variable can be accessed only within a lock-stmt. When an isolated-qual
occurs in a position where the grammar would allow it to be parsed as part of
module-init-var-quals or typed-binding-pattern, the former parse is used.
If configurable
is specified, then the initializer specified in the
module-var-init may be overridden at the time of module initialization by a
value supplied when the program is run. If such a value is supplied, then the
expression in the module-var-init is not evaluated and the variable is
initialized with the supplied value instead. A module-var-init of ?
is allowed only when configurable
is specified and means that a
configurable value must be supplied for this variable. If
configurable
is specified, then the typed-binding-pattern must use
an explicit type-descriptor rather than var
and the binding-pattern
must be just a variable-name. The type specified by the type-descriptor must be
a subtype of anydata
.
module-no-init-var-decl := metadata [public
] [final
] type-descriptor variable-name;
A module variable declared with module-no-init-var-decl
must be
initialized in the module's init
function. It must be definitely
assigned at each point that the variable is referenced. If final
is
specified, then the variable must not be assigned more than once.
module-const-decl := metadata [public
]const
[type-descriptor] identifier=
const-expr;
A module-const-decl declares a compile-time constant. A compile-time constant is an named immutable value, known at compile-time. A compile-time constant can be used like a variable, and can also be referenced in contexts that require a value that is known at compile-time, such as in a type-descriptor or in a match-pattern.
The type of the constant is the intersection of readonly
and the
singleton type containing just the shape of the value named by the constant. The
type of the constant determines the static type of a variable-reference-expr
that references this constant.
If type-descriptor is present, then it provides the contextually expected type for the interpretation of const-expr. It is a compile-time error if the static type of const-expr is not a subtype of that type. The type-descriptor must specify a type that is a subtype of anydata. Note that the type-descriptor does not specify the type of the constant, although the type of the constant will all be a subtype of the type specified by the type-descriptor.
module-enum-decl := metadata [public
]enum
identifier{
enum-member (,
enum-member)*}
enum-member := metadata identifier [=
const-expr]
A module-enum-decl provides a convenient syntax for declaring a union of string constants.
Each enum-member is defined as compile-time constant in the same way as if it had been defined using a module-const-decl. The result of evaluating the const-expr must be a string. If the const-expr is omitted, it defaults to be the same as the identifier.
The identifier is defined as a type in the same was as if it had been defined by a module-type-defn, with the type-descriptor being the union of the constants defined by the members.
If the module-enum-decl is public, then both the type and the constants are public.
So for example:
public enum Color { RED, GREEN, BLUE }
is exactly equivalent to:
public const RED = "RED"; public const GREEN = "GREEN"; public const BLUE = "BLUE"; public type Color RED|GREEN|BLUE;
module-xmlns-decl := xmlns-decl
A module-xmlns-decl
declares an XML namespace prefix with module
scope. It applies only to the source part in which it occurs, as with an
import-decl.
The semantics of xmlns-decl are described in the XML namespace declaration statement section.
Ballerina allows metadata to be attached to a construct by specifying the metadata before the construct.
metadata := [DocumentationString] [annots]
There are two forms of metadata: documentation and annotations.
annots := annotation+
annotation := @
annot-tag-reference annot-value
Annotations provide structured metadata about a particular construct. Multiple annotations can be applied to a single construct. An annotation consists of a tag and a value.
annotation-decl := metadata [public
] [const
]annotation
[type-descriptor] annot-tag [on
annot-attach-points];
annot-tag := identifier
An annotation-decl declares an annotation tag. Annotations tags are in a separate symbol space and cannot conflict with other module level declarations and definitions. The annotation tag symbol space is also distinct from the symbol space used by module prefixes and XML namespace prefixes.
The type-descriptor specifies the type of the annotation tag. The type must be a
subtype of one of the following three types: true
,
map<value:Cloneable>
,
map<value:Cloneable>[]
. If the type-descriptor is omitted,
then the type is true
.
annot-tag-reference := qualified-identifier | identifier annot-value := [mapping-constructor-expr]
An annot-tag-reference in an annotation must refer to an annot-tag declared in an annotation declaration. When an annot-tag-reference is a qualified-identifier, then the module-prefix of the qualified-identifier is resolved using import declarations into a reference to a module, and that module must contain an annotation-decl with the same identifier. An annot-tag-reference that is an identifier rather than a qualified-identifier does not refer to an annotation defined within the same module. Rather the compilation environment determines which identifiers can occur as an annotation-tag-reference, and for each such identifier which module defines that annotation tag.
Each annotation has a value. For every construct that has an annotation with a particular tag, there is also an effective value for that annotation tag, which is constructed from the values of all annotations with that tag that were attached to that construct. The effective value belongs to the type declared for the annotation tag. The type of the annotation tag determines the type of the annotation value and whether multiple annotations with the same tag on a single construct are allowed. For an annotation tag declared with type T, if T is M[] for some type mapping type M, then the type of the annotation value is M, multiple annotations are allowed, and the effective value is an array of the values for each annotation; otherwise the type of the annotation value is T, multiple annotations are not allowed, the effective value is the value of the single annotation.
If the type of the annotation value is true
, then a
mapping-constructor-expr is not allowed, and the annotation value is
true
. Otherwise the type of the annotation value must be a mapping
type M; if a mapping-constructor-expr is not specified, then it defaults to
{ }
; the mapping-constructor-expr must have static type M and the
annotation value is the result of evaluating the mapping-constructor-expr with M
as the contextually expected type.
If the annotation-decl for a tag specifies const
, then a
mapping-constructor-expr in annotations with that tag must be a const-expr and
is evaluated at compile-time with the semantics of a const-expr. Otherwise, the
mapping-constructor-expr is evaluated when the annotation is evaluated and the
ImmutableClone abstract operation is applied to the result.
An annotation applied to a module-level declaration is evaluated when the module is initialized. An annotation applied to a service constructor is evaluated when the service constructor is evaluated. An annotation occurring within a type descriptor is evaluated when the type descriptor is resolved.
annot-attach-points := annot-attach-point (,
annot-attach-point)* annot-attach-point := dual-attach-point | source-only-attach-point dual-attach-point := [source
] dual-attach-point-ident dual-attach-point-ident :=type
|class
| [object
|service
remote
]function
|parameter
|return
|service
| [object
|record
]field
source-only-attach-point :=source
source-only-attach-point-ident source-only-attach-point-ident :=annotation
|external
|var
|const
|listener
|worker
The annot-attach-points
specify the constructs to which an
annotation can be attached.
When an attachment point is prefixed with source
, then the
annotation is attached to a fragment of the source rather than to any runtime
value, and thus is not available at runtime. If any of the attachment points
specify source
, the annotation-decl must specify
const
.
When an attachment point is not prefixed with source, then the annotation is accessible at runtime by applying the annotation access operator to a typedesc value.
The available attachment points are described in the following table.
Attachment point name | Syntactic attachment point(s) | Attached to which type descriptor at runtime |
type | module-type-defn, module-enum-decl, type-cast-expr | defined type |
class | module-class-defn | defined type (which will be type of objects constructed using this class) |
function | function-defn, method-decl, method-defn, anonymous-function-expr | type of function |
object function | method-decl, method-defn | type of function |
service remote function | method-defn with remote qualifier on service object | type of function, on service value |
return | returns-type-descriptor | indirectly to type of function |
parameter | individual-param, rest-param | indirectly to type of function |
service | service-decl, object-constructor-expr with service qualifier | type of service |
field | individual-field-descriptor, object-field-descriptor | type of mapping or object |
object field | object-field-descriptor, object-field | type of object |
record field | individual-field-descriptor | type of mapping |
listener | listener-decl | none |
var | module-var-decl, local-var-decl-stmt, let-var-decl | none |
const | module-const-decl, enum-member | none |
annotation | annotation-decl | none |
external | external-function-body | none |
worker | named-worker-decl, start-action | none |
A documentation string is an item of metadata that can be associated with module-level Ballerina constructs and with method declarations. The purpose of the documentation strings for a module is to enable a programmer to use the module. Information not useful for this purpose should be provided in in comments.
A documentation string has the format of one or more lines each of which has a
#
optionally preceded by blank space.
The documentation statement is used to document various Ballerina constructs.
DocumentationString := DocumentationLine +
DocumentationLine := BlankSpace* #
[Space] DocumentationContent
DocumentationContent := (^ 0xA)* 0xA
BlankSpace := Tab | Space
Space := 0x20
Tab := 0x9
A DocumentationString
is recognized only at the beginning of a
line. The content of a documentation string is the concatenation of the
DocumentationContent
of each DocumentationLine
in the
DocumentationString
. Note that a single space following the # is
not treated as part of the DocumentationContent.
The content of a DocumentationString
is parsed as Ballerina
Flavored Markdown (BFM). BFM is also used for a separate per-module
documentation file, conventionally called Module.md
.
Ballerina Flavored Markdown is GitHub Flavored Markdown, with some additional conventions.
In the documentation string attached to a function or method, there must be
documentation for each parameter, and for the return value if the return value
is not nil. The documentation for the parameters and a return value must consist
of a Markdown list, where each list item must have the form ident -
doc
, where ident is either the parameter name or return, and doc is the
documentation of that parameter or of the return value.
The documentation for an object must contain a list of fields rather than parameters. Private fields should not be included in the list.
BFM also provides conventions for referring to Ballerina-defined names from
within documentation strings in a source file. An identifier in backticks
`X`
, when preceded by one of the following words:
type
service
variable
var
annotation
module
function
parameter
is assumed to be a reference to a Ballerina-defined name of the type indicated
by the word. In the case of parameter
, the name must be unqualified
and be the name of a parameter of the function to which the documentation string
is attached. For other cases, if the name is unqualified it must refer to a
public name of the appropriate type in the source file's module; if it is a
qualified name M:X, then the source file must have imported M, and X must refer
to a public name of an appropriate type in M. BFM also recognizes
`f()`
as an alternative to function `f`
. In both
cases, f can have any of the following forms (where `m` is a module import, `x` is a
function name, `T` is an object type name, and `y` is a method name):
x() m:x() T.y() m:T.y()
Example
# Adds parameter `x` and parameter `y` # + x - one thing to be added # + y - another thing to be added # + return - the sum of them function add (int x, int y) returns int { return x + y; }
The Ballerina platform may define additional conventions, in particular relating
to headings with particular content. For example, a heading with a content of
Deprecated
can be used to provide information about the deprecation
of the name to which the documentation string is attached.
Modules in the ballerina
organization with a module name starting
with lang.
are reserved for use by this specification. These
modules are called the lang library.
The lang library comprises the following modules. With the exception of the
lang.value
, lang.transaction
and
lang.runtime
modules, each corresponds to a basic type.
lang.array
for
basic type listlang.boolean
for
basic type booleanlang.decimal
for basic type decimallang.error
for
basic type errorlang.float
for
basic type floatlang.future
for basic type futurelang.int
for
basic type intlang.map
for
basic type mappinglang.object
for basic type objectlang.stream
for basic type streamlang.string
for basic type stringlang.table
for
basic type tablelang.typedesc
for
basic type typedesclang.xml
for
basic type xmllang.runtime
lang.transaction
lang.value
Modules in the lang library can make use generic typing. Since generic typing
has not yet been added to Ballerina, the source code for the modules use an
annotation to describe generic typing as follows. When a module type definition
has a @typeParam
annotation, it means that this type serves as a
type parameter when it is used in a function definition: all uses of the type
parameter in a function definition refer to the same type; the definition of the
type is an upper bound on the type parameter. A parameter of a function
definition can be annotated with an @isolatedParam
annotation; this
is allowed when the function is declared as isolated and the type of the
parameter is a function type; the meaning is that when the function is called in
a context that requires it to be isolated, then the argument supplied for the
parameter must also be isolated. In effect, the function is parameterized with
the isolated qualifier.
Note We plan to provide full support for generic types in a future version of this specification.
A module in the lang library can provide types that are built-in in the
sense that their meaning is defined by this specification. Each such built-in
type is a subtype of a single basic type; a built-in type that is a subtype of a
basic type B
is provided by the module
lang.B
.
The built-types provided by lang library modules are described in the following table.
Basic type | Type name | Criteria for v to belong to type |
---|---|---|
int | Unsigned8 | 0 ≤ v ≤ 255 |
Signed8 | -128 ≤ v ≤ 127 | |
Unsigned16 | 0 ≤ v ≤ 65,535 | |
Signed16 | -32,768 ≤ v ≤ 32,767 | |
Unsigned32 | 0 ≤ v ≤ 4,294,967,295 | |
Signed32 | -2,147,483,648 ≤ v ≤ 2,147,483,6487 | |
string | Char | v has length 1 |
xml | Element | v is an element singleton |
ProcessingInstruction | v is a processing instruction singleton | |
Comment | v is a comment singleton | |
Text | v is either the empty xml value or a text singleton |
Each built-in type has a type definition in the module that provides it. The
type descriptor of the type definition is the corresponding basic type. The type
definition has a @builtinSubtype
annotation, which indicates that
the meaning of the type name is built-in, as specified in the above table,
rather than coming from its type descriptor. It is an error to use the
@builtinSubtype
annotation except in a lang library module.
So, for example, the lang.int
module would include the definition:
@builtinSubtype type Signed32 int;
Semantically, these types behave like a predefined type that can be referenced
by an unqualified name, such as byte
. Syntactically, these types
are referenced by a type-reference
in the same way as if their
definitions were not built-in. A built-in type T
which is a subtype
of basic type B
can be referenced by a type-reference
M:T
, where M
is a module-prefix referring to module
ballerina/lang.B
.
For convenience, this specification refers to the built-in subtype T provided by
the module for basic type B as B:T. For example, in this specification
int:Signed32
refers to the Signed32
built-in subtype
of int
, which is provided by the lang.int
module.
The int:Unsigned8
type is equivalent to the predefined
byte
type.
new
to the name of a class defined by a class definition. The
methods and fields of an object are not in a single symbol space. Classes and
object constructors can declare fields as final.readonly
type has been added. Fields of records can also be
declared as readonly
.isolated
; this works in conjunction with readonly
to
support concurrency safety.error
keyword before the type name.__init
method of object and the __init
function
of modules have been renamed to init
.init
function.join
clause,
order by
clause, limit
clause and on
conflict
clause.< <= > >=
) have been
extended to apply to more than just numeric types.xml:Text
can be implicitly converted to a
string.public
.fail
statement has been added, along with a on
fail
clause for compound statements. If a check
expression
or action fails, it behaves like a fail
statement rather than a
return
statement.configurable
, which allows their initial values to
be specified at runtime.xml
type.xml
type can have a type parameter specifying the item
types.===
for xml has changed.x@
for accessing the attributes of an XML element has
been removed.lock
statement has been added.\u[CodePoint]
to \u{CodePoint}
so as
to align with ECMAScript. Although this is an incompatible change, the previous
syntax was not implemented.never
type has been added.as _
to include a module in the
program without using its symbols.entries
lang library
function allows it to be iterated as a sequence of key-value pairs.handle
has been added.table<T>
type descriptor shorthand has been brought
back.check
called
checkpanic
, which panics rather than returns on error.?.
operator has been added for access to optional fields.record { }
is open to anydata
rather than anydata|error
.start
are treated as actions, and so are not
allowed within expressions.^"s"
.The specification has switched to a new versioning scheme. The n-th version of the specification released in year 20xy will be labelled 20xyRn.
T...
syntax allow trailing members of a specified type.!...
syntax, there are two flavours of record type
descriptor, which use different delimiters: record {| |}
allows any
mapping that has exclusively the specified fields, whereas record {
}
allows any mapping that includes the specified fields; the former can
use the T...
syntax, whereas the latter cannot. The
!...
is no longer allowed for record binding patterns and record
match patterns.T[!...]
to T[*]
.error<*>
can be used to specify an
error type whose subtype is inferred.'ident
have been removed (compile time
constants provide a more convenient approach).untaint
expression has been removed (this will be handled by
annotations instead).arg=
from arg:
, since the latter caused syntactic
ambiguities..@
binary operator has been added for accessing annotations
at runtime.typeof
operator has been added.typedesc
type now takes an optional type parameter.future
and stream
are now
optional.=external
in place of the curly braces.d
or f
to
indicate that it represents a value belonging to the decimal or float type
respectively.Structural types and values
match
statement has been redesigned.but
expression has been removed.is
expression for dynamic type testing has been added.anydata
type has been added, which is a union of simple and
structural types.anydata|error
, rather than
any
.anydata
), and === and !== for exact
equality.Error handling
any
type no longer includes error
.check
is now an expression.throw
statement has been replaced by the panic
statement try
statement has been replaced by the trap
expression__init
methods (which can return errors).Concurrency
done
statement has been removed.Endpoints and services
Miscellaneous changes
<<
, >>
,
>>>
, &
, |
, ^
,
~
) rather than = after the argument name.:
check
always handles an error by returning it,
not by throwing it.check
is allowed in compound assignment statements.lengthof
unary expression has been removed; the length
built-in method can be used instead.next
keyword has been changed to continue
....
and ..<
operators have been added for
creating integer ranges; this replaces the foreach statement's special treatment
of integer ranges.new
.T...
requires extra fields to be of type T
and !...
disallows extra fields.extern
. The
native
keyword has been removed.The vision for the Ballerina language includes a range of functionality that is not yet included in this specification.
stream
type
to allow queries over timestamped sequences of eventsProposals for new language features relating to this and other functionality are maintained in the specification's GitHub repository.
The following contributed to establishing the design principles of the language:
The following also contributed to the language in a variety of ways (in alphabetical order):