Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting started

This chapter installs Pepper on your machine, compiles and runs a first program, and sets up your editor.

Install Pepper

Each release has the Pepper toolchain prebuilt for Linux, macOS and Windows. One command installs it:

# Linux and macOS
curl -fsSL https://raw.githubusercontent.com/justinboswell/pepper/main/scripts/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/justinboswell/pepper/main/scripts/install.ps1 | iex

It installs into ~/.pepper (%LOCALAPPDATA%\pepper on Windows), adds its bin folder to your PATH, and offers to install what linking a program needs when it is missing: clang on Linux, Xcode’s command line tools on macOS, LLVM and the Visual Studio Build Tools on Windows. Open a new terminal afterwards. Installing Pepper covers the options, what it installs, and uninstalling.

Building from source

To work on Pepper itself, or on a platform with no prebuilt binaries, build the toolchain from source: see Building Pepper from source.

Your first program

Pepper source files end in .pep. Create a file named hello.pep:

# Every program starts at main. Its return value is the process exit code.
main(): int {
    print("Hello, world!");
    return 0;
}

Every program starts at main, which here takes no arguments and returns an int (it can also take the command-line arguments; see Functions): the process’s exit code, where 0 means success. print writes a value’s text to standard output, followed by a new line. Comments start with # and run to the end of the line.

Compile and run it

Pass the source file to pepper build. It compiles and links a native executable, but does not run it, so run the result yourself:

pepper build hello.pep -o hello
./hello
Hello, world!

On Windows, name the output with .exe (-o hello.exe), since Windows will not run a file without an extension, and run it as .\hello.exe. Without -o the executable is written to the current directory, named after the source file with no extension (hello for hello.pep).

Running a program

To build and run in one step, as python hello.py does, use pepper run. It keeps the executable out of your folder, and runs it again at once when nothing changed:

pepper run hello.pep

Arguments after the file go to the program’s main; see Running a program: pep-run.

Where the compiler finds the standard library

Every program is compiled together with Pepper’s standard library, the runtime/ folder of the repository. The compiler looks for it in this order:

  1. The directory named by the PEPPER_RUNTIME environment variable, if it is set. Nothing else is searched.
  2. Starting from the directory you run it from: runtime/, .pep/runtime/, then ../runtime/, ../../runtime/, and so on, up to five levels up.
  3. Starting from the directory that holds the compiler executable: runtime/ beside it, then in each parent directory, up to five levels up.

So an installed compiler, in ~/.pepper/bin, finds ~/.pepper/runtime, and one built in target/bin finds the runtime of the clone it was built from, wherever you run it. To use a different runtime, set PEPPER_RUNTIME or link the runtime into a project’s .pep folder:

mkdir .pep
ln -s /path/to/pepper/runtime .pep/runtime

If the compiler cannot find the runtime, it stops with an error:

error: cannot find the Pepper runtime: no runtime directory near /home/me/project or the compiler; set PEPPER_RUNTIME to its path

The compiler also writes intermediate files to .pep/build/ under the directory you run it from, and keeps a build cache in .pep/cache/: each file of the program (and the standard library) is compiled to its own object, and on the next build a file whose sources did not change reuses its object without being compiled or checked again, as long as what its imports declare did not change either: editing a function’s body recompiles that file alone, while changing a signature also recompiles the files that import it. A file that compiled with warnings is checked again, so its warnings print on every build. When nothing changed, nothing is checked or linked again. The cache also keeps the standard library checked, so a program’s own files are the only ones checked on each build. Deleting .pep/cache/ is always safe.

When something is wrong

The compiler reports errors the way clang does: the file, line and column, a message, and the source line with a caret under the exact token to fix. Store the message in a variable first, then misspell its name:

main(): int {
    var greeting = "Hello, world!";
    print(greting);
    return 0;
}

The compiler reports:

main.pep:3:11: error: use of undeclared identifier 'greting'
    3 |     print(greting);
      |           ^~~~~~~

Errors are colored when the output is a terminal; set NO_COLOR to turn color off. The chapter on compiler errors covers them in more detail.

A failed assert or a panic at run time prints its message and stops the program abnormally. For example, value() on a failed Result prints Result.value on an Err value.

Useful compiler options

OptionWhat it does
-o <file>Name the output executable (or library, with --lib).
-O, -O<n>Optimize: -O0 (the default) through -O3; -O alone is -O2.
-gEmit debug info for gdb, lldb, Visual Studio and WinDbg. See debugging.
-I <dir>Add a directory to search for imported modules. See modules and imports.
-l <lib>, -L <dir>Link a C library, and add a folder to find it in. See linking.
-D <name>[=<value>]Define a build option.
--target <triple>Compile for another platform; see Cross-compiling.
--lib static, --lib sharedBuild a library for C code to link. See calling Pepper from C.
--helpList every option.

The compiler: pep-build describes every option and environment variable the compiler reads.

A program can span several files: pass the entry file, and the compiler finds the files it imports on its own.

Editor support

The VS Code extension adds syntax highlighting and a language server, which reports compiler errors as you type and provides hover information, completion, go to definition, and an outline of each file.

The packaged extension is published as a .vsix file on the repository’s releases page, under the release whose tag starts with vsix-v. Download it and install it in VS Code with Extensions: Install from VSIX… from the command palette. The language server is bundled with the extension, so there is nothing else to install.

A few things are useful once it is installed:

  • Pepper: Restart Language Server restarts the server on demand.
  • Where a value is converted by an [Implicit] constructor, the editor shows the call it stands for as an inlay hint (Path( before "notes.txt" and ) after), and hovering the value says so. The pepper.inlayHints.implicitConversions setting turns the hints off.
  • A literal passed by position shows the name of its parameter as an inlay hint (resize(width: 800, height: 600)); the pepper.inlayHints.parameterNames setting turns these off. Inside a call’s parentheses, completion offers the parameter names to name (width = ).
  • Hover and go to definition also work where the compiler has not checked the code yet: in a generic function or method nothing calls, on a match arm’s case and the names it binds (Circle(r) => ...), and on the values an enum case declares.
  • The pepper.lspPath setting points the extension at a language server you built yourself (pep-build -O tools/pep-lsp/main.pep -o target/pep-lsp/pep-lsp in a checkout of the repository; see The language server: pep-lsp). The server restarts whenever that file changes.

To install the extension from the branch you have checked out instead of the release, see The VS Code extension from a branch.