Names in Basic Storm
Names in Basic Storm are similar to the generic syntax in Storm. The
syntax of names are, however, altered slightly to allow using the dot .
operator for member
access, like C++ and Java. The difference from the generic Storm syntax is as follows:
-
The dot
.
has been replaced by a colon:
(inspired by the::
operator in C++). -
Parentheses
()
have been replaced by angle brackets<>
.
This means that names in Basic Storm generally have the following form:
a:b<c:d>:e
The rationale behind these differences is to distinguish the member access operator (.
) from the
scope resolution operator (:
). Basic Storm uses .
as the member access operator and :
as the
scope resolution operator to resemble the syntax of languages like C++. Similarly, since the name
syntax is most often used to refer to the name of types, angle brackets (<>
) were used instead of
parentheses (()
) to distinguish type parameters from function parameters.
Historical note: The original plan was for Basic Storm to overload the dot operator (.
) like
Java, so that its meaning would depend on the context in which it was used. This has not yet
happened.
To illustrate the syntax, consider the following examples:
-
core:Int
is the name of theInt
type in thecore
package. -
core:Array<core:Int>
is the name of theArray
type for theInt
type.
Short-Hand Names
Basic Storm provides a number of short-hand names for common types as follows:
-
T[]
is short forArray<T>
(an array ofT
) -
T?
is short forMaybe<T>
(eitherT
ornull
) -
K->V
is short forMap<K, V>
(a hash map fromK
toV
) -
K&->V
is short forRefMap<K, V>
(a hash map fromK
toV
that hashes based on object identity) -
fn(...)->T
is short forFn<T, ...>
(a function with parameters...
and return valueT
)
Additional type aliases can be added by providing additional productions for the rule
lang.bs.SType
.
Use Statements
Each Basic Storm file may start with zero or more use
statements. A use statement specifies a
package to "import" into the current source file. For example: use my:package;
. A use
statement
serves two purposes. First, they make all names inside the specified package visible in the current
file without qualification. For example, if use lang:bs;
is present, then the name Expr
can
refer to the type lang:bs:Expr
. Secondly, use statements import syntax from the specified package.
The imported syntax is available in the remainder of the file, but only after all use
statements.
Name Lookup
A name in Basic Storm may appear in two contexts. The first, and simplest, is when a name appears in a location where a type name appears. In this case, the name is resolved as-is after any short-hand names have been expanded.
A name may also appear in an expression, possibly with parameters and in conjunction with the member
access operator. To understand the motivation behind the name lookup rules for this case, it is
important to note that Basic Storm uses uniform function call syntax. This means that it is
possible to call the member f
of the variable x
by writing either f.x()
or x(f)
. These two
expressions are almost exactly equivalent. They only differ in which x
takes priority if there are
multiple overloads of x
available.
In general, names that appear in an expression may have the following form:
a.N<T, U, ...>(b, c, ...)
In the generic form, N
, T
and U
are fully qualified names according to the syntax above (i.e.
parts separated with :
and any parameters specified with <>
). The parameters to the last part of
N
(if any) are T
, U
, and optionally more parameters. Of cours, N
may contain multiple parts.
However, the parameters of the last part are important for the name lookup, which is why they are
explicit in the form above. Furthermore, a
, b
, and c
are expressions. The exact syntax of
expressions are covered later. What is important here is that all expressions have a type that is
known at compile-time. This type is used during name lookup. All parts of the generic form are
optional except for N
. This means that the following forms are also valid: N
, a.N
, N(a, b, ...)
, etc.
Name lookup then consists of two steps. First, the system decides on one or two names that should be resolved in the name tree as follows:
-
If the last part of
N
contain any parameters (i.e.<T, U, ...>
above), then the nameN<T, U, ...>
has to refer to a type. Any parameters in parentheses are then parameters to the constructor of the type. For example, the namecore:Array<Int>(1, 0)
refers to the constructor of the typecore:Array<core:Int>
. Note: The constructor is named__init
with suitable parameters. It is, however, not possible to explicitly call the constructor by name from Basic Storm. -
If the name has the form
a.N(...)
, with or without parameters in parentheses, then the name is interpreted asN(a, ...)
, and name lookup proceeds as usual. The terma
is remembered to be scope-altering for the future scope traversal. The last part ofN
is appended with the parametersa
,b
,c
, ... -
If the name did not have the form
a.N(...)
originally, and the name occurs in a context where a variable namedthis
exists (for example, in a member function), then two versions of the name are created. The first with parameters the type ofthis
,a
,b
, ... and the second with parametersa
,b
, ... (These correspond toN(this, a, b, ...)
andN(a, b)
). Both are resolved, but the original form takes priority at each step.
After the interpretation of the name has been determined, the name tree is traversed as follows;
-
If
N
consists of only one part, and the first parameter was marked as scope altering (step 2 above), look inside the type of the first parameter for a match.This means that expressions like
x.N()
always resolve in the type ofx
if it exists. -
Traverse the name tree from the current local scope until a package is reached. At each step,
attempt to resolve
N
with its current parameters.This means that names are resolved in the lexical context of the current file and package first. However, since this step ends at the current package, a name like
a:b
that occurs in the packagec:d
will not be able to matchc:a:b
, which would likely lead to confusion. -
If
N
consists of only one part, and the first parameter was not marked as scope altering, look inside the type of the first parameter for a match.This means that expressions like
N(x)
are allowed to match against member functions in the type ofx
, but that function calls namedN
declared lexically closer have precedence when this form is used. Compare this to step 2, which applies to names of the formx.N()
. -
Attempt to resolve
N
against the root package.This means that absolute names are matched at this stage, and that they take precedence over any
use
statements. -
Attempt to resolve
N
against the packagecore
.This makes all types in the
core
package (e.g.core:Int
,core:Str
) automatically visible everywhere. -
Attempt to resolve
N
against eachuse
statement in the current file.This means that
use
statements are used as a last restort for name resolution.