<-- Getting started || Contents || Language Reference -->
I've never written any tutorials before, so any help / criticism would be appreciated.
I would encourage you to play around with everything you learn - just following the tutorial without experimenting yourself is unlikely to help you much. This tutorial is also definitely not rigorous and complete, so I would encourage you to explore the language reference.
One nice command when you are playing around is D
. All it does is to
output the stack. Please note that it outputs to stderr, and that the
output format is entirely implementation-defined. It should definitely
not be used for anything other than exploring the language or debugging
programs.
In ErrLess, every command is only one character long. And every character is one command - even though many are no-ops. Note: Erless is case-sensitive.
First the basics:
In an ErrLess program the stack starts empty. The IP starts at the first character of the program. When the IP reaches the end of the program, it loops back to the beginning instead of halting the program.
There are two types in ErrLess: A stack, and an integer. A stack can contain either other stacks, or integers, or a combination of both.
Whenever a command tries to get a value outside the bounds of the
stack - element -1, element x
of a stack of length ≤ x
, the top
element of an empty stack, etc. - it just gets an empty stack in
return.
Now some instructions:
All whitespace - \n\r \t
- and unprintable characters are no-ops.
You can use them to structure your code.
S
starts and ends a string. There are no escape characters or
anything. It just copies everything up to the next S
. You may
notice that this means that you cannot have a capital s in a string.
We'll come to this later. You may also remember that there are only
stacks and integers - so where did the string come from? I'll
explain how strings work later, but it isn't important at the moment.
We'll also need to output the string - ErrLess doesn't automatically
output the stack when it's finished. To do this use ?
. Note: ?
doesn't automatically print a newline.
We need one last essential for our hello world program: A way to end
the program, otherwise it'll just loop back to the beginning and
keep on printing forever. To halt the program we'll use .
.
To summarise:
\n\r \t
- No-opS...S
- Construct a string?
- Print the string, no automatic newline.
- Halt the programSo now our program:
First, we construct the string: SHello, world!S
Next, we print: ?
Lastly, we end the program: .
Putting this all together we have this: SHello, world!S ? .
If you want a newline at the end use this:
SHello, world!
S?.
So how do strings work? Easy! It uses a stack containing integers,
where every integer represents a character of the string. If any
character is outside the range of valid characters, then it is treated
as a NULL
character. So to add a capital s to a string, we need to
learn a few new commands:
'
- Adds the character code of the next character to the top of
the stack
x
- Append the top value of the stack to the stack just below it.
:
- Concatenate the top two elements of the stack.
So, to create the string "There are SEAMONSTERS??":
SThere are S 'Sx SEAMONSTERS: 'Sx S??S:
Here's a view of the stack:
SThere are S
: "There are "
SThere are S 'S
: "There are " S
SThere are S 'Sx
: "There are S"
SThere are S 'Sx SEAMONSTERS
: "There are S" "EAMONSTER"
SThere are S 'Sx SEAMONSTERS:
: "There are SEAMONSTER"
SThere are S 'Sx SEAMONSTERS: 'Sx
: "There are SEAMONSTERS"
SThere are S 'Sx SEAMONSTERS: 'Sx S??S:
: "There are SEAMONSTERS??"
Unfortunately my language does not have comments. Fortunately there is a structure that can be used to simulate comments. To add a comment, just surround it with curly braces:
{ Look! A "Hello, world!" program! }
SHello, world!
S?.
{ Did you know that you can nest "comments"? Like this: {Wow!} }
It should be noted that there are ways to get the IP to enter these "comments", but you don't need to worry about that for now.
Let's make a little program that asks for a name, greets the user with their name, asks their age, and then tells them how old they'll be in eleven years. Something like this:
What is your name? John
Hello, John! How old are you? 25
You will be 36 years old in eleven year's time.
The first thing we'll need is a way to take inputs. Q
prints the top
element of the stack in the same way ?
does, and then takes one line
of input. q
- note the difference in case - prints the top element of
the stack like ?
, and then tries to get an integer as an input, and
returns an empty stack if it was unsuccessful.
So we can do the first part of the program so far:
SWhat is your name? SQ { Get the name }
SHello, S?
? { Print the name }
S! How old are you? Sq { Get the age }
.
But for the next step we'll need to add eleven to the age and then print it as a number. To add eleven to the age we'll need numbers, and some mathematical operators:
0
to 9
- Push the corresponding number to the stack
a
to f
- Push the corresponding hex number to the stack: 10 for
a
, 11 for b
, etc.
+
, -
, *
, /
- The basic mathematical operators: Addition,
subtraction, multiplication, division
#
- Output the top value of the stack as a number
Using this we can expand our program:
SWhat is your name? SQ
SHello, S? ? S! How old are you? Sq
b+ { Add eleven to the age }
SYou will be S?
# { Print the age }
S years old in eleven year's time.
S?
.
If we remove the comments:
SWhat is your name? SQ
SHello, S? ? S! How old are you? Sq
b+ SYou will be S? # S years old in eleven year's time.
S?
.
Errlang doesn't only have addition, subtraction, multiplication, and division. Here's a few more mathematical operators:
_
- Negates the top element of the stack: N_
is equivalent to
0N-
, where N is some command / set of commands that results in one
value being pushed to the stack
%
- Modulo, gets the remainder after a division
\
- Divmod, returns a stack containing two integers: The result of
the division, and the remainder
t
- N * 10ᴹ, 89t
== 8e9, or eight eight thousand million /
billion
T
- 10ᴺ, 6T
== 1e6, one million
p
- N * 2ᴹ; see t
P
- 2ᴺ; see T
What if we want to make a slightly more complex program, one that does different operations depending on the input? For this we could make a truth machine: If it gets a zero as input, it returns zero and halts immediately, and if it gets a one it keeps on printing ones forever. For now we'll assume that the input is valid.
To make this a bit easier I'll first design a truth machine in Python-like pseudocode:
inp = input()
do:
print(inp)
while inp == 1
The first part is easy: SSq
But the next part is slightly more difficult.
In ErrLess we use -1 for true and 0 for false. Why -1 and not 1? This is because -1 is represented as all ones in binary. This means that we don't need separate bitwise and logical operators. A demonstration in C++:
std::cout << (~0) << ' ' << (~1) << std::endl; // -1 -2
std::cout << (!0) << ' ' << (!1) << std::endl; // 1 0
As you can see, bitwise and logical not gives completely different results: The bitwise inversion of zero, is negative one! This is because of how computers represent numbers internally, called two's complement notation. To read more you can visit the wiki page for two's complement notation.
In ErrLess there are three comparison operators, and four bitwise/logical operators:
(8 bit integers used for demonstration purposes)
=
- Checks equality
<
- Less than
>
- Greater than
~
- NOT, returns the bitwise inverse: NOT 42 "00101010" → -43 "11010101"
|
- OR: 42 "00101010" OR 13 "00001101" → 47 "00101111"
&
- AND: 42 "00101010" AND 13 "00001101" → 8 "00001000"
^
- XOR: 42 "00101010" XOR 13 "00001101" → 39 "00100111"
It's all fine being able to get input, print stuff, compare stuff, and add numbers, but you can only do so much without control flow. ... So I introduce you to gotos:
z...Z
- When a z
is reached, skip to the next Z
, allowing
nesting. Ignore all z
's and Z
's inside "comments"
Y...y
- When a y
is reached, skip backwards to the previous
Y
, allowing nesting. Ignore all y
's and Y
's inside "comments"
{...}
- When a {
is reached, skip to the next }
, allowing
nesting. Note that this ignores both z...Z
and Y...y
.
You may notice a problem with this: z...Z
makes sections of code
permanently inaccessible, while Y...y
causes infinite loops. This
doesn't help us much! That is why there are two skip instructions:
]
- Skip forward by the number indicated by the top of the stack,
skipping backwards for negative numbers
[
- Skip backwards by the number indicated by the top of the stack,
skipping forward for negative numbers
With these you can make an if statement (assumes 0 or -1 at the top of the stack):
1-[{ CODE }
Or an if-else statement:
2-[z{ CODE-IF-TRUE z} CODE-IF-FALSE Z
Using the same principles we can also have a while loop:
Y1-[{ CODE y} 2]y
An until loop:
Y2+]{ CODE y} 2]y
A do... while loop:
Y CODE 2+]y
And a do... until loop:
Y CODE 1-[y
So far, we're busy translating the following pseudocode into Errlang:
inp = input()
do:
print(inp)
while inp == 1
And this is what we have:
SSq
Well, this should be easy:
SSq { Get input }
Y { do ... }
# { print(inp) }
1= 2+]y { while inp == 1 }
. { Halt }
But if you run this, you may notice that the program prints the input
once, and then hangs. Why is this? Very simple: The #
consumes our
input. So would the 1= 2+]
. What can we do about this? For this there
is a command that duplicates the top value of the stack: @
. So lets
modify the program accordingly:
SSq { Get input }
Y { do ... }
@# { print(inp) }
@1= 2+]y { while inp == 1 }
. { Halt }
And now we have a working truth machine!
... for now.
You've run out of tutorial! Don't worry, I'll probably add more sometime. To find out more about the language yourself, feel free to visit the reference page or the FAQ. You can also check out the examples page to see a few sample programs.
commit 2a4a5bbef22026959061378e2f7dbf86f34f132c Author: Ruan <ruan@pysoft.co.za> Date: 2021-10-23T08:30:20+02:00 Minor Fixes