Sunder is a modest systems programming language for Unix-like platforms.
Sunder tooling should build and run on any supported machine satisfying the following dependencies:
- POSIX-compatible
make
- GNU Core Utilities
- Clang or GCC
- Emscripten (WebAssembly code-generation only)
- Clang Format (development only)
Supported platforms include:
- x86-64 Linux
- ARM64 Linux
- ARM64 macOS
- WebAssembly (via Emscripten)
The top-level Dockerfile defines a Debian image with dependencies for native x86-64 Linux development pre-installed. You can build and run the Docker image with:
$ docker buildx build --platform=linux/amd64 --tag sunder . # Build the image (do this once)
$ docker run --rm --interactive --tty --volume "$(pwd)":/sunder sunder # Run the image (do this every time)
To build the compiler, run make
.
To execute the test suite and compile the example programs under the examples
directory, run make check examples
.
The top-level Makefile
contains the following important targets:
build
→ Build the compiler (defaultmake
target).check
→ Run the test suite for the language and standard library.examples
→ Compile the example programs under theexamples
directory.format
→ Runclang-format
over the compiler sources.clean
→ Remove build artifacts.
Targets will execute with CC=c99
using release mode CFLAGS
by default.
Specific CC
/CFLAGS
combinations include:
$ make <targets> CFLAGS='$(C99_DBG)' # POSIX c99 (debug)
$ make <targets> CFLAGS='$(C99_REL)' # POSIX c99 (release)
$ # Use CC=clang for Clang or CC=gcc for GCC
$ make <targets> CC=clang CFLAGS='$(GNU_DBG)' # clang/gcc (debug)
$ make <targets> CC=clang CFLAGS='$(GNU_REL)' # clang/gcc (release)
The install
target will install the Sunder toolchain into the directory
specified by SUNDER_HOME
(default $HOME/.sunder
). Run make install
with
SUNDER_HOME
specified as the directory of your choice:
$ make install # Install to the default $HOME/.sunder
$ make install SUNDER_HOME=/opt/sunder # Install to /opt/sunder
Then, add the following snippet to your .profile
, replacing $HOME/.sunder
with your chosen SUNDER_HOME
directory if installing to a non-default
SUNDER_HOME
location:
export SUNDER_HOME="$HOME/.sunder"
if [ -e "$SUNDER_HOME/env" ]; then
. "$SUNDER_HOME/env"
fi
Verify that the compiler has been successfully installed by running
sunder-compile -h
. You may need to source your .profile
in new shells until
the start of your next login session.
Sunder programs are compiled with sunder-compile
.
import "std";
func main() void {
std::print_line(std::out(), "Hello, world!");
}
$ sunder-compile -o hello examples/hello.sunder
$ ./hello
Hello, world!
The -o OUT
option may be used to specify the name of the output file. If this
option is not provided, the output file will default to the name a.out
.
Intermediate files of the form OUT.tmp.*
for output program OUT
are
generated during compilation and subsequently removed after the output file has
been created. The -k
flag will instruct the compiler not to remove these
files.
$ sunder-compile -k -o hello examples/hello.sunder
$ ls hello*
hello hello.tmp.c
$ SUNDER_CC=emcc SUNDER_ARCH=wasm32 SUNDER_HOST=emscripten sunder-compile -k -o hello.html examples/hello.sunder
$ ls hello*
hello.html hello.html.tmp.c hello.js hello.wasm
The -g
flag will instruct the compiler to generate debug information. The use
of -g
in combination with -k
facilitates debugging with GDB and LLDB.
$ sunder-compile -g -k -o hello examples/hello.sunder
$ lldb hello
(lldb) target create "hello"
Current executable set to '/Users/ashn/sources/sunder/hello' (arm64).
(lldb) b std_print_line
Breakpoint 1: where = hello`std_print_line + 32 at hello.tmp.c:25216:9, address = 0x000000010000d24c
(lldb) run
Process 80648 launched: '/Users/ashn/sources/sunder/hello' (arm64)
Process 80648 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010000d24c hello`std_print_line(__sunder_argument_0_writer=__sunder_std_writer @ 0x000000016fdff0c0, __sunder_argument_1_str=(start = "Hello, world!", count = 13)) at hello.tmp.c:25216:9
25213 __sunder_std_print_line(__sunder_std_writer __sunder_argument_0_writer, __sunder_slice_of_byte __sunder_argument_1_str)
25214 {
25215 // var return: void
-> 25216 int __sunder_return = /* zero-sized local */0;
25217 // var result: std::result[[void, *std::error_info]]
25218 __sunder_std_result__void___std_error_info__ __sunder_local_0_result = {/* uninit */0};
25219 /// [std/std.sunder:1592] STATEMENT STMT_ASSIGN
Target 0: (hello) stopped.
(lldb)
The following environment variables affect compiler behavior:
SUNDER_CC
selects the C compiler to be used when compiling generated C.
Currently, SUNDER_CC=clang
, SUNDER_CC=gcc
, and SUNDER_CC=emcc
are
supported. If SUNDER_CC
is not set, then the default C compiler is used.
SUNDER_CFLAGS
is a space-separated list of additional flags passed to the
C compiler.
SUNDER_SEARCH_PATH
is a colon-separated list of directories specifying
the module search path for import
and embed
statements.
SUNDER_ARCH
specifies the target architecture to build for. Currently,
SUNDER_ARCH=amd64
, SUNDER_ARCH=arm64
, and SUNDER_ARCH=wasm32
are
supported. If SUNDER_ARCH
is not set, then the default architecture specified
by sunder-platform arch
is used.
SUNDER_HOST
specifies the target operating system to build for.
Currently, SUNDER_HOST=emscripten
, SUNDER_HOST=linux
, and
SUNDER_HOST=macos
are supported. If SUNDER_HOST
is not set, then the
default host specified by sunder-platform host
is used.
Sunder supports compiling to WebAssembly via
Emscripten. When compiling to WebAssembly, specify
emcc
, wasm32
, and emscripten
as the C compiler, target architecture, and
target host, respectively.
$ SUNDER_CC=emcc \
SUNDER_CFLAGS="-sSINGLE_FILE --shell-file ${SUNDER_HOME}/lib/sys/sys.wasm32-emscripten.html" \
SUNDER_ARCH=wasm32 \
SUNDER_HOST=emscripten \
sunder-compile -o hello.html examples/hello.sunder
$ firefox --new-window "file://$(realpath hello.html)"
Sunder can be used for scripting by adding #!/usr/bin/env sunder-run
as the
first line of a Sunder source file.
#!/usr/bin/env sunder-run
import "std";
func main() void {
std::print(std::out(), "What is your name?\n> ");
var result = std::read_line(std::input());
if result.is_error() {
std::print_line(std::err(), result.error().*.data);
std::exit(std::EXIT_FAILURE);
}
var optional = result.value();
if optional.is_empty() or countof(optional.value()) == 0 {
std::print_line(std::err(), "unexpected empty input");
std::exit(std::EXIT_FAILURE);
}
var line = optional.value();
defer std::slice[[byte]]::delete(line);
var name = std::ascii::view_trimmed(optional.value());
std::print_format_line(std::out(), "Nice to meet you {}!", (:[]std::formatter)[std::formatter::init[[typeof(name)]](&name)]);
}
$ ./examples/greet.sunder
What is your name?
> Alice
Nice to meet you Alice!
Executing the following commands will create an environment sufficient for Sunder development and experimentation without requiring the Sunder toolchain to be installed.
$ cd /your/path/to/sunder
$ make
$ SUNDER_HOME="$(pwd)"
$ . ./env
$ sunder-run examples/hello.sunder
Hello, world!
Sunder is distributed under the terms of the Apache License (Version 2.0).
See LICENSE for more information.