Table of Contents
This chapter describes the Scaly Programming Language.
The Scaly Programming Language is defined by the structure and semantics of the statements which can be used to write a Scaly Program.
On the lowest level, a Scaly program consists of a sequence of characters. The lexical structure of the Scaly Programming Language describes how the characters of the program code are combined to form a sequence of tokens and whitespaces.
The structure of the statements is defined by the grammar of the Scaly Programming Language.
The documentation you are reading sometimes refers to the reference implementation of the Scaly Programming Language, but an alternative implementation can choose to do things in another way, or to provide additional functionality like a command line playground which the reference implementation does not have.
Particularly, the standard library which comes with the reference implementation is not part of the Scaly Programming Language in a strict sense. Nevertheless, some elements like basic types, operators, and functions of this standard library like basic types are used in the code samples that are provided.
The reference implementation of the Scaly Programming Language, which the documentation you are reading is part of, compiles a Scaly program into an LLVM assembly language module which can be processed further using the LLVM tools.
A Scaly program consists of one or more files which contain the program code. The Scaly compiler compiles a Scaly program to a piece of executable code. How this code can be executed depends on the implementation of the Scaly compiler. A file is an object from which a Scaly compiler can read a sequence of characters and parse them into statements. A file must contain zero or more complete statements.
On the lowest level, a Scaly program is made up of a sequence of characters which are read from one or more files. For the Scaly Programming Language, some characters have a special meaning, which means that they control the forming of the conversion of the character sequence into a sequence of tokens and whitespaces. These tokens are then parsed into expressions which make up a Scaly program or library.
Whitespaces are sequences of characters which have no meaning by themselves, apart from being used for separating tokens if no punctuation can be used.
Outside of comments or strings, the following characters form whitespace character sequences:
Two forward slashes start a single line comment which spans all following characters up to and including a line break.
// This is a single line comment.
A forward slash followed immediately by an asterisk starts a multiple line comment which spans all following characters up to and including an asterisk and a forward slash immediately following.
/* This is a multi-line comment. Continued comment text The comment ends now:*/
Multiple line comments can be nested:
/* This comment is /*nested*/.*/
Tokens
form the building blocks of expressions. The syntactic grammar of the
Scaly Programming Language is expressed in terms of tokens. Tokens can be
Literals are used to write constant values directly into the program.
A numeric literal starts with a digit, followed by zero or more digits,
optionally one decimal point, optional fraction digits, an optional exponent
consisting of the exponent character (E
or e
) and exponent digits.
If the first two characters are 0x
, the literal is a hexadecimal literal.
The digits that may follow may include the characters a
, b
, c
, d
, e
, f
,
A
, B
, C
, D
, E
, and F
. No decimal point or exponent is allowed for
hexadecimal literals.
Some lexically valid numeric literals:
42 1. 0.22e4567 1E6 0xFB04 0x123abc
(A minus sign is not part of a number literal. It is typically implemented as a unary operator.)
String literals start with a double quote and end with a double quote:
"This is a string"
All white space characters can be directly embedded in strings:
"A string with a line break"
Tabs, carriage returns, line feeds, and NUL characters can be escaped by a back slash
and the characters t
, r
, n
, 0
respectively. The back slash escapes
itself, and the double quote is escaped by a back slash as well.
"A line feed\n, an \"escaped\" string, an escaped \\ backslash, a \ttab, and a carriage \rreturn."
Character literals start with a single quote, continue with the character whose value is to be written, and end with a single quote:
'a' '0' // The zero digit character
The characters that can be escaped in string literals, and the single quote must be escaped in a character literal.
'\"' '\'' '\0' // The NUL character
Punctuation characters are used (alongside keywords) for building the structure of expressions.
Keywords are used (alongside punctuation characters) for building the structure of expressions.
The complete list of punctuation characters and keywords is contained in the Grammar Reference.
All character combinations which are not white space, literals, punctuation, or keywords, are identifiers. Some examples:
WindyShore foo_bar baz
Identifiers are used as names.
All combinations of operator characters are operators. The operator characters are
+
, -
, *
, /
, =
, %
, &
, |
, ^
, ~
, <
, and >
. Some examples:
+ ** < >>
Operators are used as names like identifiers.
The Scaly compiler processes a program which contains all code which is to be compiled in a single compiler run to an executable program, a library, or code which is compiled just-in-time (JITted). A program consists of zero or more files.
A file is a single sequence of characters which contains zero or more characters which make up zero or more complete statements.
A Scaly program consists of statements. Statements are the building blocks of a Scaly program. A statement can perform computation work and either return the result of the computation to the enclosing expression or bind it to an identifier which can be referred to in the current scope.
An expression performs actual computation work and usually returns a value as a result of that work. There are numerous forms of expressions in Scaly like literals, function calls, operations, and many more.
Expressions can be optionally terminated by a semicolon. Line breaks are not significant for expression termination.
The most basic expressions in Scaly are literal values. A literal value expression evaluates to the value that is written literally into a program.
1.602E-19 "baz" 'a'
There is no such thing as a boolean literal as a part of the language. Boolean constants can be defined by a runtime library.
An object expression is an expression which combines zero or more expressions, the so-called components, to an object. An object is a combination of data which are used together.
() // The empty object which contains no components. (42,"The Answer") // Contains the number 42 and a string (()) // A non-empty object which contains the empty object as its only component.
An object consisting of a single component is semantially equivalent to the component contained by that object:
(5) // => 5
The components of an object can be accessed by its index, starting with 1 at its first component. The component index must be known at compile time, it cannot be computed.
(7).1 // => 7 (1, (2, (3, (4)))).2.2.2 // => 4
The components of an object can be given a name which can be used to access them:
(brand = "IFA", model = "F9", year = 1952).year // => 1952
An array literal combines zero or more expressions of the same type:
[2, 3, 5, 7] // An array with four components
A component of an array can be accessed by appending brackets which contain the index. The index is zero-based.
[1, 2][1] // => 2
An declaration evaluates an expression and binds the value which was returned by that expression to a constant or variable:
let a = 2 // b cannot be used here let b = 3 a // 2 var c = b b // 3
The constant or variable can be used in every expression which follows its declaration in the current scope. A scope is either the global scope or the scope of a block.
A block is an expression which combines zero or more expressions in a local scope. The last expression of the block is returned.
{ 99 } // => 99
A scope gives a block a name:
scope A { let b = 2 }
From a scope, a constant can be used after the scope declaration.
A.b // => 2
A function expression evaluates to a function value. It consists of the function
keyword,
an object literal, and a block.
function (a) { a } // => function(a){a}
A function (an expression which evaluates to a function) can be called by combining it with an object to be used as an input to the function:
let getItself = function (a) { a }; getItself(2) // => 2
For the Scaly programming language, an operator is a function which receives two objects
as input. An operator expression consists of the operator
keyword, two object literals,
and a block.
operator (a) (b) { (a, b) } // => operator(a)(b){(a,b)}
Scaly knows no binary operator precedence, execution is left to right:
2 + 3 * 4 // => 20
Operator precedence can be done by putting binary operation into parentheses (which are technically object expressions with one component which simply expose the containing operation after evaluation):
2 + (3 * 4) // => 14
An operator can be called by combining an object expression with the operator and a
second object expression. The following example declares the ><
operator which combines
two expressions to an object:
let >< = operator (a) (b) { (a,b) } 2 >< 3 // => (2,3)
A special variant of the operator expression is one that combines an array literal with a block. It can combine multiple operads using one operation call.
let with = operator [a] { (a[0], a[1], a[2], a[3]) } 1 with 2 with 3 with 4 with 5 // => (1,2,3,4,5)
The order of evaluation of the operator arguments is not specified.
Binding expressions bind an expression to a pattern. A pattern is commonly an
identifier expression which is a name of the object to which the expression is bound.
Binding expressions can be constant (using let
), variable (using mutable
), or
inferred (using var
):
var a 2 var b 3 var c a + b c // => 5
Writing an equals sign after let, mutable, or var is not required by the language, but since the standard library provides the unary = operator, the binding expressions can use them. Together with optional semicolons, the code might be more readable:
var a = 2; var b = 3; var c = a + b; c // => 5
An object bound to a mutable
name can be altered by setting it to a new value using
the assignment expression:
mutable d 6 set d d + 1 d // => 7
Instead of declaring a function or using let
, a shorter and more syntax can be used:
function getItself(a) { a } operator >< (a)(b) { (a, b) } operator with [a] { (a[0], a[1], a[2], a[3]) }
External functions are functions that are provided by the runtime environment. If external functions are to be made accessible from a Scaly program, they must be declared using the following syntax:
external _fopen(filename: pointer, mode: pointer): pointer external _fclose(file: pointer)
Types describe kinds of data which can be processed by a Scaly program. Most importantly, a type determines which values a variable or constant can have, and the compiler chooses a representation of the data on the hardware on which the code of a Scaly program runs. There are primitive data types like the void object, functions, and operators, integral and floating point numbers, enumerations, bit masks, characters, pointers,and complex data types like objects, arrays, and variants which combine primitive data types.
The void data type has no value. It can be used as an object that has no data, and it can be written as an empty object expression:
()
They are used as empty input or output of functions or operators or as an option of variant data types.
Functions are objects that contain executable code which can be called, and which receive one input object (which can be void) and return one output object (which can be void as well). The type of a function is uniquely identified by its signature, i.e., the types of its input and output. Two functions which have the same signature are of the same type.
Operators are objects that contain executable code which can be called, and which receive two input objects (which can be void) and return one output object (which can be void as well). The type of an operator is uniquely identified by its signature, i.e., the types of its two input objects and its output object. Two operators which have the same signature are of the same type.
The pointer
type represents an address in the address space of the machine. It can
point to any kind of data, and the type or semantics of the data are not defined.
A pointer can be converted to any data item without any runtime or compile time checks.
The author of the code is responsible to guarantee that any instance of a pointer
which is used carrys a valid address, and that the pointer is only converted to a constant or
variable of the type of data which lives at that address.
Pointers should only be used when external functions are called by Scaly code or by Scaly functions that are called from external code.
The standard library which comes with a Scaly compiler may define integer types which are defined by their bit width and the presence or absence of a sign. These types typically define type conversion and other functions.
Numeric literals written into a Scaly program have no type by themselves. The type of the value that is generated by the compiler is either inferred from the usage of the literal, and if that is not possible, the smallest possible integer type is assumed if the literal is an integer.
The standard library which comes with a Scaly compiler may define floting point types which are defined by their bit width. These types typically define type conversion and other functions.
Numeric literals written into a Scaly program which do not represent an integral number are given a type that is inferred from the usage of that literal. If this is not possible, a floating point type is assumed that the compiler sees fit for the runtime environment for which the compiler was provided.
The char
type represents all possible character values which are possible for the
runtime environment for which the Scaly compiler is implemented.
The Scaly Programming Language makes no assumptions about the storage format of the
character value.
The standard library which comes with a Scaly compiler may define utility functions for conversion and other tasks.
An array is a sequence of objects of the same type in memory. The type of the array is defined by its length and the type of the objects that are contained in the array.
An array can have either a fixed size if the length of the array is known at compile time, or a variable size if the length of the array is not known at compile time.
In the latter case, the count of the objects is stored as a packed integer in front of the sequence of the objects in memory.
The packed integer format stores stores a number to be encoded as a sequence of digits to the base of 127. Each digit is stored in one byte, the lowest digit coming first. The highest digit has its highest bit set to 0 which signals the end of the sequence of digits of the number. The highest bit of the lower digits is set to 1. The lower 7 bits of all bytes encode the value of the digit as an unsigned byte number.
Thus, array lengths from 0 to 127 are encoded in just one byte, and lengths from 128 to 16128 are encoded in two bytes, lengths from 16128 to about 2 millions take three bytes and so on.
The string
type is technically an array of bytes. This means that the length
of the string which is stored along with the bytes is the length of the byte sequence which
represents the string and not the count of characters which the string contains.
The difference to a byte array is the fact that the runtime library can interpret the stored
byte sequence as a stream of characters and, based on that interpretation, can implement
string access and manipulation functionality.
String constants in memory are stored in the UTF-8 format.
This section describes the types of the standard library that comes with the reference implementation of the Scaly Programming Language. You can use the Scaly Programming Language without the standard library, and provide your own basic types.
An integral number type is determined by the range of integral numbers which the number can take on. Scaly defines a set of integral number types which are characterized by their bit width, and whether they carry a sign or not:
byte
and sbyte
(8 bits unsigned and signed)
short
and ushort
(16 bits signed and unsigned)
int
and uint
(32 bits signed and unsigned)
long
and ulong
(64 bits signed and unsigned)
The char
type represents exactly one character. A character is uniquely identified
by its code point which is a non-negative integral number. Which code points are valid
and the semantics of the individual characters are determined by the character set
used by the operating system and/or the runtime library.
The grammar of the expressions of the Scaly Programming Language is defined as an SGML document.
The reference implementation of the Scaly Programming Language actually generates its complete parser and all AST classes directly out of the grammar description given in the follwoing sections.
Since SGML itself is a meta-language, the language in which the grammar is formulated is expressed as the following SGML DTD which is explained below:
<!ELEMENT grammar - - (syntax+, keyword*, punctuation*)>
<!ELEMENT syntax - O (content)*> <!ATTLIST syntax id ID #REQUIRED abstract (abstract|concrete) concrete base IDREF #IMPLIED multiple (multiple|single) single top (top|nontop) nontop program (program|nonprogram) nonprogram >
<!ELEMENT content - O EMPTY> <!ATTLIST content type (syntax|keyword|punctuation|identifier|literal|eof) syntax link IDREF #IMPLIED property CDATA #IMPLIED multiple (multiple|single) single optional (optional|required) required >
<!ELEMENT keyword - O EMPTY> <!ATTLIST keyword id ID #REQUIRED >
<!ELEMENT punctuation - O EMPTY> <!ATTLIST punctuation id ID #REQUIRED value CDATA #REQUIRED >
A grammar
contains at least one syntax
rule, zero or more keyword
elementss,
and zero or more punctuation
elements.
A syntax
contains zero or more content
elements.
A syntax
rule can be abstract
or concrete
, with concrete
as the default.
An abstract syntax rule is a superset of other syntax rules. An example for an
abstract syntax is an Expression
. As a convention, an abstract syntax contains
only links to other syntax rules which indicate what the syntax can be.
An Expression
, for instance, can be a SimpleExpression
(being abstract itself),
or a Block
(which is concrete), or one of a number of other expressions.
A concrete syntax rule contains content elements which describe the contents of that syntax.
A Block
, for instance, contains a leftCurly
punctuation, multiple
Expression
elements,
and a rightCurly
punctuation. Other content can be an identifier
, a keyword
, a literal
,
or an eof
(the latter one signals the end of the file.)
A concrete syntax rule which is an instance of an abstract syntax rule,
needs to indicate its base
syntax rule.
The top-level syntax rule of a module needs the top
attribute.
The root syntax rule of the grammar needs the program
attribute.
A content
item can link
to a syntax
, keyword
, or punctuation
element.
The property
attribute is the name of the syntax member variable name in the AST.
If a content
links to a syntax
, the multiple
attribute indicates that this syntax
can occur multiple times in that context, and the optional
attribute indicates that
this syntax is optional in that context.
The keyword
has its value as its id
.
The punctuation
has its value in the value
attribute.
Below the grammar of the Scaly programming language is defined in terms of the meta grammar given in the previous section. Please note that a character sequence complying to the grammar is not necessarily a valid Scaly program. All valid Scaly programs, howver, comply with this grammar. The semantic requirements for the expressions to form a valid Scaly program are described in the Program section and the sections that follow.
<!DOCTYPE grammar SYSTEM "grammar.dtd">
<grammar>
<syntax id = Program program <content identifier property = name <content link = File multiple property = files
<syntax id = File top <content link = Segment multiple optional property = statements
<syntax id = Segment multiple <content link = Statement property = Step <content link = semicolon optional
<syntax id = Block base = PrimaryExpression <content link = leftCurly <content link = Statement multiple property = statements <content link = rightCurly
<syntax id = Statement multiple abstract <content link = Using <content link = Declaration <content link = Expression <content link = Set <content link = Break <content link = Continue <content link = Return <content link = Throw
<syntax id = Using base = Statement <content link = using <content link = Path property = path
<syntax id = Declaration multiple abstract base = Statement <content link = Let <content link = Mutable <content link = Var <content link = Thread <content link = Class <content link = Constructor <content link = Method <content link = Function
<syntax id = Let base = Declaration <content link = let <content link = Binding property = binding
<syntax id = Mutable base = Declaration <content link = mutable <content link = Binding property = binding
<syntax id = Var base = Declaration <content link = var <content link = Binding property = binding
<syntax id = Thread base = Declaration <content link = thread <content link = Binding property = binding
<syntax id = Binding <content link = Pattern property = pattern <content link = TypeAnnotation optional property = typeAnnotation <content link = Expression multiple property = expressions
<syntax id = Pattern abstract <content link = WildcardPattern <content link = IdentifierPattern <content link = ExpressionPattern
<syntax id = IdentifierPattern base = Pattern <content link = Path property = path <content link = TypeAnnotation optional property = annotationForType
<syntax id = WildcardPattern base = Pattern <content link = underscore
<syntax id = ExpressionPattern base = Pattern <content link = Expression property = expression
<syntax id = Expression multiple base = Statement <content link = PrimaryExpression property = primary <content link = Postfix multiple optional property = postfixes
<syntax id = PrimaryExpression multiple abstract <content link = Name <content link = Constant <content link = If <content link = Switch <content link = For <content link = While <content link = Do <content link = This <content link = ObjectExpression <content link = Block <content link = SizeOf
<syntax id = Name base = PrimaryExpression <content link = Path property = path <content link = GenericArguments optional property = generics <content link = LifeTime optional property = lifeTime
<syntax id = Constant base = PrimaryExpression <content literal property = literal
<syntax id = If base = PrimaryExpression <content link = if <content link = leftParen <content link = Expression multiple property = condition <content link = rightParen <content link = Block property = consequent <content link = Else optional property = elseClause
<syntax id = Else <content link = else <content link = Block property = alternative
<syntax id = Switch base = PrimaryExpression <content link = switch <content link = leftParen <content link = Expression multiple property = condition <content link = rightParen <content link = leftCurly <content link = SwitchCase multiple property = cases <content link = rightCurly
<syntax id = SwitchCase multiple <content link = CaseLabel property = label <content link = Block property = content
<syntax id = CaseLabel abstract <content link = ItemCaseLabel <content link = DefaultCaseLabel
<syntax id = ItemCaseLabel base = CaseLabel <content link = case <content link = Pattern property = pattern <content link = CaseItem multiple optional property = additionalPatterns
<syntax id = DefaultCaseLabel base = CaseLabel <content link = default
<syntax id = CaseItem multiple <content link = comma <content link = Pattern property = pattern
<syntax id = For base = PrimaryExpression <content link = for <content link = leftParen <content identifier property = index <content link = TypeAnnotation optional property = typeAnnotation <content link = in <content link = Expression multiple property = expression <content link = rightParen <content link = Block property = code
<syntax id = While base = PrimaryExpression <content link = while <content link = leftParen <content link = Expression multiple property = condition <content link = rightParen <content link = Block property = code
<syntax id = Do base = PrimaryExpression <content link = do <content link = Block property = code <content link = while <content link = leftParen <content link = Expression multiple property = condition <content link = rightParen
<syntax id = This base = PrimaryExpression <content link = this
<syntax id = Postfix multiple abstract <content link = Catch <content link = MemberAccess <content link = Subscript <content link = As <content link = Is <content link = Unwrap
<syntax id = Catch base = Postfix <content link = catch <content link = CatchPattern property = typeSpec <content link = Expression optional property = handler
<syntax id = CatchPattern abstract <content link = WildCardCatchPattern <content link = NameCatchPattern
<syntax id = WildCardCatchPattern base = CatchPattern <content link = WildcardPattern property = pattern
<syntax id = NameCatchPattern base = CatchPattern <content link = Name optional property = member <content link = leftParen <content identifier optional property = errorName <content link = rightParen
<syntax id = MemberAccess base = Postfix <content link = dot <content identifier property = member
<syntax id = Subscript base = Postfix <content link = leftBracket <content link = Expression multiple optional property = firstItems <content link = ObjectItem multiple optional property = additionalItemses <content link = rightBracket
<syntax id = As base = Postfix <content link = as <content link = Type property = typeSpec
<syntax id = Is base = Postfix <content link = is <content link = Type property = typeSpec
<syntax id = Unwrap base = Postfix <content link = exclamation
<syntax id = ObjectExpression base = PrimaryExpression <content link = leftParen <content link = Expression multiple optional property = firstItems <content link = ObjectItem multiple optional property = additionalItemses <content link = rightParen
<syntax id = ObjectItem multiple <content link = comma <content link = Expression multiple optional property = expression
<syntax id = SizeOf base = PrimaryExpression <content link = sizeof <content link = Type property = typeSpec
<syntax id = Set base = Statement <content link = set <content link = Expression multiple property = lValue <content link = colon <content link = Expression multiple property = rValue
<syntax id = Break base = Statement <content link = break <content link = Expression multiple property = lValue
<syntax id = Continue base = Statement <content link = continue
<syntax id = Return base = Statement <content link = return <content link = Expression multiple property = expression
<syntax id = Throw base = Statement <content link = throw <content link = Expression multiple property = expression
<syntax id = Class base = Declaration <content link = class <content link = Path property = path <content link = GenericParameters optional property = generics <content link = Object optional property = contents <content link = Extends optional property = baseClass <content link = Expression optional property = body
<syntax id = Path <content identifier property = name <content link = Extension multiple optional property = extensions
<syntax id = Extension multiple <content link = dot <content identifier property = name
<syntax id = GenericParameters <content link = leftBracket <content identifier property = name <content link = GenericParameter multiple optional property = additionalGenerics <content link = rightBracket
<syntax id = GenericParameter multiple <content link = comma <content identifier property = name
<syntax id = Extends <content link = extends <content link = Path property = path
<syntax id = Object <content link = leftParen <content link = Component multiple optional property = components <content link = rightParen
<syntax id = Component multiple <content identifier property = name <content link = TypeAnnotation optional property = typeAnnotation <content link = comma optional
<syntax id = Constructor base = Declaration <content link = constructor <content link = Object optional property = input <content link = Block property = body
<syntax id = Method base = Declaration <content link = method <content link = Procedure property = procedure
<syntax id = Function base = Declaration <content link = function <content link = Procedure property = procedure
<syntax id = Procedure <content identifier property = name <content link = Object optional property = input <content link = TypeAnnotation optional property = output <content link = Throws optional property = throwsClause <content link = Block property = body
<syntax id = TypeAnnotation <content link = colon <content link = Type property = typeSpec
<syntax id = Type <content identifier property = name <content link = GenericArguments optional property = generics <content link = TypePostfix multiple optional property = postfixes <content link = LifeTime optional property = lifeTime
<syntax id = Throws <content link = throws <content link = Type property = throwsType
<syntax id = GenericArguments <content link = leftBracket <content link = Type property = typeSpec <content link = GenericArgument multiple optional property = additionalGenerics <content link = rightBracket
<syntax id = GenericArgument multiple <content link = comma <content link = Type property = typeSpec
<syntax id = TypePostfix multiple abstract <content link = Optional <content link = IndexedType
<syntax id = Optional base = TypePostfix <content link = question
<syntax id = IndexedType base = TypePostfix <content link = leftBracket <content link = Type optional property = typeSpec <content link = rightBracket
<syntax id = LifeTime abstract <content link = Root <content link = Local <content link = Reference <content link = Thrown
<syntax id = Root base = LifeTime <content link = dollar
<syntax id = Local base = LifeTime <content link = at <content identifier property = location
<syntax id = Reference base = LifeTime <content link = backtick <content literal optional property = age
<syntax id = Thrown base = LifeTime <content link = hash
<keyword id = using <keyword id = let <keyword id = mutable <keyword id = var <keyword id = thread <keyword id = set <keyword id = class <keyword id = extends <keyword id = constructor <keyword id = method <keyword id = function <keyword id = this <keyword id = sizeof <keyword id = catch <keyword id = throws <keyword id = as <keyword id = is <keyword id = if <keyword id = else <keyword id = switch <keyword id = case <keyword id = default <keyword id = for <keyword id = in <keyword id = while <keyword id = do <keyword id = break <keyword id = continue <keyword id = return <keyword id = throw
<punctuation id = semicolon value = ";" <punctuation id = leftCurly value = "{" <punctuation id = rightCurly value = "}" <punctuation id = leftParen value = "(" <punctuation id = rightParen value = ")" <punctuation id = leftBracket value = "[" <punctuation id = rightBracket value = "]" <punctuation id = dot value = "." <punctuation id = comma value = "," <punctuation id = colon value = ":" <punctuation id = question value = "?" <punctuation id = exclamation value = "!" <punctuation id = at value = "@" <punctuation id = hash value = "#" <punctuation id = dollar value = "$" <punctuation id = underscore value = "_" <punctuation id = backtick value = "`"
</grammar>
Classes are a way of organizing data. Classes can have members — either primitive ones like strings or numbers, or other classes, or arrays of them.
Immutable object items — either declared locally, object members or elements of an
array or dictionary — can be assigned to either other immutable objects or fresh objects
created with new
.
Mutable object items (local, members or elements) cannot be assigned to other existing objects.
They can only be assigned to fresh objects created with new
.
In addition to the simple assignment operator =
, there is also a movement operator =!
which moves the object from the right hand side expression (which must be mutable,
and optional) to the left hand item which must be mutable or variable either.
There is also a swap operator <=>
which swaps the left hand item with the right hand item.
For swapping, the items must both be mutable.
Object arguments are passed by reference to a function. If you pass a mutable object to a function, and the function alters that object, the changes are visible to the caller after the function returned.
Classes have an important characteristic: they are self-contained data sets which means that members of an object cannot point to anything outside the object tree, and an object cannot be pointed to by anything else but the owner (if it is a class member or an array element).
Since mutable objects cannot be assigned to, classes organize their data in a strictly hierarchical way. They can be easily serialized to JSON, XML, or any other text-based or binary hierarchical representation of the data they contain. Since classes are serializable, their data can be transmitted over the network to other nodes if your program runs on a supercomputer, or sent to powerful GPU hardware on your local machine. Thus, programs using classes for passing data scale well in a distributed or heterogeneous environment.
Because classes are so easily mapped to JSON or XML, building web services with Scaly is a breeze - simply design a set of functions using classes for data transfer, and you are done.
An object is created by calling a constructor of its class. A constructor returns a new object. This object can then be assigned to an item:
mutable p: Foo = new Foo(42)
An object can be declared mutable or immutable.
An immutable item is declared with the let
keyword. Neither the object it references
can be changed, nor the item itself:
let i: Foo = new Foo(43) // i.bar = 44 // The object cannot be changed // i = new Foo(44) // The item cannot be reassigned
A mutable object is declared with the mutable
keyword. A mutable item allows for
changing the object to which its reference points, and for reassigning another reference to it:
mutable m: Foo = Foo(45) m.bar = 46 // The object can be changed m = Foo(47) // The item can be reassigned
You can copy an immutable object by assigning it to another immutable object.
let a: Foo = i let b: Foo = v v = i
A class can define exactly one parent member. A parent member is recognized by an @
at its
type declaration:
class Parent { let children: Child[] }
class Child { let parent: Parent@ }
If you assign an object with a parent member to a member of a object to contain it, this parent member is automatically set to the containing object. If the child is added to an array member, the parent member of the child points to the object which contains that array member:
mutable p: Parent = new Parent() p.Children.push(new Child()) // The parent property of the new Child points to p
A parent member is useful if your algorithm walks a tree up and down, or to implement doubly-linked lists.
A parent members can only be accessed if the object holding it is immutable:
if p.length() > 0 { let c: Child = p.children[0] // let r = c.parent // Error: Can't access a parent of a mutable object }
In Scaly, objects live on the stack, either defined as local items, or owned by or referenced by other objects, arrays, or dictionaries, which in turn live somewhere on the stack directly or indirectly (held by other objects).
If a block is left, the memory of all objects which were created in this block is recycled. Therefore, a reference must not be held by an item that outlives the object it references:
let a: Foo& { let b: Foo = Foo() // a = &b // If b goes out of scope, a would point to recycled memory }
To make b
assignable to a
, its declaration can be moved to the outer block:
let a: Foo& let b: Foo { b = Foo() a = &b }
The lifetime of an object is determined by the place where a reference to it is declared. The older an object, the longer it lives. Since older data live at least as long as younger data, it can never happen that references to dead data are accessible.
The age of data depends on where it is declared. Items declared in a local block are younger than items in the enclosing block. Parameters that are passed to a function are older than its local items:
function f(a: Foo) { let b: Bar = Bar() { let c: Caf = Caf() } }
In this example, a
is oldest, b
is younger than a
, and c
is youngest.
A reference returned by a function is assumed to be fresh by default. This means that the function creates the object (either by calling an object constructor or another function which returns a reference to a fresh object). The caller of such a function then assigns the returned reference to an item whose location determines the age of the object:
function g(): Foo { return Foo(42) // Fresh object created and returned }
function h() { let k: Foo = g() // The object lives here, accessible via k }
If a function is to return an object which is not fresh, the age of such a returned object must be made explicit by an age tag which is written after the type of the return value.
An age tag starts with a single quote (&
) and continues with digits which form
a nonnegative number. Leading zero digits are not allowed. &0
is a valid age tag,
&42
is a valid age tag as well, whereas &01
is not a valid age tag.
Since Scaly does not know global mutable data, there must be one or more parameters from which to take the returned reference in some way, age tag numbers are used to express age relations between the parameters of a function. The higher the age tag value is, the younger is the tagged reference:
function superiorFoo(fooOld: Foo&1, fooYoung: Foo&2) -> Foo&2 { if fooOld.number > fooYoung fooOld else fooYoung }
In this example, the returned refrerence can be taken from any of the two parameters, and so its age must be that of the youngest parameter.
The following example checks assignments for age validity:
function bar(mutable foo: Foo&1, mutable bar: Bar&2) { bar.foo = foo // Valid because foo is declared older // foo.bar = bar // Invalid because bar is younger }
If age tags are omitted, the age of the parameters is irrelevant:
function baz(p: Foo, q: Foo) -> bool { p.number > q }
The age of a member is assumed to be the same as the age of the object containing it (even though the object it points to might be older). Similarly, the age of an array element is assumed to be that of the array, and the age of a dictionary key or value is assumed to be that of the dictionary.