Tails Syntax

June 11, 2021 · View on GitHub

The syntax of Tails is quite simple:

  • A line of input is broken into tokens. Tokens are mostly separated by whitespace, but the quote and bracket characters that mark string, array and quotation literals are also token boundaries.
  • A token matching a literal (q.v.) pushes that literal value on the stack.
  • IF, THEN, ELSE, BEGIN, WHILE, REPEAT are reserved words for control structures, as described below.
  • Anything else is looked up as a word (function) in the vocabulary, and evaluates that word.
  • If a token doesn't match anything, it's a syntax error.

NOTE: All name lookup is case-insensitive, at least for ASCII letters.

1. Literals

TypeSyntaxExamples
NullNULLNULL, null, Null
NumberSame as C42, -8, 0xbeef, 1.234, 6.02e23
StringDouble-quoted, backslash escape"foo", "", "\"wow\""
ArraySquare brackets, space-separated[1 2 NULL "foo"], []
QuotationCurly braces; optional stack effect{DUP *}, {(# # -- #) DUP +}

Quotations are described in more detail below, under Defining New Words.

2. Built-In Words

NameInputsOutputsDescription
DROPaRemove top value from stack
DUPaa aDuplicates top value
OVERa ba b aCopies 2nd value to top
ROTa b cb c aMoves 3rd value to top
SWAPa bb aSwaps top two values
+a bcAddition, or string or array concatenation
-# ##Subtraction
*# ##Multiplication
/# ##Division
=a b#Outputs 1 if a=b, else 0
<>a b#Outputs 1 if a≠b, else 0
>a b#Outputs 1 if a>b, else 0
>=a b#Outputs 1 if a>b, else 0
<a b#Outputs 1 if a<b, else 0
<=a b#Outputs 1 if a<=b, else 0
0=a#Outputs 1 if a=0, else 0
0<>a#Outputs 1 if a≠0, else 0
0>a#Outputs 1 if a>0, else 0
0<a#Outputs 1 if a<0, else 0
LENGTHa#Length of string or array
ABS##Absolute value
MAXa bmaxMaximum of a, b
MINa bminMinimum of a, b
.aWrites text representation of a to stdout.
SP.Writes a space character to stdout.
NL.Writes a newline to stdout.
NL?Writes a newline, if necessary to start a new line.
DEFINEquote nameRegisters quote as a new word named name.
CALL... quote?Evaluates a quotation (can't be used directly yet)

3. Control Structures

("Truthy" means any value but 0 or NULL.)

TypeSyntaxDescription
ConditionalIF ... THENIF pops a value; if it's truthy, evaluates the words before THEN.
IF ... ELSE ... THENIF pops a value; if it's truthy, evaluates the words before THEN, else evaluates words before ELSE.
a {...} {...} IFELSEPops 3 params. If a is truthy calls the first quote, else calls the second.
LoopBEGIN ... WHILE ... REPEATWHILE pops a value, jumps past REPEAT if it's zero/null. REPEAT jumps back to BEGIN.
RecursionRECURSECalls the current word recursively.

4. Defining New Words

A quotation is a value that's a function, i.e. a sequence of words to evaluate. The only difference between a quotation and a word is that a word is registered in a vocabulary for the parser to find.

To define a word, just write a quotation literal, then the name as a string, then invoke DEFINE:

{ .... } "name" DEFINE

or, with a stack effect declaration:

{ (... -- ...) .... } "name" DEFINE

Stack Effects

Every word and quotation has a stack effect, which declares the number of input and output values, and optionally their types. The compiler uses this to ensure that the stack doesn't get unbalanced, and can't underflow or overflow, and that there aren't any type mismatches. (Yes, words with variable numbers of inputs or outputs are not allowed.)

Declaring a stack effect for a quotation is optional; if you don't, the compiler will figure it out on its own by examining the stack effects of the words the quotation calls. But in a quotation that defines a word it's good to declare it up front; both as human readable documentation, and so that the compiler can check your work and give you an error if the actual effect doesn't match the declaration.

A stack effect declaration follows the opening { of a quotation and consists of:

  1. An open paren (
  2. Zero or more tokens, each representing an input value on the stack, with the top of stack on the right.
  3. A separator --
  4. Zero or more tokens for output values, with top of stack on the right.
  5. A close paren )

The input/output tokens can contain ASCII letters, underscores, and these punctuation marks that denote specific types, in no particular order:

MarkType
?Null
#Number
$String
[ or ]Array
{ or }Quote

A token with no punctuation marks represents a value of any type. Otherwise it represents only the given type(s). So for example foo can be any type, while [foo]#? can only be a number, an array or null.

If an output token exactly matches an input token, that declares that at runtime it's exactly the same type as the corresponding input. So for example SWAP has a stack effect of (a b -- b a) which declares that the types in the output are the reverse of the input types. And + is declared (a#$[] b#$[] -- a#$[]), indicating that the parameters can be numbers, strings or arrays, and that the output type is the same as the first parameter's type.