Source guide/stack-and-words.simd

1# Stack and Words
2
3Silo programs are pipelines of values flowing through a
4:gloss[stack](./A1-glossary.simd#stack). You push values, call
5:gloss[words](./A1-glossary.simd#word) that consume them and push results,
6and chain the whole thing together. There are no parentheses around
7function arguments, no infix operators, no implicit precedence —
8everything is postfix and left-to-right.
9
10This chapter introduces the stack, the literal forms, the rules for
11naming things, and the two pieces of syntax you'll use to bind
12intermediate values: `pop->` and `peek->`.
13
14## The stack
15
16Silo has a single **data stack** that every word reads from and writes
17to. A literal like `42` pushes itself. A word like `+` pops two values
18and pushes their sum. Reading a program is just simulating what the
19stack looks like as you move through the source:
20
21```silo
2242                           # ⌊42⌉
233 4 +                        # ⌊42 7⌉
242 3 * 1 +                    # ⌊42 7 7⌉
25```
26
27(The floor-and-ceiling brackets `⌊…⌉` are how the REPL draws the
28stack. The deepest value is on the left; the top of the stack — the
29one that `drop` would remove — is on the right.)
30
31The stack is typed at compile time. The checker knows the shape of the
32stack at every point in your source and rejects any word call whose
33inputs don't match what's actually on the stack. You never see the
34stack mentioned in a type error the way you see a line number in a
35Python error — it's more like a Rust type mismatch, but the "types"
36include their position in the pipeline.
37
38## Stack shufflers
39
40Silo ships a small set of built-in
41:gloss[stack shufflers](./A1-glossary.simd#stack-shuffler) for rearranging
42the top few values on the stack. You'll see these in short pipelines;
43for anything longer, reach for `pop->` and `peek->` (covered later in
44this chapter) because named bindings read better than a string of
45shufflers.
46
47| Word   | Stack effect      | What it does                     |
48|--------|-------------------|----------------------------------|
49| `dup`  | `a → a a`         | Duplicate the top                |
50| `drop` | `a →`             | Discard the top                  |
51| `swap` | `a b → b a`       | Swap the top two                 |
52| `over` | `a b → a b a`     | Copy the second-from-top to top  |
53| `rot`  | `a b c → b c a`   | Rotate three: second becomes top |
54| `nip`  | `a b → b`         | Drop the second-from-top         |
55
56These are all polymorphic — the
57:gloss[stack-effect](./A1-glossary.simd#stack-effect) variables `a`, `b`,
58`c` stand for any type. For heap values, `dup` creates a new logical
59reference; the compiler's reference counter tracks the rest. That's
60covered in [the lifecycle chapter](./17-lifecycle.simd).
61
62Every shuffler has an **:gloss[arity variant](./A1-glossary.simd#arity-variant)**
63written with a `/N` suffix.
64The variant operates on groups of `N` consecutive values instead of
65single values, so you can rearrange several values in one move
66instead of chaining primitives. The `/2` family covers pairs:
67
68| Word     | Stack effect                  | What it does                       |
69|----------|-------------------------------|------------------------------------|
70| `dup/2`  | `a b → a b a b`               | Duplicate the top pair             |
71| `drop/2` | `a b →`                       | Discard the top pair               |
72| `swap/2` | `a b c d → c d a b`           | Swap the top two pairs             |
73| `over/2` | `a b c d → a b c d a b`       | Copy the second pair to the top    |
74| `nip/2`  | `a b c d → c d`               | Drop the second pair               |
75| `rot/2`  | `a b c d e f → c d e f a b`   | Rotate three pairs                 |
76
77So `3 5 dup/2` leaves `3 5 3 5` on the stack — useful when a pair
78needs to flow into two places. The bare `dup` is equivalent to
79`dup/1`; the bare forms are just the arity-1 shorthand.
80
81## Literal forms
82
83Numbers come in four representations, split along one important axis:
84**exact** versus **inexact**.
85
86```silo
87# Integers — exact, arbitrary precision. No overflow.
8842
89-7
900xFF
910b1010
922 100 pow                    # a very large integer, still exact
93
94# Fractions — exact rationals.
953/4
961/3 1/4 +                    # 7/12, not 0.5833...
97
98# Decimals — exact decimal, unbounded.
9919.99:D64
1000.1:D128                     # exactly 0.1 — no binary rounding error
101
102# Floats — IEEE 754 binary. Inexact by construction.
1033.14                         # F64 by default
1040.1                          # NOT exactly 0.1
105```
106
107Mixing an inexact value with an exact one contaminates: the result is
108inexact. That way you always know whether a number went through a
109Float at any point.
110
111The other primitive literals:
112
113```silo
114"hello world"                # UTF-8 string
115"tab:\there\n"               # escapes: \n \r \t \\ \" \0
116"\u{1F600}"                  # Unicode escape
117
118'a'                          # a single codepoint (the Char type)
119'\u{0301}'                   # combining accent — also a codepoint
120
121true false                   # Bool
122
123'ok                          # a symbol — a globally interned atom
124'error                       # with O(1) equality
125
1260..10                        # a range, 0 inclusive to 10 exclusive
1275..                          # from 5 onward
128..10                         # up to 10
129..                           # unbounded
130
131b"raw bytes"                 # a byte string
132```
133
134Symbols are Lisp-style atoms: two `'ok` literals anywhere in the
135program compare equal in constant time, and they carry no payload.
136They're useful when you want a cheap, distinct tag without reaching
137for a full enum.
138
139## Identifiers
140
141Identifiers in Silo are liberal: Unicode letters to begin, then letters
142or digits or a handful of punctuation characters. Hyphens are part of
143the identifier — `my-function` is one name, not a subtraction. The
144punctuation `?`, `!`, `'`, `/`, `<`, `>` is all valid, as are Unicode
145symbols in general — math operators like `∈`, `⊆`, `∪`, arrows like
146`↦`, and any other symbol character your domain calls for:
147
148```silo
149my-function                  # hyphens
150is-valid?                    # trailing ?
151u8>int                       # angle brackets in a conversion name
152str>bytes                    # another conversion
153m'                           # trailing apostrophe
154```
155
156Unicode math symbols are valid word names too. Silo's set-theoretic
157traits use them directly, so `a b ∪` really does mean set union:
158
159```silo
160a b ∪                        # union
161a b ∩                        # intersection
162x s ∈                        # membership
163p q ⊆                        # subset
164```
165
166Non-Latin scripts work anywhere a Latin-script word would:
167
168```silo
169土豆 pop-> p
170čuõʹnnj pop-> g
171μαθηματικά pop-> m
172```
173
174There's nothing special about these examples — they're ordinary
175identifiers under Unicode's XID_Start and XID_Continue rules, plus
176the extra punctuation Silo allows. Use whatever reads best for the
177domain you're modelling.
178
179The convention — not the rule — is:
180
181- **Lowercase** starts a word (a function, a local binding).
182- **Uppercase** starts a type or a constructor.
183
184So `Point` is a type and `point` is a variable holding one. This is
185the same convention Haskell uses.
186
187## Words
188
189A word is a function. You declare one with `:fn`:
190
191```silo
192:fn square ( Int -> Int )
193  dup *
194:end
195```
196
197The signature `( Int -> Int )` is the **stack effect**. It reads "pops
198one `Int`, pushes one `Int`." The body is ordinary Silo code. `dup`
199duplicates the top of the stack, and `*` pops two values and pushes
200their product.
201
202Call a word just by naming it:
203
204```silo
2055 square                     # 25
206```
207
208Stack effects can consume or produce multiple values. Here's a word
209that takes two integers and produces one:
210
211```silo
212:fn add ( Int Int -> Int )
213  +
214:end
215
2163 4 add                      # 7
217```
218
219The compiler checks every call against the signature. If you try to
220call `square` with a `Str` on top of the stack, the program doesn't
221compile.
222
223## Local bindings
224
225Threading values through a stack works for small words but gets
226unreadable quickly. For anything beyond a line or two, you'll
227:gloss[bind intermediate values to names](./A1-glossary.simd#local-binding).
228The two forms are:
229
230```silo
231pop->   name                 # pops the top of the stack into `name`
232peek->  name                 # copies the top into `name`, leaves it on the stack
233```
234
235Bindings are lexically scoped to the enclosing word or quotation, and
236shadowing is allowed. Here's a word that computes Euclidean distance
237between two points:
238
239```silo
240:fn distance ( F64 F64 F64 F64 -> F64 )
241  pop-> y2 pop-> x2 pop-> y1 pop-> x1
242  x2 x1 - peek-> dx
243  y2 y1 - peek-> dy
244  dx dx * dy dy * + sqrt
245:end
246```
247
248A couple of things to notice here:
249
250- The `pop->` bindings appear in **reverse order** of the signature.
251  The signature says the arguments go on left-to-right, so the last
252  one pushed is on top, and that's the first one popped.
253- `peek->` is useful when you want both the name *and* the value still
254  on the stack — here `dx` stays on the stack so the next operation
255  can use it, and is also bound to the name `dx` for readability.
256
257Shadowing is fine:
258
259```silo
260:fn example ( Int -> Int )
261  pop-> x
262  x 2 * pop-> x              # inner x shadows the outer x
263  x 1 +
264:end
265```
266
267## Key points
268
269- A program is a stream of values flowing through a single typed
270  stack.
271- Everything is postfix. No infix operators, no precedence rules.
272- Four numeric representations — Int, Fraction, Decimal, Float —
273  with exact/inexact contamination rules that keep you honest.
274- Identifiers can contain hyphens, `?`, `!`, Unicode — so
275  `is-sorted?` and `u8>int` are single names.
276- Every word carries a stack-effect signature; the compiler uses it
277  like a type signature.
278- `pop->` moves a value off the stack into a name; `peek->` copies
279  without consuming. Both are lexically scoped.
280
281The next chapter covers [control flow](./03-control-flow.simd) —
282conditionals, pattern matching, loops, early returns, and error
283short-circuiting.