Declarations
Variable and function declarations are defined by the following syntax.
DeclarationStatement:
VariableDeclaration ';'
FunctionDeclaration
ClassDeclaration
NamespaceDeclaration
VariableDeclaration:
LocalVarDeclaration
GlobalVarDeclaration
LocalVarDeclaration:
'local' Identifier {',' Identifier} ['=' Expression]
GlobalVarDeclaration:
'global' Identifier {',' Identifier} ['=' Expression]
FunctionDeclaration:
['local' | 'global'] SimpleFunctionDeclaration
SimpleFunctionDeclaration:
'function' Identifier Parameters BlockStatement
Parameters:
'(' [Identifier ['=' Expression] {',' Identifier ['=' Expression]} [',' 'vararg']] ')'
'(' 'vararg' ')'
ClassDeclaration:
['local' | 'global'] 'class' Identifier [':' Expression] '{' {ClassMember} '}'
ClassMember:
SimpleFunctionDeclaration
Identifier ['=' Expression] ';'
'this' Parameters BlockStatement
NamespaceDeclaration:
['local' | 'global'] 'namespace' Identifier [':' Expression] '{' {NamespaceMember} '}'
NamespaceMember:
SimpleFunctionDeclaration
Identifier ['=' Expression] ';'
This page only documents variable and namespace declarations; for information on function declarations, see Functions, and for class declarations, Classes.
Variable Declarations
Variables are defined much like any other C-style language, except in place of a type, you use the "local" keyword:
local x;
You can declare multiple variables on one line:
local x, y, z;
Variables can optionally have an initializer, which allows you to give the variable a value along with declaring it, rather than declaring it and giving it a value on separate lines. To initialize a variable, just write an assignment after the declaration:
local x = 5;
This declares the variable x and sets its value to 5.
Variable initializers can always be a non-constant expression. There is no difference between a variable declared at top-level and one declared within a function.
You can only initialize multiple declarations with a function call, vararg, or yield expression:
local x, y, z = func(); // x, y, z are given the return values of func local a, b = vararg; // a, b are given the first two variadic arguments local i, j = yield(); // i and j are given the first two values that this coroutine was resumed with
If the variable is given no initializer, it defaults to the value null:
function func() { return 1, 2; } local x; // is null local w, y, z = func(); // w is 1, y is 2, z is null
When declared at top-level, 'local' variables are only visible within the module.
Global Variables
Another kind of variable is a global variable. These are declared in much the same way as local variables:
global x = 5;
Globals are inserted into the nearest non-local namespace at runtime. This means that they are inserted as symbols into the current module's namespace. After the module has been initialized, other modules will be able to see its global members.
Attempting to declare a global variable more than once in the same namespace (i.e. in the same module) will result in a runtime error.
Attempting to access a global variable which doesn't exist will also result in a runtime error.
You should only use globals when you need to expose a symbol to other modules. Everything else should be local.
Namespaces
NamespaceDeclaration:
['local' | 'global'] 'namespace' Identifier [':' Expression] '{' {NamespaceMember} '}'
NamespaceMember:
SimpleFunctionDeclaration
Identifier ['=' Expression] ';'
MiniD uses namespaces to hold symbols. Namespaces are implicitly used to hold class, instance, and module members. You can also explicitly declare your own namespaces.
Namespace declarations look a lot like class declarations, and they work a lot like class declarations as well. You can create either local or global namespaces, and they default to local if neither keyword is present. All namespaces have a name. Namespaces optionally can have a 'parent' namespace, which is given after a colon after the name. If no parent is given, it defaults to null. Namespaces can have functions and variables declared within them, and the syntax for doing so is identical to that of classes.
There are two things which deserve mention, and they are linked. One is the idea of a parent namespace. Module namespaces in MiniD all have parents: each module namespace's parent is its owning package, or the global namespace if there is none. So with module "a.b", b's parent is namespace a, and a's parent is the global namespace. The global namespace has no parent. This is used to create a hierarchy of namespaces for performing global lookup from functions. If a global is looked up in a namespace, and it is not found, it goes to the parent and looks up there, continuing up the chain until either it finds the global or it runs out of namespaces to check. This leads into the second item: function environments.
When you declare a function, it is implicitly associated with a namespace called its environment. Whenever a global access occurs within that function, it first checks the context parameter (see Functions), and if that fails, looks up the global in the environment using the process described above. By default, function environments are set to the same environment as the function that created them. In most cases, this will be the enclosing module. This poses a problem when you declare functions in your own namespaces.
Say you have some code that looks like this:
module test; namespace Foo { x = 5; function f() { writefln(x); } } Foo.f(); local f = Foo.f; f();
Notice that the function Foo.f prints out x, which is probably supposed to be the x declared in the namespace.
The first call, Foo.f(), works properly and prints "5", because this is a method call -- Foo is passed as the context pointer to f(). But the second call calls the function indirectly, and this time it fails. This is because f()'s environment is not set as the namespace Foo, but as the current module's namespace -- test.
You can fix this by setting the function's environment to the namespace in which it was declared. A quick way to do this is to simply loop through all the members of the namespace, and set the environments of all the functions to the namespace. So the code now looks something like:
module test; namespace Foo { x = 5; function f() { writefln(x); } } foreach(k, v; Foo) if(isFunction(v)) v.environment(Foo); Foo.f(); local f = Foo.f; f();
But it still doesn't work quite right. Now what happens is that it can't find writefln. This is because the global lookup starts at Foo, but Foo has no parent namespace, and so writefln is never found. This is easily solved: just make the parent namespace of Foo this module's namespace.
module test; namespace Foo : test { x = 5; function f() { writefln(x); } } foreach(k, v; Foo) if(isFunction(v)) v.environment(Foo); Foo.f(); local f = Foo.f; f();
Now both calls work properly.
