The Haxial Programming Language
Expressions

When you invoke any command, for each parameter of the command you can supply an expression (except where expressions are disallowed). An expression is usually an arithmetic calculation (but other types of expressions are also possible). The calculated/evaluated result of the expression is passed to the command.

An expression consists of invocations of functions. A function accepts a number of operands, does something with them, and then returns a new value (the result/answer). A function always has exactly 1 return value (but it is possible for the single return value to contain multiple components). The return value or result of a function can be supplied as an operand to another function, creating a hierarchy of function invocations.

Expressions are written in a style of prefix notation by default. Infix notation with symbol operators can optionally be used as shortcut syntax in some cases. Shortcuts will be explained in more detail further ahead.

To add 2 numbers together using the normal/default form of expression, the expression is written like this:

Add[56, 89]

"Add" is the name of a function. "56" is the first operand, and "89" is the second operand. Commas are used to separate operands. The square brackets mark the beginning and end of the operands for this function. No whitespace characters are permitted to be present between the function name and the open-square-bracket character, however they can be present within the brackets.

Each operand can be a literal number, the name of a variable, or another function to calculate first. In the following example, 56 is added with the result of 89 multiplied by 3.

Add[56, Mul[89, 3]]

In the following example, 56 is multiplied by 4, and 89 is multiplied by 3, and those 2 products are added together.

Add[Mul[56,4], Mul[89,3]]

An operand in an expression can be one of these:

In mathematics and computer science, the arity of a function/operator/operation is the number of operands or incoming values. For example, the arity of the addition function is 2 because 2 numbers are added together to form a result. The arity of the "not" function is 1 because 1 value is toggled between true and false and returned as the result.

An arity of zero is possible. For example, the number 56 or a name representing the number 56 can be thought of as a function that has zero operands, and simply returns the value 56 (its own value). Zero-arity functions are required to be written without square brackets.

This prefix-style expression syntax was chosen as the normal/main expression syntax because it supports functions/operators of any arity (any constant number of operands), and also variadic funtions (varying number of operands), and can therefore be considered a "universal" or "all-powerful" expression syntax. Whereas infix notation (for example) supports only functions/operators with an arity of 2.

Variadic Functions

The "Add" function is variadic, meaning that it can accept a varying number of operands (minimum 2). All the operands are added together. For example, the following 2 expressions are equivalent.

Add[Add[1, 2], 3]
Add[1, 2, 3]

Other functions are also variadic, if it makes sense and is useful for them to be variadic. The "Min" (return lowest) and "Max" (return highest) functions are variadic, for example the following will evaluate to whichever of the 4 specified variables contains the lowest number:

Min[a,b,c,d]
Unary Prefix Shortcut

If a function accepts only 1 operand, then the square brackets can optionally be omitted, as a shortcut. For example, the following 2 expressions are equivalent:

Not[x]
Not x

Likewise, the following 2 expressions are also equivalent:

Not[Equal[a,b]]
Not Equal[a,b]

And these 2 expressions are equivalent:

Not[EqualNull[v]]
Not EqualNull v

And these 2 expressions are equivalent:

Not[a] && b
Not a && b

This shortcut happens to be especially convenient for variable/attribute type expressions (expressions that specify the type of a variable or attribute).

If a function is variadic and the square brackets are omitted, it becomes non-variadic (assumes a default number of operands).

Symbol Infix Shortcut

All functions have a name. Some functions have a symbol in addition to their name. Functions with a symbol can optionally be written in infix notation, as a shortcut. For example, the following 2 expressions are equivalent:

Add[56, 89]
56 + 89

Another example, consider a calculation where you want to start with the variable "a", then multiply by "b", then divide by "c", then add "d", then subtract "e". The following 2 equivalent expressions perform that calculation:

Sub[Add[Div[Mul[a, b], c], d], e]
a * b / c + d - e

When using the infix shortcut, there is no operator precedence. The expression is evaluated in the order that it appears (simply left to right). Multiplication and division are NOT evaluated ahead of addition and subtraction. If you disagree with this behavior, then read the Operator Precedence section for the reasoning behind this design decision.

The order of operations in an infix expression can be changed by inserting round brackets (parentheses). They group/isolate a sub-expression. If writing an expression in the prefix style, round brackets are unnecessary. The following 2 expressions are equivalent:

n + (a * b)
Add[n, Mul[a,b]]

Round brackets can be nested. The following 2 expressions are equivalent:

a + ((b / 2) * (c / 2))
Add[a, Mul[Div[b,2], Div[c,2]] ]

The compiler analyzes expressions and removes any unnecessary round brackets, reordering/rearranging the expression if necessary to achieve the minimally-bracketed form.

The infix and prefix styles can be mixed, meaning they can both be used in the same expression. The following 2 expressions are equivalent:

Div[a * b, 100]
Div[Mul[a,b], 100]

And these 2 are equivalent:

56 + Div[89,14]
Add[56, Div[89,14]]

And these 2 are equivalent:

a + b / Add[x,y*(6/3)] - (52 * Sub[34,1])
Sub[Div[Add[a,b], Add[x,Mul[y,Div[6,3]]]], Mul[52,Sub[34,1]]]

All function invocations using a symbol accept exactly 2 operands. If the function is variadic, it becomes non-variadic when specified by symbol. Infix notation only works with exactly 2 operands whereas the default prefix style supports any number of operands.

A function symbol can consist of 1 or more symbolic (non-alphabetic) characters. Adjacent symbol characters (without whitespace characters between them) other than brackets are grouped together and considered to be a single symbol operator (allowing for multi-character symbol operators). Whitespace, alphabetic, numeric digit characters, and underscore separate symbol operators. Adjacent bracket characters (round, square, or curly) are not grouped together, they are always considered to be separate 1-character symbol operators.

NameSymbol
Add+
Sub-
Mul*
Power**
Div/
DivRem%
Less<
Greater>
LessEqual<=
GreaterEqual>=
Equal==
NotEqual!=
And&&
NotAnd!&&
Or||
NotOr!||
Minimum<?
Maximum>?
BitOr|
BitXor^
BitAnd&
BitClear&~
BitShiftUp<<
BitShiftDown>>

Having a reasonably small number of symbols is convenient. Having a large number of symbols would make the source code difficult to read, and it would be easy to forget the meaning of symbols. Thus the number of symbols should be kept reasonably low.

If you wonder why a double equals sign ("==") is used for the "Equal" function and not a single equals sign ("="), then consider the difference between making 2 things equal versus testing/checking if 2 things are equal. Assignment versus equality. In mathematics and general usage, "a=b" normally means that "a" is assigned the value of "b" ("a" is made equal to "b"). The "Equal" function determines if its 2 operands are equal (without changing them), thus a double equals sign was chosen to mean equality testing, as distinguished from the single equals sign normally meaning assignment.

Functions

None of these functions modify any of their operands (no side-effects). Rather they return a value.

Some of the functions have a long/full name, and a short/abbreviated name. Either can be used.

FunctionDescription
Add[a, b, ...]Returns the sum (addition) of all the operands.
AddProd[n, a, b]Equivalent to Add[n, Mul[a, b]]. May optimize by using fused-multiply-add instruction.
Sub[a, b, ...]Each operand is subtracted by the next (or from the previous) to form the result.
SubRev[b, a]SubRev[b,a] is same as Sub[a,b]. May be used to avoid round brackets. May be used by an optimizer.
Dif[a, b]Subtract then force positive. Works correctly with unsigned math without underflowing. Equivalent to If[a>b, a-b, b-a].
Mul[a, b, ...]Returns the product (multiplication) of all the operands.
MulDiv[a, n, d]Returns the first operand multiplied by the second operand (using double-precision math) then divided by the third operand. Equivalent to multiplying by a fraction or ratio.
Div[a, b, ...]Returns the first operand divided by every subsequent operand.
DivRev[b, a]DivRev[b,a] is same as Div[a,b]. May be used to avoid round brackets. May be used by an optimizer.
DivRem[a, b]Returns the remainder of dividing the first operand by the second.
DivRemRev[b, a]DivRemRev[b,a] is same as DivRem[a,b]. May be used by an optimizer.
Power[a, b]
Pow
Returns the first operand raised to the power of the second operand.
PowerRem[a, b, m]
PowRem
The first operand raised to the power of the second operand, then the remainder of division by the third operand, calculated as 1 step not 2.
 
InvSign[n]If operand is positive, return it as negative, else if operand is negative, return it as positive. Equivalent to Sub[0,n].
ForcePos[n]If operand is negative, return it as positive, else return it unchanged (result is always positive). aka "absolute value". Equivalent to If[n<0, 0-n, n].
ForceNeg[n]If operand is positive, return it as negative, else return it unchanged (result is always negative). aka "negative absolute". Equivalent to If[n>0, 0-n, n].
 
Maximum[a, b, ...]
Max
Returns whichever of the operands is the highest number.
Minimum[a, b, ...]
Min
Returns whichever of the operands is the lowest number.
Clamp[n, lo, hi]Return the first operand constrained to the range specified by the second and third operands (minimum and maximum). Equivalent to Min[Max[n, lo], hi].
 
Less[a, b, ...]
LT
True if the first operand is less than the second. Or true if the operands are descending.
Greater[a, b, ...]
GT
True if the first operand is greater than the second. Or true if the operands are ascending.
LessEqual[a, b, ...]
LTE
True if the first operand is less than or equal to the second. Or true if the operands are descending or equal.
GreaterEqual[a, b, ...]
GTE
True if the first operand is greater than or equal to the second. Or true if the operands are ascending or equal.
Equal[a, b, ...]
EQ
True if all the operands are identical. If an operand is a reference, its target is accessed and compared. If all the operands are references, same as EqualTarget[].
NotEqual[a, b]
NEQ
Same as Not[Equal[a,b]]
EqualZero[n]
EQZ
Same as Equal[n,0]
NotEqualZero[n]
NEQZ
Same as Not[Equal[n,0]]. Converts an integer to a boolean.
 
EqualRef[a, b, ...]All the operands must be references. Returns true if all the references are equal. Compares the references themselves not the values of the targets. No dereferencing is performed. If references are equal, they point to the same target. If references are not equal, they point to different targets that possibly have equal values. If all the references are null, returns true. If any but not all of the references are null, returns false.
EqualTarget[a, b, ...]All the operands must be references. Returns true if all the target values are equal (regardless of whether the references are equal). Fails if any or all of the references are null. If an operand is a reference to a reference, it is dereferenced only once (first target is used, not ultimate target).
NotEqualRef[a, b]Same as Not[EqualRef[a,b]]
NotEqualTarget[a, b]Same as Not[EqualTarget[a,b]]
 
IsNullRef[r]The operand must be a reference. Returns true if the reference is null. Returns false if the reference is unnullable or is not null.
NotNullRef[r]Same as Not[IsNullRef[r]]
AnyNullRefs[r1, r2, ...]All the operands must be references. Returns true if any or all of the references are null. Same as Or[IsNullRef[r1], IsNullRef[r2], ...]
NoNullRefs[r1, r2, ...]All the operands must be references. Returns true if all of the references are not null (or false if any or all of the reference are null). Same as And[NotNullRef[r1], NotNullRef[r2], ...]
EqualNull[v]Same as Equal[v, null]. Returns true if the operand is null. Returns false if the operand is unnullable or is not null. If the operand is a reference, returns whether the target is null (not whether the reference is null).
NotEqualNull[v]Same as Not[EqualNull[v]] or Not[Equal[v, null]]
Null
null
Zero-arity function. Provides the value null. Null is often used to indicate/represent a missing, unspecified, undefined, unknown, or unsupplied value.
 
Deref[r]The operand must be a reference. Returns the target of the reference. Means that the target of the reference should be used/read/modified, not the reference itself. Fails if the reference is null. This function dereferences only once, thus in the case of the operand being a reference to a reference, the result is the first target not the ultimate target.
Deref2[r]Same as Deref[Deref[r]]. The operand must be a reference to a reference (or a reference to a reference to a reference, etc).
 
Or[a, b, ...]True if any or all of the operands are true.
Xor[a, b, ...]True if an odd number of the operands are true and the rest are false. False if an even number of the operands are true and the rest are false.
NotOr[a, b, ...]
Nor
False if any of the operands are true, or true if they are all false.
And[a, b, ...]True if all the operands are true.
NotAnd[a, b, ...]
Nand
True if any of the operands are false, or false if they are all true.
Not[a, ...]True if all the operands are false.
True
true
Zero-arity function. Provides the boolean value true. True can be used to be mean "yes", "on", "exists", etc.
False
false
Zero-arity function. Provides the boolean value false. False can be used to be mean "no", "off", "nonexistent", etc.
 
BitOr[a, b, ...]
BOr
Bitwise OR all the operands together.
BitXor[a, b, ...]
BXor
Bitwise XOR all the operands together.
BitAnd[a, b, ...]
BAnd
Bitwise AND all the operands together.
BitClear[a, b, ...]
BClr
Like BitAnd but instead of preserving those bits, it clears them. Equivalent to BitAnd[a, BitInvert[b]].
BitShiftUp[n, cnt]
BShUp
Shift the first operand up/left by the number of bits specified by the second operand.
BitShiftDown[n, cnt]
BShDwn
Shift the first operand down/right by the number of bits specified by the second operand.
BitInvert[n]
BInvert
Invert all the bits in the operand. 0 bits become 1, and 1 bits become 0.
 
If[cond, t, f]If first operand is true, return second operand, else return third operand.
IfNot[cond, f, t]If first operand is false, return second operand, else return third operand.
 
OmitZero-arity function. Used to explicitly indicate that a parameter value is to be omitted and the default value should be used.
 
Unchecked[expr]Returns the sub-expression with its functions changed to unchecked versions, where applicable. Does not override a nested "Checked" function. Unchecked meaning not checking for arithmetic overflow/underflow.
Checked[expr]Returns the sub-expression with its functions changed to checked versions, where applicable. Does not override a nested "Unchecked" function. Checked meaning checking for arithmetic overflow/underflow.
 
IsEmpty[a]Returns true if the specified text or array attribute (or other applicable type) is empty. The parameter can be a reference or the attribute directly.
NotEmpty[a]Same as Not[IsEmpty[a]].
GetSize[a]Returns size of the specified text attribute (or other applicable type). The parameter can be a reference or the attribute directly.
GetCount[a]Returns item count of the specified array attribute (or other applicable type). Parameter can be reference or attribute.
GetMaxSize[a]Returns maximum size of the specified text attribute (or other applicable type). Parameter can be reference or attribute.
GetMaxCount[a]Returns maximum item count of the specified array attribute (or other applicable type). Parameter can be reference or attribute.

Extra mathematical functions:

FunctionDescription
IntegerPart[n]Gives the integer part of the operand. Frex 2.6 yields 2.
FractionalPart[n]Gives the fractional part of the operand. Frex 2.6 yields 0.6.
RoundToIntegral[n]
RndToInt
Round number to nearest integral number.
RoundDownToIntegral[n]
RndDownToInt
Round number down to integral number. aka "floor".
RoundUpToIntegral[n]
RndUpToInt
Round number up to integral number. aka "ceil".
 
SquareRoot[n]
Sqrt
Gives the square root of the operand.
CubeRoot[n]
Cbrt
Gives the cube root of the operand.
Sine[n]
Sin
The sine of the operand. Operand in radians.
Cosine[n]
Cos
The cosine of the operand. Operand in radians.
Tangent[n]
Tan
The tangent of the operand. Operand in radians.
Logarithm[n, b]
Log
Second operand is log base.
NaturalLogarithm[n]
NatLog
Logarithm with "e" as base.
EPower[n]Calculate "e" (natural log base) to the power of the operand.
GreatestCommonDivisor[a, b]
GCD
Get the greatest common divisor of the 2 operands.

Setting a Variable

A variable can be set to the result of an expression using the "set" command. The first parameter to the "set" command is the variable to set. The second parameter is the expression. For example:

set res, Add[Sub[a, b], c];
set res, a - b + c;

The variable being set can be used in the expression, as in the following 2 non-equivalent examples. The value of "res" used in the expression is the value of "res" before it is changed by the "set" command.

set res, a - b + res;
set res, res * 2 + n;
Invert Sign (Negate)

To invert the sign of a variable (also known as "negate"), use the "InvSign" function. If the operand is positive, it is returned as negative, else if the operand is negative, it is returned as positive.

The "InvSign" function has no corresponding symbol shortcut. "-" is the symbol shortcut for the "Sub" (Subtract) function and is not shared with "InvSign".

"-" can be used for subtraction and also to specify a literal negative number. If being used for subtraction, then if the second operand (the right-side operand) is a literal number, then there must be whitespace present between the subtract operator ("-") and the literal number ("a - 3" not "a-3"). If being used to specify a literal negative number, then there must be NO whitespace between the "-" and the digits of the number ("-3" not "- 3").

Problems with the idea of using "-" to invert the sign of a variable:

  1. It looks like the variable/value is being made into a negative number, whereas actually the sign is being inverted and the result may be positive despite the presence of the negative sign.
  2. It is already being used for the subtract function.
  3. It would introduce an inconsistency into the language because "-" as negate would act as a prefix operator whereas normally symbols act as infix operators (and the nearest equivalent to infix for a unary operator is actually postfix not prefix).
Differences Between Functions and Commands

Functions and commands have similarities, but are different.