The Satellite Corporation Broadcasting Another World!

PERCENTAGES

Prices go up by 20%, a sale takes 25% off, a battery is at 50%. satellite.variable.percentage holds a percentage as its own kind of value, and it knows what those everyday sums mean. satellite.variable.percent is its second spelling, so both words declare the same type.

Writing a percentage

You write a percentage with its % touching the digits: 50%, 12.5%, -50%. The % is required. A percentage name given a plain 50 is refused before the program starts, with “ERROR: expected 50%”.

What each sum means

A percentage on its own, or with another percentage, stays a percentage. A number and a percentage together answer a number:

  • 50% + 25% is 75%, and 50% - 25% is 25%
  • 50% * 50% is 25%: a percentage of a percentage
  • 50% / 4 is 12.5%: a share of it
  • 200 * 50% is 100, and so is 50% * 200: that percentage of a number
  • 200 + 50% is 300: the number grown by it
  • 200 - 50% is 100: the number reduced by it
  • 200 / 50% is 400: the number that 200 is 50% of
shop.satlSATELLITE 004 Download
Percentages at the shop
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.percentage tax = 20%
    satellite.variable.percent sale = 25%
    satellite.variable.number price = 80
    satellite.console.display(price + tax)
    satellite.console.display(price - sale)
    satellite.console.display(price * sale)
    satellite.console.display(200 / 50%)
    satellite.console.display(tax + sale)
    satellite.console.display(sale / 2)
    satellite.console.display(tax.string + " tax")
    satellite.return(satellite)
}
OUTPUT
96
60
20
400
45%
12.5%
20% tax

80 plus 20% tax is 96, and 80 with 25% off is 60. The 25% of 80 that came off is 20.

Exact to 32 places

A percentage keeps 32 digits after the point, exactly. It is not a binary float, so 0.1% + 0.2% is 0.3% with nothing left over. There is no ceiling above: 1000000000000% is a perfectly good percentage. A 33rd digit after the point is rounded half away from zero.

exact.satlSATELLITE 004 Download
Percentages are exact, and compare with each other
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.percent battery = 50%
    satellite.console.display(0.1% + 0.2%)
    satellite.console.display(100% / 3)
    satellite.console.display(battery < 75%)
    battery = battery - 75%
    satellite.console.display(battery)
    satellite.return(satellite)
}
OUTPUT
0.3%
33.33333333333333333333333333333333%
true
-25%

A percentage compares with ==, !=, <, >, <= and >=, but only with another percentage: 50% == 50 is refused, because a percentage and a plain number are different things.

The number goes first

When you add a percentage to a number or take one away, the number goes first: you add 20% onto 80, so you write 80 + 20%. The other way round is refused, and the message tells you which way to write it:

backwards.satlSATELLITE 004 Download
For + and -, the number goes first
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.console.display(50% + 5)
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S301: TYPES_DO_NOT_MEET
satl(run): 50% + 5: a percentage is taken off or added onto a number, so the
number goes first -- 5 + 50%

directory: backwards.satl:5
syntax: satellite.console.display(50% + 5)
                                      /\

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.

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

What else is refused today

  • % and ^ with a percentage, and one percentage divided by another (50% / 50%).
  • An answer that is not a whole number: 3 * 50% is 1.5, and a percentage answering a float is not built yet, so it is refused with S402.
  • A percentage meeting a float, such as 10.0 * 50%, is not built yet.
  • A percentage name holds only a percentage, so battery = battery * 2 is refused when it runs, because that sum answers a number.
  • .string is the only conversion: .number, .binary, .hex and .reverse() are refused.

Where next

The FULL REFERENCE lists every percentage word. Whole numbers are in NUMBERS, and exact parts of a whole are in FRACTIONS.

BINARY AND HEXADECIMAL

Computers count in bits, and people who work close to the machine often write numbers the same way: in binary (base 2, the digits 0 and 1) or in hexadecimal (base 16, the digits 0 to 9 and A to F). satellite has a type for each, and both keep a number exactly as you wrote it, leading zeros and all.

Binary: a b in front

satellite.variable.binary is a number written in bits with a b in front: b1010. satellite.variable.bin is its second spelling. The b is required, so a binary name given 1010 is refused with “ERROR: expected b1010”, and so is 0b1010, the spelling some other languages use.

The width is part of the value. b0010 shows as b0010, and it is not the same binary as b10, although both are worth 2. A binary can also carry a sign: -b0101 is worth -5. There is no length limit.

flags.satlSATELLITE 004 Download
A binary number and its methods
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.binary flags = b0010
    satellite.console.display(flags)
    satellite.console.display(flags == b10)
    satellite.console.display(flags.number)
    satellite.console.display(flags.string)
    satellite.console.display(flags.bin)
    satellite.console.display(b10101010.hex)
    satellite.console.display(b1000.reverse())
    satellite.variable.bin below = -b0101
    satellite.console.display(below)
    satellite.console.display(below.number)
    satellite.return(satellite)
}
OUTPUT
b0010
false
2
b0010
0010
AA
b0001
-b0101
-5
  • .number is what the bits are worth.
  • .string is the binary as text, b and all, and .binary (or .bin) is the digits without the b.
  • .hex is its worth written in base 16.
  • .reverse() turns the bits round and keeps the width.

Hexadecimal: an x in front

satellite.variable.hex is a number in base 16 with an x in front: x1F, x00FF, xDEADBEEF. satellite.variable.hexadecimal is its second spelling. A lower-case digit is read the same as a capital one and always shown in capitals. As with a binary, the x is required, and the width and sign are kept: x00FF and xFF are worth the same and are different values.

mask.satlSATELLITE 004 Download
A hex number and its methods
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.hex mask = x00ff
    satellite.variable.hexadecimal red = xFF
    satellite.console.display(mask)
    satellite.console.display(mask == red)
    satellite.console.display(mask.number == red.number)
    satellite.console.display(mask.binary)
    satellite.console.display(mask.width)
    satellite.console.display(mask.reverse())
    satellite.console.display(xDEADBEEF.number)
    satellite.console.display(-x1F)
    satellite.return(satellite)
}
OUTPUT
x00FF
false
true
0000000011111111
16
xFF00
3735928559
-x1F

A hex has .number, .string and .hex like a binary, plus .binary, which gives four bits for every hex digit, and .width, which counts those bits.

Sums answer a plain number

In a sum, a binary or a hex counts as the number it is worth, and the answer is a plain whole number. That holds when you mix them too: b1100 + xFF is 267. They compare by worth the same way, so b1010 == 10 and b1111 == xF are both true.

To see a value in the other base, ask for its text: .hex on a binary, .binary on a hex, or either one on an ordinary number.

together.satlSATELLITE 004 Download
Binary and hex together, and converting between them
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.binary low = b1100
    satellite.console.display(low + xFF)
    satellite.console.display(b1000 + b0101)
    satellite.console.display(b1111 == xF)
    satellite.console.display(low.hex)
    satellite.console.display(xFF.binary)
    satellite.console.display((12).binary)
    satellite.console.display(255.hex)
    satellite.console.display("255".hex)
    satellite.variable.number n = b0011
    satellite.console.display(n)
    satellite.return(satellite)
}
OUTPUT
267
13
true
C
11111111
1100
FF
FF
3

Because a sum answers a number, it cannot be stored back in a binary or hex name. flags = flags + b0001 is refused when it runs (“it holds a number”). For a hex the message goes further and names the hex you meant: h = h + x01, with h holding xFF, suggests keeping 256 in a satellite.variable.number or writing x100. A number name, on the other hand, happily takes a binary or a hex and keeps its worth, as n does above.

A missing prefix

no_b.satlSATELLITE 004 Download
The b in front of a binary is required
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.binary flags = 1010
    satellite.console.display(flags)
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S301: TYPES_DO_NOT_MEET
satl(check): in satellite.main, ERROR: expected b1010

directory: no_b.satl:5
syntax: satellite.variable.binary flags = 1010
        /\

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.

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

A binary digit is 0 or 1, so b12 is refused too, and a hex name given FF asks for xFF.

Not built yet

Sums that answer a binary or a hex, rather than a number, are not built yet, and neither are the bit operators (&, |, ~, <<, >>) or .width on a binary. OPERATORS: LOGIC AND BITS shows what satl says when you try them.

Where next

The FULL REFERENCE lists every binary and hex word. Colors, which are six hex digits, are in COLORS, and ordinary whole numbers are in NUMBERS.

COLORS

THE SATELLITE CORPORATION

TITLE: COLORS
DATE:
ID: 30/42

USERNAME: Claude Code

A color on a screen is three amounts of light: red, green and blue. Written in hexadecimal, each amount takes two digits, from 00 (none) to FF (full), so a whole color is six hex digits. 87CEEB is a sky blue, FF0000 is pure red, and 000000 is black. satellite.variable.color holds exactly that, and satellite.variable.colour is its second spelling.

Making a color

On the line that declares a color, the x in front is optional: 000000, x000000, ff00aa and 87ceeb all work. However you write it, a color is shown in capitals with its x, the way a hex number is.

A color can also be see-through. Its transparency is a whole number from 0 (solid) to 99 (almost clear). You can give it after a comma when you write the color, or set it later with .transparency(n). Written on its own, .transparency reads it back.

sky.satlSATELLITE 004 Download
Declaring colors, with and without transparency
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.color sky = 87ceeb
    satellite.variable.colour ink = x000000, 50
    satellite.console.display(sky)
    satellite.console.display(ink)
    satellite.console.display(ink.transparency)
    sky.transparency(40)
    satellite.console.display(sky.transparency)
    satellite.console.display(sky)
    satellite.return(satellite)
}
OUTPUT
x87CEEB
x000000, 50
50
40
x87CEEB, 40

A color with a transparency shows as x000000, 50, just the way you write it.

Changing a color later

On a later line, c = x00ff00 always works, and so do digits that start with a letter, or that have no letters at all, such as c = abcdef and c = 123456. The one shape that needs its x is digits that start with a number and hold a letter, such as 00ff00. The comma works on a later line too.

grass.satlSATELLITE 004 Download
Giving a color a new value
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.color grass = 00ff00
    satellite.console.display(grass)
    grass = x228b22
    satellite.console.display(grass)
    grass = abcdef
    satellite.console.display(grass)
    grass = 123456, 10
    satellite.console.display(grass)
    satellite.return(satellite)
}
OUTPUT
x00FF00
x228B22
xABCDEF
x123456, 10

Comparing and converting

== and != compare two colors, and both the digits and the transparency have to match. A color has no order, so < and > are refused, and it has no arithmetic: + on two colors is refused, and the message points you to .number if you want to do sums.

  • .number is what the six digits are worth
  • .hex is the six digits, without the x
  • .binary is its 24 bits
  • .string is the color as it is shown
compare.satlSATELLITE 004 Download
Comparing colors and reading their digits
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.color red = ff0000
    satellite.variable.color also_red = xFF0000
    satellite.variable.color glass = ff0000, 30
    satellite.console.display(red == also_red)
    satellite.console.display(red == glass)
    satellite.console.display(red.number)
    satellite.console.display(red.hex)
    satellite.console.display(red.binary)
    satellite.console.display(glass.string)
    satellite.return(satellite)
}
OUTPUT
true
false
16711680
FF0000
111111110000000000000000
xFF0000, 30

red and glass have the same digits, but glass is 30 see-through, so they are not equal.

Six digits exactly

A color is always six digits. Five or seven are refused before the program starts:

short.satlSATELLITE 004 Download
A color is exactly six hex digits
satellite.include(satellite)

satellite.capsule satellite.main()
{
    satellite.variable.color pink = ff00a
    satellite.console.display(pink)
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S301: TYPES_DO_NOT_MEET
satl(check): in satellite.main, ERROR: ff00a is 5 hex digits -- a color is
exactly six hex digits, like x00FF00

directory: short.satl:5
syntax: satellite.variable.color pink = ff00a
        /\

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.

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

A transparency of 100 or more is refused the same way: the most is 99.

Using a color

A color is what you hand to the words that paint text. "OK".foreground(grass) is the word OK in the color grass holds, and six hex digits work there too: "OK".foreground(x00FF00) is OK in green. The color shows when the text reaches a terminal. See COLOR ON THE CONSOLE.

Comparing a color with a bare hex number, such as grass == x228B22, is not built yet. Compare two colors instead, or compare their .number values.

Where next

The FULL REFERENCE lists every color word. Hex numbers themselves are in BINARY AND HEXADECIMAL.

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.

OPERATORS: LOGIC AND BITS

THE SATELLITE CORPORATION

DATE:
ID: 28/42

USERNAME: Claude Code

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.

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.

GRIDS: LISTS OF LISTS

THE SATELLITE CORPORATION

DATE:
ID: 26/42

USERNAME: Claude Code

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.

MAPS

THE SATELLITE CORPORATION

TITLE: MAPS
DATE:
ID: 25/42

USERNAME: Claude Code

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.

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

THE SATELLITE CORPORATION

DATE:
ID: 23/42

USERNAME: Claude Code
CATEGORY: SATELLITE 004

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.