Menu

Operators

Parsley's operators mostly work as you'd expect from other languages, with a few notable additions: set operations on arrays and dictionaries, regex matching with ~, null coalescing with ??, inclusive ranges with .., and a family of I/O operators for files and databases.

Arithmetic

5 + 3               // 8
10 - 4              // 6
6 * 7               // 42
7 / 2               // 3       (integer division truncates)
7 / 2.0             // 3.5     (mixed → float)
7 % 3               // 1
-5                  // -5      (unary negation)

Mixed integer/float operations promote to float. Division by zero is a runtime error.

String & Array Arithmetic

Several operators are overloaded for strings and arrays:

"hello" + " world"  // "hello world"   (string concatenation)
"ha" * 3            // "hahaha"         (string repetition)
10 + "px"           // "10px"           (auto-coercion to string)

[1, 2] ++ [3, 4]    // [1, 2, 3, 4]    (array concatenation)
[1, 2] * 3          // [1, 2, 1, 2, 1, 2]
[1,2,3,4,5] / 2     // [[1, 2], [3, 4], [5]]  (chunking)

⚠️ ++ is the array/dictionary merge operator, not string concatenation. On non-arrays it wraps each side: "a" ++ "b"["a", "b"].

⚠️ + with a string on either side converts the other operand to a string and concatenates. 10 + "px" is "10px", not an error.

Dictionary Merge

++ merges dictionaries. Right-side values win on key conflicts:

{a: 1, b: 2} ++ {b: 3, c: 4}  // {a: 1, b: 3, c: 4}

Path & URL Arithmetic

+ joins path and URL segments:

@/usr/local + "bin"    // @/usr/local/bin
@./config + "app.json" // @./config/app.json

Money Arithmetic

Money supports +, - (same currency only), and *, / with scalars. Uses banker's rounding. See Money.

DateTime Arithmetic

DateTime values support + and - with integers (days) and durations. See DateTime.

Comparison

5 == 5               // true
5 != 3               // true
3 < 5                // true
5 > 3                // true
3 <= 3               // true
5 >= 5               // true

Works on numbers, strings, money, and datetimes. Equality (==, !=) works on all types.

⚠️ String comparison uses natural sort order: "file2" < "file10" is true. This means numeric substrings within strings are compared by value, not lexicographically.

Logical

!true                // false
not false            // true
true & false         // false
true | false         // true
true and false       // false
true or false        // true
true && false        // false   (alias for &)
true || false        // true    (alias for |)

All six forms (&/&&/and, |/||/or) are equivalent. and has higher precedence than or:

true | false & false   // true   — parsed as: true | (false & false)

Logical operators use truthiness (see Booleans for the full list of falsy values).

Null Coalescing

?? returns the left side unless it is null, in which case it evaluates and returns the right side. Short-circuits — the right side is not evaluated if the left is non-null.

null ?? "default"    // "default"
false ?? "default"   // false      (not null, so left wins)
0 ?? "default"       // 0          (not null, so left wins)

⚠️ ?? checks for null only, not general falsiness. Use || if you want to fall through on any falsy value.

Membership

in and not in test membership across arrays, dictionaries, and strings:

2 in [1, 2, 3]           // true    (element in array)
"x" in {x: 1, y: 2}     // true    (key in dictionary)
"ell" in "hello"          // true    (substring in string)
"z" not in [1, 2, 3]     // true
5 in null                 // false   (null-safe, never errors)

Set Operations on Arrays

&, |, and - perform set operations when both operands are arrays:

[1,2,3] & [2,3,4]   // [2, 3]         (intersection)
[1,2,3] | [2,3,4]   // [1, 2, 3, 4]   (union, deduplicated)
[1,2,3] - [2,3]     // [1]             (difference)

These also work on dictionaries (matching by key):

let d = {a: 1, b: 2, c: 3}
d & {b: 99, c: 99}  // {b: 2, c: 3}   (intersection, values from left)
d - {b: 0}          // {a: 1, c: 3}    (difference, values in right ignored)

⚠️ & and | on arrays perform set operations. On non-array values they are boolean and/or. This overloading is resolved by operand type at runtime.

Pattern Matching

~ matches a string against a regex and returns an array of matches (or null on no match). !~ returns a boolean.

"hello-123" ~ /([a-z]+)-(\d+)/
// ["hello-123", "hello", "123"]  — [0] is full match, [1..] are capture groups

"abc" ~ /z/          // null     (no match)
"abc" !~ /z/         // true     (no match → true)
"abc" !~ /b/         // false    (match → false)

Schema Checking

is and is not test whether a record or table was created from a given schema:

record is User       // true if record's schema is User
record is not User   // negated

Non-record/table values always return false (no error). See Data Model.

Range

.. creates an inclusive integer range. Supports ascending and descending:

1..5                 // [1, 2, 3, 4, 5]
5..1                 // [5, 4, 3, 2, 1]

Indexing & Slicing

let a = [10, 20, 30]
a[0]                 // 10
a[-1]                // 30       (negative index from end)
a[0:2]               // [10, 20] (slice, end exclusive)

let s = "hello"
s[1]                 // "e"
s[-2:]               // "lo"

The access rule: absence is null, out-of-range is an error

One rule covers every kind of access, dot or bracket:

  • Absence yields null. Reaching into null, or asking for a dictionary key that doesn't exist, returns null — it never errors. This is what lets chains walk through missing data:

    let u = {name: "Ada"}
    u.address.city          // null   (address is missing — chain doesn't error)
    u["address"]["city"]    // null   (bracket access behaves identically)
    null["anything"]        // null   (indexing into null returns null)
    
  • An out-of-range position is an error. Indexing a present array or string outside its bounds is almost always a bug, so it fails loudly at the fault site:

    let a = [1, 2, 3]
    a[10]                // Error: index 10 out of range (length 3)
    

Optional access [?n]

[?n] opts into leniency for out-of-range positions — returning null instead of erroring:

let a = [1, 2, 3]
a[?10]               // null   (instead of an index error)
a[?0]                // 1      (works normally when in bounds)

Dictionaries have no positional range, so [?] is unnecessary there — a missing key is already null.

Propagate, recover, or assert

Because absence is null, every hop in a chain has three modes, chosen with ??:

data["a"]["b"]                     // propagate — null flows through (the common case)
data["a"] ?? "fallback"            // recover   — supply a value at this hop
data["a"] ?? fail("a is required") // assert    — fail loudly, here, with a reason

?? fail("…") is the way to be strict mid-chain: it stops with a clear, located error exactly where you require a value to exist.

Spread

... is used in two contexts:

Rest in destructuring — collects remaining elements:

let [first, ...rest] = [1, 2, 3, 4]
// first = 1, rest = [2, 3, 4]

let {a, ...rest} = {a: 1, b: 2, c: 3}
// a = 1, rest = {b: 2, c: 3}

Spread in tags — expands a dictionary into tag attributes:

let attrs = {class: "card", id: "main"}
<div ...attrs>"content"</div>

I/O Operators

These operators are syntactic sugar for file and database operations. They are covered in detail in their respective manual pages.

File I/O

Operator Meaning Example
<== Read from file data <== @./file.txt
<=/= Fetch from URL data <=/= @https://api.example.com
==> Write to file data ==> @./output.txt
==>> Append to file line ==>> @./log.txt
=/=> Write to network target payload =/=> JSON(@https://api.example.com)
=/=>> Append to network target line =/=>> text(sftp, "/var/log/app.log")

<=/=, =/=>, and =/=>> are also usable as expressions — they return a response value that can be captured with let or used inline:

let response = <=/= JSON(@https://api.example.com/users)
let result = payload =/=> JSON(@https://api.example.com/items)

See File I/O and HTTP & Networking.

Database

Operator Meaning Example
<=?=> Query one row db <=?=> "SELECT ..."
<=??=> Query many rows db <=??=> "SELECT ..."
<=!=> Execute (INSERT, etc.) db <=!=> "INSERT ..."

See Database.

Command Execution

Operator Meaning Example
<=#=> Execute shell command @shell("ls", ["-la"]) <=#=> null

See Shell Commands.

Precedence Table

From lowest to highest:

Precedence Operators
1 (lowest) ,
2 | || or ??
3 & && and
4 == != ~ !~ in not in is <=?=> <=??=> <=!=>
5 < > <= >=
6 + - ..
7 ++
8 * / %
9 -x !x not x (prefix)
10 [] . (index/access)
11 (highest) () (call)

Key Differences from Other Languages

  • ++ merges arrays/dicts, not increment. There is no increment operator.
  • & | are overloaded: boolean logic on scalars, set operations on arrays/dicts.
  • ?? is null-only, not falsy-coalescing. false ?? x returns false.
  • ~ returns match data (array or null), not a boolean. Use !~ for a boolean "does not match" test.
  • String < > use natural sort: "file2" < "file10" is true.
  • Integer division truncates: 7 / 2 is 3. Mix with a float for decimal result: 7 / 2.0.
  • No ternary operator: use if (cond) a else b (it's an expression).

See Also