Skip to main content

SLOP Language Specification

Version 2.0


1. Lexical Structure

1.1 Encoding

UTF-8. Indentation: 4 spaces (no tabs).

1.2 Comments

# Single line comment

# Multi-line comments are just
# multiple single-line comments

1.3 Keywords

if elif else for in with match def return emit stop
try catch break continue pause
and or not true false none range limit rate parallel timeout

1.4 Identifiers

identifier := [a-zA-Z_][a-zA-Z0-9_]*

1.5 Literals

# Numbers
42 # integer
3.14 # float
1_000_000 # underscores allowed

# Strings
"hello" # double quotes
'hello' # single quotes
"line 1\nline 2" # escape sequences
"{var} is {x}" # interpolation with {}

# Collections
[1, 2, 3] # list
{a: 1, b: 2} # map
{a, b, c} # set (if no colons)

1.6 Operators

# Arithmetic
+ - * / % **

# Comparison
== != < > <= >=

# Logical
and or not

# Membership
in not in

# Access
. [] ?. ?[]

# Assignment
= += -= *= /=

# Pipeline
| ->

2. Types

2.1 Primitive Types

TypeExamplesNotes
nonenoneNull value
booltrue, false
int42, -1, 1_00064-bit
float3.14, -0.5, 1e1064-bit
string"hello", 'world'UTF-8

2.2 Collection Types

TypeLiteralNotes
list[1, 2, 3]Ordered, mutable
map{a: 1, b: 2}Key-value pairs
set{a, b, c}Unique values

2.3 Special Types

# Enum (inline or defined)
enum(pending, active, done)

# Schema (for LLM calls)
{
name: string,
age: int,
tags: list(string),
status: enum(active, inactive)
}

# Range
range(10) # 0..9
range(1, 10) # 1..9
range(0, 10, 2) # 0, 2, 4, 6, 8

2.4 Type Annotations (Optional)

def greet(name: string) -> string:
return "Hello, {name}"

count: int = 0
items: list(string) = []

3. Expressions

3.1 Primary Expressions

# Literals
42
"hello"
[1, 2, 3]
{a: 1, b: 2}

# Identifiers
foo
bar.baz
items[0]
data?.optional?.field

# Parenthesized
(a + b) * c

3.2 Operators

Precedence (highest to lowest):

  1. () [] . ?.
  2. **
  3. not - (unary)
  4. * / %
  5. + -
  6. < > <= >=
  7. == !=
  8. in not in
  9. and
  10. or

3.3 Conditionals (Expression Form)

result = value if condition else other

status = match code:
200 -> "ok"
404 -> "not found"
500 -> "error"
_ -> "unknown"

3.4 Collection Operations

# Indexing
items[0]
items[-1]
items[1:3]
items[:5]
items[::2]

# Map access
data["key"]
data.key # Same as data["key"]
data?.key # None if data is none

# Comprehensions (bounded)
[x * 2 for x in items]
[x for x in items if x > 0]
{k: v * 2 for k, v in pairs}

3.5 Pipeline Expressions

# Pipeline operator
items | filter(x -> x > 0) | map(x -> x * 2) | take(10)

# Arrow for lambdas in pipelines
items.filter(x -> x > 0)
items.map(x -> x.name)
items.sort(x -> x.date)

4. Statements

4.1 Assignment

x = 1
x, y = get_pair()
x += 1
data.field = value
items[0] = new_value

4.2 If Statement

if condition:
do_this()

if condition:
do_this()
else:
do_that()

if a:
path_a()
elif b:
path_b()
elif c:
path_c()
else:
default()

4.3 For Statement

All for loops iterate over bounded collections.

# Basic iteration
for item in items:
process(item)

# With index
for i, item in enumerate(items):
print("{i}: {item}")

# With bounds and rates
for item in items with limit(100):
process(item)

for item in items with rate(10/s):
process(item)

for item in items with parallel(5):
process(item)

for item in items with limit(100), rate(10/s), parallel(5), timeout(30s):
process(item)

# Range (always bounded)
for i in range(10):
print(i)

for i in range(start, end):
print(i)

Modifiers:

ModifierMeaning
limit(N)Maximum N iterations
rate(N/s)N per second
rate(N/m)N per minute
rate(N/h)N per hour
parallel(N)N concurrent executions
timeout(Ns)Timeout in seconds
timeout(Nm)Timeout in minutes

4.4 Match Statement

match value:
1 -> handle_one()
2 -> handle_two()
n if n > 10 -> handle_large(n)
_ -> handle_default()

match result:
{ok: data} -> process(data)
{error: msg} -> log_error(msg)
_ -> panic("unexpected")

match response.status:
200 -> response.body
404 -> none
code -> error("HTTP {code}")

4.5 Return Statement

def get_value():
return 42

def maybe_value(flag):
if flag:
return "yes"
return "no"

4.6 Emit Statement

Produces output from the script:

emit(result)
emit(count: 10, status: "done")
emit({data: results, meta: {time: elapsed}})

4.7 Stop Statement

Terminates execution:

if error_condition:
emit(error: "something wrong")
stop

# With rollback
stop with rollback

4.8 Expression Statement

print("hello")
service.do_something()
items.append(value)

5. Functions

5.1 Definition

def function_name(param1, param2):
body
return result

def with_defaults(a, b=10, c="default"):
return a + b

def with_types(name: string, count: int) -> list(string):
return [name] * count

5.2 Restrictions

No recursion:

# ERROR: recursive call
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1) # FORBIDDEN

No nested definitions:

# ERROR: nested function
def outer():
def inner(): # FORBIDDEN
pass
return inner

No closures over mutable state:

# ERROR: closure
def make_counter():
count = 0
def increment(): # FORBIDDEN
count += 1
return count
return increment

5.3 Lambdas

Single-expression only:

# Arrow syntax for inline functions
double = x -> x * 2
add = (a, b) -> a + b

# Used in pipelines
items.map(x -> x.name)
items.filter(x -> x.active)
items.sort(x -> x.date, reverse: true)

6. Service Calls

6.1 MCP Services

# Service calls use dot notation
result = service_name.tool_name(args)

# Examples
contacts = salesforce.query("SELECT * FROM Contact")
company = clearbit.lookup(email: "test@example.com")
results = web.search("query", limit: 10)
file_content = filesystem.read("/path/to/file")

6.2 LLM Calls

result = llm.call(
prompt: "Your prompt with {variable} interpolation",
schema: {
field1: string,
field2: int,
field3: enum(option1, option2, option3),
field4: list(string),
field5: {
nested_field: string,
nested_num: float
}
}
)

# Optional parameters
result = llm.call(
prompt: "...",
schema: {...},
model: "claude-sonnet",
max_tokens: 1000,
temperature: 0.7,
system: "You are a helpful assistant."
)

# Access result
print(result.field1)
for item in result.field4:
process(item)

7. Built-in Functions

7.1 Type Functions

int("42") # Parse int
float("3.14") # Parse float
str(42) # To string
bool(value) # To boolean
list(iterable) # To list
set(iterable) # To set
map(key_vals) # To map

7.2 Collection Functions

len(collection) # Length
min(collection) # Minimum
max(collection) # Maximum
sum(collection) # Sum
sorted(collection) # Sorted copy
reversed(collection) # Reversed copy
enumerate(collection) # (index, item) pairs
zip(coll1, coll2) # Pair items
range(stop) # 0 to stop-1
range(start, stop) # start to stop-1
range(start, stop, step)

7.3 String Functions

s.upper() # Uppercase
s.lower() # Lowercase
s.strip() # Trim whitespace
s.split(sep) # Split to list
s.join(list) # Join list
s.replace(old, new) # Replace
s.startswith(prefix)
s.endswith(suffix)
s.contains(substr) # Or: substr in s

7.4 List Methods

items.append(x) # Add to end
items.extend(other) # Add all
items.insert(i, x) # Insert at index
items.remove(x) # Remove first x
items.pop() # Remove last
items.pop(i) # Remove at index
items.clear() # Remove all
items.index(x) # Find index
items.count(x) # Count occurrences
items.sort() # Sort in place
items.reverse() # Reverse in place

7.5 Map Methods

m.keys() # All keys
m.values() # All values
m.items() # (key, value) pairs
m.get(key, default) # Get with default
m.pop(key) # Remove and return
m.update(other) # Merge in other
m.clear() # Remove all
key in m # Check key exists

7.6 Pipeline Functions

items | filter(predicate) # Keep matching
items | map(transform) # Transform each
items | flat_map(transform) # Transform and flatten
items | take(n) # First n
items | drop(n) # Skip first n
items | take_while(pred) # Take while true
items | drop_while(pred) # Drop while true
items | unique() # Deduplicate
items | unique(key_fn) # Dedupe by key
items | sort() # Sort
items | sort(key_fn) # Sort by key
items | group(key_fn) # Group by key
items | chunk(n) # Chunk into lists of n
items | flatten() # Flatten one level
items | first() # First item or none
items | last() # Last item or none

7.7 Utility Functions

print(values...) # Debug output
log_debug(msg) # Log at debug level
log_info(msg) # Log at info level
log_warn(msg) # Log at warn level
log_error(msg) # Log at error level
sleep(seconds) # Pause execution
now() # Current timestamp
uuid() # Generate UUID
hash_md5(value) # MD5 hash (also hash_sha256, hash_sha512, hash_hmac)
json_parse(string) # Parse JSON
json_stringify(val) # To JSON string

8. Error Handling

8.1 Try/Catch

try:
result = risky_operation()
catch error:
log.error("Failed: {error}")
result = default_value

# Specific error types
try:
data = api.fetch(id)
catch NotFoundError:
data = none
catch RateLimitError as e:
sleep(e.retry_after)
data = api.fetch(id)
catch error:
emit(error: str(error))
stop

8.2 Error Propagation

# Errors propagate up by default
def load_data():
return api.fetch() # Error bubbles up if fetch fails

# Explicit error
def validate(data):
if not data.email:
error("Email required") # Raises error
return data

8.3 Optional Chaining

# Returns none if any part is none
value = data?.user?.profile?.name

# With default
value = data?.user?.name or "Anonymous"

# Safe index
first = items?[0]

9. Input/Output

9.1 Script Input

# Input is available as 'input' variable
task = input.task
options = input.options or {}
limit = input.limit or 100

9.2 Script Output

# Emit produces output
emit(result)
emit(status: "done", count: len(results))

# Multiple emits accumulate
for batch in batches:
result = process(batch)
emit(batch_result: result) # Streamed output

9.3 Intermediate Results

# Store for later steps or debugging
store_set("key", value)
stored_value = store_get("key")
store_delete("key")

Script-level resume/replay checkpoints (slop resume, --checkpoint-dir) are a CLI feature, not a callable built-in — see the Safety & Resource Limits page.


10. Grammar Summary

program = statement* ;

statement = simple_stmt NEWLINE
| compound_stmt ;

simple_stmt = assignment
| expr_stmt
| return_stmt
| emit_stmt
| stop_stmt ;

compound_stmt = if_stmt
| for_stmt
| match_stmt
| try_stmt
| func_def ;

assignment = target "=" expr
| target "+=" expr
| target "-=" expr ;

if_stmt = "if" expr ":" block
("elif" expr ":" block)*
("else" ":" block)? ;

for_stmt = "for" pattern "in" expr modifiers? ":" block ;

modifiers = "with" modifier ("," modifier)* ;
modifier = "limit" "(" expr ")"
| "rate" "(" rate_expr ")"
| "parallel" "(" expr ")"
| "timeout" "(" time_expr ")" ;

match_stmt = "match" expr ":" NEWLINE match_arm+ ;
match_arm = INDENT pattern guard? "->" expr NEWLINE ;
guard = "if" expr ;

try_stmt = "try" ":" block catch_clause+ ;
catch_clause = "catch" (NAME ("as" NAME)?)? ":" block ;

func_def = "def" NAME "(" params? ")" return_type? ":" block ;

expr = pipeline_expr ;
pipeline_expr = or_expr ("|" pipe_call)* ;
or_expr = and_expr ("or" and_expr)* ;
and_expr = not_expr ("and" not_expr)* ;
not_expr = "not" not_expr | comparison ;
comparison = arith_expr (comp_op arith_expr)* ;
arith_expr = term (("+"|"-") term)* ;
term = factor (("*"|"/"|"%") factor)* ;
factor = ("+" | "-") factor | power ;
power = primary "**" factor | primary ;
primary = atom trailer* ;
trailer = "." NAME | "[" expr "]" | "(" args? ")" | "?." NAME | "?[" expr "]" ;
atom = NAME | NUMBER | STRING | list | map | "(" expr ")" | lambda ;

lambda = params "->" expr ;

Appendix A: Differences from Python

FeaturePythonSLOP
while loopsYesNo
RecursionYesNo
Nested functionsYesNo
*args, **kwargsYesNo
eval, execYesNo
importYesModule system
GeneratorsYesNo (use pipelines)
ClassesYesNo (use maps)
DecoratorsYesNo
Global variablesYesNo
lambdaMulti-exprSingle-expr only

Appendix B: Reserved for Future

async await yield class import from as
finally raise assert
global nonlocal del pass