Author: Claude Code

IF, WHILE AND FOR 0

IF, WHILE AND FOR

A program that only runs from top to bottom can only ever do one thing. The statements let it choose and repeat: satellite.statement.if chooses between paths, satellite.statement.while repeats while something is true, and satellite.statement.for counts its way through a loop. satellite.statement.else goes with an if. Those four are the whole family today.

Each one takes a condition in brackets, something that is true or false, and a body between { and }. A comparison such as n > 0 or name == "Ada" is the usual condition. See OPERATORS: LOGIC AND BITS for all of them.

if, else, and else if

satellite.statement.if runs its body only when its condition is true. A satellite.statement.else straight after it runs when the condition was false. Put another if straight after an else to test one more condition, as many times as you need.

weather.satlSATELLITE 004 Download
Choosing between three paths
satellite.include(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.bool raining = satellite.bool.false
    satellite.statement.if (!raining)
    {
        satellite.console.display("no umbrella today")
    }
    satellite.return(satellite)
}
OUTPUT
mild
no umbrella today

The first test that is true wins, and the rest are skipped. raining is a satellite.variable.bool, which holds satellite.bool.true or satellite.bool.false, and ! turns true into false and false into true.

A condition has to be true or false. satellite.statement.if (lives), where lives is a number, is refused when it runs: “satellite.statement.if was given a number and needs a true or false”. Write what you mean instead, such as lives > 0.

while

satellite.statement.while runs its body again and again, for as long as its condition is true. The condition is checked before every turn, so if it is false at the start the body never runs at all. Something in the body has to change, or the loop never ends.

doubling.satlSATELLITE 004 Download
Repeat while a condition holds
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.number grains = 1
    satellite.variable.number square = 1
    satellite.statement.while (grains < 1000)
    {
        satellite.console.display("square " + square.string + ": " + grains.string)
        grains = grains * 2
        square = square + 1
    }
    satellite.return(satellite)
}
OUTPUT
square 1: 1
square 2: 2
square 3: 4
square 4: 8
square 5: 16
square 6: 32
square 7: 64
square 8: 128
square 9: 256
square 10: 512

for

satellite.statement.for puts the three parts of a counting loop in one place, divided by semicolons:

  1. a declaration, which makes the counter: satellite.variable.number i = 0
  2. a condition, checked before every turn: i < 3
  3. a step, which moves the counter after every turn

The step is written without an =, because the counter’s own name is already in front of it: i + 1 means “i becomes i + 1”. Any of + - * / % ^ works, and i++ and i-- are short for adding and taking away one. The step may also be left empty, when the body moves the counter itself.

counting.satlSATELLITE 004 Download
Three ways to step a for loop
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.statement.for (satellite.variable.number i = 0; i < 3; i++)
    {
        satellite.console.display(i)
    }
    satellite.statement.for (satellite.variable.number i = 10; i > 0; i - 4)
    {
        satellite.console.display(i)
    }
    satellite.statement.for (satellite.variable.number i = 1; i <= 1000; i * 10)
    {
        satellite.console.display(i)
    }
    satellite.return(satellite)
}
OUTPUT
0
1
2
10
6
2
1
10
100
1000

The counter lives only as long as its loop. After the loop it is gone, which is why the same name i can be declared again by the next loop. A body shares every other variable with the lines around it, so a loop can add to a total declared before it.

If you are used to other languages you may write the step as i = i + 1. satl catches that before the program runs, and says how the step is written:

old_habit.satlSATELLITE 004 Download
A for's step has no = in it
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.statement.for (satellite.variable.number i = 0; i < 3; i = i + 1)
    {
        satellite.console.display(i)
    }
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S110: LINE_NOT_UNDERSTOOD
satl(check): in satellite.main, satellite.statement.for's step does not move i
-- a for's step is i and one of + - * / % ^ ** with a space on both sides, or
i++ or i--

directory: old_habit.satl:5
syntax: satellite.statement.for (satellite.variable.number i = 0; i < 3; i = i + 1)
        /\

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.

--------------------------------------------------------------------------------

Leaving a loop early

There is no break or continue in satellite today, and satl refuses both. To leave a loop early, put the loop in a capsule and use satellite.return, which ends the capsule from wherever it stands, even from deep inside a loop. See CAPSULES: SATELLITE FUNCTIONS.

Conditions cannot yet be joined with “and” or “or” (&& and || are refused today). For “and”, put one if inside another. For “or”, test each condition in turn with else if.

Where next

The FULL REFERENCE has every statement’s rules. Comparisons and ! are in OPERATORS: LOGIC AND BITS, and loops over the items of a list are in LISTS.

1

OPERATORS: LOGIC AND BITS

An operator is a sign that works on the values beside it: + adds, < compares, ! turns true into false. This post gathers satellite’s operators in one place. It shows what each one does today, and what satl says when you reach for one that is not built yet.

One rule covers every operator that sits between two values: write a space on both sides. 4 + 3 works, and 4+3 is refused.

Arithmetic

These work on numbers, and on the other number types where their own posts say so:

  • + - * / %: add, take away, multiply, divide, remainder
  • ^ raises to a power, and ** is its second spelling

/ between two whole numbers keeps the whole part, so 7 / 2 is 3. NUMBERS has the details.

Comparison

Six operators compare two values, and each one answers true or false:

  • == equal, and != not equal
  • < less than, and > greater than
  • <= less than or equal, and >= greater than or equal

They compare values of the same kind, and a few kinds that belong together: a whole number with a float, and a binary or hex with a number, by what they are worth. Strings put themselves in order with < and > too. A true or false value, and a color, have only == and !=. Two kinds that do not belong together, such as a number and a string, are refused rather than guessed at.

compare.satlSATELLITE 004 Download
Comparisons answer true or false
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.number age = 12
    satellite.console.display(age >= 12)
    satellite.console.display(age != 12)
    satellite.console.display(3 < 3.5)
    satellite.console.display(1 == 1.0)
    satellite.console.display(b1010 == xA)
    satellite.console.display("apple" < "banana")
    satellite.variable.bool grown_up = age > 17
    satellite.console.display(grown_up)
    satellite.console.display(grown_up == satellite.bool.false)
    satellite.return(satellite)
}
OUTPUT
true
false
true
true
true
true
false
true

A comparison’s answer can be kept in a satellite.variable.bool, as grown_up is, and used later in an if or a while.

Logic: not, and, or

! in front of a true or false value turns it round: !(3 >= 4) is true. It works only on a true or false, so !5 is refused.

The two other logic operators, && (and) and || (or), are not built yet. Until they are, “and” is one if inside another, and “or” is a series of tests where any one can make a value true:

ticket.satlSATELLITE 004 Download
Not, and an "and" and an "or" written with if
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.number age = 70
    satellite.variable.bool has_ticket = satellite.bool.true

    satellite.statement.if (!has_ticket)
    {
        satellite.console.display("buy a ticket first")
    }

    // and: both must be true
    satellite.statement.if (has_ticket)
    {
        satellite.statement.if (age >= 18)
        {
            satellite.console.display("welcome aboard")
        }
    }

    // or: either one is enough
    satellite.variable.bool discount = satellite.bool.false
    satellite.statement.if (age < 16)
    {
        discount = satellite.bool.true
    }
    satellite.statement.if (age >= 65)
    {
        discount = satellite.bool.true
    }
    satellite.statement.if (discount)
    {
        satellite.console.display("half price")
    }
    satellite.return(satellite)
}
OUTPUT
welcome aboard
half price

Bits

The bit operators, & (and), | (or), ~ (not), << (shift left) and >> (shift right), are not built yet, and neither are the methods .and(), .or() and .xor(). satl refuses each of them today. Here is .and(), refused before the program starts:

bits.satlSATELLITE 004 Download
Bit operations are not built yet
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.binary mask = b1100
    satellite.console.display(mask.and(b1010))
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S110: LINE_NOT_UNDERSTOOD
satl(check): in satellite.main, mask is satellite.variable.binary, and and is
not one of its methods -- only an object of a satellite.spacesuit has capsules
to call

directory: bits.satl:6
syntax: satellite.console.display(mask.and(b1010))
        /\

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.

--------------------------------------------------------------------------------

What does work on bits today is reading them: a binary’s .number, .hex and .reverse(), and a number’s .binary. See BINARY AND HEXADECIMAL.

What + does with each kind

+ means something different for each kind of value, and satellite never converts one kind into another behind your back. When two kinds do not go together, + is refused, and for text and a number the report names the conversion to write.

plus.satlSATELLITE 004 Download
One sign, several meanings
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.number score = 87
    satellite.console.display(40 + 2)
    satellite.console.display("sun" + "flower")
    satellite.console.display(1.5 + 2)
    satellite.console.display(b1100 + xFF)
    satellite.console.display(200 + 50%)
    satellite.console.display(50% + 25%)
    satellite.console.display("score: " + score.string)
    satellite.return(satellite)
}
OUTPUT
42
sunflower
3.5
267
300
75%
score: 87

Two numbers add, and two strings join. A float and a whole number add to a float. A binary and a hex add by what they are worth and answer a number. A number grows by a percentage, and two percentages add to a percentage. To put a number into text, say so with .string: "score: " + score on its own is refused with S301.

Where next

The FULL REFERENCE lists every operator. Conditions at work are in IF, WHILE AND FOR, and text and conversions are in STRINGS.

0

LISTS

A list is a row of things, kept in the order you put them in: a shopping list, the scores in a game, the names of everyone at the table. In satellite a list is a satellite.container.list, and it is one of the first containers worth learning, because almost every program has something to keep in a row.

Making a list

You write a list with braces, wherever a value can go. {1, 2, 3} is a list of three numbers, {"bread", "milk"} is a list of two strings, and {} is a list with nothing in it yet. satellite.container.list() is the same empty list, written in words.

What goes between < and > says what the list may hold. satellite.container.list<satellite.variable.string> holds only strings, and satl checks every item that goes in. A list with no <> holds anything, even a mix of numbers and text.

shopping.satlSATELLITE 004 Download
Make a list, add to it, and read it back
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.variable.string> shopping = {"bread", "milk"}
    shopping.append("eggs")
    shopping.insert(1, "tea")
    satellite.console.display(shopping)
    satellite.console.display(shopping[1])
    satellite.console.display(shopping.size)
    satellite.console.display(shopping.contains("milk"))
    shopping.remove("milk")
    shopping[3] = "duck eggs"
    satellite.console.display(shopping)
    satellite.return(satellite)
}
OUTPUT
{"tea", "bread", "milk", "eggs"}
tea
4
true
{"tea", "bread", "duck eggs"}

A few things to notice:

  • Lists count from 1. shopping[1] is the first item, the way the first line of a page is line 1. There is no item 0.
  • .append(x) adds to the end, and .insert(n, x) puts an item at position n and moves the rest along.
  • shopping[3] = "duck eggs" replaces item 3. Writing past the end does not make a list longer: .append does that.
  • .remove(x) takes out an item by what it is. .remove_at(n) takes one out by its position, and .remove_first(), .remove_last(), .truncate(n) and .clear() do what their names say.

Adding it up

A list of numbers can answer questions about itself. .sum adds every item, .max and .min find the largest and the smallest, and .join(separator) makes one string from every item with the separator in between.

scores.satlSATELLITE 004 Download
Sum, largest, smallest, join and sort
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.variable.number> scores = {30, 4, 100}
    satellite.console.display(scores.sum)
    satellite.console.display(scores.max)
    satellite.console.display(scores.min)
    satellite.console.display(scores.join(" + "))
    satellite.console.display(scores.sort().by_value())
    satellite.console.display(scores.sort().by_value().reverse())
    satellite.console.display(scores)

    satellite.container.list<satellite.variable.string> names = {"zoe", "al", "bo"}
    satellite.console.display(names.join(", "))
    satellite.console.display(names.max)
    satellite.console.display(names.sort().by_name())

    satellite.container.list<satellite.variable.number> nothing = {}
    satellite.console.display(nothing.sum)
    satellite.return(satellite)
}
OUTPUT
134
100
4
30 + 4 + 100
{4, 30, 100}
{100, 30, 4}
{30, 4, 100}
zoe, al, bo
zoe
{"al", "bo", "zoe"}
0

.sort() on its own does nothing: follow it with .by_value(), smallest first, or .by_name(), which orders by how each item reads. Put .reverse() after either to go the other way. Sorting hands you a new list and leaves the old one alone, which is why scores still reads {30, 4, 100} at the end. A list of nothing sums to 0.

.sum adds numbers. On a list of strings satl refuses it and points you to .join, which is the way to put text together.

.reserve(n) is for when you know a list is about to grow large: it makes room for n items at once, so the list does not have to keep finding more room as you append. It changes no item, and the list’s size stays what it was.

Going through a list, one item at a time

satellite 004 has no “for each” loop yet, so you walk a list with a counting loop, satellite.statement.for, and read each item by its number. Count from 1 up to and including .size.

register.satlSATELLITE 004 Download
Walk a list with a for loop
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.variable.string> class_register = {"ann", "bo", "cy"}
    satellite.statement.for(satellite.variable.number i = 1; i <= class_register.size; i++)
    {
        satellite.console.display(i.to_string() + ". " + class_register[i])
    }

    satellite.container.list copy = class_register
    copy.append("dee")
    satellite.console.display(class_register.size)
    satellite.console.display(copy.size)
    satellite.return(satellite)
}
OUTPUT
1. ann
2. bo
3. cy
3
4

The second half shows something important: copy = class_register copies the list. Changing the copy leaves the original alone. The copy costs nothing until one of the two is changed, so you never pay for a copy you do not use.

When you read past the end

Ask for an item a list does not have and satl stops, names the list, and says how many items it really holds.

past_the_end.satlSATELLITE 004 Download
Item 3 of a two-item list is refused
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.variable.number> scores = {7, 9}
    satellite.console.display(scores[3])
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S501: LINE_PAST_THE_END
satl(run): scores[3]: there is no such item -- the list holds 2 items, counting
from 1

directory: past_the_end.satl:6
syntax: satellite.console.display(scores[3])
                                        /\

a line was read by its number and the file has no line with that number. Lines
count from 1, and the last one is the file's size.
machine code 47 line_past_the_end -- satl exits with this.

--------------------------------------------------------------------------------

The second line of the report is the one to read: it names scores[3] and says the list holds 2 items. The paragraph under the caret is written for files, which count their lines from 1 the same way. scores[0] is refused the same way, because there is no item 0.

Where next

Every list word is in the FULL REFERENCE. A list can hold other lists, which makes a grid: see GRIDS: LISTS OF LISTS. To look things up by a name instead of a number, see MAPS.

0

GRIDS: LISTS OF LISTS

A list can hold anything, and that includes other lists. A list whose items are lists is a grid: rows and columns, like a spreadsheet, a times table, a crossword or a game board. satellite reaches into one with two numbers, grid[x][y].

Your first grid

Write a grid the way you write any list, with braces, and put a list at each position: {{1, 2, 3}, {4, 5, 6}} is a list of two rows, and each row holds three numbers.

To say what it holds, put one list type inside another: satellite.container.list<satellite.container.list<satellite.variable.number>> is a list of lists of numbers, and satl checks every number that goes in.

first_grid.satlSATELLITE 004 Download
Read and write one square of a grid
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.container.list<satellite.variable.number>> grid = {{1, 2, 3}, {4, 5, 6}}
    satellite.console.display(grid)
    satellite.console.display(grid[2])
    satellite.console.display(grid[2][3])

    grid[1][2] = 20
    grid[2].append(7)
    grid.append({8})
    satellite.console.display(grid)
    satellite.console.display(grid.size)
    satellite.console.display(grid[2].size)
    satellite.console.display(grid[2].sum)
    satellite.return(satellite)
}
OUTPUT
{{1, 2, 3}, {4, 5, 6}}
{4, 5, 6}
6
{{1, 20, 3}, {4, 5, 6, 7}, {8}}
3
4
22

Read grid[2][3] from left to right:

  1. grid[2] is the second item of the grid, which is the row {4, 5, 6}.
  2. [3] is the third item of that row, which is 6.

Everything a list can do, a row can do too. grid[2].append(7) adds to the end of row 2, grid[2].sum adds up row 2, and grid.append({8}) adds a whole new row. Rows do not have to be the same length: after these lines the grid has rows of 3, 4 and 1 items. Like every list, a grid counts from 1.

Building a grid with loops

Most grids are built rather than typed. Start with an empty list, add an empty row, then fill the row. Two satellite.statement.for loops, one inside the other, do the whole job: the outer loop makes the rows, and the inner loop fills each one.

times_table.satlSATELLITE 004 Download
A four-by-four times table, built by two loops
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.container.list<satellite.variable.number>> table = satellite.container.list()
    satellite.statement.for(satellite.variable.number row = 1; row <= 4; row++)
    {
        table.append(satellite.container.list())
        satellite.statement.for(satellite.variable.number column = 1; column <= 4; column++)
        {
            table[row].append(row * column)
        }
    }

    satellite.statement.for(satellite.variable.number row = 1; row <= table.size; row++)
    {
        satellite.console.display(table[row].join("  "))
    }
    satellite.console.display(table[3][4])
    satellite.return(satellite)
}
OUTPUT
1  2  3  4
2  4  6  8
3  6  9  12
4  8  12  16
12

satellite.container.list() is an empty list, the same as {}. table[row].join(" ") turns one row into one line of text, with two spaces between the numbers, so the grid prints as a table.

A grid of words: a game board

A grid can hold text just as well. Here is a noughts and crosses board, three rows of three squares, where "." is an empty square.

board.satlSATELLITE 004 Download
A noughts and crosses board
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.container.list<satellite.variable.string>> board = {{".", ".", "."}, {".", ".", "."}, {".", ".", "."}}
    board[2][2] = "X"
    board[1][3] = "O"
    board[3][1] = "X"

    satellite.statement.for(satellite.variable.number row = 1; row <= 3; row++)
    {
        satellite.console.display(board[row].join(" "))
    }
    satellite.console.display(board[2].contains("X"))
    satellite.console.display(board[2].index_of("X"))

    satellite.container.list<satellite.container.list<satellite.variable.string>> saved = board
    saved[1][1] = "O"
    satellite.console.display(board[1].join(" "))
    satellite.console.display(saved[1].join(" "))
    satellite.return(satellite)
}
OUTPUT
. . O
. X .
X . .
true
2
. . O
O . O

board[2].index_of("X") answers the position of the first "X" in row 2, or 0 when there is none.

The last four lines show that a grid is a value, like every list: saved = board makes a copy of the whole grid, rows and all. Changing a square of saved leaves board as it was. Copying costs nothing until one of them is changed.

Squares that are not there

Writing to a square past the end of a row does not make the row longer, and satl stops with a report that names the square and says how many items the row really holds. Use .append to grow a row.

outside.satlSATELLITE 004 Download
Writing past the end of a row is refused
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.container.list<satellite.variable.number>> grid = {{1, 2}, {3, 4}}
    grid[2][3] = 9
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S501: LINE_PAST_THE_END
satl(run): in grid[...] = ..., grid[2][3] = ...: there is no such item -- the
list holds 2 items, counting from 1. Writing past the end does not make the list
longer

directory: outside.satl:6
syntax: grid[2][3] = 9
            /\

a line was read by its number and the file has no line with that number. Lines
count from 1, and the last one is the file's size.
machine code 47 line_past_the_end -- satl exits with this.

--------------------------------------------------------------------------------

Where next

The FULL REFERENCE lists every list word, and every one of them works on a row. For the basics of a single list, see LISTS. Lists can sit inside other containers too, such as a list filed under a name: see MAPS.

0

MAPS

A list finds things by their position: item 1, item 2, item 3. Often you want to find something by a name instead: a phone number by a person’s name, a price by the name of the thing, a score by the player. A container that files each value under a key is called a map, or a dictionary.

In satellite it is satellite.container.map. It keeps its keys in the order you put them in, the way a Python dictionary does, so a map prints back in the order you filled it. The same container has a second word, satellite.container.index: either word declares it, and you will see index in some older examples.

Filing and finding

Declare a map with the type of its keys and the type of its values between < and >. Then age["zoe"] = 30 files 30 under the key "zoe", and age["zoe"] reads it back. Writing to a key that is already there changes its value and does not move it.

birthdays.satlSATELLITE 004 Download
Put keys in, read them, change one, take one out
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.map<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)
    satellite.console.display(age.contains("bo"))
    satellite.console.display(age.values.sum)
    satellite.console.display(age.keys.join(", "))

    age.remove("al")
    satellite.console.display(age.size)
    satellite.console.display(age.first)
    satellite.console.display(age.last)
    satellite.return(satellite)
}
OUTPUT
{"zoe": 31, "al": 4, "bo": 100}
4
{"zoe", "al", "bo"}
{31, 4, 100}
true
135
zoe, al, bo
2
zoe
bo

What each of those does:

  • age.keys and age.values answer two lists, in the same order, so the first key goes with the first value.
  • .contains(key) answers true or false. Ask it before you read a key you are not sure of.
  • .values.sum and .keys.join(", ") are list words used on those lists. On a map you say which half you mean.
  • .remove(key) takes a key and its value out. .size, .empty and .clear() work as they do on a list.
  • .first and .last are the oldest and the newest key. .remove_first() and .remove_last() take those out.

A map has keys, not positions, so the list words that need a position, such as .append, .insert and .index_of, are refused on one.

Writing a map whole

Braces make a map when each entry is a key, a : and a value: {"zoe": 30, "al": 4}. That is the same form satl prints a map in, so what you see is what you can write. A key written twice keeps its first place and its last value. An empty map is a declaration with nothing after it, or satellite.container.map(); {} on its own is an empty list.

prices.satlSATELLITE 004 Download
A map written in one line, and an empty one filled later
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.map<satellite.variable.string, satellite.variable.number> price = {"tea": 3, "cake": 5, "tea": 4}
    satellite.console.display(price)
    satellite.console.display(price["tea"])

    satellite.container.map<satellite.variable.number, satellite.variable.string> rooms = satellite.container.map()
    rooms[101] = "Ada"
    rooms[102] = "Grace"
    satellite.console.display(rooms)
    satellite.return(satellite)
}
OUTPUT
{"tea": 4, "cake": 5}
4
{101: "Ada", 102: "Grace"}

What can be a key

A key must be something that cannot change once it is filed: a number, a string, a bool, a binary or a percentage. A list can never be a key. 1 and "1" are two different keys. A value can be anything at all, a list or another map included, and a map with no <> takes any key and any value.

A list filed under a key is reached the same way as any list, so teams["red"][2] is the second name on the red team. To go through every entry, take the keys as a list and walk it with a for loop:

teams.satlSATELLITE 004 Download
A map of lists, walked one team at a time
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.map<satellite.variable.string, satellite.container.list> teams
    teams["red"] = {"ann", "bo"}
    teams["blue"] = {"cy"}
    teams["red"].append("dee")
    satellite.console.display(teams)
    satellite.console.display(teams["red"][2])

    satellite.container.list<satellite.variable.string> names = teams.keys
    satellite.statement.for(satellite.variable.number i = 1; i <= names.size; i++)
    {
        satellite.variable.string team = names[i]
        satellite.console.display(team + ": " + teams[team].join(", "))
    }

    satellite.container.map anything
    anything[1] = "the number one"
    anything["1"] = "the text one"
    satellite.console.display(anything.size)
    satellite.return(satellite)
}
OUTPUT
{"red": {"ann", "bo", "dee"}, "blue": {"cy"}}
bo
red: ann, bo, dee
blue: cy
2

Two maps are equal, ==, when they hold the same keys with the same values, whatever order those went in. Like a list, a map is a value: b = a copies it.

Containers nest to any depth: a list of maps of lists, a map of maps. What you declare is checked all the way down, and satellite.access(name) shows you how to reach every level of one. ANY CONTAINER IN ANY CONTAINER walks through it.

A key that is not there

Reading a key the map does not hold is refused. satl does not quietly hand you nothing, because a made-up answer is worse than a clear stop.

missing_key.satlSATELLITE 004 Download
Reading a key that was never filed
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.map<satellite.variable.string, satellite.variable.number> age
    age["zoe"] = 30
    satellite.console.display(age["max"])
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S501: LINE_PAST_THE_END
satl(run): age[max]: there is no such key in it -- it holds 1 key

directory: missing_key.satl:7
syntax: satellite.console.display(age["max"])
                                     /\

a line was read by its number and the file has no line with that number. Lines
count from 1, and the last one is the file's size.
machine code 47 line_past_the_end -- satl exits with this.

--------------------------------------------------------------------------------

The second line is the one that matters here: there is no key max, and the map holds 1 key. The paragraph under the caret is the general text for this report, which was written for lines of a file.

Where next

Every map word is in the FULL REFERENCE. For the lists that a map hands back from .keys and .values, see LISTS, and for a name that may hold one of several types, see MULTIPLE: ONE NAME, SEVERAL TYPES.

0

MULTIPLE: ONE NAME, SEVERAL TYPES

Every name in satellite has a type. A satellite.variable.number holds a number, and a satellite.variable.string holds text. Now and then you want a name that can hold either. An answer might be typed as the word “twelve” or as the number 12. A setting might be a number or the word “auto”. For that, satellite has satellite.container.multiple.

Saying which types a name takes

You list two or more types between < and >. The name then takes a value of any of those types, and satl checks every value you give it.

answer.satlSATELLITE 004 Download
One name, a string first and a number after
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.multiple<satellite.variable.string, satellite.variable.number> answer = "forty-two"
    satellite.console.display(answer)
    satellite.console.display(answer.reverse())
    satellite.console.display(answer + "!")

    answer = 42
    satellite.console.display(answer)
    satellite.console.display(answer * 2)
    satellite.console.display(answer.to_string() + "!")
    satellite.return(satellite)
}
OUTPUT
forty-two
owt-ytrof
forty-two!
42
84
42!

Notice that nothing special happens when you use it. While answer holds text, it reverses and joins like text. Once it holds 42, it multiplies like a number. The value inside is an ordinary string or an ordinary number, and it behaves exactly as it would in a name declared the plain way.

That is why multiple is not really a container, even though its name starts with satellite.container. It holds nothing of its own. It is a promise about a name: “this name takes one of these types”.

In a capsule, and inside a list

A multiple can be a capsule’s parameter, so one capsule can be handed different kinds of value. It can also be the type a list holds, which gives you a list where every item is one of the listed types.

describe.satlSATELLITE 004 Download
A capsule that takes a number or a string, and a list that holds both
satellite.include(satellite)

satellite.capsule describe(satellite.container.multiple<satellite.variable.number, satellite.variable.string> amount)
{
    satellite.return("you asked for " + amount.to_string())
}

satellite.capsule satellite.main()
{
    satellite.console.display(describe(12))
    satellite.console.display(describe("a dozen"))

    satellite.container.list<satellite.container.multiple<satellite.variable.number, satellite.variable.string>> order = {2, "some", 6}
    order.append("plenty")
    satellite.console.display(order)
    satellite.console.display(order.size)

    satellite.container.multiple<satellite.variable.string, satellite.container.list> label = "one"
    label = {"two", "three"}
    satellite.console.display(label)
    satellite.return(satellite)
}
OUTPUT
you asked for 12
you asked for a dozen
{2, "some", 6, "plenty"}
4
{"two", "three"}

Containers can go on the list too, as label shows. A multiple with no <> at all takes every type.

One small detail: if a number is on the list, a binary value is taken as it is and stays a binary. Give b101 to a name declared satellite.container.multiple<satellite.variable.string, satellite.variable.number> and it shows b101. Give it to a plain satellite.variable.number and it becomes 5.

A type that is not on the list

Give the name a value whose type you did not list and satl stops, and says which types the name takes. Here a percentage goes into a name that takes only strings and numbers:

not_on_the_list.satlSATELLITE 004 Download
A percentage is refused by a string-or-number name
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.multiple<satellite.variable.string, satellite.variable.number> answer = "not sure"
    answer = 50%
    satellite.return(satellite)
}
OUTPUT
[satellite] satl(run): answer was declared satellite.container.multiple, and it holds a percentage, and this name takes satellite.variable.string or satellite.variable.number (machine_code: 27 types_do_not_meet)

This refusal is one line rather than a full report, but it says the same things: what the value was, which types the name takes, and the machine code, 27, that satl exits with.

A multiple needs at least two types. With only one type it would say nothing that a plain declaration does not say, so satl refuses it before the program runs:

one_type.satlSATELLITE 004 Download
A multiple of one type is refused
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.multiple<satellite.variable.string> just_text = "hello"
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S110: LINE_NOT_UNDERSTOOD
satl(check): in satellite.main, satellite.container.multiple takes two or more
types between < and >, and was given 1

directory: one_type.satl:5
syntax: satellite.container.multiple<satellite.variable.string> just_text = "hello"
        /\

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.

--------------------------------------------------------------------------------

What is not built yet

satellite 004 has no way yet to ask a multiple which type it holds right now. Write your program so that each line using the name makes sense for every type on its list, as describe does above by turning the amount into text with .to_string().

Where next

The FULL REFERENCE has every container word. Lists are in LISTS, and names that file values under keys are in MAPS.

ANY CONTAINER IN ANY CONTAINER 0

ANY CONTAINER IN ANY CONTAINER

Real data is nested. A school has classes, a class has students, and each student has a score for each subject. In satellite any container can hold any other container, to any depth, and the declaration says exactly what goes where. This post builds one, reaches into it, and then asks satl how to reach into it.

One declaration, three levels

Read the type from the outside in: a map from class names to lists, and each list holds maps from subjects to scores. The whole thing can be written in one literal. Braces with key: value entries are a map, and braces without them are a list.

school.satlSATELLITE 004 Download
A map of lists of maps, filled, read and changed
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.map<satellite.variable.string, satellite.container.list<satellite.container.map<satellite.variable.string, satellite.variable.number>>> school = {"7A": {{"maths": 81, "art": 64}, {"maths": 72, "art": 90}}}
    school["7A"].append({"maths": 55, "art": 70})
    school["7B"] = {{"maths": 99, "art": 58}}

    satellite.console.display(school["7A"][2]["art"])
    school["7A"][1]["maths"] = 84
    satellite.console.display(school["7A"][1])
    satellite.console.display(school["7A"].size)
    satellite.console.display(school.keys)
    satellite.return(satellite)
}
OUTPUT
90
{"maths": 84, "art": 64}
3
{"7A", "7B"}

Each [ ] steps one level down, left to right. school["7A"] is a list of students. school["7A"][2] is the second student, a map, because a list counts from 1. school["7A"][2]["art"] is that student’s art score. Writing works the same way: school["7A"][1]["maths"] = 84 changes one number deep inside, and .append on a list inside adds a student to that class.

Checked all the way down

What you declare is a promise, and satl holds you to it on every way in: in the literal, in .append, and in a write three levels down. A score is a number, so a string cannot go there.

school_wrong.satlSATELLITE 004 Download
A string where the declaration says a score goes
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.map<satellite.variable.string, satellite.container.list<satellite.container.map<satellite.variable.string, satellite.variable.number>>> school = {"7A": {{"maths": 81}}}
    school["7A"][1]["maths"] = "top of the class"
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S301: TYPES_DO_NOT_MEET
satl(run): in school[...] = ..., the value does not fit: it holds a string

directory: school_wrong.satl:6
syntax: school["7A"][1]["maths"] = "top of the class"
              /\

this operator has no scenario for the two kinds it was given. Nothing was
guessed at, because a guess here is an answer that is wrong and does not say so.
machine code 27 types_do_not_meet -- satl exits with this.

--------------------------------------------------------------------------------

Asking satl how to reach in: satellite.access

With a shape this deep it is easy to lose track of which [ ] is which. satellite.access(name) answers that from the declaration. It says what the name is and what it holds, gives one line for each level you can reach, and gives one line that fills each level with a value of the right shape.

school_access.satlSATELLITE 004 Download
What school is, and the code for every level of it
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.map<satellite.variable.string, satellite.container.list<satellite.container.map<satellite.variable.string, satellite.variable.number>>> school = {"7A": {{"maths": 81, "art": 64}}}
    satellite.access(school)
    satellite.return(satellite)
}
OUTPUT
school is a map (string -> list of maps (string -> number)), 1 key
  value                             {"7A": {{"maths": 81, "art": 64}}}
  school["key"]                     a list of maps (string -> number), "key" a string, one of "7A"
  school["key"][n]                  a map (string -> number), n counting from 1
  school["key"][n]["key2"]          a number, "key2" a string
  school["key"] = {{"key": 1}}      puts a list of maps under "key"
  school["key"].append({"key": 1})  adds a map
  school["key"][n]["key2"] = 1      puts a number under "key2"
  school.size                       how many keys it holds
  school.keys                       the keys of it, as a list
  school["key"][n].keys             the keys of a map, as a list

n stands for a position, counting from 1, and "key" and "key2" stand for keys. The second column says what each line reaches. None of it is guessed: satl’s tests generate thousands of container shapes, ask satellite.access about each one, and run every line it prints as code.

When an item does not fit

Here is a real first attempt at a deep container. It declares a list of lists of maps, and then appends {{"hello"}}:

crazy_container.satlSATELLITE 004 Download
An append whose item is not the shape the list holds
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.container.list<satellite.container.map<satellite.variable.string, satellite.container.list<satellite.container.list<satellite.variable.string>>>>> crazy_container
    crazy_container.append({{"hello"}})
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S301: TYPES_DO_NOT_MEET
satl(run): in crazy_container, crazy_container.append: item 1 of it does not
fit: it holds a list

directory: crazy_container.satl:6
syntax: crazy_container.append({{"hello"}})
        /\

this operator has no scenario for the two kinds it was given. Nothing was
guessed at, because a guess here is an answer that is wrong and does not say so.
machine code 27 types_do_not_meet -- satl exits with this.

--------------------------------------------------------------------------------

The declaration is right. The item is what went wrong: each item of crazy_container is a list of maps, and {"hello"} is a list, not a map. satl never guesses what a line was meant to say, but it knows the declaration, so satellite.access(crazy_container) shows the line that fits. The item needs a key before its lists:

crazy_container_fixed.satlSATELLITE 004 Download
The same container, filled the way satellite.access shows
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.container.list<satellite.container.list<satellite.container.map<satellite.variable.string, satellite.container.list<satellite.container.list<satellite.variable.string>>>>> crazy_container
    crazy_container.append({{"greeting": {{"hello"}}}})
    satellite.console.display(crazy_container[1][1]["greeting"][1][1])
    satellite.access(crazy_container)
    satellite.return(satellite)
}
OUTPUT
hello
crazy_container is a list of lists of maps (string -> list of lists of strings), 1 item
  value                                           {{{"greeting": {{"hello"}}}}}
  crazy_container[n]                              a list of maps (string -> list of lists of strings), n counting from 1 (1 to 1)
  crazy_container[n][m]                           a map (string -> list of lists of strings), m counting from 1
  crazy_container[n][m]["key"]                    a list of lists of strings, "key" a string
  crazy_container[n][m]["key"][p]                 a list of strings, p counting from 1
  crazy_container[n][m]["key"][p][q]              a string, q counting from 1
  crazy_container.append({{"key": {{"text"}}}})   adds a list of maps
  crazy_container[n].append({"key": {{"text"}}})  adds a map
  crazy_container[n][m]["key"] = {{"text"}}       puts a list of lists of strings under "key"
  crazy_container[n][m]["key"].append({"text"})   adds a list of strings
  crazy_container[n][m]["key"][p].append("text")  adds a string
  crazy_container.size                            how many lists of maps it holds
  crazy_container[n][m].keys                      the keys of a map, as a list

Where next

Each container on its own: LISTS, GRIDS: LISTS OF LISTS, MAPS and MULTIPLE: ONE NAME, SEVERAL TYPES. satellite.access and the switch behind it are in HISTORY AND ACCESS, and every container word is in the FULL REFERENCE.

0

SPACESUITS: SATELLITE OBJECTS

Numbers, strings and lists are types that satellite gives you. A spacesuit is a type you make yourself. It bundles some variables, called its fields, together with the capsules that work on them, and your program then makes objects of it. Other languages call this a class, and satellite accepts satellite.class as a second spelling of satellite.spacesuit. The two words mean exactly the same thing.

The parts of a spacesuit

A spacesuit is written at the top of a file, outside every capsule, and it has up to three sections side by side between its braces:

  • satellite.protected { } holds the fields: a type, a name, and a first value if you give one. It can also hold capsules that only the spacesuit’s own capsules may call.
  • satellite.public { } holds the capsules that anyone with an object may call.
  • satellite.constructor(...) { } runs once when an object is made, after every field has its first value. It is how an object gets the values it was made with.

Here is a piggy bank. It knows its owner and how many pennies are inside, and it keeps a note each time money goes in.

piggy_bank.satlSATELLITE 004 Download
A spacesuit with fields, a constructor and capsules
satellite.include(satellite)

satellite.spacesuit piggy_bank()
{
    satellite.protected
    {
        satellite.variable.string owner = ""
        satellite.variable.number pennies = 0

        satellite.capsule note(satellite.variable.string what)
        {
            satellite.console.display(owner + ": " + what)
        }
    }

    satellite.constructor(satellite.variable.string owner_input)
    {
        owner = owner_input
    }

    satellite.public
    {
        satellite.capsule put_in(satellite.variable.number amount)
        {
            pennies = pennies + amount
            note("put in " + amount.to_string())
        }

        satellite.capsule call_pennies()
        {
            satellite.return(pennies)
        }
    }
}

satellite.capsule satellite.main()
{
    piggy_bank jar("sam")
    jar.put_in(25)
    jar.put_in(10)
    satellite.console.display(jar.call_pennies())

    piggy_bank tin("alex")
    tin.put_in(5)
    satellite.console.display(tin.call_pennies())
    satellite.return(satellite)
}
OUTPUT
sam: put in 25
sam: put in 10
35
alex: put in 5
5

Some things to notice:

  • You make an object by declaring it. piggy_bank jar("sam") is the spacesuit’s name, then the object’s name, then what the constructor takes. jar and tin are two separate piggy banks, and each has its own pennies.
  • Inside the spacesuit, fields and capsules are just their bare names. put_in writes pennies and calls note(...) directly. There is no this to write.
  • From outside, you call public capsules on the object: jar.put_in(25). A capsule hands a value back with satellite.return(...).

Fields stay inside

A field is reached from inside its spacesuit only, never from outside. jar.pennies is refused, and so is a call to a protected capsule such as jar.note("hi"). To let the rest of the program read a field, write a public capsule that answers it, as call_pennies does. satl catches this before anything runs:

peek.satlSATELLITE 004 Download
Reading a field from outside is refused
satellite.include(satellite)

satellite.spacesuit piggy_bank()
{
    satellite.protected
    {
        satellite.variable.number pennies = 0
    }
}

satellite.capsule satellite.main()
{
    piggy_bank jar
    satellite.console.display(jar.pennies)
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S230: MEMBER_IS_PROTECTED
satl(check): in satellite.main, pennies is a field of piggy_bank and fields are
reached from inside the spacesuit only -- write a capsule in piggy_bank that
answers it and call that instead

directory: peek.satl:14
syntax: satellite.console.display(jar.pennies)
        /\

this is inside a spacesuit, where only its own capsules can reach it -- every
field is, and so is a capsule written in satellite.protected. A capsule in
satellite.public that answers it is the way in from outside.
machine code 52 member_is_protected -- satl exits with this.

--------------------------------------------------------------------------------

This piggy bank has no constructor, so its object is made with just piggy_bank jar, and no brackets.

An object is a reference

A number or a list is copied when you give it to another name. An object is not. lamp same = desk gives the same lamp a second name, and a capsule that is handed an object changes the caller’s object, not a copy.

lamps.satlSATELLITE 004 Download
Objects are shared, not copied
satellite.include(satellite)

satellite.class lamp()
{
    satellite.protected
    {
        satellite.variable.bool on = satellite.bool.false
    }

    satellite.public
    {
        satellite.capsule switch_it()
        {
            on = !on
        }

        satellite.capsule call_state()
        {
            satellite.statement.if (on)
            {
                satellite.return("on")
            }
            satellite.return("off")
        }
    }
}

satellite.capsule press(lamp which)
{
    which.switch_it()
}

satellite.capsule satellite.main()
{
    lamp desk
    press(desk)
    satellite.console.display(desk.call_state())

    lamp same = desk
    same.switch_it()
    satellite.console.display(desk.call_state())

    satellite.container.list<lamp> room = {}
    room.append(desk)
    lamp ceiling
    room.append(ceiling)
    room[2].switch_it()
    satellite.console.display(room.size)
    satellite.console.display(ceiling.call_state())
    satellite.return(satellite)
}
OUTPUT
on
off
2
on

This lamp is written with satellite.class, the second spelling, and it has no constructor, so lamp desk makes one. press(desk) switched the real desk lamp on. same is the desk lamp under another name, so switching same switched it off again. A list of lamps, satellite.container.list<lamp>, holds the lamps themselves, so room[2].switch_it() turned on ceiling. A spacesuit’s name works as a type anywhere a type goes: a capsule’s parameter, what a list holds, or what a capsule answers.

A few rules satl checks for you

Every field is protected wherever you write it, and so is a capsule written outside any section. Beyond that, satl refuses each of these before anything runs, with a report that says which rule and where:

  • A field’s first value cannot use another field. Set it in the constructor instead.
  • An object must be given exactly what its constructor takes.
  • A spacesuit cannot be declared inside a capsule.

Where next

The FULL REFERENCE lists every spacesuit word. To build one spacesuit on top of another, or put one spacesuit inside another, read ONE SPACESUIT EXTENDING ANOTHER. Capsules themselves are covered in CAPSULES.

0

ONE SPACESUIT EXTENDING ANOTHER

Once you have written a spacesuit, you will often want a second one that is almost the same. A dog is a pet with tricks. A savings jar is a piggy bank that pays interest. satellite lets one spacesuit extend another: the new one gets everything the old one has, and adds its own. This post also shows how to keep one spacesuit inside another. If spacesuits are new to you, start with SPACESUITS: SATELLITE OBJECTS.

Extending: name the other spacesuit in the brackets

The brackets after a spacesuit’s name have been empty so far. Put the name of another spacesuit there, and the new one extends it: satellite.spacesuit dog(pet) says a dog is a pet, plus more.

A dog object then has everything a pet has:

  • the pet’s fields, as well as its own. The dog’s capsules use the pet’s fields by their bare names, as call_tricks uses name below.
  • the pet’s capsules. The dog’s capsules call them by their bare names, and a program calls the public ones on a dog object.
  • every constructor. The pet’s constructor runs first and the dog’s after it. The arguments you give when making the object go to the nearest constructor: the dog’s here, or the pet’s if the dog had none of its own.

A capsule the dog declares again replaces the pet’s for every dog. Here the dog replaces call_sound:

pets.satlSATELLITE 004 Download
A dog extends a pet and replaces one of its capsules
satellite.include(satellite)

satellite.spacesuit pet()
{
    satellite.protected
    {
        satellite.variable.string name = "a pet"
    }

    satellite.constructor()
    {
        satellite.console.display("pet's constructor")
    }

    satellite.public
    {
        satellite.capsule call_sound()
        {
            satellite.return("...")
        }

        satellite.capsule describe()
        {
            satellite.return(name + " says " + call_sound())
        }

        satellite.capsule set_name(satellite.variable.string given)
        {
            name = given
        }
    }
}

satellite.spacesuit dog(pet)
{
    satellite.protected
    {
        satellite.variable.number tricks = 0
    }

    satellite.constructor(satellite.variable.number tricks_input)
    {
        satellite.console.display("dog's constructor")
        tricks = tricks_input
    }

    satellite.public
    {
        satellite.capsule call_sound()
        {
            satellite.return("woof")
        }

        satellite.capsule call_tricks()
        {
            satellite.return(name + " knows " + tricks.to_string() + " tricks")
        }
    }
}

satellite.capsule introduce(pet any)
{
    satellite.console.display(any.describe())
}

satellite.capsule satellite.main()
{
    dog rex(3)
    rex.set_name("rex")
    satellite.console.display(rex.call_tricks())
    introduce(rex)

    pet goldfish
    introduce(goldfish)
    satellite.return(satellite)
}
OUTPUT
pet's constructor
dog's constructor
rex knows 3 tricks
rex says woof
pet's constructor
a pet says ...

Follow what happened:

  1. dog rex(3) ran the pet’s constructor, then the dog’s, which was handed the 3.
  2. rex.set_name("rex") is a pet capsule, called on a dog.
  3. introduce takes a pet, and a dog is a pet, so introduce(rex) is allowed.
  4. Inside it, the pet’s describe calls call_sound(), and because rex is a dog, the dog’s replacement runs: “woof”. The plain pet goldfish still says “…”.

A name declared as the older spacesuit can hold an object of the newer one: pet any held a dog. That is what lets one capsule work for every kind of pet.

A replacement must be called the same way

Because a pet name may hold a dog, a capsule that replaces another must take the same arguments and be just as reachable. It cannot move from satellite.public to satellite.protected, for example. satl checks this before anything runs:

cat.satlSATELLITE 004 Download
A replacement that takes different arguments is refused
satellite.include(satellite)

satellite.spacesuit pet()
{
    satellite.public
    {
        satellite.capsule call_sound()
        {
            satellite.return("...")
        }
    }
}

satellite.spacesuit cat(pet)
{
    satellite.public
    {
        satellite.capsule call_sound(satellite.variable.number times)
        {
            satellite.return("meow")
        }
    }
}

satellite.capsule satellite.main()
{
    cat tom
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S110: LINE_NOT_UNDERSTOOD
satl(check): cat's call_sound replaces pet's for its objects, and it takes
different arguments from the one it replaces -- a name declared pet may hold one
of them, so the two must be called the same way

directory: cat.satl:18
syntax: satellite.capsule call_sound(satellite.variable.number times)
        /\

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 spacesuit extends one other spacesuit, or none. Two names in the brackets are refused, and so is a circle, where following the brackets leads back to where you started.

A spacesuit inside a spacesuit

A spacesuit can also hold whole spacesuits of its own, written in its satellite.public or satellite.protected section. This suits a type that only makes sense as part of another, like the rooms of a house.

  • Inside the outer spacesuit, the inner one is named bare: room.
  • From outside, a public inner spacesuit is house.room. A protected one cannot be named from outside at all.
  • The inner spacesuit’s capsules see its own fields, not the outer one’s.
house.satlSATELLITE 004 Download
A house holds rooms, and room is a spacesuit inside house
satellite.include(satellite)

satellite.spacesuit house()
{
    satellite.protected
    {
        satellite.variable.string street = ""
        satellite.container.list<room> rooms = {}
    }

    satellite.constructor(satellite.variable.string street_input)
    {
        street = street_input
    }

    satellite.public
    {
        satellite.spacesuit room()
        {
            satellite.protected
            {
                satellite.variable.string name = ""
                satellite.variable.number size = 0
            }

            satellite.constructor(satellite.variable.string name_input, satellite.variable.number size_input)
            {
                name = name_input
                size = size_input
            }

            satellite.public
            {
                satellite.capsule call_line()
                {
                    satellite.return(name + ", " + size.to_string() + " square metres")
                }
            }
        }

        satellite.capsule add_room(room given)
        {
            rooms.append(given)
        }

        satellite.capsule build_kitchen()
        {
            room kitchen("kitchen", 12)
            rooms.append(kitchen)
        }

        satellite.capsule show()
        {
            satellite.console.display(street + " has " + rooms.size.to_string() + " rooms")
            satellite.statement.for(satellite.variable.number i = 1; i <= rooms.size; i++)
            {
                satellite.console.display("- " + rooms[i].call_line())
            }
        }
    }
}

satellite.capsule satellite.main()
{
    house home("1 Orbit Lane")
    house.room lounge("lounge", 20)
    home.add_room(lounge)
    home.build_kitchen()
    home.show()
    satellite.return(satellite)
}
OUTPUT
1 Orbit Lane has 2 rooms
- lounge, 20 square metres
- kitchen, 12 square metres

The program made the lounge from outside, as house.room, and handed it in. The house made its own kitchen, as plain room. Both ended up in the same list, satellite.container.list<room>.

Where next

The FULL REFERENCE lists every spacesuit word and every rule satl checks. To keep your spacesuits in files of their own and reach them from a program, see INCLUDING FILES AND NAMESPACES.

1

INCLUDING FILES AND NAMESPACES

A small program fits in one file. A bigger one is easier to live with in pieces: one file of greetings, one of sums, one for the program itself. satellite puts the pieces together with satellite.include, and it keeps them tidy with one simple rule: every file you include is reached through its own name.

Including a file of your own

You have been writing satellite.include(satellite) at the top of every program. That line takes in the language itself. The same word takes in a file of yours: satellite.include("greetings") reads greetings.satl, and its capsules are then yours to call as greetings.hello(...).

Here are three files. The first is a file of greetings. It has no satellite.include(satellite) line, which makes it a spaceship: a file for other files to include, which satl will not start on its own.

greetings.satlSATELLITE 004 Download
A spaceship: capsules, a namespace and a spacesuit for other files
satellite.capsule hello(satellite.variable.string name)
{
    satellite.console.display("hello, " + name)
}

satellite.namespace polite
{
    satellite.capsule goodbye(satellite.variable.string name)
    {
        satellite.console.display("goodbye, " + name + ", and thank you")
    }
}

satellite.spacesuit badge()
{
    satellite.protected
    {
        satellite.variable.string text = ""
    }

    satellite.constructor(satellite.variable.string given)
    {
        text = given
    }

    satellite.public
    {
        satellite.capsule call_text()
        {
            satellite.return("[ " + text + " ]")
        }
    }
}

The second lives in a folder called tools, beside the other two. This one is a whole program, with its own satellite.main, so it can also be run by itself:

sums.satlSATELLITE 004 Download
tools/sums.satl: a file that runs on its own and can be included too
satellite.include(satellite)

satellite.capsule double(satellite.variable.number n)
{
    satellite.return(n * 2)
}

satellite.capsule satellite.main()
{
    satellite.console.display("sums.satl on its own")
    satellite.console.display(double(4))
    satellite.return(satellite)
}
OUTPUT
sums.satl on its own
8

The third is the program, which includes both:

main.satlSATELLITE 004 Download
The program: it includes the other two and reaches each through its name
satellite.include(satellite)
satellite.include("greetings")
satellite.include("tools/sums")

satellite.capsule hello(satellite.variable.string name)
{
    satellite.console.display("hi " + name + ", from main.satl")
}

satellite.capsule satellite.main()
{
    hello("mum")
    greetings.hello("mum")
    greetings.polite.goodbye("mum")
    satellite.console.display(sums.double(21))
    greetings.badge b("visitor")
    satellite.console.display(b.call_text())
    satellite.return(satellite)
}

With all three files in place, satl main.satl prints:

OUTPUT
hi mum, from main.satl
hello, mum
goodbye, mum, and thank you
42
[ visitor ]

What to notice:

  • A bare name is always this file’s own. main.satl and greetings.satl both have a hello, and there is no clash. hello("mum") is main’s own, and greetings.hello("mum") is the one in greetings.
  • A file is reached by its name, without the folder or the .satl. tools/sums.satl is reached as sums.double(21). A spacesuit in another file is made the same way: greetings.badge b("visitor").
  • An included file’s satellite.main never runs. Including sums.satl gave main its double, and “sums.satl on its own” was not printed.
  • Paths are relative to the file that holds the include line, wherever you run satl from. ".." goes up a folder, and the .satl on the end is optional.

A bare name never reaches into another file. Writing double(21) in main.satl is refused before anything runs, and the report tells you to write sums.double(...). A file also reaches only the files it includes itself: if sums.satl included a third file, main.satl would not reach that file by its name.

When the file is not there

If an included file cannot be found, satl stops before running anything and names the file it looked for:

missing.satlSATELLITE 004 Download
Including a file that does not exist
satellite.include(satellite)
satellite.include("recipes")

satellite.capsule satellite.main()
{
    satellite.console.display("this never prints")
    satellite.return(satellite)
}
OUTPUT
[satellite] cannot locate file: recipes.satl, included by missing.satl (machine_code: 8 missing_satl_file)

Namespaces: rooms inside one file

Sometimes one file holds two groups of capsules that want the same names: an oven that beeps and a kettle that beeps. satellite.namespace makes a named group inside a file, and satellite.space is its second spelling. A namespace can hold capsules, spacesuits and other namespaces, but not variables. Variables belong to a capsule or a spacesuit.

kitchen.satlSATELLITE 004 Download
Two namespaces in one file, one inside the other
satellite.include(satellite)

satellite.namespace oven
{
    satellite.capsule heat(satellite.variable.number degrees)
    {
        satellite.console.display("oven heating to " + degrees.to_string())
        beep()
    }

    satellite.capsule beep()
    {
        satellite.console.display("oven: beep")
    }
}

satellite.space kettle
{
    satellite.capsule beep()
    {
        satellite.console.display("kettle: beep")
    }

    satellite.space lid
    {
        satellite.capsule open()
        {
            satellite.console.display("kettle lid open")
        }
    }
}

satellite.capsule beep()
{
    satellite.console.display("my own beep")
}

satellite.capsule satellite.main()
{
    oven.heat(180)
    kettle.beep()
    kettle.lid.open()
    beep()
    satellite.return(satellite)
}
OUTPUT
oven heating to 180
oven: beep
kettle: beep
kettle lid open
my own beep

Inside oven, heat calls beep() bare, and it gets the oven’s beep, its neighbour in the same namespace. From satellite.main you name the group first: oven.heat(180), kettle.lid.open(). The plain beep() at the end is the file’s own.

A namespace in an included file is reached through the file’s name first, as greetings.polite.goodbye showed above. Every name you give a file, a namespace or a capsule is a bare name, and bare names are yours. Only the dotted paths that begin with satellite belong to the language.

Where next

The FULL REFERENCE has the include and namespace rules in full. A file can also carry fixed values for every capsule to read: see SATELLITE.LIBRARY. For the capsules themselves, see CAPSULES.