Sloplang Language Reference
Sloplang is a dynamically-typed, expression-oriented scripting language designed for solving algorithmic puzzles (e.g. Advent of Code). It features clean syntax, first-class functions, pipeline operators, and built-in support for grids and graph algorithms.
Table of Contents
- Basics
- Data Types
- Variables
- Operators
- Control Flow
- Functions & Lambdas
- Pipeline Operator
- Structs & Enums
- Grids
- Mutation
- Standard Library
Basics
Comments
-- This is a line comment
--- This is a
block comment ---
Indentation
Sloplang uses tab-based indentation to delimit blocks. 4 spaces are automatically converted to tabs. Blank lines do not affect indentation.
Expressions
Everything is an expression. The last expression in a block is its return value. This includes let, for, if, match, and loop.
Data Types
| Type | Examples | Notes |
|---|---|---|
| Int | 42, -5, 0 |
64-bit signed integer |
| Float | 3.14, -2.5 |
64-bit floating point |
| String | "hello", 'world', "" |
Supports interpolation and escapes |
| Bool | true, false |
|
| Nil | nil |
Keyword literal; implicit from blocks without a value |
| List | [1, 2, 3], [] |
Ordered, heterogeneous |
| Object | { x: 10, y: 20 }, {:} |
Key-value pairs (string keys) |
| Set | {1, 2, 3}, {} |
Only Int, String, Bool elements |
| Struct | Point { x: 1, y: 2 } |
Named record type |
| Enum | Color::Red |
Tagged union |
| Grid | grid "ab\ncd" ... |
2D grid of mapped values |
Strings
Both single and double quotes work identically:
let greeting = "hello"
let name = 'world'
Interpolation -- embed expressions inside {...}:
let name = "world"
print "hello {name}" -- hello world
print 'sum is {1 + 2}' -- sum is 3
Escape sequences: \n \t \\ \" \' \{ \}
Multiline strings -- triple-quoted, strips leading/trailing newlines:
let text = """
first line
second line
"""
let other = '''
also works
with single quotes
'''
Strings as Lists
Strings work everywhere lists do. Functions that operate on lists automatically treat strings as lists of characters:
"hello" | reverse -- "olleh"
"hello" | first -- "h"
"abracadabra" | distinct -- "abrcd"
"hello" | where ? != "l" -- "heo"
"dcba" | order_by ? -- "abcd"
"hello" | contains "ell" -- true (substring check)
"hello" | map ? -- ["h", "e", "l", "l", "o"]
"hello" | take 3 -- "hel"
"hello" | skip 2 -- "llo"
Functions that preserve structure (reverse, distinct, where, order_by, take, skip) return strings. Functions that transform (map, flat_map, enumerate, zip) return lists.
Truthiness
- Truthy: non-zero Int, all Float, all String,
true, non-empty collections - Falsy:
false,nil,0
Variables
let x = 10 -- immutable binding
let mut y = 20 -- mutable binding
y = 30 -- reassignment (mutable only)
let returns the assigned value:
print let x = 42 -- 42
Destructuring
let [a, b, c] = [1, 2, 3]
let { x, y } = { x: 10, y: 20 }
Operators
Arithmetic
| Operator | Description | Types |
|---|---|---|
+ |
Add / concatenate | Int, Float, String, List |
- |
Subtract | Int, Float |
* |
Multiply / cartesian product / repeat | Int, Float, List |
/ |
Divide (integer for Int) | Int, Float |
% |
Modulo | Int |
Cartesian Product & Repeat
[1, 2] * [3, 4] -- [[1, 3], [1, 4], [2, 3], [2, 4]]
[1, 2] * 3 -- [1, 2, 1, 2, 1, 2]
Comparison
| Operator | Description |
|---|---|
== |
Equal |
!= |
Not equal |
< |
Less than |
> |
Greater than |
<= |
Less than or equal |
>= |
Greater than or equal |
Logical
| Operator | Description |
|---|---|
and |
Logical AND (short-circuit, returns actual value) |
or |
Logical OR (short-circuit, returns actual value) |
not / ! |
Logical NOT (unary) |
and and or return actual values (Python-style):
andreturns the first falsy value, or the last value if all truthyorreturns the first truthy value, or the last value if all falsy
"hello" or "default" -- "hello"
nil or "fallback" -- "fallback"
nil or 0 or "yes" -- "yes"
1 and 2 and 3 -- 3
nil and "never" -- nil
This makes or useful as a null-coalescing operator:
let val = lookup[key] or 0
Set/List Operations
| Operator | Description |
|---|---|
+ |
Union (sets), concatenation (lists) |
* |
Cartesian product (lists) |
& |
Intersection |
^ |
Symmetric difference (XOR) |
Unary
| Operator | Description |
|---|---|
- |
Negation (Int, Float) |
not / ! |
Logical NOT (Bool) |
Precedence (high to low)
*,/,%+,-&^..,..=(range)<,>,<=,>===,!=andor
Control Flow
If / Else
if x > 0
"positive"
else if x < 0
"negative"
else
"zero"
If is an expression and returns the value of the executed branch. The else branch is optional (returns nil if omitted).
For Loop
for item in [1, 2, 3]
print item
Supports destructuring:
for [key, value] in [["a", 1], ["b", 2]]
print "{key}: {value}"
Supports ranges:
for x in 1..10
print x
Returns the value of the last iteration (or the break value). Use break to exit early.
While Loop
let mut i = 0
while i < 10
i = i + 1
while loops while the condition is truthy. Returns the value of the last iteration (or break value).
Loop (Infinite)
let mut i = 0
loop
if i >= 10
break i
i = i + 1
break exits the loop and can carry a return value (defaults to nil).
Continue
continue skips to the next iteration of for, while, or loop:
for x in 1..=10
if x % 2 == 0
continue
print x -- prints 1, 3, 5, 7, 9
Match
Pattern matching on a value:
match value
0 => "zero"
1 => "one"
_ => "other"
Supports enum variant destructuring:
match result
Result::Ok { value } => value
Result::Err { message } => print message
Identifiers in patterns bind the matched value. Use _ as a wildcard.
Cond-style Match
A match with no scrutinee evaluates conditions until one is truthy (like cond in Lisp):
match
x > 10 => "big"
x > 5 => "medium"
_ => "small"
Useful for FizzBuzz-style logic:
for x in 1..=20
match
x % 15 == 0 => "FizzBuzz"
x % 3 == 0 => "Fizz"
x % 5 == 0 => "Buzz"
_ => x
| print
Functions & Lambdas
Arrow Syntax
let double = x -> x * 2
let add = (a, b) -> a + b
Block body:
let process = x ->
let y = x * 2
y + 1
Currying via nested arrows:
let add = x -> y -> x + y
add 1 2 -- 3
Early Return
Use return to exit a lambda early:
let classify = x ->
if x < 0
return "negative"
if x == 0
return "zero"
"positive"
[1, -2, 3] | map x ->
if x < 0
return 0
x * 2
-- [2, 0, 6]
Implicit Lambda (?)
The ? placeholder creates an anonymous function inline. It works in pipelines, let values, object/struct field values, and trailing blocks:
[1, 2, 3] | map ? * 2 -- [2, 4, 6]
[1, 2, 3] | where ? > 1 -- [2, 3]
Multiple ? in the same expression refer to the same parameter:
[2, 3, 4] | map ? * ? -- [4, 9, 16]
Unary operators work with ?:
[3, 1, 2] | order_by -? -- [3, 2, 1] (descending)
[true, false] | where !? -- [false]
In function calls (wraps the whole expression including binary ops):
["ab", "cde"] | map len(?) * 2 -- [4, 6]
["hello world"] | map replace(?, " ", "_") -- ["hello_world"]
[1, 2, 3] | where int(?) > 1 -- [2, 3]
In string interpolation:
[1, 2, 3] | map "val: {?}" -- ["val: 1", "val: 2", "val: 3"]
["hi", "hey"] | map "{?} has {len(?)} chars"
In object fields (useful for config maps):
flood_fill({ grid: g, start: [0, 0], walkable: ? == 1 })
In index expressions:
let scores = { a: 10, b: 30 }
keys(scores) | order_by scores[?]
Calling Functions
double(5) -- parenthesized call
double 5 -- paren-free (single arg)
add 1 2 -- paren-free (multiple args)
add(1, 2) -- parenthesized (multiple args)
Paren-free calls correctly handle dot access and indexing on arguments:
print m.x -- prints field x of m
print list[0] -- prints first element
Pipeline Operator
The | operator passes the left-hand value as the first argument to the right-hand function:
[3, 1, 2] | reverse | first -- 2
"hello world" | words | len -- 2
[1, 2, 3, 4, 5]
| where ? > 2
| map ? * ?
| sum -- 50
Trailing Block Arguments
Functions that take a callback can receive it as an indented block:
[1, 2, 3] | map
? * 2
| print -- [2, 4, 6]
logs | lines | map
split(?, "|")
| where ?[1] == "ERROR"
| count
This is especially useful for complex callbacks that don't fit on one line.
Structs & Enums
Structs
struct Point { x, y }
let p = Point { x: 10, y: 20 }
print p.x -- 10
print p.y -- 20
Enums
Bare variants:
enum Color { Red, Green, Blue }
let c = Color::Red
Data-carrying variants:
enum Result { Ok { value }, Err { message } }
let ok = Result::Ok { value: 42 }
Match on enum variants:
match ok
Result::Ok { value } => print value
Result::Err { message } => print message
Grids
Create a 2D grid by mapping characters to values:
let g = grid "ab\ncd"
"a" => 1
"b" => 2
"c" => 3
"d" => 4
Use _ as a wildcard pattern for unmapped characters.
Accessing
g[0, 0] -- value at row 0, col 0
g[0] -- row 0 as a list
Indexing
- Positive indices:
0is first element - Negative indices:
-1is last,-2is second-to-last, etc. - Works on lists and strings
Slicing
Python-style slicing on lists and strings:
let xs = [1, 2, 3, 4, 5]
xs[1:3] -- [2, 3]
xs[:2] -- [1, 2]
xs[3:] -- [4, 5]
xs[-2:] -- [4, 5]
Mutation
Variables declared with let mut support field and index assignment:
Field Assignment
let mut m = { x: 1, y: 2 }
m.x = 10
print m -- {x: 10, y: 2}
Works on objects and structs. Structs only allow assignment to existing fields.
Index Assignment
let mut xs = [1, 2, 3]
xs[0] = 10
print xs -- [10, 2, 3]
Map Dynamic Key Access
Objects support dynamic key access via indexing:
let m = { x: 1, y: 2 }
let key = "x"
m[key] -- 1
m["z"] -- nil (missing keys return nil)
Keys are coerced to strings, so integer indices work too:
let m = frequencies [1, 2, 2, 3]
m[2] -- 2
Ranges
Range syntax creates lists of integers:
1..5 -- [1, 2, 3, 4] (exclusive end)
1..=5 -- [1, 2, 3, 4, 5] (inclusive end)
0..3 -- [0, 1, 2]
Ranges work in for loops:
for x in 1..=10
print x
Standard Library
Parsing & Strings
| Function | Signature | Description |
|---|---|---|
lines |
String -> List[String] |
Split string by newlines |
words |
String -> List[String] |
Split string by whitespace |
chars |
String -> List[String] |
Split string into characters |
ints |
String -> List[Int] |
Extract all integers (incl. negative) from string |
split |
(String, String) -> List[String] |
Split string by separator |
split_on |
(List, value) -> List[List] |
Split a list on occurrences of a value |
trim |
String -> String |
Remove leading/trailing whitespace |
replace |
(String, String, String) -> String |
Replace all occurrences of a substring |
starts_with |
(String, String) -> Bool |
Check if string starts with prefix |
ends_with |
(String, String) -> Bool |
Check if string ends with suffix |
upper |
String -> String |
Convert to uppercase |
lower |
String -> String |
Convert to lowercase |
index_of |
(String|List, value) -> Int|nil |
Find index of substring/element (nil if not found) |
int |
value -> Int |
Parse/convert to integer (accepts String, Float, Int) |
float |
value -> Float |
Parse/convert to float (accepts String, Int, Float) |
bool |
value -> Bool |
Convert to boolean based on truthiness |
Higher-Order Functions
| Function | Signature | Description |
|---|---|---|
map |
(List|String, fn) -> List |
Apply function to each element |
where |
(List|String, fn) -> List|String |
Filter elements by predicate |
flat_map |
(List|String, fn) -> List |
Map then flatten one level |
find |
(List|String, fn) -> value|nil |
First element matching predicate (nil if none) |
any |
(List|String, fn) -> Bool |
True if any element matches predicate |
all |
(List|String, fn) -> Bool |
True if all elements match predicate |
fold |
(List|String, init, fn) -> value |
Reduce with accumulator: fn(acc, item) |
reduce |
(List|String, fn) -> value |
Like fold but uses first element as init |
order_by |
(List|String, fn) -> List|String |
Sort by key function |
min_by |
(List|String, fn) -> value |
Element with minimum key value |
max_by |
(List|String, fn) -> value |
Element with maximum key value |
group_by |
(List|String, fn) -> List[List] |
Group by key: [[key, [items]], ...] |
Aggregation
| Function | Signature | Description |
|---|---|---|
sum |
List -> Int|Float |
Sum all elements |
product |
List -> Int |
Multiply all elements |
min |
List -> value or (a, b, ...) -> value |
Minimum element (1 arg = collection, 2+ = compare args) |
max |
List -> value or (a, b, ...) -> value |
Maximum element (1 arg = collection, 2+ = compare args) |
count |
List -> Int |
Number of elements (alias for len) |
len |
collection -> Int |
Length of List, String, Object, Set, or Struct |
first |
List|String -> value |
First element |
last |
List|String -> value |
Last element |
List Manipulation
| Function | Signature | Description |
|---|---|---|
sort |
List|String -> List|String |
Sort elements in ascending order |
at |
(List|String, Int) -> value |
Element at index (supports negative indices) |
reverse |
List|String -> List|String |
Reverse element order |
distinct |
List|String -> List|String |
Remove duplicates (preserves order) |
skip |
(List|String, Int) -> List|String |
Skip first n elements |
take |
(List|String, Int) -> List|String |
Take first n elements |
zip |
(List|String, List|String) -> List[List] |
Pair elements: [[a1, b1], [a2, b2], ...] |
enumerate |
List|String -> List[List] |
Add indices: [[0, item], [1, item], ...] |
flatten |
List -> List |
Flatten one level of nesting |
chunks |
(List, Int) -> List[List] |
Split into chunks of size n |
windows |
(List, Int) -> List[List] |
Sliding windows of size n |
transpose |
List[List] -> List[List] |
Transpose rows and columns |
frequencies |
List -> Map |
Count occurrences: {value: count, ...} |
join |
(List, String) -> String |
Join elements with separator |
repeat |
(List|String, Int) -> List|String |
Repeat n times |
contains |
(List|Set|String, value) -> Bool |
Check membership or substring |
push |
(List|String, value) -> List|String |
Append element (returns new collection) |
Set Operations
| Function | Signature | Description |
|---|---|---|
add |
(Set, value) -> Set |
Add element to set (returns new set) |
remove |
(Set|Struct, value) -> Set|Struct |
Remove element from set or field from struct |
Object & Struct Inspection
| Function | Signature | Description |
|---|---|---|
keys |
Object|Struct -> List[String] |
List of key/field names |
values |
Object|Struct -> List |
List of values |
Math
| Function | Signature | Description |
|---|---|---|
abs |
number -> number |
Absolute value (Int or Float) |
pow |
(Int, Int) -> Int |
Exponentiation |
sign |
number -> Int |
Returns -1, 0, or 1 |
gcd |
(Int, Int) -> Int |
Greatest common divisor |
lcm |
(Int, Int) -> Int |
Least common multiple |
even |
Int -> Bool |
True if even |
odd |
Int -> Bool |
True if odd |
Combinatorics
| Function | Signature | Description |
|---|---|---|
choose |
(List, Int) -> List[List] |
All combinations of size n |
permute |
(List, Int) -> List[List] |
All permutations of size n |
[1, 2, 3] | choose 2 -- [[1, 2], [1, 3], [2, 3]]
[1, 2, 3] | permute 2 -- [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
Grid Functions
| Function | Signature | Description |
|---|---|---|
width |
Grid -> Int |
Grid width (columns) |
height |
Grid -> Int |
Grid height (rows) |
neighbors4 |
(Int, Int) -> List[List] |
4-directional neighbors (up, down, left, right) |
neighbors8 |
(Int, Int) -> List[List] |
8-directional neighbors (all surrounding cells) |
Graph Algorithms
bfs
Breadth-first search. Takes a config object with:
start-- starting nodegoal-- target value or predicate functionneighbors-- function returning neighbors of a node
bfs({ start: 0, goal: 10, neighbors: x -> [x + 1, x + 2] })
Returns the path from start to goal as a list.
flood_fill
Flood fill on a grid. Takes a config object with:
grid-- the grid to fillstart--[row, col]starting positionwalkable-- predicate function for traversable cells
flood_fill({ grid: g, start: [0, 0], walkable: ? != "#" })
Returns a list of [row, col] positions reached.
I/O
| Function | Signature | Description |
|---|---|---|
print |
value -> Nil |
Print value to stdout |
input |
String -> String |
Read file contents (path relative to .slop file) |
part |
(Int, value) -> Nil |
Print formatted: Part N: value |
input resolves paths relative to the directory containing the .slop script, so you can keep input files alongside your solution:
-- If your script is at puzzles/day01.slop
-- this reads puzzles/input.txt
let data = input "input.txt"
part supports trailing block syntax:
part 1
[1, 2, 3] | sum
part 2
[1, 2, 3] | product
-- Part 1: 6
-- Part 2: 6