BEFORE YOU START
This tutorial is for people who have written programs before, in any language. It moves quickly through ten lessons, from hello world to threads, and every example on this page has been run through satl, the SATELLITE interpreter. What you see under each program is exactly what it prints.
If you have never programmed, start with BEGINNERS instead. If you want every word of the language in one place, that is the FULL REFERENCE. To get satl on your computer, see INSTALL.
To try an example, save it under the file name shown above it, then run it:
satl hello.satlEach lesson ends with an exercise. Try it before you read the answer below it.
LESSON 1: HELLO, WORLD
Here is a complete SATELLITE program.
(satellite)
satellite.capsule satellite.main()
{
satellite.console.display("Hello, World!")
satellite.return(satellite)
}Hello, World!Every program you run needs three things, and satl checks for all three before anything runs:
satellite.include(satellite)says this file is a program.satellite.capsule satellite.main()is where it begins. A capsule is SATELLITE’s word for a function.satellite.return(satellite)is where it ends. Its place is the last line of main.
Leave one out and satl tells you which one is missing and what to type.
The one rule behind every name
Look at the names in that program. Every one of them starts with satellite., and that is the most important rule in the language: a dotted path that starts with satellite names something the language owns, and a bare name is something you own. satellite.console.display is the language’s word for writing a line. When you write apples or greet or account, that is yours. You can always tell at a glance which is which.
How a program is laid out
- One statement is one line. There are no semicolons.
- Braces go on lines of their own after an
if, awhileor afor, as in every example here. //starts a comment that runs to the end of the line.satlwrites its own title block (the language’s name, version and build) to standard error before your program starts. Only what your program displays goes to standard output, so you can pipe it or send it to a file.
satellite.console.display writes one value and then ends the line. Give it end= to end the line with something else, or with nothing at all. It also takes foreground=, background=, bold= and italic= for colour and style; see DISPLAYING TEXT and COLOR ON THE CONSOLE.
(satellite)
satellite.capsule satellite.main()
{
satellite.console.display("Loading", end="...")
satellite.console.display(" done")
satellite.console.display("careful", foreground=xFF8800, bold=satellite.bool.true)
satellite.return(satellite)
}Loading... done
carefulAt a terminal, careful shows in bold orange. Into a pipe or a file the colour is left out, which is why it prints plain here.
satl --repl opens the satl prompt, where you can type one line and see it run straight away. It is a good place to try a single display, such as satellite.console.display(2 ^ 100). For every option the command takes, see THE SATL COMMAND.
Exercise 1
Write countdown.satl, which prints 3... 2... 1... liftoff! on a single line, using four displays.
Answer 1
(satellite)
satellite.capsule satellite.main()
{
satellite.console.display("3", end="... ")
satellite.console.display("2", end="... ")
satellite.console.display("1", end="... ")
satellite.console.display("liftoff!")
satellite.return(satellite)
}3... 2... 1... liftoff!More on this lesson: YOUR FIRST SATELLITE PROGRAM.
LESSON 2: VALUES AND TYPES
A variable is declared with its type, then its name, then its value. The type is the language’s, so it starts with satellite.. The name is yours.
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.number apples = 7
satellite.variable.number people = 2
satellite.console.display(apples / people)
satellite.console.display(apples % people)
satellite.console.display(2 ^ 100)
satellite.variable.float price = 19.99
satellite.console.display(price * 3)
satellite.console.display(7.0 / 2)
satellite.console.display(0.1 + 0.2 == 0.3)
satellite.variable.string name = "Ada"
satellite.console.display("Hello, " + name + "!")
satellite.console.display("apples: " + apples.to_string())
satellite.console.display(name.reverse())
satellite.variable.bool enough = apples > people
satellite.console.display(enough)
satellite.return(satellite)
}3
1
1267650600228229401496703205376
59.97
3.5
true
Hello, Ada!
apples: 7
adA
trueWhat to notice:
satellite.variable.numberis a whole number with no size limit but your memory. It never wraps around, so2 ^ 100is exact.^is power./between two whole numbers is whole-number division, so7 / 2is 3. Write7.0 / 2when you want 3.5.satellite.variable.floatis decimal all the way down, so0.1 + 0.2 == 0.3is true.- Every operator is written with a space on both sides.
n+3is refused, and so isn += 1: writen = n + 1. - A number added to text joins it as its digits:
"apples: " + applesprintsapples: 5, andapples + " apples"prints5 apples. Text that spells a number is added instead:4 + "2"is the number 6. Going the other way,"42".numberreads a number out of text. - A name keeps its type. A number name refuses a string, and a string name refuses a number.
satellite.variable.boolholdssatellite.bool.trueorsatellite.bool.false, and every comparison answers one.
SATELLITE has more kinds of value than most languages, each written the way you would read it:
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.percent tip = 15%
satellite.console.display(80 * tip)
satellite.console.display(80 + tip)
satellite.variable.binary flags = b0110
satellite.console.display(flags)
satellite.console.display(flags.number)
satellite.variable.hex mask = x00FF
satellite.console.display(mask.number)
satellite.variable.color sky = x87CEEB
satellite.console.display(sky)
satellite.variable.fraction third = 1/3
satellite.console.display(third < 1/2)
satellite.variable.infinity big = satellite.infinity()
satellite.console.display(big > 10 ^ 100)
satellite.return(satellite)
}12
92
b0110
6
255
x87CEEB
true
true80 + tip is 80 grown by 15%. A binary keeps the width you wrote it with, so b0110 shows its leading zero. A fraction is written with a touching slash, 1/3; with spaces, 1 / 3 is division. Each of these has a post of its own: NUMBERS, FLOATS, STRINGS, PERCENTAGES, BINARY AND HEXADECIMAL, COLORS, FRACTIONS, INFINITY and OPERATORS.
Exercise 2
A bill comes to 80 and you want to leave a 15% tip. Write tip.satl, which prints tip: 12 and then total: 92. Keep each amount in a number variable and convert it for the sentence.
Answer 2
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.number bill = 80
satellite.variable.percent rate = 15%
satellite.variable.number tip = bill * rate
satellite.variable.number total = bill + rate
satellite.console.display("tip: " + tip.to_string())
satellite.console.display("total: " + total.to_string())
satellite.return(satellite)
}tip: 12
total: 92LESSON 3: CAPSULES
A capsule is a function. Its parameters are written the way every declaration is, type then name. satellite.return(value) hands the answer back, from wherever it stands.
(satellite)
satellite.capsule satellite.main()
{
greet("world", 2)
satellite.variable.number n = twice(21)
satellite.console.display(n)
satellite.console.display(sign_of(-5))
satellite.return(satellite)
}
satellite.capsule greet(satellite.variable.string who, satellite.variable.number times)
{
satellite.variable.number i = 0
satellite.statement.while (i < times)
{
satellite.console.display("hello, " + who)
i = i + 1
}
}
satellite.capsule twice(satellite.variable.number n)
{
satellite.return(n * 2)
}
satellite.capsule sign_of(satellite.variable.number n)
{
satellite.statement.if (n < 0)
{
satellite.return("below zero")
}
satellite.return("zero or above")
}hello, world
hello, world
42
below zeroWhat to notice:
- The order capsules are declared in does not matter. main can come first.
- There are no globals in SATELLITE. A capsule runs in a frame of its own and cannot see the caller’s variables. What you hand it is all it has.
- Each argument is checked against its parameter’s type when the call is made. A float parameter wants a float, so pass
100.0, not100. - A capsule that answers nothing can leave out
satellite.return; reaching its closing brace ends it.
Namespaces and library values
Group capsules with satellite.namespace and call them through its name. When every capsule needs the same fixed value, write it once at the top of the file as satellite.library.name = value. Library values are written down, never worked out, and nothing can change them, so they are settings, not globals.
(satellite)
satellite.library.freezing = 32.0
satellite.namespace convert
{
satellite.capsule to_fahrenheit(satellite.variable.float celsius)
{
satellite.return(celsius * 9 / 5 + satellite.library.freezing)
}
}
satellite.capsule satellite.main()
{
satellite.console.display(convert.to_fahrenheit(100.0))
satellite.console.display(convert.to_fahrenheit(37.5))
satellite.console.display(convert.to_fahrenheit(-40.0))
satellite.return(satellite)
}212.0
99.5
-40.0Every file is a namespace too. satellite.include("shapes") at the top of your program lets you call that file’s capsules as shapes.area(2, 3). See INCLUDING FILES AND NAMESPACES and SATELLITE.LIBRARY.
The words your program was started with
main may take one parameter: the arguments variable. It holds the words after your file name on the command line, as args.argument_1, args.argument_2 and so on, and args.length counts them with the program itself included. The name args is yours to choose.
(satellite)
satellite.capsule satellite.main(satellite.variable.arguments args)
{
satellite.statement.if (args.length > 1)
{
satellite.console.display("hello, " + args.argument_1)
}
satellite.statement.else
{
satellite.console.display("hello, whoever you are")
}
satellite.return(satellite)
}hello, whoever you areRun with a word after the file name, it greets that word:
satl greet.satl Adahello, AdaThe same variable carries satl’s settings and facts about your machine. See THE ARGUMENTS VARIABLE.
Exercise 3
Write gcd(a, b), the greatest common divisor, by Euclid’s rule: when b is 0 the answer is a, and otherwise it is gcd(b, a % b). Print gcd(48, 18) and gcd(1071, 462).
Answer 3
A capsule may call itself. Here the call sits inside satellite.return(...), so it is an ordinary call: each level waits for the one below it to answer. Euclid’s rule needs only a few dozen levels even for very large numbers, so that is no trouble. When a capsule must go very deep, make its call to itself a line of its own and its last statement; satl runs that shape as a loop, however deep it goes.
(satellite)
satellite.capsule gcd(satellite.variable.number a, satellite.variable.number b)
{
satellite.statement.if (b == 0)
{
satellite.return(a)
}
satellite.return(gcd(b, a % b))
}
satellite.capsule satellite.main()
{
satellite.console.display(gcd(48, 18))
satellite.console.display(gcd(1071, 462))
satellite.console.display(gcd(2 ^ 80, 6 ^ 20))
satellite.return(satellite)
}6
21
1048576More on this lesson: CAPSULES.
LESSON 4: STATEMENTS AND LOOPS
SATELLITE has three statements for making decisions and repeating work: satellite.statement.if (with satellite.statement.else), satellite.statement.while and satellite.statement.for.
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.number temperature = 18
satellite.statement.if (temperature > 25)
{
satellite.console.display("hot")
}
satellite.statement.else satellite.statement.if (temperature > 15)
{
satellite.console.display("mild")
}
satellite.statement.else
{
satellite.console.display("cold")
}
satellite.variable.number fuel = 10
satellite.statement.while (fuel > 0)
{
fuel = fuel - 3
}
satellite.console.display(fuel)
satellite.statement.for(satellite.variable.number i = 3; i >= 1; i--)
{
satellite.console.display(i)
}
satellite.statement.for(satellite.variable.number i = 0; i <= 10; i + 5)
{
satellite.console.display(i)
}
satellite.return(satellite)
}mild
-2
3
2
1
0
5
10What to notice:
- A
forhas three parts separated by semicolons: a declaration, a condition, and a step. The step isi++,i--, or the counter and one operator with a space on both sides, such asi + 5ori * 2. You do not writei =in the step. - The counter belongs to the loop. Using
iafter the loop has ended is refused. - The body of an
ifor a loop shares the variables around it, sofuelchanged inside thewhileis still changed after it. - A condition is one comparison today.
&&and||are not built, so put oneifinside another, or chainelse ifas above. - There is no
break. To leave a loop early, put the loop in a capsule andsatellite.returnfrom inside it:
(satellite)
satellite.capsule first_over(satellite.container.list<satellite.variable.number> numbers, satellite.variable.number limit)
{
satellite.statement.for(satellite.variable.number i = 1; i <= numbers.size; i++)
{
satellite.statement.if (numbers[i] > limit)
{
satellite.return(numbers[i])
}
}
satellite.return(0)
}
satellite.capsule satellite.main()
{
satellite.console.display(first_over({72, 95, 88, 99}, 90))
satellite.console.display(first_over({1, 2, 3}, 90))
satellite.return(satellite)
}95
0Exercise 4
The classic: print the numbers from 1 to 15, but print Fizz for multiples of 3, Buzz for multiples of 5, and FizzBuzz for multiples of both.
Answer 4
(satellite)
satellite.capsule satellite.main()
{
satellite.statement.for(satellite.variable.number i = 1; i <= 15; i++)
{
satellite.statement.if (i % 15 == 0)
{
satellite.console.display("FizzBuzz")
}
satellite.statement.else satellite.statement.if (i % 3 == 0)
{
satellite.console.display("Fizz")
}
satellite.statement.else satellite.statement.if (i % 5 == 0)
{
satellite.console.display("Buzz")
}
satellite.statement.else
{
satellite.console.display(i)
}
}
satellite.return(satellite)
}1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzzMore on this lesson: IF, WHILE AND FOR.
LESSON 5: LISTS AND INDEXES
A list is written with braces anywhere a value goes. satellite.container.list<type> checks every item that goes in; with no <> a list holds anything. Lists count from 1, the way the lines of a file do.
(satellite)
satellite.capsule satellite.main()
{
satellite.container.list<satellite.variable.number> scores = {72, 95, 88}
scores.append(61)
satellite.console.display(scores)
satellite.console.display(scores[1])
satellite.console.display(scores.size)
satellite.console.display(scores.sum)
satellite.console.display(scores.max)
satellite.console.display(scores.contains(95))
satellite.console.display(scores.sort().by_value())
satellite.console.display(scores.sort().by_value().reverse())
satellite.console.display(scores.join(" + "))
satellite.container.list<satellite.variable.number> copy = scores
copy[1] = 0
satellite.console.display(scores[1])
satellite.container.list<satellite.container.list<satellite.variable.string>> board = {{"X", "O", "X"}, {" ", "X", "O"}, {"O", " ", "X"}}
board[2][1] = "O"
satellite.console.display(board[2].join("|"))
satellite.return(satellite)
}{72, 95, 88, 61}
72
4
316
95
true
{61, 72, 88, 95}
{95, 88, 72, 61}
72 + 95 + 88 + 61
72
O|X|OWhat to notice:
.append,.insert(n, x),.remove(x),.remove_at(n),.remove_last()and.clearchange the list..sort(),.reverse(),.sum,.max,.minand.joinanswer a new value and leave the list alone..sort()is always followed by.by_value()(smallest first) or.by_name()(by how each item reads).copy = scorescopies the list, so changing the copy leavesscoresalone. The copy costs nothing until one of them is written to.board[2][1]reaches into a list of lists, to read or to write.- Reading past the end, or
scores[0], stops the program with a report. - To walk a list, use
foranda[i], as in the last lesson.
Indexes: keys and values
satellite.container.index<key, value> files values under keys, like a dictionary in Python. Its keys come back in the order you put them in. A key can be a number, a string, a bool, a binary or a percentage. (satellite.container.map is a second word for the same container, and a map can be written whole: {"zoe": 30, "al": 4}.)
(satellite)
satellite.capsule satellite.main()
{
satellite.container.index<satellite.variable.string, satellite.variable.number> age
age["zoe"] = 30
age["al"] = 4
age["bo"] = 100
age["zoe"] = 31
satellite.console.display(age)
satellite.console.display(age["al"])
satellite.console.display(age.keys)
satellite.console.display(age.values.sum)
satellite.console.display(age.contains("bo"))
age.remove("al")
satellite.console.display(age.size)
satellite.return(satellite)
}{"zoe": 31, "al": 4, "bo": 100}
4
{"zoe", "al", "bo"}
135
true
2Reading a key that is not there stops the program rather than inventing an answer, so ask .contains(key) first when you are not sure.
Exercise 5
Here are the votes for the office drink: {"tea", "coffee", "tea", "water", "coffee", "tea", "juice"}. Count them into an index, print the index, then print the winner as tea wins with 3 votes.
Answer 5
(satellite)
satellite.capsule satellite.main()
{
satellite.container.list<satellite.variable.string> votes = {"tea", "coffee", "tea", "water", "coffee", "tea", "juice"}
satellite.container.index<satellite.variable.string, satellite.variable.number> counts
satellite.statement.for(satellite.variable.number i = 1; i <= votes.size; i++)
{
satellite.variable.string drink = votes[i]
satellite.statement.if (counts.contains(drink))
{
counts[drink] = counts[drink] + 1
}
satellite.statement.else
{
counts[drink] = 1
}
}
satellite.console.display(counts)
satellite.container.list<satellite.variable.string> drinks = counts.keys
satellite.variable.string best = drinks[1]
satellite.statement.for(satellite.variable.number i = 2; i <= drinks.size; i++)
{
satellite.statement.if (counts[drinks[i]] > counts[best])
{
best = drinks[i]
}
}
satellite.console.display(best + " wins with " + counts[best].to_string() + " votes")
satellite.return(satellite)
}{"tea": 3, "coffee": 2, "water": 1, "juice": 1}
tea wins with 3 votesMore on this lesson: LISTS, GRIDS: LISTS OF LISTS, MAPS and MULTIPLE, which lets one name hold any of several types.
LESSON 6: SPACESUITS
A spacesuit is SATELLITE’s class: a type you write yourself, with its own variables and capsules. (satellite.class is a second spelling of the same word.) It has three sections:
satellite.protectedholds its fields, and any capsules only its own capsules may call.satellite.constructor(...)runs when an object is made, after every field has its first value.satellite.publicholds the capsules anyone with an object may call.
(satellite)
satellite.spacesuit account()
{
satellite.protected
{
satellite.variable.string owner = ""
satellite.variable.number balance = 0
}
satellite.constructor(satellite.variable.string who, satellite.variable.number opening)
{
owner = who
balance = opening
}
satellite.public
{
satellite.capsule deposit(satellite.variable.number amount)
{
balance = balance + amount
}
satellite.capsule withdraw(satellite.variable.number amount)
{
satellite.statement.if (amount > balance)
{
satellite.return(satellite.bool.false)
}
balance = balance - amount
satellite.return(satellite.bool.true)
}
satellite.capsule summary()
{
satellite.return(owner + " has " + balance.to_string())
}
}
}
satellite.capsule pay_rent(account from)
{
from.withdraw(700)
}
satellite.capsule satellite.main()
{
account ada("Ada", 1000)
ada.deposit(250)
satellite.console.display(ada.summary())
satellite.console.display(ada.withdraw(5000))
pay_rent(ada)
satellite.console.display(ada.summary())
account same = ada
same.deposit(1)
satellite.console.display(ada.summary())
satellite.return(satellite)
}Ada has 1250
false
Ada has 550
Ada has 551What to notice:
- An object is made by declaring it: the spacesuit’s name, the object’s name, and the constructor’s arguments, as in
account ada("Ada", 1000). With no constructor arguments it is justaccount ada. - Inside its capsules a spacesuit’s fields are their bare names. There is no
this. - Fields are reached from inside the spacesuit only.
ada.balancefrom main is refused before anything runs; write a public capsule that answers it. - An object is a reference.
same = adanames the same account, andpay_rentchanged the caller’s account. (Numbers, strings and lists are values, and are copied.)
One spacesuit extending another
Name another spacesuit in the brackets to extend it. The new one gets its fields and capsules, and a capsule it declares again replaces the old one for its objects, even when the old spacesuit’s own capsules call it.
(satellite)
satellite.spacesuit pet()
{
satellite.protected
{
satellite.variable.string name = ""
}
satellite.constructor(satellite.variable.string given)
{
name = given
}
satellite.public
{
satellite.capsule sound()
{
satellite.return("...")
}
satellite.capsule describe()
{
satellite.return(name + " says " + sound())
}
}
}
satellite.spacesuit dog(pet)
{
satellite.public
{
satellite.capsule sound()
{
satellite.return("woof")
}
}
}
satellite.capsule satellite.main()
{
pet goldie("Goldie")
dog rex("Rex")
satellite.container.list<pet> all = {goldie, rex}
satellite.statement.for(satellite.variable.number i = 1; i <= all.size; i++)
{
satellite.console.display(all[i].describe())
}
satellite.return(satellite)
}Goldie says ...
Rex says woofdog has no constructor of its own, so rex("Rex") hands its argument to pet‘s. A list of pet can hold a dog.
Exercise 6
Write a spacesuit stack that keeps strings in a list, with push(item), pop(), which answers the last item pushed and takes it off, and size(). Push three plates, then pop two.
Answer 6
(satellite)
satellite.spacesuit stack()
{
satellite.protected
{
satellite.container.list<satellite.variable.string> items = {}
}
satellite.public
{
satellite.capsule push(satellite.variable.string item)
{
items.append(item)
}
satellite.capsule pop()
{
satellite.variable.string top = items.last
items.remove_last()
satellite.return(top)
}
satellite.capsule size()
{
satellite.return(items.size)
}
}
}
satellite.capsule satellite.main()
{
stack plates
plates.push("blue")
plates.push("green")
plates.push("red")
satellite.console.display(plates.size())
satellite.console.display(plates.pop())
satellite.console.display(plates.pop())
satellite.console.display(plates.size())
satellite.return(satellite)
}3
red
green
1More on this lesson: SPACESUITS and ONE SPACESUIT EXTENDING ANOTHER.
LESSON 7: FILES
In SATELLITE a text file is a list of lines, counting from 1. satellite.file.new(path) makes a new file and satellite.file.open(path) opens one that is already there. Both answer a satellite.variable.file, and the file’s methods are spelled like a list’s.
(satellite)
satellite.capsule satellite.main()
{
satellite.console.display(satellite.file.exists("shopping.txt"))
satellite.variable.file list = satellite.file.new("shopping.txt")
list.append("bread")
list.append("milk")
list.append("apples")
list.close()
satellite.console.display(satellite.file.exists("shopping.txt"))
satellite.variable.file again = satellite.file.open("shopping.txt")
satellite.console.display(again[2])
satellite.console.display(again.search("app"))
again.replace(2, "oat milk")
again.insert(1, "coffee")
satellite.statement.for(satellite.variable.number i = 1; i <= again.size; i++)
{
satellite.console.display(i.to_string() + ". " + again[i])
}
satellite.variable.file twice = satellite.file.new("shopping.txt")
satellite.console.display(twice.ok)
satellite.return(satellite)
}false
true
milk
3
1. coffee
2. bread
3. oat milk
4. apples
falseWhat to notice:
f[n]reads linenas a string..search(text)answers the number of the first line that contains the text, or 0..index_of(text)finds a line that is exactly the text..append,.insert(n, text),.replace(n, text),.remove_at(n)and friends change lines..replace("old", "new")given two strings changes text instead of a whole line.newnever replaces a file, andopennever makes one. When either cannot do its job, the program carries on and the handle’s.okis false;.errorsays why.- Changes reach the disk at
.close()or.save, and whole or not at all. You cannot lose work by forgetting: when the program ends, satl saves and closes every file still open. - A path without a leading
/is taken from the folder your program is in, not from where you ransatl. - Reading a line that is not there stops the program with a report, as a list does.
Exercise 7
Write the squares of 1 to 5 into squares.txt, one to a line, and close it. Then open it again, add up every line (each line is text, so read it back with .number), and print 5 lines, adding up to 55.
Answer 7
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.file out = satellite.file.new("squares.txt")
satellite.statement.for(satellite.variable.number i = 1; i <= 5; i++)
{
satellite.variable.number square = i * i
out.append(square.to_string())
}
out.close()
satellite.variable.file back = satellite.file.open("squares.txt")
satellite.variable.number total = 0
satellite.statement.for(satellite.variable.number line = 1; line <= back.size; line++)
{
total = total + back[line].number
}
satellite.console.display(back.size.to_string() + " lines, adding up to " + total.to_string())
satellite.return(satellite)
}5 lines, adding up to 55More on this lesson: FILES.
LESSON 8: THREADS
A thread runs a capsule at the same time as the rest of your program. It takes three steps:
satellite.thread.new(capsule(arguments))works out the arguments now and makes the thread. It does not run the capsule yet..start()runs the capsule on a thread of its own and returns at once..join()waits for it to finish and answers what the capsule handed back..wait()is a second name for.join().
(satellite)
satellite.capsule add_up(satellite.variable.number from, satellite.variable.number to)
{
satellite.variable.number total = 0
satellite.statement.for(satellite.variable.number i = from; i <= to; i++)
{
total = total + i
}
satellite.return(total)
}
satellite.capsule satellite.main()
{
satellite.variable.thread low = satellite.thread.new(add_up(1, 50000))
satellite.variable.thread high = satellite.thread.new(add_up(50001, 100000))
satellite.console.display(low)
low.start()
high.start()
satellite.variable.number total = low.join() + high.join()
satellite.console.display(total)
satellite.console.display(low)
satellite.return(satellite)
}(thread add_up, not started)
5000050000
(thread add_up, finished)Displaying a thread tells you which capsule it runs and how far it has got.
What a thread shares
Numbers, strings and lists are values: a thread gets its own copy. An object of a spacesuit is a reference, so a thread given one works on the same object as main.
(satellite)
satellite.spacesuit box()
{
satellite.protected
{
satellite.variable.number held = 0
}
satellite.public
{
satellite.capsule put(satellite.variable.number n)
{
held = n
}
satellite.capsule get()
{
satellite.return(held)
}
}
}
satellite.capsule change_both(satellite.container.list<satellite.variable.number> numbers, box b)
{
numbers.append(4)
b.put(99)
}
satellite.capsule satellite.main()
{
satellite.container.list<satellite.variable.number> numbers = {1, 2, 3}
box b
satellite.variable.thread t = satellite.thread.new(change_both(numbers, b))
t.start()
t.join()
satellite.console.display(numbers)
satellite.console.display(b.get())
satellite.return(satellite)
}{1, 2, 3}
99Stopping a thread
.stop() asks a thread to stop at its next statement. It never cuts a statement in half. After a stop, .join() answers nothing.
(satellite)
satellite.capsule forever()
{
satellite.variable.number spins = 0
satellite.statement.while (spins >= 0)
{
spins = spins + 1
}
}
satellite.capsule satellite.main()
{
satellite.variable.thread spinner = satellite.thread.new(forever())
spinner.start()
spinner.stop()
spinner.join()
satellite.console.display(spinner)
satellite.return(satellite)
}(thread forever, stopped)Three more things worth knowing:
- When main reaches
satellite.return(satellite), every thread still running is asked to stop and is waited for. A thread started on the last line may never get to run, so join the threads whose work you need. - When several threads display at once, their lines may come in any order, but every line comes out whole.
- A refusal on a thread is reported with its own line and caret, and
.join()then stops the program with the same code.
Exercise 8
Count the numbers from 1 to 60,000 that are divisible by 3 or by 7. Split the work over three threads of 20,000 numbers each, add up what they answer, and check it against one plain call. (Remember that || is not built: use an else if.)
Answer 8
(satellite)
satellite.capsule count_in(satellite.variable.number from, satellite.variable.number to)
{
satellite.variable.number found = 0
satellite.statement.for(satellite.variable.number i = from; i <= to; i++)
{
satellite.statement.if (i % 3 == 0)
{
found = found + 1
}
satellite.statement.else satellite.statement.if (i % 7 == 0)
{
found = found + 1
}
}
satellite.return(found)
}
satellite.capsule satellite.main()
{
satellite.variable.thread first = satellite.thread.new(count_in(1, 20000))
satellite.variable.thread second = satellite.thread.new(count_in(20001, 40000))
satellite.variable.thread third = satellite.thread.new(count_in(40001, 60000))
first.start()
second.start()
third.start()
satellite.variable.number found = first.join() + second.join() + third.join()
satellite.console.display(found)
satellite.console.display(count_in(1, 60000))
satellite.return(satellite)
}25714
25714More on this lesson: THREADS.
LESSON 9: LOCKS
When several threads write to one object at the same time, their writes can land on top of each other. Four threads that each add 1 to a shared counter 5,000 times should reach 20,000. Without a lock they come up short, and by a different amount each run: two runs of the program below with its votes.lock() line taken out gave 11,719 and 11,520.
The fix is one line. Every object has a lock, and it starts off. .lock() turns it on, and from then on each statement that writes to the object holds the lock for that one statement.
(satellite)
satellite.spacesuit tally()
{
satellite.protected
{
satellite.variable.number count = 0
}
satellite.public
{
satellite.capsule add_one()
{
count = count + 1
}
satellite.capsule total()
{
satellite.return(count)
}
}
}
satellite.capsule count_to(tally t, satellite.variable.number times)
{
satellite.statement.for(satellite.variable.number i = 1; i <= times; i++)
{
t.add_one()
}
}
satellite.capsule satellite.main()
{
tally votes
votes.lock()
satellite.variable.thread a = satellite.thread.new(count_to(votes, 5000))
satellite.variable.thread b = satellite.thread.new(count_to(votes, 5000))
satellite.variable.thread c = satellite.thread.new(count_to(votes, 5000))
satellite.variable.thread d = satellite.thread.new(count_to(votes, 5000))
a.start()
b.start()
c.start()
d.start()
a.join()
b.join()
c.join()
d.join()
satellite.console.display(votes.total())
satellite.return(satellite)
}20000How the lock behaves:
- Turning the lock on locks nothing by itself. A statement that writes the object holds the lock alone, for that one statement: assigning one of its fields, calling a method on one of its fields (such as
items.append(x)), or calling one of its capsules. - A statement that only reads the object waits while a write is happening, and never waits for another read.
- An
if,whileorforholds the lock for its condition only, never for its body. - A file has a lock too, and
f.lock()turns it on..unlock()turns either one off again. - Until
.lock()is called, nothing is ever locked for an object, and threads share it freely. - If two threads end up waiting for each other, for example two objects locked in opposite orders, satl stops the line with S728 WAIT_NEVER_ENDS instead of freezing.
Exercise 9
Write a spacesuit guest_book with a list of names, a sign(name) capsule and a count() capsule. Start four threads that each sign it 250 times with a different name, wait for all four, and print the count.
Answer 9
(satellite)
satellite.spacesuit guest_book()
{
satellite.protected
{
satellite.container.list<satellite.variable.string> names = {}
}
satellite.public
{
satellite.capsule sign(satellite.variable.string name)
{
names.append(name)
}
satellite.capsule count()
{
satellite.return(names.size)
}
}
}
satellite.capsule visit(guest_book book, satellite.variable.string who)
{
satellite.statement.for(satellite.variable.number i = 1; i <= 250; i++)
{
book.sign(who)
}
}
satellite.capsule satellite.main()
{
guest_book book
book.lock()
satellite.variable.thread a = satellite.thread.new(visit(book, "Ada"))
satellite.variable.thread b = satellite.thread.new(visit(book, "Grace"))
satellite.variable.thread c = satellite.thread.new(visit(book, "Alan"))
satellite.variable.thread d = satellite.thread.new(visit(book, "Edsger"))
a.start()
b.start()
c.start()
d.start()
a.wait()
b.wait()
c.wait()
d.wait()
satellite.console.display(book.count())
satellite.return(satellite)
}1000Leave the lock on for a list that several threads append to. Appending to an unlocked list from several threads at once can crash satl.
More on this lesson: LOCKS.
LESSON 10: READING AN ERROR REPORT
When something goes wrong, satl stops and prints a critical error report. It is worth learning to read one, because it tells you almost everything. Here is a program with a typing mistake:
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.number total = 10
satellite.console.display("adding up")
satellite.console.display(totl + 5)
satellite.return(satellite)
}--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S201: NAME_NOT_DECLARED
satl(check): in satellite.main, totl has no satellite.variable line declaring it
directory: typo.satl:7
syntax: satellite.console.display(totl + 5)
/\
this name was used and no satellite.variable line ever declared it. A name has
to be given a type before it can hold anything.
machine code 25 name_not_declared -- satl exits with this.
--------------------------------------------------------------------------------Read it from the top:
S201: NAME_NOT_DECLAREDis the S-code and its name. The number says how bad it is: S0xx is a warning and the run carries on, and the numbers climb from there.satl(check): in satellite.main, ...says what went wrong, in a sentence.checkmeans satl found it while checking the program, before anything ran, which is whyadding upwas never printed. A report that sayssatl(run)was found while the program was running, after the lines before it had done their work.directory: typo.satl:7is the file and the line number.syntax:quotes that line, and the caret/\points into it.- The paragraph after it explains the kind of mistake in general.
machine code 25is the exit status. satl exits 0 when a program runs to its end, and with the machine code when it stops, so a script can test it. The report goes to standard error, like the title block, so2> /dev/nullhides both:
satl typo.satl 2> /dev/null
echo $?25Here is a mistake found while running. The program printed start before it reached line 7:
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.number n = 4
satellite.console.display("start")
satellite.console.display(n+3)
satellite.return(satellite)
}--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S110: LINE_NOT_UNDERSTOOD
satl(run): every math operation is written with a space on both sides of the
sign, and this + has none
directory: spaces.satl:7
syntax: satellite.console.display(n+3)
/\
satellite has no scenario for this line -- it is spelled in a shape the language
does not have a meaning for.
machine code 13 satl_line_not_understood -- satl exits with this.
--------------------------------------------------------------------------------A few refusals are still reported on a single line that starts with [satellite], with the machine code in brackets at its end. A value of the wrong type handed to a capsule is one of them:
(satellite)
satellite.capsule double(satellite.variable.number n)
{
satellite.return(n * 2)
}
satellite.capsule satellite.main()
{
satellite.console.display(double(21))
satellite.console.display(double("21"))
satellite.return(satellite)
}[satellite] satl(run): double's n was declared satellite.variable.number, and it holds a string (machine_code: 27 types_do_not_meet)The first call printed 42; the second stopped the program with machine code 27.
Exercise 10
This program should print the average of three test scores. Run it, read the report, and fix it.
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.number total = 72 + 95 + 88
satellite.console.dispaly("average: " + total / 3)
satellite.return(satellite)
}--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S110: LINE_NOT_UNDERSTOOD
satl(check): in satellite.main, satellite.console has no word named dispaly --
did you mean satellite.console.display?
directory: average.satl:6
syntax: satellite.console.dispaly("average: " + total / 3)
/\
satellite has no scenario for this line -- it is spelled in a shape the language
does not have a meaning for.
machine code 13 satl_line_not_understood -- satl exits with this.
--------------------------------------------------------------------------------Answer 10
The report says satellite.console has no word named dispaly, and asks whether you meant satellite.console.display. You did: the two letters are the wrong way round. Nothing ran, because satl checks every word before it starts. Spell it right:
(satellite)
satellite.capsule satellite.main()
{
satellite.variable.number total = 72 + 95 + 88
satellite.console.display("average: " + total / 3)
satellite.return(satellite)
}average: 85More on this lesson: READING AN ERROR REPORT.
WHERE TO GO NEXT
You have now used every part of the language that most programs need. From here:
- The FULL REFERENCE has every word SATELLITE has, in one place.
- WINDOWS shows how a program opens a window of its own, and READING INPUT how it asks a person to type a line.
- THE TERMINAL colours the whole terminal your program runs in.
- THE HELP SYSTEM explains the language’s own help topics, and HISTORY AND ACCESS covers two of its settings.
- HOW SATELLITE 004 RUNS looks under the hood.
- To build
satlyourself from its source, see BUILD. Licences are listed on the LICENSES page.
