SSymbolic

Symbolic for Dummies

The complete, leave-nothing-out, hold-your-hand guide to the Symbolic language. If you can copy and paste, you can do every single thing in this document. There is no step you have to "already know."

How to use this page:

  • Read top to bottom the first time.
  • After that, jump to the section you need.
  • Every code block is a real program or fragment. Lines starting with ::: are comments — the computer ignores them; we use them to show what prints.

The one rule to remember: code lives in files ending .sym. That's it. Not .rs, not .py. Symbolic programs are .sym files.


Table of contents

  1. Install it (every platform)
  2. Run your first program
  3. The shape of a program
  4. The 4 grammar rules (the master key)
  5. Printing things
  6. Values you can write down (literals)
  7. Registers (variables)
  8. Every operator
  9. Bit widths (the comma)
  10. Making decisions (if / else)
  11. Repeating things (loops)
  12. Stopping (halt, break, panic)
  13. Pattern matching
  14. Functions
  15. Built-in functions (the toolbox)
  16. Memory and segments
  17. Reading input
  18. Hash cells (global storage)
  19. Structs (your own types)
  20. Enums and matching them
  21. Generics
  22. Traits, impls, and methods
  23. Closures
  24. Ternary computing (base-3)
  25. Building for any platform (targets)
  26. Using sigil (the project tool)
  27. When something goes wrong
  28. The complete symbol cheat-sheet
  29. "How do I…?" recipe list
  30. Where to go next

1. Install it (every platform)

You need two programs: symc (the compiler) and sigil (the project tool). Pick the row that matches your computer. Type the commands exactly.

Linux / macOS / FreeBSD — easiest (one line)

curl -fsSL https://dist.sigil-lang.org/get.sh | sh

This downloads symc + sigil and adds them to your PATH automatically. Open a new terminal afterward so they work. (No curl? Install it, or use the clone method below.)

Any Unix from a copy of the repo (no internet needed after cloning)

git clone https://github.com/oda-00/symbolic.git
cd symbolic
sh dist/install.sh          # copies the prebuilt symc + sigil and sets PATH

Want to rebuild from source instead of using the prebuilt copy? Run bash install.sh — it builds the compiler with itself and proves it works, then installs. Either way, open a new terminal when it finishes.

Windows

Symbolic runs on Windows. The one-line curl method is Unix-only, so on Windows use a clone:

git clone https://github.com/oda-00/symbolic.git
cd symbolic
powershell -ExecutionPolicy Bypass -File dist\install.ps1

This copies symc.exe + sigil.exe and adds them to your user PATH. Open a new PowerShell window afterward.

Android (Termux)

Same as Linux — the installers detect Termux automatically:

sh dist/install.sh

Did it work?

Open a new terminal and type:

sigil help

If you see a list of commands, you're done. 🎉 If it says "command not found," you didn't open a new terminal, or PATH wasn't set — run the installer again and read its last line.


2. Run your first program

There are two ways to run Symbolic code. Learn both; use whichever you like.

Way A — the easy way, with sigil (recommended)

sigil makes a little project for you and runs it.

sigil new hello      # makes a folder called hello/ with a starter program
cd hello
sigil run            # builds and runs it

You should see:

hello, rune!

That's a working program. The code is in hello/src/main.sym — open it and change the text.

Way B — the raw way, with symc

symc reads a program from "standard input" and writes a finished program to "standard output." On Unix:

echo '((hi!\n)) > @screen
!!' > hi.sym
symc < hi.sym > hi        # compile: hi.sym  ->  hi (a real program)
chmod +x hi               # mark it runnable (Unix only)
./hi                      # run it          ->  hi!

On Windows it's almost the same, but the output is a .exe and you don't need chmod:

symc < hi.sym > hi.exe
.\hi.exe

< means "feed this file in." > means "save the output here." This is your shell's plumbing, not Symbolic's.


3. The shape of a program

A Symbolic program is just a list of statements, top to bottom. There is no main, no imports, no boilerplate.

((hello, world\n)) > @screen   ::: do this
!!                             ::: then stop
  • (( ... )) is text (a "string").
  • > means flow: send the thing on the left to the place on the right.
  • @screen is the screen.
  • !! stops the program.
  • ::: starts a comment — everything after it on that line is ignored.

That is a complete program. Run it and it prints hello, world.


4. The 4 grammar rules (the master key)

Symbolic has no keywords (no if, while, return, etc.). Instead a few symbols combine by rules. Learn these and you can guess the language.

RULE 1.  -  negates      flips a symbol's meaning   (+ add → - subtract)
RULE 2.  +  extends      broadens/loosens a symbol  (= equal → =+ greater-equal)
RULE 3.  ,  reduces      shrinks the size in bits   ($a → $a, is 32-bit)
RULE 4.  spacing matters touching = one combined symbol; spaced = two symbols
PLUS:    doubling/tripling intensifies   (+ add → ++ multiply → +++ power)

Watch one family grow:

+    add
++   multiply     ::: doubled = intensified
+++  power        ::: tripled = intensified more
-    subtract     ::: negated
--   divide
---  modulo (remainder)

And the question-mark family:

?[c]{ ... }      do once if c is true        (an "if")
?[c]{ ... }?     do over and over while true (a "loop" — note the trailing ?)
-?{ ... }        otherwise                    (an "else" — the - negates the ?)
??               match a value against cases

You don't have to memorize every symbol — Section 28 is a full cheat-sheet. Just know the rules so the symbols make sense.

The naming rule: names for registers, functions, and types use letters, digits, and _, and are at most 6 characters long. Keep names short.


5. Printing things

Four ways to put something on the screen. Use the right one for what you have.

((words go here\n)) > @screen   ::: 1) print text. \n is a new line.
:wrint { [42] } !               ::: 2) print a NUMBER in decimal, + a newline   -> 42
:wrch  { [65] } !               ::: 3) print ONE byte. 65 is the letter 'A'      -> A
:wrch  { [10] } !               ::: 10 is a newline byte

The fourth way prints a chunk of memory (you'll meet it in Section 16):

:wrbuf { [$ptr] & [$len] } !    ::: 4) print $len bytes starting at address $ptr

Quick rules:

  • Have text? (( ... )) > @screen.
  • Have a number and want to read it (like 42)? :wrint.
  • Have a single character/byte? :wrch.

⚠️ $x > @screen sends the raw byte, not the digits. 99 > @screen prints the character c (byte 99), not 99. To see 99, use :wrint.


6. Values you can write down (literals)

42            ::: a decimal number
0xFF          ::: hexadecimal      -> 255
0b1010        ::: binary           -> 10
3.14          ::: a floating-point number
(A)           ::: a character      -> its byte value, 65
((hi\n))      ::: a string (text). \n = newline, \t = tab, \\ = backslash

The default number is a 64-bit integer. Characters are just their byte number, so (A) is exactly 65.


7. Registers (variables)

A register is Symbolic's word for a variable. It holds one 64-bit number.

Make one (and read it)

80 > ~$bill          ::: create $bill, put 80 in it
:wrint { [$bill] } ! ::: 80
  • ~$name = write (create or change). The ~ means "I am binding this."
  • $name = read (just look at it; changes nothing).

Change one

To change a register, write to it again. The pattern is read → compute → write back:

0 > ~$n              ::: $n = 0
$n + 1 > ~$n         ::: read $n (0), add 1, store -> $n = 1
$n + 1 > ~$n         ::: $n = 2
:wrint { [$n] } !    ::: 2

That read-compute-write line is the single most common thing you will write.

Copying

5 > ~$a
$a > ~$b             ::: $b is now 5 too (numbers are copied)
:wrint { [$b] } !    ::: 5

💡 The ~ is also Symbolic's "ownership" mark (like Rust). For plain numbers it just means "write here." It matters more for big values, where flowing a value into a new name moves it. The compiler will warn you if you read a register before you ever wrote it.


8. Every operator

Every operator takes values and produces a value you can flow somewhere (print it, store it, etc.). Examples use :wrint so you can see the result.

Arithmetic

:wrint { [3 + 4]   } !   ::: 7    add
:wrint { [3 ++ 4]  } !   ::: 12   multiply
:wrint { [3 +++ 4] } !   ::: 81   power  (3 to the 4th)
:wrint { [10 - 3]  } !   ::: 7    subtract
:wrint { [10 -- 3] } !   ::: 3    divide (whole-number; throws away remainder)
:wrint { [10 --- 3]} !   ::: 1    modulo (the remainder)

Comparison (these give 1 for true, 0 for false)

Read them by the grammar: = is the base, + pushes toward greater, - pushes toward less.

:wrint { [5 == 5]  } !   ::: 1    equal
:wrint { [5 != 3]  } !   ::: 1    not equal
:wrint { [5 =+ 3]  } !   ::: 1    greater-or-equal   (>=)
:wrint { [5 =++ 3] } !   ::: 1    greater-than       (>)
:wrint { [5 -= 3]  } !   ::: 0    less-or-equal      (<=)
:wrint { [5 --= 3] } !   ::: 0    less-than          (<)

Because they give 1/0, you can store the answer:

7 =++ 4 > ~$big          ::: $big = 1
:wrint { [$big] } !      ::: 1

Bitwise and shifts

:wrint { [12 & 10]   } !  ::: 8     AND
:wrint { [12 &+ 10]  } !  ::: 14    OR    (& extended by +)
:wrint { [12 -&+ 10] } !  ::: 6     XOR   (- negates, &, extended)
:wrint { [-& 0]      } !  ::: -1    NOT   (a prefix; flips all bits)
:wrint { [1 -< 4]    } !  ::: 16    shift left
:wrint { [256 <+ 2]  } !  ::: 64    shift right
:wrint { [1 --< 1]   } !  ::: rotate left
:wrint { [1 <++ 1]   } !  ::: rotate right

Precedence (what binds tightest)

From loosest to tightest:

comparisons        ==  !=  --=  -=  =+  =++
bitwise            &   &+  -&+
shifts / rotates   -<  <+  --<  <++
add / subtract     +   -
multiply/div/mod   ++  --  ---
power              +++       (right to left)

So 2 + 3 ++ 4 means 2 + (3 ++ 4) = 2 + 12 = 14.

🧠 Brain-dead-simple safety tip: if you're not sure how an expression groups, don't guess — split it. Put each step into its own register on its own line. It always works and always reads clearly:

3 ++ 4 > ~$t       ::: 12
2 + $t > ~$r       ::: 14

9. Bit widths (the comma)

Numbers are 64-bit by default. A touching comma shrinks the size (Rule 3). You can ignore this until you do low-level work.

$a        ::: 64-bit (normal)
$a,       ::: 32-bit
$a,,      ::: 16-bit
$a,,,     ::: 8-bit
>,        ::: a 32-bit flow

(A comma with spaces around it is a different thing — a separator. Spacing matters, Rule 4.)


10. Making decisions (if / else)

if

?[ condition ]{ body } runs the body once if the condition is true (non-zero).

5 > ~$x
?[$x =++ 3]{                ::: if x > 3
    ((big\n)) > @screen
}
::: big

else

-?{ body } is the "otherwise" branch (the - negates the ?).

?[$x =++ 10]{ ((big\n))   > @screen }
-?{           ((small\n)) > @screen }
::: small

else-if (a ladder)

Nest them. Check the most specific case first. The closing braces stack up at the end:

?[$n == 0]{ ((zero\n))  > @screen } -?{
?[$n == 1]{ ((one\n))   > @screen } -?{
            ((other\n)) > @screen }}

Three opens, so two }} at the end close the chain.


11. Repeating things (loops)

Add a trailing ? to the closing brace and the if becomes a loop: it re-checks the condition every pass and repeats while it's true.

0 > ~$i
?[$i --= 5]{               ::: while i < 5
    :wrint { [$i] } !
    $i + 1 > ~$i           ::: ⚠️ advance the counter, or it loops forever!
}?
::: 0 1 2 3 4

🧠 The #1 beginner bug is forgetting $i + 1 > ~$i. If your program hangs, you forgot to move the counter. Press Ctrl-C to stop it.

A "loop forever then break" is also common (see next section for !!>):

0 > ~$i
?[1 == 1]{                 ::: 1 == 1 is always true -> forever
    ?[$i =+ 5]{ !!> }      ::: until i >= 5, then break out
    :wrint { [$i] } !
    $i + 1 > ~$i
}?
::: 0 1 2 3 4

12. Stopping (halt, break, panic)

!!       stop the whole program, success (like exit 0)
!!!      panic: stop immediately because something is wrong (abort)
!!>      break out of the loop you're inside right now
!!>      ::: jump to just after the innermost }?

Use !! at the very end of most programs (top-level). Use !!> to leave a loop early. Use !!! only when continuing would be a bug.


13. Pattern matching

?? checks a value against a list of cases. Each case is pattern > { body }. _ means "anything else," so you always cover every possibility.

3 > ~$n
?? $n {
    0 > { ((zero\n))  > @screen }
    1 > { ((one\n))   > @screen }
    _ > { ((other\n)) > @screen }   ::: $n is 3 -> prints "other"
}

This is cleaner than a long if/else-if ladder when you're comparing one value to many fixed options.


14. Functions

A function is a named, reusable block that can take inputs and give back a value.

Declare one

:add { [i64:$a] & [i64:$b] }   ::: name :add, two inputs $a and $b
    $a + $b >!?                ::: >!? gives the value back to the caller
  • :add is the name.
  • { [i64:$a] & [i64:$b] } is the input list. Each input is [type:$name], separated by &. i64 means "64-bit integer" — the normal number type.
  • >!? returns the value.

Why write i64:? It tells the compiler "this is a new input named $a," as opposed to a value you're passing in. Inputs always have the type written.

Call one

:name { [value] & [value] } ! — the ! means "do it now." Capture the answer with > ~$dest:

:add { [20] & [22] } ! > ~$sum
:wrint { [$sum] } !            ::: 42

A function with no inputs is called :name { } !.

As many inputs as you want

:sum8 { [i64:$a]&[i64:$b]&[i64:$c]&[i64:$d]&[i64:$e]&[i64:$f]&[i64:$g]&[i64:$h] }
    $a + $b + $c + $d + $e + $f + $g + $h >!?
:sum8 { [1]&[2]&[3]&[4]&[5]&[6]&[7]&[8] } ! > ~$r
:wrint { [$r] } !              ::: 36

Return early

>!? can appear anywhere, even inside an if:

:max { [i64:$a] & [i64:$b] }
    ?[$a =+ $b]{ $a >!? }      ::: if a >= b, hand back a and stop
    $b >!?
:max { [3] & [9] } ! > ~$m
:wrint { [$m] } !              ::: 9

A function that calls itself (recursion)

:fac { [i64:$n] }
    ?[$n --= 2]{ 1 >!? }       ::: when n < 2, the answer is 1
    $n - 1 > ~$m
    :fac { [$m] } ! > ~$r      ::: call myself with n-1
    $n ++ $r >!?               ::: n * fac(n-1)
:fac { [5] } ! > ~$f
:wrint { [$f] } !              ::: 120

🧠 Program shape: define all your functions first, then write the top-level statements that use them, then !!.


15. Built-in functions (the toolbox)

These are always available — no import needed. They're how your program talks to the world. Call them like any function (with !).

Call it like this What it does
:wrint { [n] } ! print number n in decimal, plus a newline
:wrch { [b] } ! print one byte b (e.g. 10 = newline, 65 = A)
:wrbuf { [ptr] & [len] } ! print len bytes starting at address ptr
:rdall { } ! > ~$p read all of standard input into memory; $p points at [length (8 bytes)][the bytes…]
:alloc { [n] } ! > ~$p grab n bytes of memory; $p is the address
:ld8 { [addr] } ! > ~$v load 1 byte from addr
:ld64 { [addr] } ! > ~$v load 8 bytes (one number) from addr
:st8 { [addr] & [v] } ! store 1 byte v at addr
:st64 { [addr] & [v] } ! store 8 bytes v at addr

(There are more for ternary math — see Section 24 — and raw system calls via :sysc for advanced file/OS work.)


16. Memory and segments

Segments

Big named areas of the computer, all written @name:

Segment What it is
@screen the display (standard output)
@inp the keyboard (standard input)
@mem general memory (@mem[address])
@stck the stack
@sec secure memory
@net @rng @time @sys network, randomness, the clock, system vectors

You've already used one: ((hi\n)) > @screen.

The heap: grab memory, put things in it, read them back

:alloc { [64] } ! > ~$p         ::: get 64 bytes; $p is where they are
123456789 > ~$v
:st64 { [$p] & [$v] } !          ::: write 8 bytes at $p
:ld64 { [$p] } ! > ~$r           ::: read them back
:wrint { [$r] } !                ::: 123456789

65 > ~$c
:st8 { [$p + 8] & [$c] } !       ::: write the byte 65 ('A') at $p+8
:ld8 { [$p + 8] } ! > ~$b
:wrch { [$b] } !                  ::: A
:wrch { [10] } !                  ::: newline

Addresses are just numbers, so to walk through memory you add an index: [$p + 8 + $i].


17. Reading input

:rdall reads everything typed/piped in. The memory it returns starts with an 8-byte length, then the raw bytes. This program echoes its input back:

:rdall { } ! > ~$buf
:ld64 { [$buf] } ! > ~$len       ::: first 8 bytes = how many bytes followed
0 > ~$i
?[$i --= $len]{                  ::: for each byte...
    :ld8 { [$buf + 8 + $i] } ! > ~$ch
    :wrch { [$ch] } !            ::: ...print it
    $i + 1 > ~$i
}?
!!

Run it and feed it text:

printf 'round trip' | ./echo
::: round trip

18. Hash cells (global storage)

A hash cell is a named box that any part of your program can read or write — a global. Three kinds, by how long they live:

Written Lives Use it for
#name this run only (changeable) counters, global tables
##name survives across runs (where supported) saved state
###name baked into the program, never changes constants

Declare with a starting value, then read/write like a register:

###MAX 1000          ::: a constant
#count 0             ::: a changeable global

###MAX > ~$m
:wrint { [$m] } !    ::: 1000

5 > ~#count          ::: write the cell
#count + 1 > ~#count ::: change it
:wrint { [#count] } !::: 6

Content-addressed cells (a built-in hash map)

#[:fn & key] runs your hash function :fn on key to pick a slot, then reads/writes there. Collisions are handled for you.

:h { [i64:$k] } $k --- 64 >!?    ::: a hash function: key mod 64

111 > ~#[:h & 7]                 ::: store 111 at slot hash(7)
222 > ~#[:h & 71]                ::: 71 mod 64 = 7 -> collides; handled automatically
#[:h & 7]  > ~$a  :wrint { [$a] } !   ::: 111
#[:h & 71] > ~$b  :wrint { [$b] } !   ::: 222

19. Structs (your own types)

A struct bundles several fields into one type. Name a type with ::Name.

::Pt { i32:[x] & i32:[y] }       ::: a type "Pt" with two fields x and y

Make one, read fields with ., write a field with > ~$p.field:

::Pt { [100] & [50] } > ~$p      ::: create a Pt; x=100, y=50
:wrint { [$p.x] } !              ::: 100
75 > ~$p.x                       ::: fields can change
:wrint { [$p.x] } !              ::: 75
:wrint { [$p.y] } !              ::: 50

(i32 is a 32-bit integer; f64 is a decimal number. Use i64 when unsure.)


20. Enums and matching them

An enum is a type that is one of several named variants. Declare variants with .Name:

::Shape { .Circle { i64:[r] } & .Square { i64:[s] } }

You match a value's variant the same way as Section 13, with ??. For plain numbers, matching looks like this:

3 > ~$n
?? $n {
    0 > { :wrint { [100] } ! }
    1 > { :wrint { [101] } ! }
    _ > { :wrint { [999] } ! }   ::: $n is 3 -> 999
}

_ is the catch-all, so every case is covered.


21. Generics

A generic lets one function work for any type. Write the type placeholder as a name between dollar signs: $T$.

:id $T$ { [$T$ $x] }   ::: $T$ is "some type"; $x is of that type
    $x >!?
:id { [42] } ! > ~$r
:wrint { [$r] } !       ::: 42

One function body serves every type you call it with.


22. Traits, impls, and methods

^.^ attaches methods (functions that belong to a type) to a struct. Inside, $self is the value the method was called on.

::Pt { i32:[x] & i32:[y] }
^.^ ::Pt {
    :sum { } $self.x + $self.y >!? }   ::: a method named :sum
}
  • ^.^ ::Pt { ... } — methods that belong to ::Pt.
  • ^.^ ::Pt ::Draw { ... } — methods that fulfill a trait ::Draw (name the trait after the type).

Methods are resolved at compile time (no runtime lookup), so they're free — they compile to exactly the code you'd write by hand. This is what makes iterators (a struct holding its position, advanced by methods) cost nothing extra in a loop.


23. Closures

A closure is a little anonymous function written >{ ... }. It can capture registers from around it (by value — they're frozen at creation).

Capture-only (no inputs), call it with just !:

3 > ~$a
4 > ~$b
>{ $a + $b } > ~$add    ::: captures $a and $b
$add ! > ~$r            ::: call it
:wrint { [$r] } !       ::: 7

With an input, write type:$name > before the body, and pass an argument list when calling:

10 > ~$base
>{ i32:$x > $x + $base } > ~$f   ::: input $x, captures $base
$f ! { [5] } > ~$s
:wrint { [$s] } !                ::: 15

24. Ternary computing (base-3)

Symbolic has rare built-in base-3 types, for logic with a third state and for balanced-ternary math. You can skip this unless you need it.

Three-valued logic (trit): False / True / Unknown

Codes: 0=False, 1=True, 2=Unknown. Built-ins follow Kleene logic (Unknown spreads sensibly):

:tand { [1] & [2] } ! > ~$a  :wrint { [$a] } !   ::: 2   True AND Unknown = Unknown
:tor  { [0] & [1] } ! > ~$b  :wrint { [$b] } !   ::: 1   False OR True = True
:tnot { [1] } ! > ~$c        :wrint { [$c] } !   ::: 0   NOT True = False

:teq compares two trits. A trool adds a fourth state, Undefined, that always spreads (models hardware "don't care").

Balanced-ternary integers (digits -1, 0, +1)

:bt { [100] } ! > ~$a            ::: encode 100 in balanced ternary
:bt { [23]  } ! > ~$b
:btadd { [$a] & [$b] } ! > ~$c   ::: add them
:btp { [$c] } !                  ::: 123  (decode + print)
:bt { [-5] } ! > ~$d
:btp { [$d] } !                  ::: -5

Balanced ternary stores negative numbers without a separate sign bit.


25. Building for any platform (targets)

One symc builds for 13 platforms. Pick one with --target (default is x64-linux). The output is a finished binary — no extra tools needed.

symc --target x64-linux   < prog.sym > prog          # Linux x86-64
symc --target arm64-linux < prog.sym > prog          # Linux ARM
symc --target x64-windows < prog.sym > prog.exe      # Windows
symc --target wasm32      < prog.sym > prog.wasm     # WebAssembly (runs in a browser/WASI)

All the targets:

--target You get
x64-linux Linux x86-64 program
arm64-linux Linux ARM64 program
riscv64-linux Linux RISC-V program
loongarch64 Linux LoongArch program
x64-macos / arm64-macos macOS program (Intel / Apple Silicon)
ios-arm64 iOS program
android-arm64 Android program
x64-windows Windows .exe
x64-uefi UEFI application
x64-freebsd FreeBSD program
wasm32 WebAssembly (WASI)
spirv SPIR-V GPU shader

To run a binary built for another CPU on your machine, use an emulator, e.g. qemu-aarch64 ./prog for ARM, or wasmtime run prog.wasm for wasm.

With sigil, you don't pass --target on the command line — you set it once in your project's Sigil.toml (next section).


26. Using sigil (the project tool)

sigil is like cargo (Rust) or npm (JavaScript). A Symbolic project is called a rune. Here is every command:

Command What it does
sigil new <name> make a new project folder <name>/
sigil build compile this project to target/out
sigil run build, then run it
sigil test build and run it; passes if it exits cleanly
sigil bench build and run it as a benchmark (also times the compile)
sigil clean delete the target/ folder
sigil add <name> add a dependency to Sigil.toml
sigil install download the dependencies listed in Sigil.toml
sigil update re-download the latest of each dependency
sigil publish scan, then publish this rune to the registry
sigil scan compile to wasm and run it sandboxed in a browser to check it's safe
sigil help print the command list

A typical session

sigil new game        # make it
cd game
sigil run             # build + run
# ...edit src/main.sym...
sigil run             # run again
sigil test            # check it still works

The project file: Sigil.toml

Every project has one. It's plain text:

[package]
name = "game"
version = "0.1.0"
description = "my game"
authors = ["you"]
entry = "src/main.sym"     # the file to compile

[dependencies]              # other runes you depend on

[build]
target = "x64-linux"        # change this to build for another platform!

To build for Windows instead, change one line: target = "x64-windows". To build for the web: target = "wasm32".

Dependencies

sigil add liba      # writes liba into [dependencies]
sigil install       # fetches it into runes/

sigil finds the compiler through an environment variable SIGIL_CC (set for you by the installer). The registry it downloads from is SIGIL_REGISTRY.


27. When something goes wrong

The compiler prints an error with a line number. The two you'll hit most:

error: ... name '...' exceeds maximum length of 6 characters

→ A register/function/type name is too long. Names are max 6 characters. Shorten it.

warning: register 'x' may be used before initialization

→ You read $x before you ever wrote ... > ~$x. Write a value into every register before you read it.

Other common situations:

Symptom Cause Fix
Program hangs forever a loop's counter isn't advancing add $i + 1 > ~$i inside the loop; press Ctrl-C to stop
Prints a weird letter instead of a number you flowed a number to @screen use :wrint { [n] } ! instead
command not found: sigil PATH not set / old terminal open a new terminal; re-run the installer
permission denied: ./prog (Unix) forgot to mark it runnable chmod +x prog
Wrong/garbage math result precedence surprise split the expression into one step per line (Section 8 tip)

28. The complete symbol cheat-sheet

NAMES (the sigil tells you what kind of thing it is)
  $x        register (variable)        ~        write / ownership
  :f        function / label           ::T      a type
  #h        hash cell (this run)       ##h      hash cell (saved)
  ###h      hash cell (constant)       @s       memory segment
  $T$       generic type               :::      comment

LITERALS
  42 decimal   0xFF hex   0b1010 binary   3.14 float
  (A) char     ((text\n)) string         \n \t \\ escapes

ARITHMETIC         COMPARISON          BITWISE / SHIFT
  +   add            ==  equal           &    and
  ++  multiply       !=  not equal       &+   or
  +++ power          =+  >=              -&+  xor
  -   subtract       =++ >               -&   not (prefix)
  --  divide         -=  <=              -<   shift left
  --- modulo         --= <               <+   shift right
                                         --<  rotate left
                                         <++  rotate right

FLOW & CONTROL
  >        flow value into destination     >!?     return from function
  ?[c]{}   if (run once)                    ?[c]{}? loop while c
  -?{}     else                             ??      pattern match
  !        call a function                  !!      halt (exit ok)
  !!>      break out of a loop              !!!     panic (abort)

GROUPING
  [ ]   arguments / parameters / grouping   { }  blocks
  &     separator (and bitwise-and)         .    field access
  ,     width reduction (touching)

ADVANCED
  #[:fn & key]   content-addressed hash slot
  ^.^            method / trait impl block   >{ ... }  closure
  ::T { f & f }  struct/enum type             $self     the method's own value

29. "How do I…?" recipe list

I want to… Do this
Print text (( text\n )) > @screen
Print a number :wrint { [n] } !
Print one character :wrch { [byte] } !
Make a variable value > ~$name
Change a variable $name + 1 > ~$name
Add / multiply / divide a + b / a ++ b / a -- b
Remainder a --- b
Compare a == b, a =++ b (>), a --= b (<)
Do something if true ?[cond]{ ... }
Otherwise ... -?{ ... }
Loop while true ?[cond]{ ... }?
Stop a loop early !!>
End the program !!
Match one value to many cases ?? $x { 0 > {..} _ > {..} }
Write a function :name { [i64:$a] } $a + 1 >!?
Call a function :name { [arg] } ! > ~$result
Get memory :alloc { [bytes] } ! > ~$p
Store / load a number :st64 { [$p] & [v] } ! / :ld64 { [$p] } !
Read all input :rdall { } ! > ~$buf (then :ld64 { [$buf] } is the length)
Make a global #name 0 then x > ~#name
Make a constant ###NAME value
Define a type ::Pt { i32:[x] & i32:[y] }
Use a field $p.x (read), v > ~$p.x (write)
Start a project sigil new app && cd app && sigil run
Build for Windows set target = "x64-windows" in Sigil.toml
Build for the web set target = "wasm32" in Sigil.toml

30. Where to go next

You now know how to do everything in the language. To go deeper:

  • TUTORIAL.md — build seven real programs, from a tip calculator to Conway's Game of Life.
  • BY_EXAMPLE.md — every feature as a tiny runnable snippet.
  • LANGUAGE_GUIDE.md — the precise reference.
  • ../examples/ — real programs you can run and edit. The examples/features/ folder has one minimal program per feature.
  • SELFHOSTING.md — how Symbolic compiles itself.
        Open examples/, change a number, run it, see what happens.
        That is the whole job. You can do this.