Packages
Onyx has a relatively simple code organization system that is similar to other languages.
To organize code, Onyx has packages.
A package is collection of files that define the public and private symbols of the package.
To use the symbols inside of a package, each file it is using the package.
This is done with the use
keyword.
Let's say we have two source files, foo.onyx
and bar.onyx
They are part of the packages foo
and bar
respectively.
If bar.onyx
wants to use some code from foo.onyx
, it has to use foo
before it can do so.
This makes the foo
package accessible inside of bar.onyx
.
// foo.onyx
// Declares that this file is part of the "foo" package.
package foo
some_interesting_code :: () {
// ...
}
// bar.onyx
// Declares that this file is part of the "bar" package.
package bar
use foo
func :: () {
foo.some_interesting_code();
}
It is important to note, that while it may be a good idea to organize your source files into directories that correspond to the package that they are in, there is no limitation as to which files can be part of which packages. Any file can declare that it is part of any package. There may be a future language feature to optionally limit this.
Scoping
While controversial, Onyx has opted to have a public by default symbol system. Unless marked otherwise, all symbols inside a package are accessible from outside that package. If there are implementation details to hide, they can be scoped to either the current package or the current file.
Use #package
before a binding to declare that the scope is internal to the package.
Any file that is part of the same package can see the symbol, but external files cannot.
package foo
public_interface :: () {
internal_details();
}
#package
internal_details :: () {
// ...
}
Use #local
before a binding to declare that the scope is internal to the file.
Only things in the same file can see the symbol.
package foo
public_interface :: () {
super_internal_details();
}
#local
super_internal_details :: () {
// ...
}
Note, while Onyx is white-space agnostic, it is common to write the
#package
and#local
directives on a separate line before the binding.
If you have a large set of implementation details, it might be more readable to use the block version of #local
and #package
.
public_interface :: () {
}
#local {
lots :: () {
}
of :: () {
}
internal :: struct {
}
details :: enum {
}
}
Notable packages
There are several package names that have been taken by the Onyx standard library and/or have a special use.
builtin
package
The builtin
package is notable because it is required by the compiler to exist.
It contains several definitions needed by the compiler, for example Optional
and Iterator
.
These definitions live in core/builtin.onyx
, which is a slight misnomer because the builtin
package is separate from the core
module.
builtin
is also special because its public scope is mapped to the global scope of the program.
This means that anything defined in the package is available without using any packages.
runtime
package
The runtime
package is required by the compiler to exist, as the compiler places several variables in it related to the current operating system and selected runtime.
runtime.vars
package
runtime.vars
is used for configuration variables.
It is the "dumping ground" for symbols defined on the command line with the -D
option.
Use #defined
to tell if a symbol was defined or not.
use runtime
// Compile with -DENABLE_FEATURE to enable the feature
Feature_Enabled :: #defined(runtime.vars.ENABLE_FEATURE)
runtime.platform
package
runtime.platform
is an abstraction layer used by the core libraries that handles interacting with OS/environment things, such as reading from files and outputting a string.
core
package
The core
package houses all of the standard library.
The way the Onyx packages are structured, the compiler does not know anything about the core
package.
If someone wanted to, they could replace the entire core library and the compiler would not be affected.
main
package
The main
package is the default package every file is a part of if no package
declaration is made.
The standard library expects the main
package to have a main
procedure that represents the start of execution.
It must be of type () -> void
or ([] cstr) -> void
.
If there is not an entrypoint in the program because it is a library, simply use -Dno_entrypoint
when compiling,
or define a dummy main
with no body.