Pitruck 1.5 Technical Reference
Pitruck is a dynamically-typed, interpreted scripting language implemented in Rust. It features a lightweight, C-style syntax with modern conveniences like optional chaining, null-coalescing, pattern matching, template strings, and first-class functions.
A major feature of Pitruck is its built-in HTTP server capabilities, allowing scripts to be instantly served as web endpoints with zero external dependencies.
Getting Started
Pitruck source files use the .pr extension.
Hello World
print "Hello, World!"
CLI Usage
The Pitruck executable provides a REPL, script runner, package manager, and a web server:
pitruck # Start the interactive REPL
pitruck script.pr # Run a source file
pitruck script.pr --speed # Run and show execution timing (lex, parse, run)
pitruck --serve script.pr # Serve script.pr as an HTTP handler (port 8000)
pitruck --serve public/ --port 8080 # Serve a directory using file-based routing
pitruck lib install <url> # Download and install a library
pitruck lib list # List installed libraries
pitruck lib delete <name> # Remove an installed library
Lexical Structure
Comments
Pitruck supports three comment styles:
# Python-style single-line comment
// C-style single-line comment
/* Multi-line
block comment
*/
Identifiers
Identifiers must begin with a letter or underscore _ and can contain letters, numbers, and underscores.
Keywords
The following words are reserved and cannot be used as identifiers:
var, bring, func, return, if, elif, else, while, for, in, print, class, self, match, break, continue, try, catch, and, or, not, true, false, null.
Strings
Strings can be defined using double quotes, triple quotes (for multi-line strings), or backticks (for template strings).
var normal = "Hello\nWorld"
var raw_multiline = """Line 1
Line 2"""
var name = "Alice"
var template = `Hello ${name}!`
Supported string escapes: \n, \t, \r, \e, \", \`, \\, \$, \xHH (Hex), \u{HHHH} (Unicode).
Numbers
All numbers are 64-bit floating-point (IEEE 754 f64). Underscores can be used for readability.
var num = 1_000_000.5
Data Types
Pitruck is dynamically typed. The primitive types are:
- Number: 64-bit float.
- String: UTF-8 string.
- Bool:
trueorfalse. - Null: Represents the absence of a value (
null).
Complex types include:
- List: Ordered, mutable array of values.
[1, 2, 3] - Dict: Key-value map. Keys must be strings, numbers, or booleans.
{"key": "value"} - Function: First-class callable object (closures and lambdas).
- Class / Instance: Object-oriented classes and their instantiations.
- BoundMethod: A method bound to its instance context.
Truthiness
In conditionals, only false and null are considered falsey. Everything else (including 0, 0.0, and "") evaluates to true.
Variables
Variables are declared using the var keyword. They are block-scoped.
var a = 10
{
var a = 20 # Shadows outer 'a'
print a # 20
}
print a # 10
Attempting to access or assign to an undeclared variable throws a RuntimeError.
Operators
Arithmetic
+, -, *, /, % (Modulo)
Note: The + operator also acts as a string concatenator. It coerces numbers into strings when one operand is a string. It also concatenates Lists.
Comparison
==, !=, <, >, <=, >=
Values of different types are generally not equal, except via formatting comparison fallback in lists.
Logical
and, or, not
and and or are short-circuiting.
Null-Coalescing & Optional Chaining
| Operator | Example | Description |
|---|---|---|
?? | a ?? b | Returns a if it is not null, otherwise returns b. |
?. | obj?.prop | Returns obj.prop if obj is not null, else null. |
?[] | arr?[idx] | Returns arr[idx] if arr is not null, else null. |
Assignment
=, +=, -=, *=, /=
Ternary
condition ? true_expr : false_expr
Control Flow
If / Elif / Else
if x < 10 {
print "Small"
} elif x < 20 {
print "Medium"
} else {
print "Large"
}
Loops
While loop:
var i = 0
while i < 10 {
i += 1
if i == 5 { continue }
if i == 8 { break }
}
For-In loop: Iterates over a List or a String.
for item in [1, 2, 3] {
print item
}
for char in "abc" {
print char
}
Match Statement
Pattern matching on values.
match status {
200 => { print "OK" }
404 => { print "Not Found" }
_ => { print "Unknown" }
}
Functions
Declaration
func add(a, b) {
return a + b
}
If no return is reached, the function implicitly returns null.
Lambdas (Closures)
Anonymous functions use the fat arrow => syntax. They capture their surrounding lexical environment.
var multiply = (a, b) => a * b
var block_lambda = (x) => {
return x * 2
}
Classes and Objects
Classes encapsulate methods and fields. Instantiation does not use a new keyword; you simply call the class as a function. The init method acts as the constructor.
class Person {
func init(name, age) {
self.name = name
self.age = age
}
func greet() {
print "Hi, I am " + self.name
}
}
var p = Person("Alice", 30)
p.greet()
Modules & Imports
Use the bring keyword to load external .pr files. The interpreter searches the current directory, the lib/ subdirectory, and the executable's path.
bring math_utils # Loads math_utils.pr or lib/math_utils.pr
# Functions/Classes defined in the module become available in the global scope.
Note: Modules are executed in the global context and modify the global scope. Module inclusion is cached (a module is only loaded once per execution).
Error Handling
Runtime errors can be caught using try/catch. The caught exception is represented as a dictionary containing type, message, and line.
try {
var x = 10 / 0
} catch (err) {
print "Error occurred on line " + to_string(err.line)
print err.message
}
HTTP Web Server
A flagship feature of Pitruck is its ability to natively serve HTTP requests via the --serve CLI flag.
Modes of Operation
- Single Handler:
pitruck --serve index.prfunnels all requests throughindex.pr. - File-Based Routing:
pitruck --serve public/routesGET /abouttopublic/about.pr, falling back topublic/index.prif the specific file does not exist.
The Request / Response Contract
When running in --serve mode, the interpreter automatically injects two global instances into your script: request and response.
request object
request.method(String): "GET", "POST", etc.request.path(String): The URL path (e.g., "/about").request.query_str(String): The raw query string.request.query(Dict): Parsed URL query parameters.request.form(Dict): Parsed form data (if Content-Type isapplication/x-www-form-urlencoded).request.body(String): Raw request body.request.headers(Dict): HTTP headers (keys are lowercase).
response object
Modify this object to shape the HTTP response.
response.status(Number): Defaults to 200.response.body(String): The content to send back.response.headers(Dict): Custom headers to send back. (e.g.response.headers["Content-Type"] = "application/json").
response.body starts with <!DOCTYPE or <. If so, it sets it to text/html; charset=utf-8. Otherwise, it defaults to text/plain; charset=utf-8.
--serve mode, outbound HTTP requests (http_request) and file I/O (sys_readfile, sys_writefile) are disabled for security.
Server Example
# file: server.pr
if request.path == "/api" {
response.headers["Content-Type"] = "application/json"
var data = { "status": "success", "method": request.method }
response.body = json_encode(data)
} else {
response.body = "<h1>Hello from Pitruck!</h1>"
}
Standard Library
Pitruck features a rich set of built-in functions.
Math & Utilities
rand(min, max): Returns a random integer between min and max (inclusive).range(stop)|range(start, stop)|range(start, stop, step): Returns a list of numbers.to_number(val): Converts string or bool to a float.to_string(val): Converts a value to its string representation.is_number(val): Returnstrueif the value can be parsed as a number.typeof(val): Returns "number", "string", "bool", "null", "list", "dict", "function", "class", or "instance".clone(val): Performs a deep clone of a List or Dict.math_abs(n),math_sqrt(n),math_pow(a, b),floor(n),ceil(n),round(n): Standard math operations.
String Manipulation
len(str_or_list_or_dict): Returns the length.split(str, sep): Splits string into a list.trim(str): Removes leading/trailing whitespace.upper(str),lower(str): Changes casing.replace(str, from, to): Replaces occurrences of a substring.starts_with(str, prefix),ends_with(str, suffix): Boolean checks.substr(str, start, length?): Extracts a substring.char_at(str, index): Gets character at index.pad_left(str, width, fill),pad_right(...): Pads a string.repeat_str(str, count): Repeats stringcounttimes.index_of(str_or_list, needle): Returns index of first occurrence, or -1.html_escape(str): Escapes&, <, >, ", 'for HTML safety.
Lists & Dictionaries
push(list, val): Appends value to a list.pop(list): Removes and returns the last element of a list.contains(str|list|dict, needle): Checks for existence (substring, list element, or dict key).keys(dict): Returns a list of dict keys.values(dict): Returns a list of dict values.remove(dict|list, key_or_index): Removes an item by key or index.join(list, sep): Joins list elements into a string.list_slice(list_or_str, start, end): Extracts a slice.list_reverse(list_or_str): Reverses a list or string.list_sort(list): Sorts a list in place using string representation.list_sort_by(list, cmp_fn): Sorts using a comparator function that returns a number (>0 to swap).list_map(list, fn): Returns a new mapped list.list_filter(list, fn): Returns a new filtered list.list_reduce(list, fn, initial_acc): Reduces a list to a single value.
JSON & Encoding
json_encode(val): Serializes to a JSON string.json_decode(str): Parses a JSON string.url_encode(str),url_decode(str): Application/x-www-form-urlencoded escaping.
System & Network
time(): Returns current time formatted as "HH:MM:SS".timestamp(): Returns Unix timestamp in seconds.sys_os(): Returns the host OS name.sys_exit(code): Terminates the process.sys_sleep(ms): Blocks execution formsmilliseconds.sys_env(key): Reads an environment variable.sys_writefile(path, contents): Writes string to a file. (Disabled in --serve)sys_readfile(path): Reads file to string. (Disabled in --serve)sys_fileexists(path): Returns boolean.http_request(method, url, body, headers_dict): Makes an outbound HTTP request. Returns a dict containingstatus,ok,body, andheaders. (Note: HTTPS is not natively supported by the outbound client. Disabled in --serve mode.)input(prompt): Reads a line from STDIN.clear(): Clears the terminal screen via ANSI escape codes.
Runtime Internals
The Pitruck interpreter operates in three main phases:
- Lexical Analysis: Source code is converted into a token stream.
- Parsing: A recursive descent parser builds an Abstract Syntax Tree (AST).
- Execution: A tree-walk interpreter evaluates the AST nodes.
Variable Resolution & Speed
To optimize lookups during the tree-walk phase, Pitruck hashes variable names (via a custom FNV-1a style hashing function in symbol.rs). A lightweight compiler pass (compiler::resolve_program) attaches these pre-computed 64-bit integer hashes to AST nodes. The interpreter scope is a flat vector of (hash, Value) tuples, allowing faster resolution than deep string map lookups.
Memory Model
Reference-counted, garbage-collected types (Lists, Dicts, Functions, Instances) are backed by Rust's Rc<RefCell<T>>. This permits mutable sharing across scopes and instances without exposing manual memory management to the user. (Note: Circular references will leak memory as there is no mark-and-sweep GC).
Limitations & Known Quirks
- Outbound HTTPS: The built-in
http_requestonly supports raw HTTP over TCP port 80 (or custom ports). It does not include a TLS backend. - Concurrency: Pitruck scripts run single-threaded. The web server spawns a new OS thread per connection, but shared mutable state across connections is not natively supported in user space.
- Integer Precision: Because all numbers are
f64, integers lose exact precision beyond2^53 - 1. - Error Messages: Parse errors report the exact line and column, but runtime errors only report the line number.