Comments
// comments must be preceded by two front-slashes, and there are no multiline comments yet
Identifiers
Identifiers must start with A-Z, a-z, $, _, or any Unicode character above 128. After the first letter, numbers 0-9 are valid as part of the identifier. Identifiers are case-sensitive so 'HELLO' is not the same as 'hello' or 'Hello'.
Validabcdef _abc $abc a01
√ π ひらがな カタカナ ಠ_ಠ¡ ★☆❶❷㉚㍄㏒Invalid
0abc 5def
Reserved Keywords
These are keywords reserved by the language for special purposes. This means you cannot use these words as is for any purpose other than their intended language purpose. All keywords are case-senstive, which means something like 'RETURN' would not be a keyword. Keywords inside of variable names does not count that keyword as a keyword (e.g. 'return_value' is not considered a keyword).
func if else while return this delete continue break var null as in has for in foreach super global class try catch static typeof print preinc$ predec$ postinc$ postdec$ not$ bnot$ neg$ shr$ shl$ lsr$ add$ sub$ div$ idiv$ mod$ imod$ mul$ band$ bor$ xor$ lt$ gt$ lte$ gte$ eq$ noteq$
Expressions
Operator Precedence
The table is ordered from highest (0) to lowest (16) precedence.
Precedence | Operator type | Associativity | Individual operators |
0 | grouping | n/a | ( … ) |
array declaration | [ … ] | ||
local variable | left-to-right | var … | |
1 | function call | left-to-right | … ( … ) |
array member | … [ … ] | ||
2 | scope member | left-to-right | … . … |
3 | scope contains | left-to-right | … has … |
type cast | … as … | ||
loop item | … in … | ||
4 | post increment | left-to-right | … ++ |
post decrement | -- … | ||
5 | pre increment | right-to-left | ++ … |
pre decrement | -- … | ||
unary negation | - … | ||
unary + | + … | ||
bitwise not | ~ … | ||
boolean not | ! … | ||
variable reference | & … | ||
delete | delete … | ||
continue | n/a | continue | |
break | break | ||
6 | multiplication | left-to-right | * |
division | / | ||
integer division | ~/ | ||
modulus | % | ||
integer modulus | ~% | ||
7 | addition | left-to-right | + |
subtraction | - | ||
8 | bitwise shift | left-to-right | << |
>> | |||
<<< | |||
9 | relational | left-to-right | < |
<= | |||
> | |||
>= | |||
10 | equality | left-to-right | == |
!= | |||
identical | === | ||
!== | |||
11 | bitwise and | left-to-right | & |
12 | bitwise xor | left-to-right | ^ |
13 | bitwise or | left-to-right | | |
14 | boolean and | left-to-right | && |
15 | boolean or | left-to-right | || |
16 | ternary conditional | right-to-left | … ? … : … |
assignment | = | ||
+= | |||
-= | |||
*= | |||
/= | |||
~/= | |||
%= | |||
~%= | |||
<<= | |||
>>= | |||
>>>= | |||
&= | |||
^= | |||
|= |
Functions
You declare functions with the 'func' keyword in MiniC. Functions cannot be declared like they can in many well known languages, yet. The declaration must be assigned to a variable.
i = func(){ };
Then to use it, treat the variable as a function call
i();
You can use in conjunction with functions, the 'return' keyword to return values.
i = func() { return 10; }; print(i()); // prints out '10'
There is also the keyword 'this' that can be used in conjunction with functions to return scopes. More on this in scopes.
Arrays
Arrays, unlike in many popular scripting languages, are not a free-for-all collection type, they are merely a stack. For a free-for-all like collection type, look into Scopes.
Arrays can be declared by using two square brackets.
arr = [];
Arrays can be declared with values already set by separating expressions by commas.
arr = [10, 15, 20];
Arrays of arrays (...of arrays (...etc)) are also supported.
arr = [[10, 15], [20, 25], [30, 35]];
You access arrays with square backets and the index where the value in the array sits. The index of arrays start at 0.
print(arr[0]);
To access arrays of arrays, simply add another bracket and the index of that object.
print(arr[0][0]);
Arrays can take any value.
arr = ['Hello', 10, 3.4e+3, ['World'], {var m='hello'}];
Scopes
Scopes are probably the most complex feature of MiniC and have a lot of complexity regarding what you can do with them. Scopes are what they say they are. They are scopes that have their own container of variables. They do not act like closures at the moment, although it is a planned feature.
Declare via FunctionScopes can be declared immediately or through a function call. This example shows how to declare through a function.
i = func() { m = 5; return this; };
After declaring that function you can then call it.
example = i();
The variable 'example' has become a scope.
Immediate ScopeTo declare an immediate scope, use the curly braces, '{' and '}', to place data in.
i = { m = 10 };
To declare with multiple values, separate by commas the values
i = { m = 10, n = 15, g = { f = 9 } };Accessing Scopes
Accessing scopes is done via the scope accessor symbol '.' (a dot).
example = { m = 5 }; example.m
That would return the value 5. Be careful though, MiniC backtracks through scopes to locate variable names.
m = 10; example = { m = 5 }; example.m; // error, m is not a member of 'example' scope
This error is because 'm' was already declared on a higher scope, and MiniC found it and set it instead of setting a local variable called 'm' to that value. This applies to both immediate scopes and functions. To correct this problem always use the 'var' keyword beforehand.
m = 10; example = { var m = 5 }; example.m; // returns the value 5Alternative access
MiniC provides an alternative way to access members of a scope via array accessors. This allows for reflection-esque features when accessing scopes.
example = { var m = 5 }; example['m']; // returns the value 5
You can also access arrays within scopes via the brackets.
m = { i = [10] }; print(m['i'][0]);
And since the array brackets are parsed on the same operator precedence as parenthesis for function calls, this also works.
m = { i = [ func() { return 10; } ] }; print(m['i'][0]());
Assuming you've been following this up 'til now, you can safely assume that this is also alright.
m = { i = [ func() { return 10; } ] }; print(m.i[0]());Scope Overloading
You can overload operators for scopes to mimic class-like features of C++ and some other popular languages. To do so you must declare a special variable name for the function. The following table shows what the special variable names are.
Variable Name | Number of Parameters | Overloaded Operator |
preinc$ | 1 | ++ … |
predec$ | 1 | -- … |
postinc$ | 1 | … ++ |
postdec$ | 1 | … -- |
not$ | 1 | ! |
bnot$ | 1 | ~ |
neg$ | 1 | - … |
shr$ | 2 | >> |
shl$ | 2 | << |
lsr$ | 2 | >>> |
add$ | 2 | + |
sub$ | 2 | - |
div$ | 2 | / |
idiv$ | 2 | ~/ |
mod$ | 2 | % |
imod$ | 2 | ~% |
mul$ | 2 | * |
band$ | 2 | & |
bor$ | 2 | | |
xor$ | 2 | ^ |
lt$ | 2 | < |
gt$ | 2 | > |
lte$ | 2 | <= |
gte$ | 2 | >= |
eq$ | 2 | == |
noteq$ | 2 | != |
An example of this...
Vector = { var new = func( x, y ) { return { var x = x, var y = y, var add$ = &Vector.add, var sub$ = &Vector.sub, var mul$ = &Vector.mul, var div$ = &Vector.div, var idiv$ = &Vector.intdiv }; }, var add = func( a, b ) { return Vector.new(a.x + b.x, a.y + b.y); }, var sub = func( a, b ) { return Vector.new(a.x - b.x, a.y - b.y); }, var mul = func( a, b ) { if ( typeof(b) == 'number' ) return Vector.new(a.x * b, a.y * b); return Vector.new(a.x * b.x, a.y * b.y); }, var div = func( a, b ) { if ( typeof(b) == 'number' ) return Vector.new(a.x / b, a.y / b); return Vector.new(a.x / b.x, a.y / b.y); }, var intdiv = func( a, b ) { if ( typeof(b) == 'number' ) return Vector.new(a.x ~/ b, a.y ~/ b); return Vector.new(a.x ~/ b.x, a.y ~/ b.y); } }; test = Vector.new(10, 12); test2= Vector.new(33, 23); test3 = test + test2; print(test3.x + ',' + test3.y); print("------------"); test3 = test / 3; print(test3.x + ',' + test3.y); print("------------"); test3 = test ~/ 3; print(test3.x + ',' + test3.y); print("------------"); test3 = test2 - test; print(test3.x + ',' + test3.y); print("------------"); test3 = test2 * 3; print(test3.x + ',' + test3.y); print("------------");
Function Delegates
The language provides a feature to internally communicate functions with MiniC. In order to accomplish this you must use the 'IVMDelegate' Monkey interface and implement a class for the function. For example, to interface the function 'sin', you would do the following.
Class SineDelegate Implements IVMDelegate Method Exec:VMValue(scope:VMScope, params:VMValue[]) Return VMValue.CreateFloat(Sin(params[0].dataFloat)) End End
After creating a delegate you have to register it with the script. Assuming that the variable 'script' is a VMScope type.
script.Register("sin", New SineDelegate())
You can also lower the memory cost of repeated delegates for the same function over multiple scripts by using singletons.
Class SineDelegate Implements IVMDelegate Method Exec:VMValue(scope:VMScope, params:VMValue[]) Return VMValue.CreateFloat(Sin(params[0].dataFloat)) End Function Instance:VMValue() If g_Instance = Null g_Instance = VMValue.CreateDelegate(New SineDelegate()) End Return g_Instance End Global g_Instance:VMValue End
Then just directly insert into the scope.
script.Register("sin", SineDelegate.Instance())
Alternatively, you can register it globally if multiple scripts use the same interpreter (aka VM class)
script.RegisterGlobal("sin", SineDelegate.Instance())
Internal Objects
You can interface internal objects in Monkey with MiniC. Understanding how to interface classes might be tricky at first, but I think it's pretty easy to catch on. To do this you must use the 'IVMObjectInterface' Monkey interface and implement in a class.
Class ExampleObject Implements IVMObjectInterface
Then you must create the methods that the interface requires
Method VMSet:VMValue( scope:VMScope, varName:String, data:VMValue ) Method VMGet:VMValue( scope:VMScope, varName:String ) Method VMCall:VMValue( scope:VMScope, varName:String, params:VMValue[] )
The VMSet method is for 'setters' or 'mutators' of that object. The VMGet method is for 'getters' or 'accessors' of that object. The VMCall method is for method calls in that object.
Now you must setup the methods to return the values wanted by the interpreter. In the end, it will look something like this example.
Import minic Function Main:Int() Local vm:VM = New VM() Local script:String = "" script += "a = new_example();" script += "print(a.length);" script += "a.length = 10;" script += "print(a.length);" script += "a.changeLengthTo(15);" script += "print(a.length);" Local parsed:VMScope = vm.LoadScript(script, Null, "test script") parsed.Register("new_example", New ExampleObjectDelegate()) parsed.Exec() End Class ExampleObject Implements IVMObjectInterface Method VMSet:VMValue( scope:VMScope, varName:String, data:VMValue ) Select varName Case "length" length = Int(data.dataFloat) End Return Null End Method VMGet:VMValue( scope:VMScope, varName:String ) Select varName Case "length" Return VMValue.CreateFloat(length) End Return Null End Method VMCall:VMValue( scope:VMScope, varName:String, params:VMValue[] ) Select varName Case "changeLengthTo" ChangeLengthTo( Int(params[0].dataFloat) ) End Return Null End Method ChangeLengthTo:Void( length:Int ) Self.length = length End Field length:Int End Class ExampleObjectDelegate Implements IVMDelegate Method Exec:VMValue(scope:VMScope, params:VMValue[]) Return VMValue.CreateObject(New ExampleObject()) End End