Releases: Neat-Lang/neat
Neat 0.7.1: I Can't Think Of A New Title Every Time
Implement quoted format string "$(a + b =)"
=> "a + b = 5"
.
-2.0
is now a float literal instead of an expression.
1f
is equivalent to 1.0f
.
Add Vulkan demo. Some C importer fixes.
--unittest
now only enables unittest for the root package.
--unittest=name
enables unittests for the name
package in particular.
Also some caching fixes involving case()
.
Neat 0.7.0: Tentative Windows Support?!
For the first time, we have a Windows build in the downloads!
Note that you must have llvm-mingw with LLVM 15 installed and its \bin\
folder in the PATH
for this to work. Then, running build.bat
should produce a neat.exe
that you can run.
Breaking change: the fail
tuple attribute has been removed.
It was redundant with the Error
class, which is supposed to be the superclass of every error class. For identical behavior, map the former fail
type to breakelse
with case
.
The rest in v0.7.0 is mostly just small bugfixes.
Neat 0.6.0: The Cache What Was Promised
- Breaking change: class fields without
mut
can no longer be mutated.
This change is necessary to allow optimization of refcounting on access to immutable class fields.
In other words, for a class field assigned to an immutable variable:The field access now incurs zero refcounting overhead.Class obj = new Class; Field field = obj.field;
- Macros are now cached between compiler runs, providing a significant speedup on warm cache.
As usual, if you get strange compiler or linker errors,rm -rf .obj
.
I have been looking to add macro caching for years. The entire functionality of macro import
is designed around it. And yet, there were always complications. Well, I've finally come upon a straightforward way that reuses the taskpool system to track changes in files that contributed to a macro ("bill of materials") without requiring a full reparse. The cache files are stored in .obj/macrocache_*
and are simple JSON. Performance benefit varies widely based on project complexity, but should be "noticeable" in any case, with the greatest benefit seen in the testsuite, where macro compilation previously consumed the majority of runtime.
Neat 0.5.3: Prelude to Breakage
- Assignment to index now works through
alias
:
alias x = array[0]; x = 5;
- Allow class fields to be declared
mut
.
This as yet does nothing; it's just parser prep.
Obviously the big upcoming change in 0.6 here is that class fields will no longer be mutable by default. This will require a lot of rewriting; all I can say in excuse is that it is absolutely necessary for performance reasons. The upside of requiring mut
on class fields is that class fields that are not mutable will no longer require a ref increase when accessing the field on a lexical-scoped expression. This should give a moderate but solid speedup going forward.
Neat 0.5.2: Finally Fast-ish
I finally figured out why Neat was unexpectedly a lot slower than D. Turns out, passing 16 bytes (a D array) on AMD64 is very speedy as it's just passed in registers; however, passing 24 bytes (a Neat array) requires an alloca because the SysV ABI demands it be passed as a pointer. For string heavy code, this forces a lot of allocas and ends up with programs spending most of their time doing stack shuffling.
Luckily, while we need to pass structs conforming to the SysV ABI, arrays aren't actually structs and we can decide how we pass them. So structs, tuples and sumtypes are now passed as separate parameters. This alone basically brings the benchmark to a 2x speedup and brings Neat, hopefully, within striking distance of D.
Neat 0.5.1: Make related_post_gen Fast Hopefully
We're not supposed to add optimizations directly for the benchmark. So have Neat 0.5.1, with a whole bunch of generic performance optimizations added for "no particular reason."
But first:
Semi-big language change: &var
now requires mut
.
You can no longer take the address of any variable not declared mut
. This has been an open problem for a long time: the refcounter absolutely relies on mut
to tell it if a variable is allowed to change. if you can just take a pointer to a variable and assign to it, that bypasses all protections. Since you can just make (almost) any variable mut
this should not present any issues.
This used to be an issue for the refcounter, but as of this release neat will engage in more intense optimization of non-mut
variables. As a result, if you somehow manage to mutate a non-mut
variable, your changes may straight up not be visible in other parts of the code.
I'll take this opportunity to draw your attention to a gaping hole in the language. You can call struct methods on variables that are not mut
. These methods can then mutate variables in the struct. If you do this, you will basically break everything. The long-term fix to this is to annotate methods with mut
and only allow mut
methods to mutate this
. The short-term fix is to be careful with what methods you call.
__moveEmplace
, __copyEmplace
These are mainly useful for macro writers. __moveEmplace(source, dest)
will write source
into dest
without any refcounting. __copyEmplace(source, dest)
will write a copy of source
into dest
. Note that neither of them refcount dest
, so they should be used when initializing uninitialized variables and arrays. Use __copyEmplace
if you wish to access source
after the call; with __moveEmplace
, this is illegitimate code.
Loop speedups
for (value in array)
type loops no longer produce pointless bounds checks. This is, I want to emphasize again, a fully generic optimization that has absolutely nothing to do with any particular benchmark that Neat did absolutely terribly on.
And some small stuff
- Fix LLVM backend spacer alignment.
- Implement
a %= b;
. - Add std.algorithm.zip.
- Vectors are now index assignable.
- Add ArraySource, ArraySink
- Add Time.year, month, etc. Add
Time.monotonic
for benchmarking.
Neat 0.5.0: Let me try something.
Again with the new syntax! When will it end?
Two big new things.
If let(var)
This month's first change is complementary with 0.4.0's breakelse
feature.
You can now write if
statements like so:
if let(int var = expr?) {}
If you use the let
form, the truth value of the variable expression will not be considered.
Only the breakelse
branches from within the expression can prevent the if
branch from being taken.
In other words, it acts equivalent to if (true) { int var = expr?; ... }
.
(Why not just use that then? Some day, I'll probably disable breakelse
outside the if
expression.
It's just too confusing. But then, this idiom will still work.)
The goal is to enable usecases where, for instance, you want to conditionally access an integer
from a data struct, but you don't actually care about the value of the integer, only that it is present.
C interprets 0
as false
because it has an impoverished typesystem that heavily overloads integers,
ie. see error codes and -1
. I was (am) considering removing if
's ability to parse integers as booleans,
but then people coming from C would be very confused, especially if there is another reason (breakelse
)
why the branch might not be taken. Better to throw in a new syntax, at least that way
they know that they have to go look at the manual.
std.macro.easymacro
Previously, the only way to write macros was the cumbersome "load a function, the function instantiates a
macro class, which then hooks the compiler and adds a new syntax rule, which then..." workflow. This is
clearly not competetive with even C++, let alone D's CTFE. So I am proud to introduce std.macro.easymacro
:
void test() {
macro {
print("Hello World from compile-time code!");
code {
print("Hello World from runtime code!");
}
}
}
The code
statement can quasiquote-inject any variables from the macro
block with the standard $var
syntax. code
and macro
blocks can be mixed and recursed arbitrarily.
Nested macro
blocks can access variables from surrounding macro
blocks directly.
If a code
statement is evaluated multiple times, it is inserted into the surrounding function multiple
times.
The macro
statement contents are loaded into the compiler and run at compiletime, just like regular macros.
A good example for how this all works is the new JSON stream parser at std/json/stream.nt.
JSON stream parser
Oh yeah, we have a JSON stream parser now. Basic types, arrays and structs can be automatically encoded
and decoded. See the unittests in std/json/stream.nt for examples.
fail
annotations are out (ish), Error
is in
There were always problems with the fail
attribute. Why does it only work inside tuples? What happens if
you remove all non-fail
types from a tuple with one fail
type? As a result of these issues, the
functionality has been "soft-deprecated": it still works fine, but you are recommended to use
subclasses of std.error.Error
instead. Error
classes are also automatically returned by ?
, but also if
the only return type is Error
, ?
correctly results in a bottom expression.
__CALLER__
__RANGE__
is a built-in expression that evaluates to the range expression of its syntax node.
Complementing this, we now have __CALLER__
. If __CALLER__
is set as the default value of a LocRange
parameter, it will be set to the range expression of the call to that function.
Note that it is an error to use this expression outside a parameter's default value.
That's it! Enjoy!
Neat 0.4.3: Fixing else for once
Minor bugfix: In a if x else b
and a else b
, b
is now correctly refcounted if it is a
local variable.
State of play is still:
auto var = obj?.field? else return false;
if (auto var = obj?.field?) { }
The compiler has been ported to use this idiom internally: field.notNull
has been replaced across
the board with the somewhat more ruthless, if accurate, field? else die
(die
being a thin wrapper around exit(1)
). The relevant commit serves as a practical demonstration of breakelse
in the guise of ?
.
Note that ?
is overloaded between error propagation and breakelse
handling: it will return errors from
a sumtype, and it will treat nullable T
as a sumtype of (T | null)
and breakelse
the null,
but not both at once. This occasionally necessitated the appropriately-irate fun()?? else die
.
Neat 0.4.2: Breaking else again
Okay, I'm restructuring breakelse
again. Now, the ternary operator a if b else c
fully supports breakelse
and has a shortened form: a else b
(the "short ternary operator"). In that case, the if-test is always true, so the else case can only be reached by breakelse
or ?
.
In exchange, .else()
is removed entirely: it was redundant with ?
, and the code ended up with two different operations in the same expression that did the same thing. a.else(b)
should be replaced with (a? else b)
, or a.(that? else b)
if you really need the property form.
State of play:
auto var = obj?.field? else return false;
if (auto var = obj?.field?) { }
Neat 0.4.1: Breakelse, amended
Some more polish on breakelse
in this one.
expr.else
now creates a breakelse targetable scope inexpr
.
This is because if you have a feature calledbreakelse
, and a
property calledelse
, one better jump to the other.
(I'm a bit worried that I'm giving too much weight here to the
termbreakelse
, which I basically picked out of a hat without
too much thought, but it seems to be working so far.)nullableObject?
no longer returnsnull
. Instead, it willbreakelse
.
Generally,null
is treated equivalently to:else
inbreakelse
features.
This allowsnullableObject?.property.else(...)
and generallynullableObject?
in if statements.
There's a bit of an ugly detail here: .else
must be called on a sumtype with
:else
or nullptr_t
(like nullable Object
). So you end up with an expression
containing a chain of ?
, and a final .else
that ... also does
the thing that ?
does. foo?.bar.else(...)
.
That is, what you may expect, foo?.bar?.else(...)
, won't actually work,
because bar?
already got rid of the null
. The reason for this is that it
is not entirely trivial from a compiler perspective to check if the else
branch actually ever gets taken. So allowing .else
to be called on arbitrary
expressions would lead to code with .else
properties that are not actually
needed (anymore).
This is one of the implementation details of this design that I'll very
likely revisit later.
edit: You know what? That's stupid and broken, I'll definitely revisit it. Look out for 0.4.2!