Introduction

Welcome to The Tortuga Programming Language, an introductory book about Tortuga.

Tortuga is a functionally-oriented concurrent programming language. The runtime is a Rust program to provide performance and memory safety; the language compiles to WebAssembly. Using WebAssembly allows developers to utilize their favorite programming language to write extensions for the runtime.

Who is Tortuga For

Tortuga is currently under active development and makes no guarantees about backwards compatibility. At this time, only use Tortuga for experimentation.

Getting Started

Let’s get you started with Tortuga. In this chapter, we’ll discuss:

  • Installing Tortuga on Linux, macOS, and Windows.
  • Writing a program that computes factorial.
  • Using the tortuga command-line interface.

Installation

Tortuga can be used in various ways. The most common is to download the compiled binaries for your system from the latest release on the repository.

Download

Each new version of Tortuga creates a release in the repository. A release contains compressed (.tar.gz and .zip) assets for Linux, macOS, and Windows.

After you download the asset for your system, use the tools for your operating system to decompress the assets into an executable. Then, place the executable in a directory that is part of your PATH environment variable.

Cargo

Alternatively, you can install any version of tortuga from source using cargo with:

cargo install tortuga

Embedded

To embed the language in a Rust program, add the tortuga crate as a dependency in your Cargo.toml:

tortuga = { version = "*", default-features = false }

Docker

To use the language in a container, use the ghcr.io/misalcedo/tortuga image:

docker run -it --rm ghcr.io/misalcedo/tortuga

Playground

Try Tortuga entirely in your browser with WebAssembly.

Click the play button to execute the code directly in your browser!

f(x) = x^2 + 3*x - 1/2
g(x) = 0.5 + f(x)
f(2) - g(2) = 0.5

Factorial

In this chapter we will implement factorial using the Tortuga Programming Language. In mathematics, the factorial of a non-negative integer n, denoted by n!, is the product of all positive integers less than or equal to n. The factorial of n also equals the product of n with the next smaller factorial. The factorial of 0 is equal to 1 (i.e., 0! = 1).

Implementation

All numbers in the Tortuga Programming Language are signed real numbers. Since factorial is only defined for non-negative integers, our implementation will round all real numbers to their nearest integer and report an error for non-negative numbers.

Copy & Paste

Create a file named factorial.ta with the following contents:

round(n) = round(n, n % 1)
round(n, remainder) ? (remainder >= 0.5) = 1 + n - (n % 1)
round(n, remainder) ? (remainder < 0.5) = n - (n % 1)

; base case
factorial(n) ? (n = 0) = 1

; factorial
; inductive case
factorial(n) ? (n > 0) = [
    i = round(n)
    i * factorial(i - 1)
]

factorial(9)

Run

To run the file use the command-line interface from your favorite terminal:

tortuga run factorial.ta

You should see the value 362880 printed to your terminal. The Tortuga Programming Language automatically prints the value of the last expression in a program.

Command-Line Interface

The tortuga command-line tool is used to interact with all aspects of the Tortuga Programming Language. After you have installed tortuga, you can run the tortuga help command in your terminal to view the available commands.

The common commands are:

  • tortuga — Starts an interpreter instance in an interactive prompt.
  • tortuga run <file> — Builds then runs an input file. The value of the last expression in the file is printed to the terminal.

Design

The following are a set of design decisions for Tortuga roughly separated into categories.

Goals

  • Sending messages is the only language supported side-effect (i.e. statement).
  • All functions are side-effect free.
  • Variables are constant functions with no arguments.
  • Functions may only be declared once.
  • All data types are immutable.
  • Compiler warnings are reserved exclusively for deprecated functionality to be removed in a future major version. The compiler can optionally fail when warning are found to ensure future compatibility.
  • Compiler attempts to find as many errors in a single run as possible.
  • Focus on concurrent performance and ease of use; single-threaded performance is not a focus.
  • No key words in the grammar. Prefer mathematical symbols to C-like syntax, and C-like syntax to key words.

Non-Goals

  • Space efficiency.
  • Single-threaded performance.
  • Low-level (i.e., systems) programming.
  • Object-Oriented Programming.

Data Types

  • Tortuga has no String type. String programming makes internationalization more difficult. Instead the standard library will provide a mechanism to map byte string triplets (key, language, and an optional region) into a different byte string.
  • No null, nil, etc. All types are concrete types.
  • All numbers are float-like. The goal is to provide enough accuracy where Tortuga could be used to perform calculations for science or money. Space efficiency and speed are not goals for numerical operations, only accuracy and precision.
  • Tortuga has no boolean type and no control flow. Instead it relies on pattern matching, comparisons and dynamic dispatch to perform control flow and on regular numbers for boolean logic.
  • Numbers can be encoded in any radix up to and including 36 (e.g. 0-9, A-Z, a-z).
  • Tortuga provides a fixed size binary sequence called a byte string. The string may be used as a buffer, resized, or modified via patches. Byte strings have an optional padding length to denot how much of the final byte is padding for bit sequences rather than byte sequences.
  • Tortuga has a tuple type that can hold arbitrary types in each field. Tuple are callable with an index to extract a field. Also, tuples can be used as linked-lists (i.e. first and rest).
  • Tortuga has a range type to that supports inclusive and exclusive bounds at the low and high end.

Expressions

  • Tortuga has no statements. Every operation returns a value. For example sending a message returns the sent message to allow sending the same message to multiple recipients.
  • Tortuga has no expression or statement terminator (i.e., no semicolon at the end of a statement).
  • Expressions may span more than one line at any point.
  • Order of precedence follows the mathematical order (i.e., parentheses, exponentiation, multiplication and division, addition and subtraction).
  • Comparisons and pattern matching have a lower precedence than mathematical operations.

Identifiers

  • Identifiers must start with a unicode character withe the XID_START property, followed by zero or more XID_CONTINUE characters.
  • Variables are declared implicitly when in a pattern match. Most of the trade offs mentioned in Crafting Interpreters are avoided by having immutable variables that cannot be shadowed. If a new variable with the same name is introduced in an outer scope, the existing variable assignment can be treated as a compiler error (i.e., easily detected as an error). Also, modules are a single file and are the root scope so “global” variables are limited to the current file.
  • Variables may not be mutated once assigned.
  • Variables cannot shadow an existing variable in an enclosing lexical scope.
  • Variables may not be used in mathematical operations until assigned to.

Control Flow

  • Tortuga has no built-in control flow. Instead programs must rely on recursion, pattern matching and dynamic dispatch.

Scopes

  • Tortuga only has lexical scoping, therefore every scope is static.

Modules

  • A module in Tortuga defines a set of functions, variables and procedures.
  • Any of the declared items can be exported for others to consume.
  • Modules cannot be nested within one another.
  • Modules may be namespaced via a directory structure; directories cannot also be modules.
  • Modules are not imported. Instead they are locally aliased in order to shorten their references in the local module. A function can always be referenced without an alias.
  • Libraries are sets of modules that may be downloaded locally by the compiler.
  • Libraries may be version using semantic versioning. Multiple versions of the same library may be referenced by a project at any given time.
  • Projects define aliases for each library module that are implicitly added at the start of that project’s modules. The libraries are stored locally using a directory structure to have separate namespaces for different versions. So different versions can be used even in the same project.

Functions

  • All functions in tortuga must return a value.
  • Functions cannot have any side effect or mutate data or variables.
  • Functions that use [...] block notation introduce a new scope.
  • Functions can only return a single value. Use a tuple to return multiple values.
  • Functions can call other functions.

Procedures

  • Procedures may call functions, other procedures or send messages (the only side-effect in the language).
  • Processes can be spawned only from a procedure.

Tuples

  • Tuples are the core type in Tortuga to be used in place of classes for grouping data. They are similar to Rust and Python tuples in that they are a sequence of unnamed fields. The fields are named through pattern matching.

Processes

  • The building block for concurrency is a process.
  • Processes can do anything a function can do plus: receive messages, send messages and create other processes.
  • Tortuga has no synchronization or locking primitives.

Standard Library

  • Provides mathematical primitives.
  • Provides concurrency building blocks (e.g. consensus, 2-phase commit, etc.).
  • Provides access to a key-value block store.
  • Provides a logging process.
  • Provides networking primitives (e.g. servers, clients, connections, TLS, TCP, UDP, etc.).

This is the complete Extended Backus Normal Form (eBNF) grammar definition for Tortuga.

Syntax Grammar

The syntactic grammar of Tortuga is used to parse a linear sequence of tokens into a nested syntax tree structure. The root of the grammar matches an entire Tortuga program (or a sequence of comparisons to make the interpreter more useful).

program     = expressions | comparisons EOF ;
expressions = expression+ ;
comparisons = expression comparison+ ;
comparison  = comparator expression ;

Expression

A program is a series of expressions. Expressions produce values. Tortuga has a number of binary operators with different levels of precedence. Some grammars for languages do not directly encode the precedence relationships and specify that elsewhere. Here, we use a separate rule for each precedence level to make it explicit.

expression = assignment | arithmetic ;
assignment = function "=" block ;
block      = "[" expression expression+ "]" | expression ;

arithmetic = epsilon ;
epsilon    = modulo ( "~" modulo )? ;
modulo     = sum ( "%" sum )* ;
sum        = product ( ( "+" | "-") product )* ;
product    = power ( ( "*" | "/" ) power )* ;
power      = primary ( "^" primary )* ;

call       = primary arguments* ;
primary    = number | IDENTIFIER | grouping ;
number     = "-"? NUMBER ;
grouping   = "(" expression ")" ;

Pattern Rules

The grammar allows pattern-matching in function definitions instead of having built-in control flow. These rules define the allowed patterns.

pattern    = function | refinement | bounds ;
function   = name parameters? ;
refinement = name comparator arithmetic ;
bounds     = arithmetic inequality name inequality arithmetic ;

Utility Rules

To keep the above rules a little cleaner, some grammar is split out into a few reused helper rules.

arguments  = "(" expression ( "," expression )* ")" ;
parameters = "(" pattern ( "," pattern )* ")" ;

name       = "_" | "@" IDENTIFIER ;
inequality = "<" | "<=" | ">" | ">=" ;
equality   = "=" | "<>" ;
comparator = equality | inequality ;

Lexical Grammar

The lexical grammar is used during lexical analysis to group characters into tokens. Where the syntax is context free, the lexical grammar is regular – note that there are no recursive rules.

IDENTIFIER  = XID_START XID_CONTINUE* ;
NUMBER      = NONZERO DIGIT? "#" ( "0" | NATURAL | REAL | FRACTION) ;
NATURAL     = NZ_ALPHANUM ALPHANUM* ( "." "0"? )? ;
REAL        = NZ_ALPHANUM ALPHANUM* "." ALPHANUM*? NZ_ALPHANUM ;
FRACTION    = "0"? "." ALPHANUM*? NZ_ALPHANUM ;

NZ_ALPHANUM = NZ_DIGIT | ALPHA ;                
ALPHANUM    = DIGIT | ALPHA ;
ALPHA       = "a" ... "z" | "A" ... "Z" ;
NZ_DIGIT    = "1" ... "9" ;
DIGIT       = "0" ... "9" ;

Operators

The associativity and precedence of the various operators in Tortuga are defined below.

PrecedenceOperationSymbolLeft AssociativeRight AssociativeNon-Associative
1Epsilon~X
2Modulo%XX
3Add+XX
3Subtract-X
4Multiply*XX
4Divide/X
5Exponent^X
6Pattern Match=X
7Inequality<>XX
7Less Than<XX
7Less Than or Equal To<=XX
7Greater Than>XX
7Greater Than or Equal To>=XX