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'.

Valid
abcdef
_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 Function

Scopes 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 Scope

To 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 5
Alternative 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