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.