Skip to content
Brendan Hansen edited this page Dec 18, 2020 · 8 revisions

The Onyx Programming Language

Project Goals

This project was started for me to learn the ins and outs of compiler and programming language design. I do not intend for this language to be used for anything "professional". It is purely for me to learn and experiment with compiler architecture. To that end, I believe I have made a considerable amount of progress towards a "real" and "professional" programming language.

Getting Started

Currently, the Onyx compiler is only supported on Linux. This is due to no reason other than me running solely Linux and not having access to a Windows or MacOS system. I suspect it would work on the Windows Subsystem for Linux, but I have not tested that. If would gladly accept a pull request adding compilation support to other platforms.

First, get the Onyx source code from either the Releases page, or cloning the latest master branch using:

$ git clone https://github.com/brendanfh/onyx.git

Then compile and intall everything using:

$ mkdir -p build && sudo make clean install

High-level language features

Intuitive symbol resolution

Symbols in the global scope are always resolvable, even if the definition for a symbol comes after its use. This means Onyx does not have header files like C or C++, since procedures and variables can be resolved out of order. Take this example:

main :: proc (args: [] cstring) {
    val := fib(20);
    print(val);
}

fib :: proc (n: i32) -> i32 {
    switch n {
        case 0 do return 1;
        case 1 do return 1;
        case #default {
            return fib(n - 1) + fib(n - 2);
        }
    }
}

main can call fib, even though fib is declared lexographically after main. In fact, the recursion of fib also works due to this feature. Before this feature was implemented, recursion in any form did not work.

Package System

Organizing code is a perpetual problem in all programming language. "Where should this function go?" is something I ask myself too frequently. To aid in this problem, the following features / design decisions are available in Onyx:

  • #include_file adds a file to the queue of files to process. Note, this does not textually include the contents of the file; it just tells the compiler to add the file to a queue to process later. If a file has already been included, it is ignored.
  • #include_folder adds a folder to the search path of files. By default, the only folder in the search path is the current directory, though this is subject to change.
  • a package system described below

Every onyx source file is part of a package; if no package is explicity stated, the file is part of the main package. All symbols declared in the file are added to the package's public scope, unless they explicitly marked with the #private directive. It is worth mentioning that the directory structure does not correlate to the package structure at all. Any file from anywhere can be part of any package. Through my experience, I've found that langauges that try to enforce a directory structure become hard to maintain. I believe it is the programmers job to organize their source files into something that makes sense to them. For this reason, Onyx is folder structure agnostic.

To use symbols from another package, use the use package package_name statement at the top level. There are several variations of use package:

  • use package pac will add all public symbols from pac to the package scope of the current package.
  • use package pac as other will add the symbol other into the package scope, which can be used to access all public symbols of the package pac.
  • use package pac { foo, bar as other } will add only the symbol foo from the package pac, as well as add a symbol other which aliases to the bar symbol in the package pac.

An important thing to note is that use package statements are not transitive. This means that if A uses package B, and B uses package C, A does not have access to the symbols in C, unless A also explicitly uses the package C.

Another important note is that currently, there is no way to introduce a symbol at the file scope. All symbols in a file will be added to the corresponding package. File scoped symbols may be added in the future.

The |> operator

Though Onyx is not a functional language in almost any respect, it borrows the |> operator from langauges like Elixir and F#. The semantics of the operator are as follows:

  • left |> right(param1, param2) is translated to right(left, param1, param2). This makes it closer to the Elixir version of the operator. The left side of the operator becomes the first parameter of the calling function.
  • The operator is left associative, which means it can be chained intutively. The following examples are equivalent:
o |> foo(p1) |> bar(p2)

(o |> foo(p1)) |> bar(p2)

foo(o, p1) |> bar(p2)

bar(foo(o, p1), p2)

Because of the existance of this operator, it is common to design an API where many of the functions look like:

api_func :: proc (val: T, ...) -> T {
    // ...
    return val;
}

This makes it easy to chain many of the api functions together. The StringBuilder is a good example of this.

Compiler progress

There are a couple of missing features of the compiler that will need to be addressed fairly soon, such as being able to compile on Windows. A very rough list of things being worked on can be found in the docs/plan file.

Clone this wiki locally