The Quxlang language is well off to being a usable programming language, though not quite there yet. Many of the most difficult aspects of the core language frontend have been completed, although there are still a few heavy hitters left over.
I am pleased to announce that constructors/destructors appear to be mostly functional and working aside from named parameters in initialization expressions (works if you call the class directly), along with pointer arithmetic and the various types of pointers.
Consider for example, the following code which now works in the constexpr interpreter:
::test_argument_passing_helper FUNCTION(@arg bam, @int & I32)
{
int := int + arg.x;
}
::test_argument_passing FUNCTION()
{
VAR b bam;
b.x := 5;
VAR i I32;
b.y := i<-;
test_argument_passing_helper( @int i, @arg b);
ASSERT(i == 6);
}
::bam CLASS
{
.x VAR I32;
.y VAR -> I32;
.CONSTRUCTOR FUNCTION(@OTHER CONST& bam)
{
.x := OTHER.x;
.y := OTHER.y;
}
.CONSTRUCTOR FUNCTION(@OTHER TEMP& bam)
{
.x <-> OTHER.x;
.y <-> OTHER.y;
}
.DESTRUCTOR FUNCTION()
{
IF (.y??)
{
.y-> := .y-> + 1;
}
}
}
A few things can be observed in Quxlang, first we do not have rvalue references like &&, and instead use TEMP& to designate a temporary. This is in contrast to C++ where the reference and qualifier type are separate. In Quxlang, the qualifier is a property of the reference, so objects which are not references do not have qualifiers.
You may also notice that the arguments are not passed in the same order that they are declared. Unlike C++, Quxlang supports named arguments, which allows you to use names (e.g. @source, @dest) instead of positions for arguments. Hopefully this in combination with overloading allows Quxlang to be more flexible than C++ and reduces bugs!
Identifiers in Quxlang must use lower case, but there are special cases where keyword identifiers can be used, such as THIS and OTHER. Keyword identifiers are only allowed if they match a preset list of keywords, and have special semantics.
Another thing to point out is that in Quxlang values are zero-initialized by default, so you don’t need to explicitly write := 0. Currently initialization is mandatory, however later this will be an opt-out behavior. For example, a syntax similar to VAR a I32 := UNINITIALIZED; will be supported. The exact syntax hasn’t yet been determined.
By default, Quxlang will implement a copy constructor for classes which are implicit datatypes. A type is implicitly a datatype if all the members are themselves datatypes. Datatypes in Quxlang implement a few properties, but the gist is that a datatype can be used as the key in a map and also converted between a serialized and memory representation.
Quxlang implements a swap operator <->. The swap operator can be used to swap two objects or values in an expression like a <-> b.
I will note that as of now, an implicit assignment operation is to be defined, however the behavior is a bit different from C++. In Quxlang, the assignment operators by convention return VOID, so statements like a := b := c := 4; are not valid. Also, the default assignment operators take their parameters by-value and not by-reference. This behavior was chosen because it can both reduce development burden and improve code correctness in a few scenarios, for example consider in pseudocode:
VAR a := expr{"+", 4, expr{"*", 5, 6}};
a.rhs.rhs := a;
In the standard C++ behavior of taking assignment by reference, this creates a bit of a problem, we are trying to assign from an object which is deleted in order to make room for itself in the structure.
It’s worth noting this is only the default assignment behavior, so alternate behaviors can be used where there are optimizations to be made in assign-by-reference (for example, buffer re-use in container types) or refcounted types where intelligent logic can reduce refcounter traffic better.
Array pointers are made available which take a form like =>> I32. Array pointers are similar to instance pointers, but they point to a position within an array, and you can do array arithmetic on them.
The goal of array pointers is to make calling conventions more clear, and which values are exposed to modification by external functions. The goal is to introduce aliasing optimizations on these pointer types to improve performance.
We can get the address of an object using <- operator, and dereference a pointer using ->. Thus x<- -> is a no-op. Operator <- and -> operate on instance pointers, which point to only one object. For doing array arithmetic, we use array pointers, which we can access using OPERATOR[&], for example, my_array[& 0] gets the address of the 0id/1st element of the array, and my_array[& 2] gets the 2id/3rd element of the array.
One thing that has been in-progress is the introduction of constant data, such as string constants. Literal genvalues have a limitation, namely that they cannot exist as objects. Literal genvalues can only be used by builtin intrinsics, for example the use of a NUMERIC_LITERAL in the intrinsic function I32::.CONSTRUCTOR(@THIS NEW &I32, @OTHER NUMERIC_LITERAL) generates the lci (load constant integer) QVMIR instruction. However, literals do not have an object representation, which prevents them being used in user-defined types. To remedy this, each *_LITERAL type will have an associated *_CONSTANT type, which is an object containing pointers to the read-only data underlying the literal. The constant is intended to have the .BEGIN() and .END() functions, which will return =>> U8, although this will be changed to =>> BYTE when the BYTE type is implemented. STRING_CONSTANT and NUMERIC_CONSTANT can be used by user-defined types to access the raw characters underlying the constant for construction of memory backed mutable strings or bigints for example.
Major additions required include global variables, and then we can switch to the middle end (which should be relatively simple in comparison to the front-end, given LLVM helpfully does most of that work for us!)
Leave a comment