Because there exists a official lua language reference, I will only cover in this document the differences.
vardecl ::= name [ ´:´ type ]
type ::= ‘byte’ | ‘sbyte’ | ‘short’ | ‘ushort’ | ‘int’ | ‘uint’ | ‘long’ | ‘ulong’ | ‘float’ | ‘double’ | ‘decimal’ | ‘datetime’ | ‘char’ | ‘string’ | ‘object’ | ‘type’ | ‘luatype’ | ‘thread’ | ‘table’ | name { ‘.’ name }
How mention, NeoLua uses the whole set of the .net type system.
It is possible to assign local variables a type. This is very important if you want to enforce a type in your script.
local a : byte = 23; -- declare an integer variable and assign the value 23
local b : System.String = 42; -- declare a string variable and assign the
-- the value 42, that will be convert to a string
You can add the full type name (namespace + type ame) or you can use a alias.
Aliases can be define on the LuaType
class, with the static method RegisterTypeAlias
or use the ´const´ name ´typeof´ type
.
Another reason to use typed locals is to avoid unboxing and boxing operations (performance!).
local a : int, b : int = 23, 42;
return a + b;
Because, both types are well known the NeoLua compiler can emit a integer add operation. If you write it in normal lua like
local a, b = 23, 42;
return a + b;
the compile emits a dynamic add operation, that will be resolve the integer add operation on the first hit during runtime.
There are also some automatic type decissions, like
-- i is an integer (no declaration is necessary)
for i = 0,9,1 do
print(i);
end;
Global values are always declared as objects, because they are members on the global table/environment. All calls will be late bound. If a class (e.g. LuaTable) implement's IDynamicMetaObjectProvider it will also generate code, that will be evaluated dynamic.
The dot .
stands for the get member operation, it will also succed, if the member is nil
or doesn't exists.
If you use the colon :
it is a member call. You can also do member calls on properties, they just return the value as result. If the member doesn't exists,
it will throw a runtime exception (on the table
a member that has the value nil
is a none existing value).
t = { a = 42 }
return t.a, -- 42
t:a, -- 42
t:a(), -- 42
t:a(23), -- 42
t.b, -- nil
t:b; -- LuaRuntimeException
This will reduce the "Can not call nil object" exceptions.
cast ::= ‘cast’ ‘(‘ type ‘,’ expr ‘)’
To enforce a type in a expression, you can use the keyword cast. The first argument is the type and the second is the expression, that will be converted.
cast(int, variable);
cast(byte, 34);
cast(System.Text.StringBuilder, var);
The type must be known during the compile time, so it is not possible to use a variable that holds a type.
local sb = clr.System.Text.StringBuilder();
function GetText()
return "Hallo";
end;
sb:Append(cast(string, GetText()));
sb:Append(cast(string, string.upper(" Welt")));
sb:Append( ( GetText() ) ) -- will convert the result to a single value
print(sb:ToString());
The return type of an lua-function is always a multi set of values (LuaResult
). There is no
overload for this type, there is only one, who is closed to it (Append(char[])). But a
string can not converted to an character value. Without the explicit cast you will result in an cast-exception.
const ::= `const´ vardecl [ `=´ expr | `typeof´ type ]
With the const keyword it is possible to declare a value, that is not set to the global environment. So, it will not produce any overhead at runtime, and will not accessible to the host program.
const a = 3 - 2; -- the result of the expression will be assigned to a (int)
return a * 2; -- the result will be 2 and not -1, because 1 is included and not 3 - 2
const StringBuilder typeof System.Test.StringBuilder;
local sb : StringBuilder = StringBuilder(); -- declare a variable of the type StringBuilder, none dynamic
sb:Append('Hello'); -- call the method append, none dynamic
typeof
declares a constant for the compiler with the LuaType
object for the clr type. It is possible to use constant in cast
expressions, type declarations and you can call static methods of the type.
const Env typeof System.Environment;
return Env:MachineName, Env:GetEnvironmentVariable("PATH");
foreach ::= ‘foreach’ vardecl ‘in’ expr ‘do’ block ‘end’
foreach
is easy way to enumerate lists or arrays. If the result of the expression implements the System.Collections.IEnumerable interface,
it will go throw the whole list an stores the current value in the loop-variable.
foreach c in "Hallo" do
print(c); -- prints every letter
end;
The result of an function is enumerable. So, this is a nice way to access unknown result.
function text()
return "Hallo", "Welt";
end;
foreach c in text() do
print(c); -- prints the two words
end;
foreach c in cast(string, text()) do
print(c); -- prints every letter
end;
doloop ::= ‘do’ ‘(’ vardecl { ‘,’ vardecl } ‘=’ expr { ‘,’ expr }) block ‘end’
‘(’ { ‘function’ [ ‘(’ vardecl [ ‘:’ type ] ‘)’ ] block ‘end’ ‘,’ } ‘)’
NeoLua extents the do
to a try statement. Every variable, that is declared between the braces will automatically disposed in a finally block at the end of the loop.
do (sw = clr.System.IO.StreamReader([[C:\Projects\NeoLua\trunk\NeoCmd\Samples\FileRead.lua]]))
-- error("test");
print(sw:ReadToEnd());
end;
The file handle will be released, also when an exception is thrown.
Example for a catch statement.
do
error("test");
end(
function (e)
print(e.Message);
-- rethrow; -- to rethrow the exception, or error(e) to set a new stacktrace
end,
function
print("finally");
end
)
lambda ::= ‘(‘ vardecl { ‘,’ vardecl } ´)´ ´:´ type block ‘end’
It is possible to create a typed function signature.
function add(a : int, b : int) : int -- produces Func<int, int, int>
return a + b;
end;
It is also possible to enforce only some arguments.
function add(a, b : int) -- Func<object, int, LuaResult>
return a + b;
end;
In NeoLua it is possible to use parameter names on function calls.
call ::= expr ‘(‘ [ [ identifier = expr { ‘,’ identifier = expr } ] ´)´
Important:
local function add(a, b)
return a + b;
end;
return add(a=40, b=2);
This example will not return 42
, because the function signature is not Func(object a, object b) : object
it is Func<object,object,object>(object arg1, object arg2) : object
. The reason is that lambda functions
always use predefined signatures.
return add(arg1=40, arg2=2); -- is 42
If you do not declare a return type, then NeoLua functions always return a LuaResult
.
g.dochunk("function f() return 1,2,3; end;", "test.lua");
LuaResult r = g.test(); -- returns a LuaResult
Console.WriteLine("{0}", r[0] + r[1] + r[2]); -- prints "6"
Console.WriteLine("{0}", r[4]); -- prints nothing
LuaResult
implements the dynamic interface:
dynamic r2 = g.test();
Console.WriteLine("{0}", r2); -- prints "1"