Beta Release 0.1.13

Its been nearly 6 months since a release of Onyx — which is way too long! I've been putting off releasing this version until I felt there were enough changes to justify a new release. Now there are so many little changes made over these past months, it is due time to push them out to everyone!

Syntax Additions and Changes

Closures

Closures have existed in Onyx for several years, but the syntax was quite poor. Here is what they used to look like.

main :: () {
    capture_me := "some string"

    f := (x, [capture_me]) => {
        printf("{}: {}", capture_me, x)
    }

    f(10) // "some string: 10"
}

This syntax was partially inspired by C++, but moving the capture group inside the parentheses. I have never felt happy with this syntax, and using it always feels clunky to type and a pain to look at.

The new syntax is not as "concise", but is much easier to read and is already used by other programming languages, most notably PHP. Simply place the use keyword after the close parentheses of the parameter list.

main :: () {
    capture_me := "some string"

    f := (x) use (capture_me) => { // still need '=>' because this is a quick procedure
        printf("{}: {}", capture_me, x)
    }

    g := (x: i32) use (capture_me) -> void { // can omit '-> void'
        printf("{}: {}", capture_me, x)
    }

    f(10) // "some string: 10"
    g(10) // "some string: 10"
}

Note, in the future, explicitly listing captured values may not be necessary.

Unwrap operator

Onyx now has another post-fix unary operator called the unwrap operator, written x!. It is used by the Optional and Result types as an shorthand for x->unwrap().

main :: () {
    iterator := Iterator.from(1 .. 5)

    value := Iterator.next(iterator)! // Unwrap here
    printf("value: {}\n", value)      // Prints 'value: 1'

    iterator
        |> Iterator.map(x => x * 2)
        |> Iterator.next()! // You can "pipe" into unwrap, just like try (?)
        |> printf("another value: {}\n", _) // Prints 'another value: 4'
}

It can be overloaded using #operator!.

MyType :: struct { /* ... */ }

#operator! :: (mt: MyType) -> i32 {
    // ...
}

Custom commands

Custom commands allow the Onyx CLI to be extended to include new tooling for Onyx development. These tools used to be only installable globally, and they were not indexed, so you had to know which tools you had installed.

Now, these custom commands can be installed globally in the ONYX_PATH/tools directory, or per project in the ./.onyx directory. These commands always come in the form of .wasm files that accept the remaining command-line arguments. In other words,

$ onyx tool-name arg1 arg2

is equivalent to

$ onyx run resolved/path/to/tool-name.wasm -- arg1 arg2

Custom commands now show up in onyx help. They get their description by reading a custom section in the WASM binary called onyx-command-description. If you are creating a custom command and would like to set this, you can use the #wasm_section directive like so:

#wasm_section "onyx-command-description" "A concise description here."

New Core APIs

Onyx now has support for some common interchange formats and functionality used in web and server development.

Protobufs

There is now an official protobuf library for Onyx that supports serialization and deserialization. There is also a protoc plugin that you can use to generate Onyx types from a .proto file.

Basic JWT support

Onyx how has the beginnings of core.crypto.keys, with basic support for decoding and encoding HS256 JWTs. Once Onyx has support for other crypto functionality, namely RSA and EC, other token signatures can be implemented.

XML decoding

Onyx now supports decoding XML documents in core.encoding.xml. Encoding XML should just be done with formatted printing to a dyn_str.

Other items of note

Changes to #load directive

When loading more files into the current compilation, the #load directive behaved in an arguably strange way. It had a global search path, that you could append directories to using #load_path or -I dir. Unless you explicitly prefixed the file path with ./, it would search through all known paths to find a match. When it did not find a match, it would default to loading the file from the current directory of the running "onyx" process.

This behavior is quite non-standard amongst programming languages, and so it is revised in this release. Now, all #load directives are relative to the path of the current file, unless it is prefixed with a mapped folder name, followed by a colon, like so.

#load "foo" // Will load `./foo.onyx`
#load "core:foo" // Will load `$ONYX_PATH/core/foo.onyx`

By default there is only one mapped directory, called core, that is set to ONYX_PATH/core. You can map your own directories using --map-dir NAME:DIR on the command line.

Note, because of this change, the -I command-line argument has been removed, and the #load_path directive has been deprecated and no longer does anything. Mapped directories can only be set at the command-line.

Short-circuiting logic operators

Onyx has had the standard boolean operators && and || since its inception. But until this release, they did not behave how they do in all other programming languages — they did not short-curcuit.

That was fixed in this release. These operators now short-circuit as you would expect them to.

Fixed-sized arrays passed by value

When trying to use fixed-sized arrays as "vector-like" types, there was a lot of unexpected behavior when passing them to functions. This was because fixed-sized arrays like [4] i32 were passed by reference. In other words, a pointer to the first element was implicitly passed so any changes made to the array would be reflected in the original array.

These semantics were copied from C and seemed logical for the time. But seeing how Onyx has evolved in a different direction, this no longer makes sense, so it has been changed — all fixed-sized arrays are now passed by value. If you still want the pass by reference semantics, use a slice type instead ([] T).

Referencing Git branches as dependencies

When specifying dependencies in onyx-pkg.kdl, you can now target a particular branch from a repository, instead of a version. This will re-clone the branch ever time you run onyx pkg sync, which is useful when testing a package you are developing.

dependencies {
    some-package branch="feature-branch" git="https://github.com/user/repo"
}

Goals for 0.1.14

My primary focus for 0.1.14 will be to refactor the entire codebase of the compiler to separate out the CLI from the compiler internals. The compiler internals will be available as a standard dynamic library and header file, so they can be embedded into interesting places. The CLI will then be rewritten to use these compiler internals.

While in this process, I may extend the capabilities of compiler extensions with more hooks and program introspection. But, I have not yet designed what that system will look like, so that may be on hold until 0.1.15 or later.

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)

You can also run onyx self-upgrade if you are on MacOS or Linux!

Happy programming!

Full Changelog

Additions

  • Unwrap operator, x!.
    • Similar to try operator (x?), but panics if value is not present.
  • "Field update" syntax. A shorthand to change a field of a structure.
    • .{ ..old_value, field = new_value }
  • onyx watch now works on MacOS.
  • onyx run-watch to automatically re-run the program on changes.
  • #wasm_section directive to add custom sections to the WASM binary.
  • Custom commands per project
    • Installed in a .onyx directory
  • onyx pkg build can now run shell commands and multi-stage builds.
  • Stalled compiler hook to allow for code injection when the compiler stalls.
  • Slice.map
  • Slice.map_inplace
  • Slice.fold1
  • Slice.get_opt
  • Iterator.from
  • iter.group_by
  • core.alloc.debug
  • core.os.args
  • core.crypto.hmac
  • core.crypto.keys
  • core.encoding.json.Value.as_entry_array
  • core.encoding.base64 { encode_url, decode_url }
  • core.encoding.xml

Changes

  • Capture/closure syntax is now (params) use (captures) ....
    • (x: i32) use (variable) -> str { ... }
    • (x) use (variable) => { ... }
  • && and || now short-circuit.
  • Fixed-sized arrays ([N] T) are now passed by value.
  • The size of the tag field for unions is now dependent on the number of variants.
  • Parsing structs no longer sometimes needs #type.
  • Renamed core.alloc.memdebug to core.alloc.memwatch.

Bugfixes

  • Many, many random bugs.
© 2020-2024 Brendan Hansen