Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Expressions

MiniD is a C-style language, and inherits most of the algebraic expressions from the language family.

Base Expressions

BaseExpression:
	Assignment
	Expression

An expression is either an assignment or a "normal" expression. Assignments don't have a value, while "normal" expressions do.

Assignments

Assignment:
	AssignmentLHS {',' AssignmentLHS} '=' Expression
	AssignmentLHS '+=' Expression
	AssignmentLHS '-=' Expression
	AssignmentLHS '~=' Expression
	AssignmentLHS '*=' Expression
	AssignmentLHS '/=' Expression
	AssignmentLHS '%=' Expression
	AssignmentLHS '<<=' Expression
	AssignmentLHS '>>=' Expression
	AssignmentLHS '>>>=' Expression
	AssignmentLHS '|=' Expression
	AssignmentLHS '^=' Expression
	AssignmentLHS '&=' Expression
	AssignmentLHS '?=' Expression
	'++' PrimaryExpression
	'--' PrimaryExpression
	PrimaryExpression '++'
	PrimaryExpression '--'

AssignmentLHS:
	Identifier
	// Note - for these, the PostfixExpression must start with Identifier or with 'this'.
	PostfixExpression '[' Expression ']'
	PostfixExpression '[' [Expression] '..' [Expression] ']'
	PostfixExpression '.' Identifier

Regular assignments in MiniD come in two flavors: regular and multiple. Multiple assignments allow multiple destinations (on the left), and are indicated by writing a function call, vararg, or yield expression as the source. Regular assignments may only have one target, and have anything but a function call or vararg expression as the source.

This is done for a couple reasons. One, one of the only uses of multiple assignment in Lua is to get multiple values from a function or vararg expression. Two, multiple assignment in most cases looks cryptic, and it is sometimes difficult to determine which expression matches up with which destination, such as in the following:

obj.variable, table[some * long + expression], array[index] = func(abc, def, ghi), anotherTable["expression"], vararg;

By restricting the number of sources to one, it at least makes it obvious that all the values for the destinations are coming from one place.

Operation assignments (op=) - These operators are shortcuts for longer expressions. Instead of writing x = x + 4, you can instead write x += 4. In order for this to be possible, the expression on the left side must be both a valid lvalue, or "place to put a value", and a valid rvalue, or "source of a value". Additionally, the left-hand side is only evaluated once, saving time for changing the value of a complex left-hand value.

The metamethod names for these are: opAddAssign, opSubAssign, opCatAssign, opMulAssign, opDivAssign, opModAssign, opShlAssign, opShrAssign, opUshrAssign, opOrAssign, opXorAssign, and opAndAssign.

Optional assignment (?=) - This is an operator which will only assign the value on the right hand side to the left hand side if the left hand side currently holds null. So the expression "a ?= b" is semantically equivalent to "if(a is null) a = b".

Pre- and Post-Increment and Decrement - These operators add or subtract the integer value 1 to or from their target. Because they are assignments, they do not have a value, and therefore cannot be embedded in other expressions. This means the the prefix forms are semantically equivalent to the postfix forms; they only both exist for convenience. In either case, increment is rewritten as "target += 1" and decrement is rewritten as "target -= 1", so to overload these operators using metamethods, use the opAddAssign or opSubAssign metamethods.

"Normal" Expressions

Expression:
	ConditionalExpression

Normal expressions all have a value. That's it.

Conditional Expressions

ConditionalExpression:
	OrOrExpression '?' Expression ':' ConditionalExpression

This is called the "conditional operator" or the "ternary operator." It works like an if-else statement, except it's an expression that yields a value. The expression before the '?' is evaluated. If it is true, the result of the conditional expression is the second expression. Otherwise, the result is the final expression.

Logical Expressions

OrOrExpression:
	AndAndExpression
	OrOrExpression '||' AndAndExpression

AndAndExpression:
	OrExpression
	AndAndExpression '&&' OrExpression

Logical Or (||) - The left-hand expression is evaluated and converted to a boolean (it must be convertible to a boolean for it to work). If the result is true, the right-hand side is skipped and the expression returns the left-hand side. If the left-hand side is false, the right-hand side is evaluated, and the expression returns the right-hand value. Optionally skipping the right-hand side is called "short-circuit evaluation."

Logical And (&&) - The left-hand expression is evaluated and converted to a boolean (it must be convertible to a boolean for it to work). If the result is false, the right-hand side is skipped and the expression returns the left-hand side. If the left-hand side is true, the right-hand side is evaluated, and the expression returns the right-hand value.

Bitwise Operators

OrExpression:
	XorExpression
	OrExpression '|' XorExpression

XorExpression:
	AndExpression
	XorExpression '^' AndExpression

AndExpression:
	EqualExpression
	AndExpression '&' EqualExpression

Both operands of all of these operators must be integers, or types which have opOr, opXor, or opAnd metamethods defined.

These perform typical bitwise manipulations.

Equality and Identity

EqualExpression:
	RelExpression
	EqualExpression '==' RelExpression
	EqualExpression '!=' RelExpression
	EqualExpression 'is' RelExpression
	EqualExpression '!' 'is' RelExpression

Equality (==) sees if two values have the same value. It works as follows: If the two values are different types, the left-hand's opCmp metamethod is attempted. If both sides are numerical types (int or float), they are tested for equality. If both sides are any other value type (null, bool, char), they are tested for equality. Otherwise, both sides must be reference types; if they point to the same object (that is, they have the same pointer value), they compare as equal. Otherwise, the left-hand's opCmp metamethod is attempted. Inequality simply inverts the result of equality.

Identity (is) is defined as two things being fundamentally the same -- same type, same contents. For non-reference types, this is the same as equality (although "0 is 0.0" will evaluate to false unlike "0 == 0.0", since 0 and 0.0 are different types). For reference types, this sees if two references point to the same location, and nothing more (no metamethods or anything).

One important difference between equality and identity is that equality is more strict than identity. If you use equality to compare values of different types (except for comparing ints to floats), and there is no opCmp metamethod defined for the left-hand side, an error will be thrown. However, if you use identity, the comparison will simply return false if the values are of different types. So if you want to see if a value is null, you should use "is" instead of "==".

Comparison

RelExpression:
	ShiftExpression
	RelExpression 'as' ShiftExpression
	RelExpression 'in' ShiftExpression
	RelExpression '!' 'in' ShiftExpression
	RelExpression '<' ShiftExpression
	RelExpression '<=' ShiftExpression
	RelExpression '>' ShiftExpression
	RelExpression '>=' ShiftExpression
	RelExpression '<=>' ShiftExpression

Most of these operators compare their arguments and return a boolean value representing whether the test was true or false. For basic types, their function is obvious. Characters and strings are compared by codepoint value, not lexicographically. For other types, it will try to call the opCmp metamethod; if none exists, an exception is thrown.

The as expression will attempt to cast the class instance on the left side to the class type on the right side. If the cast is successful, the left side is returned. If not, null is returned.

The in and !in expressions test for the existence of elements in containers. Their results are always booleans. If the right side is a string, the left side must be a character, and the string is searched for the character. If the right side is an array, it will be searched element-by-element for the value on the left side. If the right side is a table, its keys will be searched for the value on the left side (and therefore, the left side must not be null). If the right side is a namespace, the left side must be a string, and it will be searched for in the keys of the namespace. For all other types, the opIn metamethod is attempted. The result is converted to a boolean, and will be automatically inverted for !in.

The three-way comparison operator, <=>, is a bit different from the other comparison operators. It compares its two arguments, and instead of a boolean value, it returns an integer: a negative integer if the left side is less than the right, a positive integer if the left side is greater than the right, and 0 if the two operands are equal. This is basically like calling opCmp on the left value and passing it the right, except this also works for types without an opCmp. You might use this in a statement such as "if((a <=> b) < 0)", though that's probably not the best use for it. A more common use is when you're overloading opCmp and need to return a three-way comparison value.

Bit Shifting

ShiftExpression:
	ShiftExpression '<<' AddExpression
	ShiftExpression '>>' AddExpression
	ShiftExpression '>>>' AddExpression

These shift the operand on the left by the operand on the right. This can only be performed on integers and on other types which have opShl, opShr, or opUShr metamethods.

There are two right-shift operators. The first, ">>", is signed right shift. This shifts all but the top bit (the sign bit) right, and "smears" the top bit into the new locations. This preserves sign with signed numbers. The other kind, ">>>", is unsigned right shift. This shifts all the bits to the right, and fills in the new locations with 0s.

Addition, Subtraction, and Concatenation

AddExpression:
	MulExpression
	AddExpression + MulExpression
	AddExpression - MulExpression
	AddExpression ~ MulExpression

Addition and subtraction work as expected for numeric types. For other types, it will attempt to call the opAdd or opSub metamethods, and will throw an exception if none exist.

Concatenation is achieved with the tilde (~) operator. This is the act of putting two lists of items into a single list. Concatenation always creates a new object; the sources are not affected. If both operands are arrays or strings, concatenation is straightforward. It is also possible to concatenate a string with a character or to concatenate two characters, resulting in new strings. All other types will attempt the opCat metamethod.

Multiplication, Division, and Modulo Arithmetic

MulExpression:
	UnaryExpression
	MulExpression '*' UnaryExpression
	MulExpression '/' UnaryExpression
	MulExpression '%' UnaryExpression

As with other mathematical operators, these work as expected for numeric types. For other types, it will attempt to call the opMul, opDiv or opMod metamethods.

Unary Expressions

UnaryExpression:
	PostfixExpression
	'-' UnaryExpression
	'!' UnaryExpression
	'~' UnaryExpression
	'#' UnaryExpression
	'coroutine' UnaryExpression

Negation (-). This negates the given expression; as such, it can only be applied to numeric types and types with the opNeg metamethod. Expressions such as -4 are translated into negative numeric literals during the semantic pass.

Logical Not (!). This operates on a boolean expression, or an expression which can be implicitly converted to a boolean. The result is the boolean value opposite of what it was passed; so !true gives false and !false gives true.

Bitwise Not (~). Also known as "complement." This flips all the bits in the operand, so that all 0s become 1s and vice versa. Can be applied to integers and to types with an opCom metamethod.

Length (#). This is an operator for retrieving the length of a value. For tables, if there is no opLength metamethod, this returns the number of key-value pairs in the table. For strings, it returns the number of characters, and for arrays, the number of elements.

coroutine. This takes a function closure (either a script closure or a native closure), and creates a thread object from it. See Functions for information on coroutines.

Postfix Expressions

PostfixExpression:
	PrimaryExpression
	PostfixExpression '[' Expression ']'
	PostfixExpression '[' [Expression] '..' [Expression] ']'
	PostfixExpression '.' (Identifier | 'super' | 'class')
	PostfixExpression '(' [Arguments] ')'
	PostfixExpression '(' 'with' Arguments ')'

Arguments:
	Expression {',' Arguments}

Index expressions. There are really two kinds of indexing: getting a value and setting a value. By default, arrays, tables, strings, classes, and instances can be indexed for getting a value, and all those but strings can be indexed for setting a value (since strings in MiniD are immutable). For strings and arrays, it checks to see if the index is an integer. If it is, but it's negative, it is converted into an index from the end (i.e. -1 means the last element in the array, -2 means second-to-last etc.). If the index is valid, the array or string is indexed as usual. For all types (strings and arrays included), if the default indexing method fails for whatever reason, any available opIndex or opIndexAssign metamethods are tried. If those don't exist, or are invalid, or fail, then the indexing operation fails.

Slice expressions. These allow you to get subsets of list-like types. Like indexing, there are two kinds: getting a slice and setting a slice. Arrays and strings can have slices taken from them; slicing creates a new object, unless both indices are left out, in which case the original array is returned. Array slices always point into the source array's data, so modifying the slice's data will modify the original array's data. Arrays can also be slice-assigned. There are two kinds of array slice-assigning. If the value being assigned is another array, then the contents of that array will be copied into the destination array. The length of the source array must match the length of the slice. If the value being assigned is anything other than an array, then all the elements of the slice of the destination array are set to that value. As with indexing, for all types, if the default slicing mechanism fails, the opSlice or opSliceAssign metamethods are tried.

The indices of a slice expression are both optional. Leaving an index out means "to this end of the container." So leaving the first index out means the slice starts at the first element; leaving the second index out means the slice ends at the last element. You can also use negative indices to start indexing from the end of the container, so -1 means the last element, -2 means the second-to-last, and so on.

Omitted slice indices are passed as null parameters to metamethods, if metamethods are called.

Dot expressions. There are three things which can follow a dot in a dot expression. In the first case, when an identifier follows the dot, it's really just syntactic sugar for an index expression. The identifier is interpreted as a string index into the indexed object, or if then followed by a call expression, as the name of the method to call.

If the dot is followed by the 'super' keyword, it will get the superclass of the class or instance on the left side of the dot. If the given class or instance has no base class, it will give null. Using it on any other types will throw an exception.

If the dot is followed by the 'class' keyword, it will get the class of the instance on the left side of the dot. Using it on any other types will throw an exception.

Call expressions. Using call expressions, you can call functions, classes (to make new class instances), threads (to resume them), and other types which have an opCall metamethod. If the expression before the parentheses is a dot expression, the right side of the dot expression is the name of the method to call, and the left side of the dot expression is passed as the 'this' parameter of the called function. This is a method call. When a non-method call occurs, the value passed as the 'this' parameter is determined in other ways. See Functions for more details.

If you want to call a function with an arbitrary context rather than having MiniD figure out the context, you can use the with keyword to indicate the context with which to call the function. This can be done with regular function calls and with method calls. Examples:

local function f()
{
	writefln(this);
}

f(); // prints the name of the current module
f(with 5); // prints '5'

local class A
{
	mX;

	function constructor(x)
	{
		mX = x;
	}

	function f(y)
	{
		writefln(mX, ", ", y);
	}
}

local a = A(5);
a.f(10); // prints '5, 10'
A.f(10); // prints 'null, 10', since the class 'A' has been passed as the context
A.f(with a, 10); // prints '5, 10', since now the instance 'a' is passed as the context

Primary Expressions

PrimaryExpression:
	Identifier
	'this'
	'null'
	'true'
	'false'
	'vararg'
	IntLiteral
	FloatLiteral
	CharLiteral
	StringLiteral
	'function' [Identifier] Parameters (BlockStatement | Expression)
	'class' [Identifier] [':' Expression] '{' {ClassMember} '}'
	'(' Expression ')'
	TableCtor
	ArrayCtor
	NamespaceCtor
	'yield' '(' [Arguments] ')'
	'super' ['.' Identifier] '(' [Arguments] ')'

Identifiers. A lone identifier can refer to local variables, local variables declared in enclosing functions, and global variables. This is determined by searching for a local declaration in the function and enclosing functions; if none is found, the identifier is assumed to refer to a global.

this. this is a hidden parameter to every function which represents the context with which the function should operate. For some functions, the this parameter doesn't have much meaning, but for things like class methods, the this parameter is the object on which the function was called. See Functions for more info.

null. null is a type all its own, and it means the absence of any useful type. All variables default to null. If you don't pass enough arguments to a function, the extra arguments are initialized to null. Extra variables on the left-hand side of an assignment are assigned null.

true and false. These are literals of the boolean type. They are the only values the boolean type can hold.

vararg. This is a special expression, only available in variadic functions (see Functions). You can think of vararg as a sort of function which returns multiple values, which correspond to the extra parameters passed to the function. You can use it like so:

function foo(vararg)
{
	local x, y = vararg; // x and y will be set to the first two arguments (or null, if there are none)

	// Create an array whose fields are the varargs
	local args = [vararg];

	foreach(i, v; args)
		writefln("arg[", i, "] = {}", v);
}

Literals. Integer, float, string, and character literals each correspond to their own types.

Function literals. These expressions return a "closure," or instance of the function. These can then be passed around, returned, called etc. Function literals can be given no name (as in "function(){}"), in which case they are given an automatically-generated name by the compiler. You can also put a name before the parentheses to help make error messages easier to read. Secondly, the body of a function literal can either be a normal-looking function body, or it can be an expression. The expression is made to be the return value of the function literal. So writing "function(x) x * x" is shorthand for "function(x) { return x * x; }".

Class literals. You can declare classes inline, much as you can functions. Class literals can be given an optional name, like "class A {}". If no name is given, it is given an automatically-generated name by the compiler. The result of the class literal is the new class type itself.

Parenthesized expressions. You simply use parentheses to change the order of operations. For example, the expression 4 + 5 * 6 will be evaluated using mathematical OOO - that is, multiplication will be done first, and then the addition, giving a result of 34. However, by enclosing the addition in parentheses, such as (4 + 5) * 6, the expression in the parentheses will be evaluated first - giving 9 - and then the multiplication occurs - giving 54. This should be familiar to anyone who knows a bit of simple algebra.

Parenthesized expressions take on a special meaning when they are placed around expressions which give multiple results (function call, vararg, or yield expressions). In this case, the number of results is fixed to exactly one. So while "vararg" would give a list of all the variadic arguments to a function (which could be 0 or more items), "(vararg)" gives exactly one result, which will be the first variadic argument, or null if none were passed.

Yield expressions. Yield expressions look and work a lot like a function call. They take parameters and can return multiple results. They are used to give up control of the currently-executing thread to the calling thread. For more information on yield expressions, see the Functions section.

Super calls. If, in a class, you wish to make a call to a base class's constructor or implementation of a method, you can use a super call to do so. If you simply write it as a function call, as in "super(1, 2, 3)", it will be interpreted as a call to the base class's constructor. Of course, if the class in which this appears has no base class, this is an error. The second form is for calling base class methods, as in "super.func(1, 2)". This will call the base class's implementation of func. Actually, the constructor form is simple syntactic sugar for the expression "super.constructor(1, 2, 3)" -- these two expressions are semantically identical.

Note that super calls are only allowed in class methods. Outside of class methods, you can call the super class method of a given object using something like "a.super.func(with a, 1, 2)". You can also just explicitly state the class to look up the method in, i.d. "SomeClass.func(with a, 1, 2)".

Table Constructors

TableCtor:
	'{' [TableField {',' TableField}] '}'

TableField:
	Identifier '=' Expression
	'[' Expression ']' '=' Expression
	SimpleFunctionDeclaration

Table constructors are a sort of associative array literal. They create a new table every time they are executed. Inside the braces exist the field initializers.

The first form allows you to create "members" in the table. This is something like the dot syntax, in that it's just sugar for using a string index. So something like { foo = 5 } means that the value 5 lives at index "foo".

The second form allows you to use any arbitrary expression as the index, by enclosing it in brackets. The first form is just sugar for ["name"] = value.

The third form allows you to use syntactic sugar for declaring a function as a value. Instead of writing

local t = 
{
	f = function()
	{

	}
}

You can write the more natural:

local t = 
{
	function f()
	{

	}
}

Commas in table constructors are usually required, except after function-style fields, where it is optional. So:

local t = 
{
	function f() { } // OK to skip comma here
	x = 4, // have to have comma here
	g = function() { }, // have to have comma here, not function-style field
	y = 5
}

Array Constructors

ArrayCtor:
	'[' [Expression {',' Expression}] ']'

Array constructors are very straightforward. Like table constructors, each execution of an array constructor creates a new array. The first expression is placed into position 0, the next into 1, and so on. The number of expressions is the length of the array. Commas are required between values.

Namespace Constructors

NamespaceCtor:
	'namespace' Identifier [':' Expression] '{' {NamespaceMember} '}'

NamespaceMember:
	SimpleFunctionDeclaration
	Identifier ['=' Expression] ';'

Namespace constructors look a lot like classes. The Identifier is the name that is given to the namespace. The optional Expression after the colon is the namespace's parent. The parent is only used when a global is looked up in a function that has this namespace as its environment and the global doesn't exist in this namespace. If no expression is given, the namespace's parent defaults to null. Lastly the members of the namespace are very similar to those of classes. You can either declare functions, in which case the name becomes the string index, or variables, where the name also becomes the string index.

Note that any functions you declare inside the namespace will not have the namespace set as its environment. If you want that to happen, you have to do it manually. Here's a simple way to do so:

local ns = namespace X { ... };

foreach(k, v; ns)
	if(isFunction(v))
		v.environment(ns);

This snippet will set the environments of all the functions in ns to ns.