Back to Sloplang

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

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):

  • and returns the first falsy value, or the last value if all truthy
  • or returns 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)

  1. *, /, %
  2. +, -
  3. &
  4. ^
  5. .., ..= (range)
  6. <, >, <=, >=
  7. ==, !=
  8. and
  9. or

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: 0 is first element
  • Negative indices: -1 is last, -2 is 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 node
  • goal -- target value or predicate function
  • neighbors -- 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 fill
  • start -- [row, col] starting position
  • walkable -- 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

You are not logged in.