What is tourniquet?
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:
- Semicolon (U+003b;
;
) - Parentheses (U+0028,
U+0029;
(
,)
) - Any scalar value with the major general category “Separator” (abbreviated as “Z” — this is basically just whitespace characters)
- Any scalar value with the major general category “Other” (abbreviated as “C” — this is basically just control characters and some other special-sauce stuff)
…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