The Swift Scripting Language
A Swift script describes data, application components, invocations of applications components, and the inter-relations (data flow) between those invocations.
Data is represented in a script by strongly-typed single-assignment variables. The syntax superficially resembles C and Java. For example, { and } characters are used to enclose blocks of statements.
Conventions
The following conventions will be used throughout the guide when describing syntax:
-
Square brackets indicate optional items: [something optional]
-
Angle brackets indicate a labeled item that is to be substituted when writing actual code: <variableName> can be used to mean a, b, myVariable, etc.
-
Single quotes indicate a literal character or string when the literal could otherwise be interpreted as being part of a special syntax symbol: '[' means a literal left square bracket and not the beginning of an optional item.
-
The pipe symbol is used to indicate two or more mutually exclusive choices: A | C | B means either A or B or C.
-
Items in ALL_CAPS represent non-terminal sequences
-
The symbol := is used to define a non-terminal sequence: DIGITS := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Variables
Swift variables are used to represent data. All variables have a type, which is specified when the variable is declared. A variable can be declared (and optionally assigned a value) using the following syntax:
<type> <name> [= <value>];
Examples:
int a; string message = "A message";
Variables can be assigned a value at most once. The following Swift program will result in a compile-time error:
int a = 2; a = 3;
Data Types
Types in Swift can be atomic or composite. An atomic type can be either a primitive type or a mapped type. Swift provides a fixed set of primitive types, such as integer and string. A mapped type indicates that the actual data does not reside in CPU addressable memory (as it would in conventional programming languages), but in POSIX-like files. Composite types are further subdivided into structures and arrays. Structures are similar in most respects to structure types in other languages.
Primitive Types
type | contains | example |
---|---|---|
int |
32/64 bit integers (depending on system architecture) |
-1, 0, 9999999 |
string |
strings of text |
"This is string value" |
float |
a double-precision floating point number |
3.1, 4.5e-23 |
boolean |
true/false |
Mapped atomic types
Mapped atomic types are types that indicate data that resides in a (single) file. Users can define any number of mapped atomic types to distinguish between different types of files used in a Swift program. A mapped atomic type can be defined as follows:
type <typename>;
When declaring a mapped type, an optional mapper can be specified in order to describe the location of the file where data is stored. The following example declares a mapped atomic type and variable that points to a file named "shane.jpg":
type image; image photo <"shane.jpg">;
![]() |
See also: File Mapping, Mappers, External Type |
Structures
Structures are types that can be used to encapsulate a fixed number of values and are similar to structures in C. The syntax for declaring a structure is a follows:
type <structName> { [<type1> <fieldName1>; [<type2> <fieldName2>; [... [<typeN> <fieldNameN>]...]]] }
Fields in a structure can be accessed as follows:
<variable>.<fieldName>
The following example declares and uses a structure named "Employee" that holds some basic data:
type employee { string name; int id; string location; } employee e; e.name = "John Doe"; e.id = 1000; e.location = "Room 1401";
![]() |
See also: readData(), writeData() |
Arrays
Swift arrays are data types that contain a variable number of items of the same type. Each item in an array has an associated key or index. Arrays can be declared as follows:
<itemType>'['<keyType>']' <arrayVariableName>;
Currently, key types are restricted to primitive types. If the keyType is omitted, it defaults to int:
<itemType>'[]' <arrayVariableName>;
Arrays in Swift are automatically grown to accommodate the number of items stored in them. Additionally, arrays are sparse. In other words, array keys can be arbitrary values (as long as they match the declared key type).
Array items are accessed using the following syntax:
<arrayVariableName>'['<keyValue>']'
For example, the following snippet of code declares an array with integer keys (the default) that stores string values and adds a few items to it:
string[] array; array[0] = "Zero"; array[2] = "Two"; array[100] = "One hundred";
An alternative way of initializing arrays is with array expressions. When array expressions are used, indices are automatically created by Swift, starting from zero for the first array item and incrementing by one for each subsequent element:
string[] array = ["Zero", "One", "Two"] trace(array[0]); // "Zero" trace(array[1]); // "One" trace(array[2]); // "Two"
The following is an example of an array with strings as key values:
float[string] constants; constants["PI"] = 3.14159; constants["e"] = 2.71828;
![]() |
See also: Auto Key Type |
File Mapping
Variables with a mapped atomic type or a composite type containing mapped atomic types will have an associated physical file for every mapped data item. Mappers allow a user to specify how Swift data is associated with those physical files. Mappers are specified as part of the variable declaration as follows:
<type> <variableName> '<'<mapperName>[;<mapperParamName1>=<value> [,<mapperParamName2>=<value>[...]]]'>';
In the case when the type is a mapped atomic type, a simpler syntax can be used, since the mapping involves a single file:
<type> <variableName> '<'"<fileName>"'>';
In the case of more complex data structures, mapper implementations can use an algorithmic scheme to generate file names from the field names in the data structure. The following example shows how an array of structures is mapped by the simple_mapper:
type blob; // declare an atomic mapping type type employee { string name; // primitive type int id; blob data; // only the blob fields will have an associated file blob history; } // declare an array of structures mapped using the simple_mapper employee[] employees <simple_mapper; prefix = "edata", suffix = ".dat">; employees[0] = fetchEmployee("John Doe"); employees[1] = fetchEmployee("Richard Roe"); employees[2] = fetchEmployee("Paula Poe");
Swift data path | File name |
---|---|
employees[0].data |
edata_0000_data.dat |
employees[0].history |
edata_0000_history.dat |
employees[1].data |
edata_0001_data.dat |
employees[1].history |
edata_0001_history.dat |
employees[2].data |
edata_0002_data.dat |
employees[2].history |
edata_0002_history.dat |
If a mapper is not specified for a variable of a type that is mapped or contains mapped items, Swift automatically sets the mapper for that variable to the concurrent_mapper.
Swift provides a number of mappers that can be used in different scenarios. For a complete list, see the mappers section.
Procedures
Procedures in Swift allow encapsulation of re-usable parts of a program. Procedures have parameters and one or more return values. Procedures returning a single value are invoked using the following syntax:
<variableName> = <procedureName>([<parameter1>[, <parameter2>[, ...]]]);
When a procedure returns multiple values, the variable list to which the return values are assigned must appear in parentheses:
(<variableName1>[, <variableName2>[,...]]]) = <procedureName>(<parameters>);
Examples:
float a, b, c, d, e; a = f(); b = g(a); c = h(a, b); (d, e) = g(a, b);
Application Procedures
Application procedures are used to describe how Swift invokes external applications. Application procedures are defined as follows:
app (RETURNS) <procedureName>(PARAMETERS) { <applicationName> [<cmdlineParameter1>[<cmdlineParameter2>[...]]] [ stdout=<fileName1>][ stderr=<fileName2>]; } RETURNS := [<type1> <returnName1>[, <type2> <returnName2>[, ...]]] PARAMETERS := [<type1> <paramName1>[, <type2> <paramName2>[, ...]]]
The command line parameters following the application name are passed to the application on the command line, while the stdout and stderr parameters can be used to redirect the standard output/error streams of the process.
For example, the following example lists a procedure which makes use of the ImageMagick http://www.imagemagick.org/ convert command to rotate a supplied image by a specified angle:
image photo <"shane.jpg">; image rotated <"rotated.jpg">; app (image output) rotate(image input) { convert "-rotate" angle @filename(input) @filename(output); } rotated = rotate(photo, 180);
The convert application will be invoked as if the following was typed in a command prompt:
convert -rotate 180 shane.jpg rotated.jpg
The end result is:
For details about how Swift invokes applications, see Application Invocations.
![]() |
See also: Application Invocations, @filename() |
Arrays and Parallel Execution
Arrays of values can be declared using the [] suffix. Following is an example of an array of strings:
string pets[] = ["shane", "noddy", "leo"];
An array may be mapped to a collection of files, one element per file, by using a different form of mapping expression. For example, the filesys_mapper maps all files matching a particular unix glob pattern into an array:
file frames[] <filesys_mapper; pattern="*.jpg">;
The foreach construct can be used to apply the same block of code to each element of an array:
foreach f,ix in frames { output[ix] = rotate(f, 180);
Sequential iteration can be expressed using the iterate construct:
step[0] = initialCondition(); iterate ix { step[ix] = simulate(step[ix-1]); }
This fragment will initialise the 0-th element of the step array to some initial condition, and then repeatedly run the simulate procedure, using each execution’s outputs as input to the next step.
Associative Arrays
By default, array keys are integers. However, other primitive types are also allowed as array keys. The syntax for declaring an array with a key type different than the default is:
<valueType>[<keyType>] array;
For example, the following code declares and assigns items to an array with string keys and float values:
float[string] a; a["one"] = 0.2; a["two"] = 0.4;
In addition to primitive types, a special type named auto can be used to declare an array for which an additional append operation is available:
int[auto] array; foreach i in [1:100] { append(array, i * 2); } foreach v in array { trace(v); }
Items in an array with auto keys cannot be accessed directly using a primitive type. The following example results in a compile-time error:
int[auto] array; array[0] = 1;
However, it is possible to use auto key values from one array to access another:
int[auto] a; int[auto] b; append(a, 1); append(a, 2); foreach v, k in a { b[k] = a[k] * 2; }
Ordering of execution
Non-array variables are single-assignment, which means that they must be assigned to exactly one value during execution. A procedure or expression will be executed when all of its input parameters have been assigned values. As a result of such execution, more variables may become assigned, possibly allowing further parts of the script to execute.
In this way, scripts are implicitly parallel. Aside from serialisation implied by these dataflow dependencies, execution of component programs can proceed in parallel.
In this fragment, execution of procedures p and q can happen in parallel:
y=p(x); z=q(x);
while in this fragment, execution is serialised by the variable y, with procedure p executing before q.
y=p(x); z=q(y);
Arrays in Swift are more monotonic - a generalisation of being assignment. Knowledge about the content of an array increases during execution, but cannot otherwise change. Each element of the array is itself single assignment or monotonic (depending on its type). During a run all values for an array are eventually known, and that array is regarded as closed.
Statements which deal with the array as a whole will often wait for the array to be closed before executing (thus, a closed array is the equivalent of a non-array type being assigned). However, a foreach statement will apply its body to elements of an array as they become known. It will not wait until the array is closed.
Consider this script:
file a[]; file b[]; foreach v,i in a { b[i] = p(v); } a[0] = r(); a[1] = s();
Initially, the foreach statement will have nothing to execute, as the array a has not been assigned any values. The procedures r and s will execute. As soon as either of them is finished, the corresponding invocation of procedure p will occur. After both r and s have completed, the array a will be closed since no other statements in the script make an assignment to a.
Compound procedures
As with many other programming languages, procedures consisting of Swift script can be defined. These differ from the previously mentioned procedures declared with the app keyword, as they invoke other Swift procedures rather than a component program.
(file output) process (file input) { file intermediate; intermediate = first(input); output = second(intermediate); } file x <"x.txt">; file y <"y.txt">; y = process(x);
This will invoke two procedures, with an intermediate data file named anonymously connecting the first and second procedures.
Ordering of execution is generally determined by execution of app procedures, not by any containing compound procedures. In this code block:
(file a, file b) A() { a = A1(); b = A2(); } file x, y, s, t; (x,y) = A(); s = S(x); t = S(y);
then a valid execution order is: A1 S(x) A2 S(y). The compound procedure A does not have to have fully completed for its return values to be used by subsequent statements.
More about types
Each variable and procedure parameter in Swift script is strongly typed. Types are used to structure data, to aid in debugging and checking program correctness and to influence how Swift interacts with data.
The image type declared in previous examples is a marker type. Marker types indicate that data for a variable is stored in a single file with no further structure exposed at the Swift script level.
Arrays have been mentioned above, in the arrays section. A code block may be applied to each element of an array using foreach; or individual elements may be references using [] notation.
There are a number of primitive types:
type | contains |
---|---|
int |
integers |
string |
strings of text |
float |
floating point numbers, that behave the same as Java doubles |
boolean |
true/false |
Complex types may be defined using the type keyword:
type headerfile; type voxelfile; type volume { headerfile h; voxelfile v; }
Members of a complex type can be accessed using the . operator:
volume brain; o = p(brain.h);
Sometimes data may be stored in a form that does not fit with Swift’s file-and-site model; for example, data might be stored in an RDBMS on some database server. In that case, a variable can be declared to have external type. This indicates that Swift should use the variable to determine execution dependency, but should not attempt other data management; for example, it will not perform any form of data stage-in or stage-out it will not manage local data caches on sites; and it will not enforce component program atomicity on data output. This can add substantial responsibility to component programs, in exchange for allowing arbitrary data storage and access methods to be plugged in to scripts.
type file; app (external o) populateDatabase() { populationProgram; } app (file o) analyseDatabase(external i) { analysisProgram @o; } external database; file result <"results.txt">; database = populateDatabase(); result = analyseDatabase(database);
Some external database is represented by the database variable. The populateDatabase procedure populates the database with some data, and the analyseDatabase procedure performs some subsequent analysis on that database. The declaration of database contains no mapping; and the procedures which use database do not reference them in any way; the description of database is entirely outside of the script. The single assignment and execution ordering rules will still apply though; populateDatabase will always be run before analyseDatabase.
Data model
Data processed by Swift is strongly typed. It may be take the form of values in memory or as out-of-core files on disk. Language constructs called mappers specify how each piece of data is stored.