A comprehensive manual for the Scraps programming language
scraps new <project_dir> — scaffolds a project with .gitignore, clanker.toml, and void/main.scraps (Hello World). scraps build [project_dir] — validates clanker.toml, sources dir, and module files exist. scraps --verify <file.scraps> — runs in safe mode (no IO/network/etc.), fails on test(...) and disallowed calls.
- Paradigm: Expression-first with newline-terminated statements
- Values:
Int,Float,Bool,Str,Box(list),Function,None - Purity: Functions are pure by default, use
rewirefor side effects - Unicode: Full Unicode support for identifiers and strings
print "Hello, World!"
x = 42
y = x + 17
print y
- Create a new project scaffold:
scraps new myproj- Creates
.gitignore,clanker.toml, andvoid/main.scraps(Hello World)
- Build (validate manifest and module files):
scraps build [project_dir]- Parses
clanker.tomland checks that allmodulesexist under[paths].sources(defaultvoid)
- Verify (safe run: blocks IO/network/side effects):
scraps --verify path/to/file.scraps- Fails on
test(...)assertions or disallowed calls; prints focused Trace on errors - Summarizes assertions: e.g.
Verify passed (tests: N) - Disallows: file read/write, http/tcp/udp/tls/ws/raw sockets, event loop, proxy/timeouts/pool configuration, and module mutation (source/import/ship/rename)
- Allows typed input:
input_str,input_int(verify checks correct parsing;input_intfails on invalid integers) - Rewire ceremony is allowed in verify mode (type/memory safe); normal type rules still apply
- Philosophy: Verification should be about “proving it’s right,” not just “disallowing everything.” The current defaults are conservative for determinism and hermeticity, but the model supports richer checks.
- Near‑term options (and what they’d look like):
- Read‑only files: allow
read <- pathwithin project sandbox, fail if path escapes, reject non‑UTF‑8, enforce max size, hash input for reproducibility. - HTTP/network: allow with explicit fixtures or deterministic mocks (e.g.,
HTTP_FIXTURES=...), or require a recorded cassette; fail if response differs from the cassette. - Event loop/timers: allow with virtual time; fail on wall‑clock calls; require bounded waits.
- Proxy/timeout/pool: allow reading current config but block mutation, unless running in a capability sandbox.
- Module ops: allow
importfrom the declaredclanker.tomlonly; blockship/renameduring verify.
- Read‑only files: allow
- Capability/allow‑list flags (future):
scraps --verify --allow=read,http(import-only),import- Each allowed effect must pass logic rules (path sandboxing, cassette checks, virtual time). Fail if not satisfied.
- Tests as contracts: keep using
test(...)as contracts; pair with static analyses and symbolic checks to erase guards and enable stronger optimizations when obligations are proved.
print "=== verify contracts ==="
x = int(2.9)
test (x == 2)
b = int_box()
pack(1, 2, 3) -> b
test (count(b) == 3)
text = substring("hello", 0, 2)
test (text == "he")
print "ok"
Run: scraps --verify path/to/file.scraps
→ Verify passed (tests: 3)
print "=== verify input ==="
n = input_int("n? ")
test (n == 7)
Run: printf '7\n' | scraps --verify path/to/file.scraps
→ Verify passed (tests: 1)
- Run a file:
scraps path/to/file.scraps
- Syntax & Basics - Variables, expressions, comments
- Data Types - Numbers, strings, booleans, boxes
- Operators - Arithmetic, comparison, logical
- Control Flow - IF/ELSE, WHILE, TEST
- Functions - Definition, calls, purity
- Arrows & Data Flow - PACK, UNPACK, data manipulation
- Rewire System - Side effects and mutations
- Core-8 Hardware Functions - Low-level system access
- Character Classification - Text analysis
- Number Parsing - String to number conversion
- String Manipulation - Text processing
- Math Functions - Mathematical operations
- I/O Functions - Input/output operations
- Network Functions - TCP networking
- Complete Examples - Real-world code samples
- Function Reference - Alphabetical function list
- Error Handling - Common errors and solutions
- Performance Notes - Optimization tips
# Variable assignment
x = 42
name = "Alice"
is_ready = TRUE
# Expressions
result = x + 17
message = "Hello, " + name
# Single line comment
x = 42 # End of line comment
# Multi-line comments not supported
# Use multiple single-line comments instead
# Expressions are evaluated left to right with standard precedence
result = 2 + 3 * 4 # 14 (not 20)
grouped = (2 + 3) * 4 # 20
# Newlines terminate statements
x = 42
y = 17
Scraps now enforces static types per variable binding at runtime (with compile-time checks where possible):
- The first assignment establishes a variable’s type (Int, Float, Bool, Str, Box, Function, None).
- Any later assignment must match the original type. To change a type, explicitly use rewire (see below).
- Typed constructors/casts:
int(x)converts from Int/Float/Bool/Str → Int (string must be a valid integer)str(x)converts any value to its display string
- Typed box constructors:
int_box()creates an empty box intended for integersstr_box()creates an empty box intended for strings
Examples:
x = 1 # x is Int
x = 2 # OK
x = "two" # Compile/runtime TYPE error (different type)
y = str(5) # y is Str → "5"
z = int(2.9) # z is Int → 2
ib = int_box()
pack(1, 2, 3) -> ib # OK
pack("x") -> ib # Compile-time TYPE error (BoxInt expects Int elements)
sb = str_box()
pack("a", "b") -> sb # OK
place(0:"Z") -> sb # OK
place(1:9) -> sb # Compile-time TYPE error (BoxStr expects Str elements)
Type changes require an explicit rewire ceremony (see Rewire System).
- First assignment locks variable type; subsequent assignments must match unless rewired.
- Change type via rewire ceremony or
rewire_symbol("x")before assignment. - Casts:
int(x)→ Int (from Int/Float/Bool/Str; strings must parse)str(x)→ Str (display string)
- Typed boxes:
int_box()→ BoxInt; elements must be Int (compile-time checked when known)str_box()→ BoxStr; elements must be Str (compile-time checked when known)
- Example (type change):
x = 1
rewire x {
x = "now string"
} -> x
print x # now string
# Integers
age = 25
negative = -42
large = 999999
# Floating point
pi = 3.14159
temperature = -12.5
scientific = 1.23e-4
# String literals
greeting = "Hello, World!"
empty = ""
with_quotes = "She said \"Hello\""
# Unicode support
unicode_text = "🚀 Scraps Language 🎯"
chinese = "你好世界"
# Boolean values
is_true = TRUE
is_false = FALSE
# Boolean expressions
result = (x > 5) |< (y < 10) # Logical AND
# Create empty box
my_box = box()
# Add items
pack(42) -> my_box
pack("hello") -> my_box
pack(TRUE) -> my_box
# Access items
first_item = unpack(0) <- my_box
second_item = unpack(1) <- my_box
# Get size
size = count(my_box)
# None represents absence of value
empty_var = None
result = some_function() # May return None
# Basic arithmetic
sum = 5 + 3 # 8
difference = 5 - 3 # 2
product = 5 * 3 # 15
quotient = 5 / 3 # 1.666...
remainder = 5 mod 3 # 2
# Unary operators
negative = -42
positive = +17
# Equality
equal = (5 == 5) # TRUE
not_equal = (5 != 3) # TRUE
# Ordering
less = (3 < 5) # TRUE
less_equal = (3 <= 3) # TRUE
greater = (5 > 3) # TRUE
greater_equal = (5 >= 5) # TRUE
# Logical AND
both_true = TRUE |< TRUE # TRUE
mixed = TRUE |< FALSE # FALSE
- Unary (
-,+) - Multiplication, Division, Modulo (
*,/,mod) - Addition, Subtraction (
+,-) - Comparisons (
<,<=,>,>=) - Equality (
==,!=) - Logical AND (
|<)
# Simple IF
IF (x > 0) {
print "Positive number"
} ELSE {
print "Zero or negative"
}
# Nested conditions
IF (score >= 90) {
grade = "A"
} ELSE {
IF (score >= 80) {
grade = "B"
} ELSE {
IF (score >= 70) {
grade = "C"
} ELSE {
grade = "F"
}
}
}
# Note: ELSE is always required
# Basic loop
counter = 0
WHILE (counter < 5) {
print counter
counter = counter + 1
}
# Loop with condition
found = FALSE
i = 0
WHILE ((i < count(items)) |< (found == FALSE)) {
IF (unpack(i) <- items == target) {
found = TRUE
} ELSE {
i = i + 1
}
}
# Test assertions for validation (halts on failure)
x = 42
test (x == 42) # OK
test (x > 50) # Fails: halts with error pointing to line
# Use in testing and validation
result = calculate_something()
test (result > 0) # Halts with clear error if FALSE or non-boolean
Behavior:
- Evaluates the expression; if it is
TRUE, execution continues. - If
FALSE, execution halts with:TEST failed: condition evaluated to FALSE (at file:line). - If the expression is not boolean, execution halts with:
TEST failed: non-boolean value: <value>and context.
# clanker.toml - Project manifest file
[modules]
# Define module mappings
lexer = "lexer.scraps"
parser = "parser.scraps"
main = "main.scraps"
[paths]
# Source directory
sources = "void"# Import module by name (from clanker.toml)
import("lexer")
# Import with alias
import("lex") <- "lexer"
# After import, module's global variables and functions become available
print count(KEYWORDS) # Access KEYWORDS from lexer module
# Import multiple modules
import("lexer")
import("codegen")
import("parser")
# Create module factory function
fn(use()) {
# Define module contents
MODULE_VERSION = "1.0.0"
fn(use(x)) {
x * 2
} -> double_value
# Ship exports all definitions created in this function
ship_result = ship()
ship_result
} -> my_module
# Execute to create and export the module
result(my_module) # Returns "Module 'my_module' shipped with N exports"
# Rename a module in the module system
result1 = rename("old_module_name", "new_module_name")
print result1 # "OK" if successful
# After renaming, import with new name
import("new_module_name")
# Mark symbols as rewirable for dynamic binding
rewire_symbol("dynamic_var") # Returns None
rewire_symbol("dynamic_func") # Returns None
# Now these symbols can be used with rewire statements
fn(use()) {
rewire dynamic_var = "Hello World!"
rewire dynamic_func = some_function
dynamic_var
} -> setup_dynamics
Use the rewire ceremony to apply changes (including type changes) to an existing binding without introducing a new name. The output of the ceremony is the same variable:
# Rewire a variable in place; the result is the same symbol
x = 1
rewire x {
x = x + 41
} -> x
print x # 42
# Change type with rewire
rewire x {
x = "forty-two"
} -> x
print x # forty-two
Notes:
- The body runs in a function scope. On
result(...), the VM applies the new value of the target symbol back to the caller’s environment. - Type changes are permitted during rewire.
- No temporary variables are created; the ceremony returns the same symbol (avoids shadowing/renaming).
# my_module.scraps - Define functions and variables
CONSTANTS = box()
pack("VALUE1", "VALUE2") -> CONSTANTS
fn(use(x, y)) {
x + y
} -> add_function
# Use ship() for formal exports
fn(use()) {
# Reference definitions to export them
CONSTANTS
add_function
ship() # Export all definitions created in this scope
} -> my_module_factory
result(my_module_factory) # Execute to ship the module
Use Cases: Code organization, reusable libraries, modular compilation, dependency management
Performance: Module loading ~2-5ms, import resolution ~500ns per symbol
# Basic function
fn(use(x, y)) {
sum = x + y
sum
} -> add_numbers
# Function with multiple operations
fn(use(name, age)) {
greeting = "Hello, " + name
info = "You are " + age + " years old"
result = greeting + ". " + info
result
} -> create_greeting
# Call function
result = add_numbers(5, 3)
message = create_greeting("Alice", "25")
# Functions are expressions
total = add_numbers(10, add_numbers(5, 3))
# Functions are pure by default - no side effects
fn(use(x)) {
# This cannot modify variables outside the function
local_var = x * 2
local_var
} -> double_value
# To access function result, use result()
print result(double_value(5)) # Prints 10
# Pack single items
my_box = box()
pack(42) -> my_box
pack("hello") -> my_box
# Pack multiple items
pack(1, 2, 3) -> my_box
# Unpack by index
first = unpack(0) <- my_box
second = unpack(1) <- my_box
# Unpack with bounds checking
IF (count(my_box) > 2) {
third = unpack(2) <- my_box
} ELSE {
third = None
}
# Pick single element (same as unpack)
my_box = box()
pack("apple", "banana", "cherry", "date") -> my_box
second = pick(1) <- my_box # "banana"
# Pick multiple elements by indices
selected = pick(0, 2, 3) <- my_box # ["apple", "cherry", "date"]
# Pick from strings
text = "hello"
char = pick(1) <- text # "e"
chars = pick(1, 2, 4) <- text # "elo"
# Place value at specific index
my_box = box()
pack("apple", "banana", "cherry") -> my_box
# Replace existing value
place(1:"ORANGE") -> my_box # ["apple", "ORANGE", "cherry"]
# Place beyond current size (auto-resizes with None)
place(5:"GRAPE") -> my_box # ["apple", "ORANGE", "cherry", None, None, "GRAPE"]
# Multiple placements
place(0:"FIRST", 2:"THIRD") -> my_box
Typed boxes enforce element types at compile time when known:
ib = int_box()
place(0:"a") -> ib # Compile-time TYPE error (expects Int)
sb = str_box()
pack(1) -> sb # Compile-time TYPE error (expects Str)
# FISSION - Split string into characters
text = "hello"
chars = fission("") <- text
# chars = ["h", "e", "l", "l", "o"]
# FUSION - Join characters into string
chars = box()
pack("h", "e", "l", "l", "o") -> chars
text = fusion("") -> chars
# text = "hello"
# Split by single delimiter
sentence = "apple,banana,cherry"
fruits = fission(",") <- sentence
# fruits = ["apple", "banana", "cherry"]
# Split by multiple delimiters (NEW!)
source = "x = 42 + 17\nprint x"
delims = box()
pack(" ") -> delims
pack("\n") -> delims
tokens = fission(delims) <- source
# tokens = ["x", "=", "42", "+", "17", "print", "x"]
# CONTAINS - Check if item exists in box
keywords = box()
pack("print") -> keywords
pack("if") -> keywords
pack("while") -> keywords
result = contains(keywords, "print") # TRUE
result = contains(keywords, "xyz") # FALSE
# Count items in box
my_box = box()
pack(1, 2, 3) -> my_box
size = count(my_box) # 3
# Count characters in string
text = "hello"
length = count(fission("") <- text) # 5
# Rewire allows controlled side effects
counter = 0
fn(use()) {
rewire counter = counter + 1
counter
} -> increment
# Call and get result
new_value = result(increment())
print new_value # 1
# Rewire with string for dynamic binding
var_name = "dynamic_var"
rewire var_name = "Hello, World!"
# This creates a variable named "dynamic_var"
print dynamic_var # "Hello, World!"
Low-level hardware access functions for systems programming:
# Read from memory
value = mem_load(address, width) # width: 1, 2, 4, or 8 bytes
# Write to memory
mem_store(address, value, width)
# Memory barrier for synchronization
mem_fence()
# Atomic compare-and-swap
old_value = mem_cmpxchg(address, expected, new_value)
# Disable interrupts
old_state = int_disable()
# Enable interrupts
int_enable(old_state)
# Halt CPU until interrupt
cpu_halt()
# Get current time counter
current_time = time_counter()
# Get timer frequency
frequency = time_freq()
# Calculate elapsed time
start = time_counter()
# ... do work ...
end = time_counter()
elapsed_ns = (end - start) * 1000000000 / time_freq()
Use Cases: Device drivers, operating systems, real-time systems, hardware control
Performance: Memory operations <100ns, timing functions ~200-400ns
Functions for analyzing individual characters:
# Check character types
is_digit("5") # TRUE
is_digit("a") # FALSE
is_alpha("A") # TRUE
is_alpha("5") # FALSE
is_space(" ") # TRUE
is_space("a") # FALSE
is_alnum("A") # TRUE (alphanumeric)
is_alnum("!") # FALSE
# Operators: +, -, *, /, %, =, !, <, >, &, |, ^, ~
is_operator("+") # TRUE
is_operator("=") # TRUE
is_operator("a") # FALSE
# Punctuation: (, ), {, }, [, ], ,, ;, :, ., ?, ", ', `
is_punctuation("(") # TRUE
is_punctuation(".") # TRUE
is_punctuation("a") # FALSE
# Symbols: @, #, $, \, _
is_symbol("@") # TRUE
is_symbol("_") # TRUE
is_symbol("a") # FALSE
# Get comprehensive category
get_char_category("a") # "alpha"
get_char_category("5") # "digit"
get_char_category("+") # "operator"
get_char_category("(") # "punctuation"
get_char_category("@") # "symbol"
get_char_category(" ") # "space"
get_char_category("€") # "other"
# Get character code
code = char_code("A") # 65
# Create character from code
char = char_from_code(65) # "A"
Use Cases: Lexical analysis, input validation, text processing, parser construction, tokenization
Performance: ~583ns per operation, can process ~1.7M characters per second
Convert strings to numbers with automatic whitespace handling:
# Parse integers
num = parse_int("42") # 42
neg = parse_int("-17") # -17
spaces = parse_int(" 123 ") # 123 (auto-trim)
# Parse floating point
pi = parse_float("3.14159") # 3.14159
sci = parse_float("1.23e-4") # 0.000123 (scientific notation)
zero = parse_float("0.0") # 0.0
# Error handling
# parse_int("abc") -> Runtime error with descriptive message
Use Cases: Configuration parsing, user input processing, data file reading, calculator implementation
Performance: ~1250ns per operation, can parse ~800K numbers per second
Comprehensive text processing functions:
upper = to_upper("hello") # "HELLO"
lower = to_lower("WORLD") # "world"
mixed = to_upper("Hello!") # "HELLO!"
# Extract substring
text = "Hello, World!"
greeting = substring(text, 0, 5) # "Hello"
world = substring(text, 7, 12) # "World"
# Find substring
pos = index_of("Hello World", "World") # 6
not_found = index_of("abc", "xyz") # -1
# Check prefix/suffix
starts = starts_with("Hello", "He") # TRUE
ends = ends_with("World", "ld") # TRUE
Use Cases: Lexical analysis, template processing, configuration parsing, text search and replace
Performance: ~833ns per operation, excellent for text processing
Comprehensive mathematical operations:
PI # 3.14159...
TAU # 6.28318... (2π)
E # 2.71828...
# Absolute value and sign
abs(-5) # 5
sign(-3) # -1
sign(0) # 0
sign(7) # 1
# Min/max
min(5, 3) # 3
max(5, 3) # 5
# Power and roots
pow(2, 3) # 8
sqrt(16) # 4
sin(PI / 2) # 1.0
cos(0) # 1.0
tan(PI / 4) # 1.0
# Inverse functions
asin(1) # PI/2
acos(1) # 0
atan(1) # PI/4
atan2(1, 1) # PI/4
ln(E) # 1.0 (natural log)
log10(100) # 2.0 (base 10)
log2(8) # 3.0 (base 2)
floor(3.7) # 3
ceil(3.2) # 4
round(3.5) # 4
trunc(3.9) # 3
# Print values
print "Hello"
print 42
print TRUE
# Print expressions
print (5 + 3)
print ("Result: " + result)
# Read a line from stdin (without trailing newline)
name = input_str("Enter your name: ") # returns Str
print ("Hello, " + name)
# Read an integer safely by casting
age = input_int("Enter age: ")
print ("You are " + age + " years old")
Notes:
input_str([prompt]) -> stringreturns Strinput_int([prompt]) -> intreturns Int; errors if the input is not a valid integer
TCP networking capabilities:
# Connect to server
socket = tcp_connect("127.0.0.1", 8080)
# Send data
tcp_send(socket, "Hello, Server!")
# Receive data
response = tcp_receive(socket, 1024)
# Close connection
tcp_close(socket)
# Listen for connections
server = tcp_listen("0.0.0.0", 8080)
# Accept client
client = tcp_accept(server)
# Handle client communication
message = tcp_receive(client, 1024)
tcp_send(client, "Echo: " + message)
# Cleanup
tcp_close(client)
tcp_close(server)
Use Cases: Web servers, API clients, distributed systems, network protocols
# Calculator with basic operations
fn(use(a, b, op)) {
IF (op == "+") {
a + b
} ELSE {
IF (op == "-") {
a - b
} ELSE {
IF (op == "*") {
a * b
} ELSE {
IF (op == "/") {
a / b
} ELSE {
"Unknown operation"
}
}
}
}
} -> calculate
# Test calculator
result1 = calculate(10, 5, "+") # 15
result2 = calculate(10, 5, "*") # 50
print result1
print result2
# Word counter
fn(use(text)) {
words = fission(" ") <- text
count(words)
} -> count_words
# Clean and process text
fn(use(text)) {
# Convert to lowercase
clean = to_lower(text)
# Count words
word_count = count_words(clean)
# Return info
result = box()
pack("text", clean) -> result
pack("words", word_count) -> result
result
} -> process_text
# Test text processing
sample = "Hello World! This is a Test."
info = process_text(sample)
print info
# Basic token types
TOKEN_NUMBER = "NUMBER"
TOKEN_WORD = "WORD"
TOKEN_SPACE = "SPACE"
# Tokenize simple text
fn(use(source)) {
tokens = box()
chars = fission("") <- source
i = 0
WHILE (i < count(chars)) {
char = unpack(i) <- chars
IF (is_digit(char)) {
# Collect number
number = ""
WHILE ((i < count(chars)) |< (is_digit(unpack(i) <- chars))) {
number = number + unpack(i) <- chars
i = i + 1
}
token = box()
pack("type", TOKEN_NUMBER) -> token
pack("value", number) -> token
pack(token) -> tokens
} ELSE {
IF (is_alpha(char)) {
# Collect word
word = ""
WHILE ((i < count(chars)) |< (is_alnum(unpack(i) <- chars))) {
word = word + unpack(i) <- chars
i = i + 1
}
token = box()
pack("type", TOKEN_WORD) -> token
pack("value", word) -> token
pack(token) -> tokens
} ELSE {
IF (is_space(char)) {
# Skip whitespace
i = i + 1
} ELSE {
# Unknown character
i = i + 1
}
}
}
}
tokens
} -> simple_lexer
# Test lexer
source = "hello 123 world 456"
tokens = simple_lexer(source)
print tokens
box()- Create empty boxint_box()- Create empty typed box for integersstr_box()- Create empty typed box for stringspack(items...) -> box- Add items to boxunpack(index) <- box- Get item from boxpick(indices...) <- box- Select multiple elements from box or stringplace(index:value, ...) -> box- Set values at specific indices in boxcount(box)- Get box sizefission(delimiter) <- string- Split string (single delimiter)fission(delimiters) <- string- Split string (multiple delimiters in box)fusion(delimiter) -> box- Join stringscontains(box, item)- Check if item exists in boxprint value- Output valuetest condition- Assert conditionresult(function_call)- Get function resultimport(module_name)- Load and import module from clanker.tomlimport(alias) <- module- Import module with aliasship()- Export all definitions created in current function scoperename(from, to)- Rename a module in the module systemrewire_symbol(symbol_name)- Mark symbol as rewirable for dynamic binding
mem_load(ptr, width) -> int- Read memorymem_store(ptr, val, width)- Write memorymem_fence()- Memory barriermem_cmpxchg(ptr, expect, val) -> int- Atomic compare-swapint_disable() -> int- Disable interruptsint_enable(state)- Enable interruptscpu_halt()- Halt until interrupttime_counter() -> int- Get time countertime_freq() -> int- Get timer frequency
is_digit(char) -> bool- Check if digitis_alpha(char) -> bool- Check if letteris_space(char) -> bool- Check if whitespaceis_alnum(char) -> bool- Check if alphanumericchar_code(char) -> int- Get character codechar_from_code(int) -> char- Create characteris_operator(char) -> bool- Operator characters (+-*/%=!<>&|^~)is_punctuation(char) -> bool- Punctuation ((){}[],;:.?"'`)is_symbol(char) -> bool- Symbols (@ # $ \ _)get_char_category(char) -> string- One of: alpha, digit, space, operator, punctuation, symbol, other
parse_int(string) -> int- Parse integerparse_float(string) -> float- Parse floating point
to_upper(string) -> string- Convert to uppercaseto_lower(string) -> string- Convert to lowercasesubstring(string, start, end) -> string- Extract substringindex_of(string, substring) -> int- Find substringstarts_with(string, prefix) -> bool- Check prefixends_with(string, suffix) -> bool- Check suffix
int(x) -> int- Convert Float/Bool/Str to Int (string must parse as integer)str(x) -> string- Convert any value to its display string
abs(x),sign(x),min(x, y),max(x, y)pow(x, y),sqrt(x),floor(x),ceil(x),round(x)sin(x),cos(x),tan(x),asin(x),acos(x),atan(x)ln(x),log10(x),log2(x)
tcp_connect(host, port) -> sockettcp_send(socket, data)tcp_receive(socket, size) -> stringtcp_try_receive(socket, size) -> string— Non-blocking; empty string if nonetcp_close(socket_or_listener)tcp_listen(port) -> listenertcp_accept(listener) -> socket
http_get(url[, headers]) -> [status:int, headers:[[k,v]], body:string]http_post(url, data[, headers]) -> [status, headers, body]http_put(url, data[, headers]) -> [status, headers, body]http_delete(url[, headers]) -> [status, headers, body]json_encode(value) -> stringjson_decode(string) -> value- Headers format:
[["Header-Name", "Value"], ...]
base64_encode(string) -> stringbase64_decode(string) -> stringurl_encode(string) -> stringurl_decode(string) -> string
dns_resolve(hostname) -> ip_string
Mini-trace shows a compact, per-step view of execution.
- Enable by setting the environment variable
SCRAPS_TRACE(any value).- Example:
SCRAPS_TRACE=1 cargo run -- path/to/file.scraps
- Example:
- Columns:
ip— instruction pointer (showsfn Ninside functions)opcode— mnemonic of the current operationValue stack— brief view of the stack valuesBoxes— top-most box on the stack (to visualize mutations)Var— variable used on this row (load/store), shown once per relevant rowOutput— printed text, shown on print rows
- Runtime errors include a focused “Trace:” snippet with the same columns and a few rows around the failing instruction. The row where the error occurs is highlighted in red.
Tip: Use rewire_symbol("x") before changing a variable’s type dynamically (e.g., from Int to Str), or use the rewire ceremony.
event_register(socket, event_types) -> event_id(event_types: string or box of strings:read|write|accept|connect)event_unregister(event_id) -> boolevent_poll() -> [[socket_id, event], ...]event_wait(timeout_ms|-1) -> [[socket_id, event], ...]event_wait_any(socket_list[, event_types][, timeout_ms|-1]) -> [socket_id, event] | None
udp_bind(address) -> socket(e.g., "0.0.0.0:9999")udp_send(socket, data, target_addr) -> booludp_receive(socket, max_bytes) -> [data, source_addr]udp_try_receive(socket, max_bytes) -> [data, source_addr] | Noneudp_close(socket) -> booludp_join_multicast(socket, multicast_addr[, interface_addr]) -> booludp_leave_multicast(socket, multicast_addr[, interface_addr]) -> booludp_set_multicast_ttl(socket, ttl:int) -> booludp_set_multicast_loopback(socket, enabled:bool) -> booludp_set_broadcast(socket, enabled:bool) -> booludp_send_broadcast(socket, data, port) -> bytes_sent:intudp_send_multicast(socket, data, multicast_addr, port) -> bytes_sent:intudp_is_multicast(address) -> booludp_is_broadcast(address) -> bool
tls_connect(host, port) -> tls_connectiontls_listen(port) -> tls_listenertls_accept(tls_listener) -> tls_connectiontls_send(tls_connection, data) -> booltls_receive(tls_connection, max_bytes) -> stringtls_try_receive(tls_connection, max_bytes) -> string | Nonetls_close(tls_connection|tls_listener) -> bool
ws_connect(url) -> websocketws_send(websocket, text) -> boolws_receive(websocket) -> stringws_try_receive(websocket) -> string(empty string if none)ws_send_binary(websocket, string_bytes) -> boolws_receive_bytes(websocket) -> [byte:int, ...]ws_try_receive_bytes(websocket) -> [byte:int, ...](empty if none)ws_close(websocket) -> bool
raw_socket_create(protocol:int) -> raw_socket | "Error: ..."raw_socket_set_header_included(raw_socket, included:bool) -> boolraw_socket_send(raw_socket, data:string_bytes, target_addr) -> bytes_sent:intraw_socket_receive(raw_socket, max_bytes) -> [data:string_bytes, source_addr]raw_socket_close(raw_socket) -> boolraw_socket_info(raw_socket) -> [protocol:int, protocol_name, header_included:bool]packet_build_icmp_echo(id:int, sequence:int, data:string_bytes) -> packet_bytes:stringpacket_build_ipv4_header(source, dest, protocol:int, data_len:int) -> header_bytes:stringpacket_calculate_checksum(data:string_bytes) -> checksum:int
get_interfaces() -> [name, ...]get_interface_info(name) -> [name, type, is_up, is_loopback, is_multicast, mtu, addresses:[[ip, netmask, broadcast|"None"], ...], mac]get_interface_stats(name) -> [name, is_up, mtu, address_count, has_ipv4, has_ipv6]get_primary_interface() -> name | Noneget_loopback_interface() -> name | Noneget_interfaces_by_type(type) -> [name, ...]where type ∈ {ethernet, wireless, loopback, tunnel, virtual, unknown}get_up_interfaces() -> [name, ...]get_interface_by_ip(ip) -> name | Noneget_best_interface(dest_host_or_ip) -> name | None
ipv6_get_dual_stack_mode() -> mode:stringipv6_set_dual_stack_mode(mode:string) -> boolipv6_resolve_dual_stack(host) -> [preferred_addr, preferred_family]ipv6_parse_address(address) -> [canonical, is_link_local, is_site_local, is_unique_local, is_multicast, is_loopback, is_unspecified, is_global] | Erroripv6_get_multicast_address(group) -> [address, scope_id:int, is_multicast:bool]ipv6_is_ipv6_address(address) -> boolipv6_is_ipv4_address(address) -> boolipv6_get_address_info(address) -> [canonical, scope_id:int, is_link_local, is_site_local, is_unique_local, is_multicast, is_loopback, is_unspecified, is_global]ipv6_get_config() -> [mode, ipv4_enabled:bool, ipv6_enabled:bool]ipv6_create_dual_stack_socket(host, port, prefer_ipv6:bool) -> [socket_addr, family, port]
proxy_set_global(protocol, proxy_type, host, port[, username][, password]) -> boolproxy_set_specific(protocol, target_host, target_port, proxy_type, proxy_host, proxy_port[, username][, password]) -> boolproxy_set_default(proxy_type, host, port[, username][, password]) -> boolproxy_remove_global(protocol) -> boolproxy_remove_specific(protocol, target_host, target_port) -> boolproxy_add_bypass(host_or_domain) -> boolproxy_remove_bypass(host_or_domain) -> boolproxy_get_bypass_list() -> [host_or_domain, ...]proxy_get_info(protocol[, target_host][, target_port]) -> [proxy_type, host, port, username, source] | Noneproxy_clear_all() -> boolproxy_stats() -> [global_count:int, specific_count:int, has_default:bool, bypass_count:int]
timeout_set_global(protocol, connect_ms, read_ms, write_ms) -> booltimeout_set_specific(protocol, host, port, connect_ms, read_ms, write_ms) -> booltimeout_get_info(protocol) -> [connect_ms, read_ms, write_ms, source] | Nonetimeout_remove(protocol[, host][, port]) -> booltimeout_clear() -> booltimeout_summary() -> [global_count:int, specific_count:int, default_connect_ms:int, default_read_ms:int, default_write_ms:int]
pool_configure(max_connections:int, max_idle_seconds:int, max_lifetime_seconds:int) -> boolpool_clear() -> boolpool_stats() -> [total:int, idle:int, active:int]
read(filename) -> stringwrite(content, filename) -> stringsource(filename) -> value
deg(rad) -> degrees,rad(deg) -> radianssin_deg(x),cos_deg(x),tan_deg(x)(x in degrees)atan2(y, x),hypot(a, b),trunc(x),frac(x)mod(a, b),div(a:int, b:int),divmod(a:int, b:int) -> [q, r]pow_int(base, exponent:int)nearly_equal(a, b, eps)clamp(x, lo, hi)
length([x, y, ...]) -> floatdot([a...], [b...]) -> floatsum([x...]) -> float,mean([x...]) -> floatlinspace(start, end, n:int) -> [floats...]range(start, end, step) -> [ints|floats]
Syntax Errors:
- Missing
ELSEinIFstatement - Unmatched parentheses or braces
- Invalid operators (use
|<instead of&&)
Runtime Errors:
- Index out of bounds in
unpack - Invalid arguments to functions
- Division by zero
- Type mismatches
Function Errors:
- Wrong number of arguments
- Invalid argument types
- Calling undefined functions
Scraps provides descriptive error messages:
Runtime error: UNPACK: index 5 out of bounds (box has 3 elements)
Runtime error: PARSE_INT: 'abc123' is not a valid integer
Runtime error: SUBSTRING start index must be <= end index
- Use
printstatements to trace execution - Test functions independently before combining
- Check box sizes before unpacking
- Validate inputs in functions
- Use
testassertions to catch errors early
- Character classification: ~583ns per operation
- Number parsing: ~1250ns per operation
- String manipulation: ~833ns per operation
- Core-8 operations: <100ns to 500ns per operation
- Minimize box operations in tight loops
- Cache function results when possible
- Use character classification instead of string comparisons
- Batch string operations when processing large texts
- Prefer direct arithmetic over function calls for simple math
- Boxes grow dynamically - no need to pre-allocate
- Strings are immutable - operations create new strings
- Functions are lightweight - no significant overhead
Everything that can be an expression, is an expression. This makes the language more composable and functional in nature.
Functions are pure by default, requiring explicit rewire for side effects. This makes code more predictable and easier to reason about.
Full Unicode support throughout the language, from identifiers to string processing.
The language aims to be simple and consistent, with minimal special cases and exceptions.
This completes the Scraps Language Scrapbook. For the latest updates and examples, check the test files and compiler implementation.