Beta Release 0.1.10
This release brings standard library uniformity, minor syntax changes, and JavaScript interop! You can now easily interface with the DOM and other JS APIs from directly within Onyx.
JavaScript Interop
Onyx now has a proper JavaScript interop layer, which makes using Onyx from JavaScript easier than ever.
This interop comes in two parts: the #js
directive and the core.js
package.
The #js
directive is used to include JS source code in the project.
When any #js
directive is encountered in the code, an additional JS file will be generated by the compiler.
#js
has the following syntax.
// Include JS from string literal
#js """
// Raw JS source code here
"""
// Inlcude JS from file
#js #file "./source.js"
// Specify order of JS partial with an int literal
// Lower numbers are inserted first
#js #order 100 #file "./source.js"
This by itself does not help you interop with JavaScript, but it is required to have the compiler also generate the additional source code to make your project work. In fact, you may never have to use it directly, as it is more intended for library authors.
To actually interop with JS, there is now the core.js
package. This package was inspired
by Go's syscall/js
, which provides a thin abstraction
layer over JavaScript's common operations, like function calls, new
and obj["foo"]
.
Here is a simple example/demo that creates a button on the page, and when it is clicked, alerts with a message.
use core.js
main :: () {
document := js.Global->get("document");
button := document->call("createElement", "button");
button->set("innerHTML", "Click me!");
button->call("addEventListener", "click", js.func((this, args) => {
js.Global->call("alert", "Hello from Onyx!");
return js.Undefined;
}));
document->get("body")->call("appendChild", button);
}
To use this in on your page, you can use the following JavaScript.
It will load the JavaScript file generated by the compiler, and use the
Onyx
class to load and link the WebAssembly binary, from
which you can call start
to start the program.
<script type="module">
import Onyx from "/out.wasm.js"
(await Onyx.load("/out.wasm")).start()
</script>
Finally, compile the code with the -r js
flag.
onyx build -r js program.onyx
While this is a very low-level interface to JavaScript, it does enable Onyx to be used in a whole new class of applications on the web. More auxiliary packages can be developed to provide specialized APIs for coming things such as the DOM, WebSockets, or WebGL.
Deprecation of #inject
As I have written more Onyx code, I have realized I use #inject
all the time.
It has become so ubiquitous in my code that I experimented with extending the parser so the #inject
directive would not be required, and I loved it. For this reason, #inject
is being deprecated.
Now you can simply add a new scoped binding like you would with any other binding.
Dog :: struct {}
// No #inject here!
Dog.speak :: (d: &Dog) {
println("Bark!");
}
Note that without the #inject
keyword, there is no equivalent of the
block form.
Each scoped binding must be specified individually on a new line. I prefer this,
as it prevents what I consider unnecessary indentation.
In a future release yet to be determined, the #inject
directive will be
removed entirely, but for now all programs with it will still compile.
Array
and Slice
structures
There has been a long running inconsistency in Onyx where dynamic arrays and slices were
not like other types, in that they could not have methods and required you to use functions
defined in the standard library from the core.array
and core.slice
packages.
This has finally been addressed with the addition of the Array
and Slice
builtin
structures. They serve as "namespaces" for methods in dynamics arrays and slices respectively.
The core library now defines all dynamic array functionality as methods on Array
and all
slice functionality as methods on Slice
. All existing definitions from core.array
and core.slice
still exist to maintain backwards compatibility, but the new locations of
these definitions should be preferred.
// No need to use anything :)
main :: () {
arr := make([..] i32);
for i in 0 .. 10 {
// Use as a method.
arr->push(i);
// Use a static function on Array.
Array.push(&arr, i);
}
logf(.Info, "Array: {}", arr);
}
These changes represent a larger move towards standardizing the pattern that associated procedures
for a type are defined as bindings within the type's scope. That way, the consumer of library can either choose
to use the procedure as a "method", i.e. x->proc(y)
, or as a standard procedure prefix with the type
name, i.e. X.proc(&x, y)
. When the procedure lives in a package, they are forced to use the standard
procedure syntax.
Minor syntax additions
Auto-disposing locals
A very common pattern in Onyx is to create/allocate a resource, then defer
the release/free of the resource
on the next line. As an experiment, there is now a way to automate the freeing of a resource, called auto-disposing locals (I'm still working on the name...).
To mark a local variable as auto-disposing, simply place the use
keyword in front of its declaration.
main :: () {
use arr := make([..] i32);
}
This code will automatically insert a deferred call to the builtin overloaded procedure __dispose_used_local
, which
can be overloaded to define how to dispose of the resource.
It has the delete
procedure in the list of overloads, so anything you can call delete
on,
you can use
. This is the equivalent code without use
.
main :: () {
arr := make([..] i32);
defer __dispose_used_local(&arr);
}
As stated before, this feature is experimental and probably not perfect yet, but after using the
using
keyword in C#, I wanted to have something similar in Onyx.
Inclusive Range (..=
)
As a small convenience, there is now an inclusive range operator, ..=
.
This operator is equivalent to x .. (y + 1)
.
While a small addition, it cleans up a lot of ugly + 1
s around many codebases.
Revamped CLI
The command-line interface got a major face-lift in this update.
It features new shorthand for common commands like build
and run
.
It also includes colors on Linux and MacOS.
This might not seem worth mentioning but these little improvements add to the overall developer
experience and make Onyx feel more polished and production-ready.
Updating
To update to the newest version of Onyx simply use the same install script found on the homepage. It will automatically detect your previous install and will override it with the new version.
$ sh <(curl https://get.onyxlang.io -sSfL)
In the future, you will be able to use the onyx self-upgrade
command!
Full Changelog
Additions
- JavaScript interop
core.js
package for JS FFI.#js
directive to build a JavaScript file during compilation.
- Implicit injections
#inject
is no longer required in some cases
- Named return values
- Official builds for Linux AARCH64
Slice
andArray
structures for placing methods on slices and dynamic arrays.- Range type improvements
range64
type..=
operator that is a range, with an inclusive upper end.
- New alternate error format that may become the default in the future.
- Use
--error-format v2
or set environment variableONYX_ERROR_FORMAT
to bev2
.
- Use
- Auto-disposing locals (experimental)
use x := ...
- Core library functions
- New process spawning API with builder pattern (
os.command
) sync.MutexGuard
sync.Channel
hash.sha1
net.dial
net.resolve
- integer constants
i8.MIN
,i64.MAX
, etc.
- New process spawning API with builder pattern (
Removals
os.with_file
Changes
- Revamped CLI
- Shorthand commands (r for run, b for build, etc.)
- Improved appearance
- Better help pages
- Note: flags must now appear before all files
- Better error messages for common issues
Array
should be preferred overcore.array
Slice
should be preferred overcore.slice
str
should be preferred overcore.string
Bugfixes
- Fixed compiler crash when trying to iterate over something that is not iterable.
- Fixed wrong implementation of futexes on MacOS.
- Fixed implementation of
platform.__time()
Contributors
- @josdelien for suggesting JS interop, CLI improvements, inclusive ranges, and named return values as features to implement this release.
- @Syuparn (1 pull request)