What is tourniquet?

tourniquet logo [CC BY-SA 4.0+]

tourniquet is a stack-based programming language that is concatenative to a fault, and focused on simplicity to the point of unusability.

In English, a tourniquet is a tool used to inhibit the flow of blood through a limb, to prevent excessive blood loss. The tourniquet programming language is a tool used to inhibit the flow of programming language constructs through your brain, to prevent excessive (in)sanity loss.

The stack

In French, the word « tourniquet » refers to various turning/revolving implements, like carousels, revolving doors, and turnstiles. In the tourniquet programming language, there are no variables. Instead, tourniquet programs revolve around the stack, which acts as a revolving door that takes in, spits out, and rearranges values. In tourniquet, the use of the stack is implicit; all functions simply operate on the stack, and nothing else.

In fact, a tourniquet program is merely a sequence of zero or more functions. The functions are evaluated in the order of their sequence, and that’s it. Take, for example, this trivial function definition in tourniquet, which simply adds one to the number on the top of the stack:

add_one := 1 + ;

Here, we define (using the := syntax) a function called add_one. In the body of the definition, 1 is the name of a function that pushes the integer 1 onto the top of the stack. And + is the name of yet another function, which adds together the two topmost values on the stack. The ; merely ends the definition. We can visualise add_one like so, assuming that we already had 2 on top of the stack:

┄┄┄┲━━━┓
 … ┃ 2 ┃
┄┄┄┺━━━┛
↓ 1
┄┄┄┲━━━┳━━━┓
 … ┃ 2 ┃ 1 ┃
┄┄┄┺━━━┻━━━┛
↓ +
┄┄┄┲━━━┓
 … ┃ 3 ┃
┄┄┄┺━━━┛

Here, we’re using to represent everything that is on the stack, but isn’t close enough to the top of the stack for us to actually care. This sequence of stack visualisations goes from our initial state (2 is on top), to the state after applying the 1 function (1 is on top, with 2 just below it), to the state after applying the + function (3 is on top).

Quoting

As shown in the previous chapter, the syntax of tourniquet is quite simple: a function is defined by its name, followed by :=, followed by a sequence of functions (the functions being separated by whitespace), and finally ended with a semicolon (;).

But there is an additional bit of the syntax that we must address: quoting. The only way in which round brackets/parentheses ((, )) are used in tourniquet is for quotation. Wrapping a sequence of zero or more functions inside of parentheses results in the sequence itself being pushed onto the top of the stack. This effectively creates an anonymous function, and then pushes it onto the stack.

Quoting is important for some purposes. For example, there is a built-in function called if that serves the same purpose as “if” statements in other languages — that is, it allows for conditional execution. Say that we wanted to define a function similar to add_one (defined in the previous chapter), but have it only add one when the other summand is odd. That way, the resulting integer is always even. We could do that like so:

make_even := dup 2 % 0 = () (1 +) if;

And we could visualise the application of this function like so, assuming that we start with 3 on top of the stack:

┄┄┄┲━━━┓
 … ┃ 3 ┃
┄┄┄┺━━━┛
↓ dup
┄┄┄┲━━━┳━━━┓
 … ┃ 3 ┃ 3 ┃
┄┄┄┺━━━┻━━━┛
↓ 2
┄┄┄┲━━━┳━━━┳━━━┓
 … ┃ 3 ┃ 3 ┃ 2 ┃
┄┄┄┺━━━┻━━━┻━━━┛
↓ %
┄┄┄┲━━━┳━━━┓
 … ┃ 3 ┃ 1 ┃
┄┄┄┺━━━┻━━━┛
↓ 0
┄┄┄┲━━━┳━━━┳━━━┓
 … ┃ 3 ┃ 1 ┃ 0 ┃
┄┄┄┺━━━┻━━━┻━━━┛
↓ =
┄┄┄┲━━━┳━━━━━━━┓
 … ┃ 3 ┃ false ┃
┄┄┄┺━━━┻━━━━━━━┛
↓ ()
┄┄┄┲━━━┳━━━━━━━┳━━━━┓
 … ┃ 3 ┃ false ┃ () ┃
┄┄┄┺━━━┻━━━━━━━┻━━━━┛
↓ (1 +)
┄┄┄┲━━━┳━━━━━━━┳━━━━┳━━━━━━━┓
 … ┃ 3 ┃ false ┃ () ┃ (1 +) ┃
┄┄┄┺━━━┻━━━━━━━┻━━━━┻━━━━━━━┛
↓ if
┄┄┄┲━━━┓
 … ┃ 4 ┃
┄┄┄┺━━━┛

dup is a built-in function that duplicates the value that’s on top of the stack. % is a built-in function that performs the modulo operation, using the same nomenclature as other languages like C, Python, Rust, etc. And, as mentioned before, sequences of functions can possibly be empty (i.e. length of zero), so () represents a quotation of the identity function (the function that does nothing to the stack).

Finally, the built-in function if consumes the top three values on the stack. The bottom such value is a boolean value that determines whether the first function (if it’s true), or the second function (if it’s false), is applied. The middle such value is the first function, and the top such value is the second function.

app

Closely related to quotation is the built-in function app. app is short for apply, which means that it simply applies the function that is on top of the stack. The most clear way to see how app works is to observe that the following two functions are equivalent (using the same example from the previous chapter):

add_one := 1 +;

add_one' := (1 +) app;

Comments

Oh, and while we’re talking about syntax, we can’t go without any comments, can we? For this, tourniquet supports C89-style comments:

/**
 * This is a comment.
 *
 * This kind of comment is a reasonable way to format a documentation comment
 * for the function defined directly below.
 *
 * This function increments the integer on top of the stack by one.
 */
add_one /* This is also a comment. */ := 1 + /* This is a comment, too. */;

Note that tourniquet only supports C89-style comments. In particular, C99-style comments that begin with a pair of forward slashes (//) and end with a newline are not supported. And, like in C89, comments cannot be nested — so something like /* /* */ */ probably doesn’t mean what you think it means.

Names

The keen-eyed reader will have noticed that, in the “The stack” chapter of this book, we said:

[…] 1 is the name of a function that pushes the integer 1 onto the top of the stack.

And it’s true; 1 is the name of a built-in function. 1 is a perfectly valid identifier in tourniquet. This may come as a surprise for those who expected 1 to be a numeric literal!

In fact, any string of one or more Unicode scalar values that does not contain any of the following scalar values:

and that does not contain any of the following as a substring:

  • := (U+003a U+003d)
  • /* (U+002f U+002a)
  • */ (U+002a U+002f)

…is a valid identifier in tourniquet.

Formal syntax

Every tourniquet program is also a valid string of UTF-8.

With that said, the following is the formal syntax for a tourniquet program (called source_file in the EBNF specification below), using W3C-style EBNF notation:

source_file ::= wsc (definition wsc)*

definition ::= identifier wsc ":=" sequence ";"

sequence ::= wsc (sequence_element wsc)*

sequence_element ::= identifier | "(" sequence ")"

identifier ::= identifier_char+ - ":=" - "/*" - "*/"

identifier_char ::= [^;()] - SEPARATOR_OR_OTHER


/* ===== Whitespace & comments ===== */

wsc ::= (whitespace | comment)*

whitespace ::= UNICODE_SEPARATOR+

comment ::= "/*" (ANY - "*/")* "*/"


/* ===== Unicode stuff ===== */

ANY ::= [#x0000-#x10fffd]

SEPARATOR_OR_OTHER ::= UNICODE_SEPARATOR | UNICODE_OTHER

UNICODE_SEPARATOR ::= #x0020 | #x00a0 | #x1680 | #x2000 | #x2001 | #x2002
                    | #x2003 | #x2004 | #x2005 | #x2006 | #x2007 | #x2008
                    | #x2009 | #x200a | #x2028 | #x2029 | #x202f | #x205f
                    | #x3000

UNICODE_OTHER ::=  #x0000 |   #x0001 |   #x0002 |  #x0003 |  #x0004 |  #x0005 | #x0006
                |  #x0007 |   #x0008 |   #x0009 |  #x000a |  #x000b |  #x000c | #x000d
                |  #x000e |   #x000f |   #x0010 |  #x0011 |  #x0012 |  #x0013 | #x0014
                |  #x0015 |   #x0016 |   #x0017 |  #x0018 |  #x0019 |  #x001a | #x001b
                |  #x001c |   #x001d |   #x001e |  #x001f |  #x007f |  #x0080 | #x0081
                |  #x0082 |   #x0083 |   #x0084 |  #x0085 |  #x0086 |  #x0087 | #x0088
                |  #x0089 |   #x008a |   #x008b |  #x008c |  #x008d |  #x008e | #x008f
                |  #x0090 |   #x0091 |   #x0092 |  #x0093 |  #x0094 |  #x0095 | #x0096
                |  #x0097 |   #x0098 |   #x0099 |  #x009a |  #x009b |  #x009c | #x009d
                |  #x009e |   #x009f |   #x00ad |  #x0600 |  #x0601 |  #x0602 | #x0603
                |  #x0604 |   #x0605 |   #x061c |  #x06dd |  #x070f |  #x0890 | #x0891
                |  #x08e2 |   #x180e |   #x200b |  #x200c |  #x200d |  #x200e | #x200f
                |  #x202a |   #x202b |   #x202c |  #x202d |  #x202e |  #x2060 | #x2061
                |  #x2062 |   #x2063 |   #x2064 |  #x2066 |  #x2067 |  #x2068 | #x2069
                |  #x206a |   #x206b |   #x206c |  #x206d |  #x206e |  #x206f | #xe000
                |  #xf8ff |   #xfeff |   #xfff9 |  #xfffa |  #xfffb | #x110bd
                | #x110cd |  #x13430 |  #x13431 | #x13432 | #x13433 | #x13434
                | #x13435 |  #x13436 |  #x13437 | #x13438 | #x1bca0 | #x1bca1
                | #x1bca2 |  #x1bca3 |  #x1d173 | #x1d174 | #x1d175 | #x1d176
                | #x1d177 |  #x1d178 |  #x1d179 | #x1d17a | #xe0001 | #xe0020
                | #xe0021 |  #xe0022 |  #xe0023 | #xe0024 | #xe0025 | #xe0026
                | #xe0027 |  #xe0028 |  #xe0029 | #xe002a | #xe002b | #xe002c
                | #xe002d |  #xe002e |  #xe002f | #xe0030 | #xe0031 | #xe0032
                | #xe0033 |  #xe0034 |  #xe0035 | #xe0036 | #xe0037 | #xe0038
                | #xe0039 |  #xe003a |  #xe003b | #xe003c | #xe003d | #xe003e
                | #xe003f |  #xe0040 |  #xe0041 | #xe0042 | #xe0043 | #xe0044
                | #xe0045 |  #xe0046 |  #xe0047 | #xe0048 | #xe0049 | #xe004a
                | #xe004b |  #xe004c |  #xe004d | #xe004e | #xe004f | #xe0050
                | #xe0051 |  #xe0052 |  #xe0053 | #xe0054 | #xe0055 | #xe0056
                | #xe0057 |  #xe0058 |  #xe0059 | #xe005a | #xe005b | #xe005c
                | #xe005d |  #xe005e |  #xe005f | #xe0060 | #xe0061 | #xe0062
                | #xe0063 |  #xe0064 |  #xe0065 | #xe0066 | #xe0067 | #xe0068
                | #xe0069 |  #xe006a |  #xe006b | #xe006c | #xe006d | #xe006e
                | #xe006f |  #xe0070 |  #xe0071 | #xe0072 | #xe0073 | #xe0074
                | #xe0075 |  #xe0076 |  #xe0077 | #xe0078 | #xe0079 | #xe007a
                | #xe007b |  #xe007c |  #xe007d | #xe007e | #xe007f | #xf0000
                | #xffffd | #x100000 | #x10fffd

Legal

The tourniquet Book — and the tourniquet logo (tourniquet.svg) — are both licensed under the terms of version 4.0 of the Creative Commons Attribution-ShareAlike International license (see https://creativecommons.org/licenses/by-sa/4.0/ for more info), or any later version of the same license, at your option (“CC-BY-SA-4.0-or-later”).

You can find the source used to build this book at: https://codeberg.org/tourniquet/tourniquet_book