sysl

Installation

Install the compiler from the tap, or build it from source.

Install

brew install sysl-lang/tap/sysl

That is a native binary — there is no JVM under it and nothing to start up. It brings LLVM with it, which sysl needs at runtime: the compiler emits textual LLVM IR and hands it to clang to assemble and link, and llvm-ar is what builds a library into a .syslib.

It also brings pkgconf. A package that binds an installed C library can name it — requires { pkg_config { sdl3 = "…" } } — and sysl asks pkg-config where that library’s headers and link line are, so building against SDL3 or cairo needs no flags. macOS ships no pkg-config and the libraries do not bring one, so the formula does.

Check it, and see what it offers:

sysl --version
sysl --help

macOS on Apple silicon, and Linux on x86_64 and arm64. The tap picks the right one for the machine it is run on. Anything else builds from source, below.

The Linux binaries need glibc 2.34 or newer — Ubuntu 22.04, Debian 12, RHEL 9, and anything later. That is a property of the machine they were built on rather than of sysl, and it is the one way an install can fail that the formula cannot check for you: version GLIBC_2.34 not found from the dynamic loader is this, and not a corrupted download. On an older distribution, build from source.

Your first compile

echo 'main()
    print("Hello, sysl!")' > hello.sysl
sysl run hello.sysl

The first run prints a line on stderr about building the standard module. That is expected and it happens once — see below.

sysl build compiles without running, leaving an executable you can ship:

sysl build hello.sysl -o hello
./hello

Build from source

The compiler is a Scala 3 cross-project, so the path in is a clone and an sbt build. You want this if you are working on sysl, or if you are on a platform the tap has no binary for.

why
JDK 17+the compiler is written in Scala and runs on the JVM
sbt 1.12+builds it
clangsysl emits textual LLVM IR; clang assembles and links it
llvm-aronly for building a library — a .syslib is an ar archive of objects
pkg-configonly for a package that names an installed C library; without it, say where the library is with --include-path and --link-path

clang is the only one most systems already have — pkg-config is common on Linux and absent from a stock macOS. On macOS the Xcode command-line tools supply one; on Debian and Ubuntu it is the clang package.

llvm-ar matters only when you build a library of your own, and it has to be the LLVM one: a .syslib holds objects for the machine it was built for, and a platform archiver indexes only its own format and silently drops the rest. On a Mac, Homebrew keeps its LLVM deliberately off the PATH, so sysl looks in /opt/homebrew/opt/llvm/bin as well. --ar names one anywhere else.

git clone https://github.com/sysl-lang/sysl.git
cd sysl
sbt syslJVM/compile

The JVM target is the one to develop against. JS and Native cross-targets exist in the build, and the Native one is what the released binary is built from:

SYSL_RELEASE=1 sbt syslNative/nativeLink

Without SYSL_RELEASE that links in debug mode, which is much faster and is what you want while working on the compiler.

Check it

./run-example.sh

That compiles examples/hello.sysl all the way to a native binary and runs it. If you see Hello, sysl! followed by a page of output, everything is in place.

To run a different file, name it — and anything after a -- goes to the program rather than to sysl:

./run-example.sh examples/args.sysl -- -n one two

Under the script is the CLI, which you can call directly:

sbt "syslJVM/run run examples/hello.sysl"

The standard library

Every program is compiled against the standard module, and its source ships with the compiler. An install puts it at share/sysl/library under the install prefix — on a Homebrew Mac that is $(brew --prefix)/share/sysl/library — and the compiler finds it from its own location, the way rustc finds its sysroot. Running out of a checkout, it is the library/ directory in the tree. Nothing has to be configured, and there is no variable to set.

The directory was called lib/ until it was renamed, and both spellings are still looked for with the new one first — so a compiler installed before the change, and a checkout that has not been updated, both go on working.

It is meant to be read. The library is ordinary sysl, laid out as an ordinary sysl library, and every function in it is one a program could have written; sysl build-lib <root> --std is the same command that builds anybody else’s.

You do not have to build the artifact, either: when nothing usable is at the default path, the compiler builds it out of that source, says so on stderr, and gets on with the compilation. A fresh install just works.

It goes in your cache directory — ~/Library/Caches/sysl/ on macOS, ~/.cache/sysl/ on Linux, or wherever XDG_CACHE_HOME points — under a fingerprint of the library it was built from. So it is built once per machine rather than once per project, nothing is written into your source tree, and installing a compiler with a different library gets its own entry instead of a stale hit. Nothing there is ever evicted, and everything in it is derived: deleting the directory costs one rebuild.

Two flags matter when you want something other than that. --std-lib <path> names an artifact explicitly, and an artifact you named is never rebuilt behind your back — if it will not read, the compilation stops and says so. --no-std-lib compiles the standard module from its source instead of linking a prebuilt one, with no toolchain involved at all, which is the path the compiler’s own test suite takes.

Optimization

-O names the level handed to clang, spelled the way clang spells one — -O2, -Os, -O0 — and it reaches every object a build produces rather than only the final link. It can also be written --optimize 2.

The default is -O1, not off. That is worth knowing because it is unusual: -O0 is a different instruction selector rather than merely a slower one, it is the mode a back end’s own test suite covers least, and a real miscompile was found living there. If you drop to -O0 to make something easier to debug and the behaviour changes, suspect that before your program.

If something goes wrong

clang: command not found — sysl got as far as emitting IR and had nothing to hand it to. Install clang and try again. Installing from the tap brings LLVM with it, so this is a built-from-source problem.

llvm-ar complaints when building a library — you have the platform archiver, not LLVM’s. Point at LLVM’s with --ar /path/to/llvm-ar.

cannot find the standard module's source — the compiler could not find the library it ships with, and the message lists every path it tried. From a package install that means the install is incomplete: reinstall it. From a checkout it usually means the working directory is not in the tree. Either way SYSL_LIB=/path/to/library names the library root outright, where the root is the directory holding sysl — that is, the library above library/sysl, not library/sysl itself.

sbt is slow on the first run — it is downloading Scala and the dependency tree. This happens once.

Search

Esc
to navigate to open Esc to close