Slang (Simple LANGuage) is a simple interpreted language built in Rust as a means of experimenting with Rust.
Slang is a C-like language with a syntax that should be very familiar to JavaScript programmers.
Slang is not meant to be used for serious programming, it is a toy language built as a means to learn Rust.
You can run Slang either in REPL mode or by executing a script file.
# runs REPL mode
$ ./slang
# runs script file
$ ./slang script.sl
Slang supports debug mode, this basically just vomits the internal processing out to your console as the program runs.
This can be enabled by using the debug(true); function, or can
be switched off via debug(false);.
The following is a non-exhaustive list of features that Slang supports.
let x = 5;
let y = 10;
// x == 5, y == 10
let x = 5;
let y = 10;
let z = x + y;
// z == 15
let x = 5;
let y = 10;
let z = x + y;
if (z == 15) { z } else { -1 }
// outputs 15
let x = 5;
let y = 10;
let z = x + y * 2;
// z == 25
let add = function(x, y) { x + y };
add(5, 10); // outputs 15
// same as
function add(x, y) {
x + y
}
add(5, 10); // outputs 15
let f = fn() {
let x = 0;
while (x < 5) {
if (x == 3) {
return x;
}
let x = x + 1;
}
99;
};
f();
"hello" + " " + "world";
// outputs "hello world"
let a = [1, 2, 3, 4, 5];
for (let i = 0; i < len(a); i = i + 1) {
print(a[i]);
}
// outputs 1, 2, 3, 4, 5
for (let i = 0; i < len(a); i++) {
print(a[i]);
}
// also outputs 1, 2, 3, 4, 5
let f = file_open("test.txt", "w+");
file_write(f, "Hello, world!");
file_seek(f, 0, "start");
print(file_read(f));
file_close(f);
let obj = {
x: 5,
y: 10,
add: fn() {
return this.x + this.y;
},
inner: {
z: [1, 2, 3],
sum: function () {
let sum = 0;
for (let i = 0; i < len(this.z); i++) {
sum = sum + this.z[i];
}
return sum;
}
}
};
print(obj.add());
// outputs 15
print(obj.inner.sum());
// outputs 6
Slang supports class definitions with constructors and methods. Classes provide a cleaner syntax for creating objects with shared behavior.
class Calculator {
function add(a, b) { a + b; }
function multiply(a, b) { a * b; }
}
let calc = new Calculator();
print(calc.add(2, 3)); // 5
print(calc.multiply(4, 5)); // 20
The special construct method is called when you create a new instance with new.
Use this to set fields on the instance:
class Point {
function construct(x, y) {
this.x = x;
this.y = y;
}
function distanceFromOrigin() {
Math::sqrt(this.x * this.x + this.y * this.y);
}
}
let p = new Point(3, 4);
print(p.x); // 3
print(p.y); // 4
print(p.distanceFromOrigin()); // 5
Methods can access instance fields via this. Methods can also call other methods
on the same instance:
class MathUtils {
function factorial(n) {
if (n <= 1) {
1;
} else {
n * this.factorial(n - 1);
}
}
}
let math = new MathUtils();
print(math.factorial(5)); // 120
Slang uses value semantics for objects. To mutate instance state, methods should
return this after making changes, and callers should reassign the result:
class Counter {
function construct(start) {
this.value = start;
}
function increment() {
this.value = this.value + 1;
this; // Return this to allow reassignment
}
function getValue() {
this.value;
}
}
let c = new Counter(0);
c = c.increment(); // Reassign to capture mutation
c = c.increment();
c = c.increment();
print(c.getValue()); // 3
Since mutating methods return this, you can chain method calls:
class StringBuilder {
function construct() {
this.value = "";
}
function append(s) {
this.value = this.value + s;
this;
}
function build() {
this.value;
}
}
let result = new StringBuilder()
.append("Hello")
.append(" ")
.append("World!")
.build();
print(result); // "Hello World!"
Each instance created with new is independent:
class Box {
function construct(val) {
this.value = val;
}
function get() {
this.value;
}
}
let a = new Box(100);
let b = new Box(200);
print(a.get()); // 100
print(b.get()); // 200
Classes don't require a construct method. If omitted, new simply creates
an empty instance with the class methods attached:
class Utils {
function double(x) { x * 2; }
function triple(x) { x * 3; }
}
let u = new Utils();
print(u.double(5)); // 10
print(u.triple(5)); // 15
Classes in Slang are syntactic sugar over objects:
class Foo { ... }creates aClassobject containing all method definitionsnew Foo(args)creates a plain object with all methods copied in- If a
constructmethod exists, it's called withthisbound to the new instance - The modified
this(with any fields set in the constructor) is returned
This means instances are regular objects that can be manipulated with Obj:: helpers:
class Person {
function construct(name) {
this.name = name;
}
}
let p = new Person("Alice");
let keys = Obj::keys(p); // ["name", "construct"]
print(Obj::has(p, "name")); // true
let makeAdder = fn(x) {
function(y) { x + y; }; // this inner fn closes over x
};
let addTwo = makeAdder(2);
print(addTwo(3)); // => 5
let addTen = makeAdder(10);
print(addTen(7)); // => 17
function isNumberGreaterThanZero(num) {
if (num > 0) {
Result::Ok(num);
} else {
Result::Err("Number less than zero");
}
}
let resultOne = isNumberGreaterThanZero(5);
Result::andThen(resultOne, fn (num) {
print(num);
});
let resultTwo = isNumberGreaterThanZero(-12);
Result::andThen(resultTwo, fn (num) {
print("This shouldn't happen");
});
if (Result::isErr(resultTwo)) {
print("failure");
}
function findValueInList(val) {
let list = [1,2,3,4,5];
for (let i = 0; i < len(list); i++) {
if (list[i] == val) {
return Option::Some(i);
}
}
Option::None();
}
let optA = findValueInList(3);
Option::andThen(optA, fn (idx) {
print("Found value in list at index");
print(idx);
Option::Some(idx);
});
let optB = findValueInList(-2);
Option::andThen(optB, fn (val) {
print("Should not happen");
});
if (Option::isNone(optB)) {
print("Value not in list");
}
int("42"); // 42 (Integer)
float("3.14"); // 3.14 (Float)
str(123); // "123" (String)
bool(0); // false
bool("true"); // true
// Result-based variants
let r1 = Type::int("99"); // Result::Ok(99)
let r2 = Type::float("oops"); // Result::Err("float(): could not parse float from \"oops\"")
if (Result::isErr(r2)) { print("bad cast"); }
let t1 = regexIsMatch("hello123", "[a-z]+[0-9]+"); // true
let t2 = regexIsMatch("hello", "[0-9]+"); // false
let m1 = regexFind("abc123xyz", "\d+"); // Some("123")
let m2 = regexFind("no-digits-here", "\d+"); // None()
let r = regexReplace("foo 123 bar 456", "\d+", "X"); // foo X bar X
let c1 = regexMatch("abc123", "([a-z]+)(\d+)"); // Some(["abc123", "abc", "123"])
let c2 = regexMatch("no-digits", "(\d+)"); // None()
namespace SomeNamespace {
function add(a, b) {
a + b;
}
}
SomeNamespace::add(5, 7); // 12
Code inside a namespace is exported as a single object. Top-level code outside any namespace remains private to the file. You can import another file with a string path (absolute or relative to the current working directory):
import "lib/math.sl";
Math::sqrt(9);
If multiple files declare the same namespace, their members are merged; later imports override earlier definitions of the same member name.
You can tag functions and publish values to all subscribers of a tag. Tags are
declared before a function with (:TagOne, :TagTwo); publishing uses -> :Tag.
Publishes can be chained; each stage receives the return values from the previous
stage (with null values dropped).
Argument shaping per subscriber:
- 0 params → no arguments passed
- 1 param → a single array of all non-null values from the previous stage
-
1 params → values are packed positionally; if more values than params, the last param receives the remaining values as an array; if fewer, missing params get
null.
Example:
(:Square)
function square(values) { // one-arg subscriber gets an array
let n = values[0];
let s = n * n;
print(s);
return s;
}
(:Print)
function output(values) { // also one-arg -> array
print(values[0]);
}
let a = 5;
let b = 7;
a + b -> :Square -> :Print // prints 144 twice in the sample test
let opened = File::open("test.txt", "w+");
let f = Result::unwrapOr(opened, 0);
let res1 = File::read(123); // Result::Err()
let res2 = File::write(123, "data"); // Result::Err()
let res3 = File::write(f, 42); // Result::Err()
let _ = File::close(f);
let res4 = File::read(f); // Result::Err()
let a = Result::isErr(res1);
let b = Result::isErr(res2);
let c = Result::isErr(res3);
let d = Result::isErr(res4);
Slang ships with a comprehensive standard library that is preloaded into the global environment whenever you run a script or the REPL.
-
Option
- Represents optional values:
Some(v)orNone. - Exposed as a namespace object with the following helpers:
Option::Some(value)– wrap a value in anOption.Option::None()– create an empty option.Option::isSome(opt)/Option::isNone(opt)– boolean checks.Option::unwrapOr(opt, default)– returns the inner value or a default.Option::map(opt, fn)/Option::fmap(opt, fn)– transform the inner value if present.Option::andThen(opt, fn)/Option::bind(opt, fn)– monadic bind;fnshould return anOption.
- Represents optional values:
-
Result
- Represents the outcome of computations that can succeed or fail:
Ok(v)orErr(e). - Exposed as a namespace object with helpers:
Result::Ok(value)/Result::Err(errorValue).Result::isOk(res)/Result::isErr(res).Result::unwrapOr(res, default)– returns inner value onOk, default onErr.Result::map(res, fn)/Result::fmap(res, fn)– transform the success value.Result::andThen(res, fn)/Result::bind(res, fn)– monadic bind;fnshould return aResult.
- Represents the outcome of computations that can succeed or fail:
-
Type
- Safe, Result-wrapped casts:
Type::int(value)→Result::Ok(Integer)orResult::Err("...")Type::float(value)→Result::Ok(Float)orResult::Err("...")Type::str(value)→Result::Ok(String)(never errors)Type::bool(value)→Result::Ok(Boolean)orResult::Err("...")
- Accepted inputs:
int: integers, finite floats (truncates), booleans (1/0), strings parsable as i64.float: floats, integers, booleans (1/0), strings parsable as f64.str: any value viato_string.bool: booleans, integers/floats (non-zero => true), strings"true"/"false"/"1"/"0",null→ false.
- Free-function aliases
int(value),float(value),str(value),bool(value)perform the same conversions but raise an error instead of returningResult. - Type introspection:
Type::of(value)– returns a string describing the type:"integer","float","boolean","string","array","object","function","option","result","null".Type::isInt(value)/Type::isFloat(value)/Type::isNumber(value)– numeric type checks.Type::isBool(value)/Type::isString(value)– primitive type checks.Type::isArray(value)/Type::isObject(value)– compound type checks.Type::isCallable(value)– returnstruefor functions and builtins.Type::isIterable(value)– returnstruefor arrays and strings.Type::isNull(value)– returnstrueif value isnull.Type::isOption(value)/Type::isResult(value)– monad type checks.
- Safe, Result-wrapped casts:
-
Array
- Higher-order functions:
Array::map(arr, fn)– returns a new array withfn(element)applied to each element.Array::filter(arr, fn)– returns a new array containing only elements wherefn(element)istrue.Array::reduce(arr, initial, fn)– folds the array from left to right, callingfn(acc, element).Array::find(arr, fn)– returnsOption::Some(element)for the first element wherefn(element)istrue, orOption::None()if none match.Array::some(arr, fn)– returnstrueif any element matches the predicate.Array::every(arr, fn)– returnstrueif all elements match the predicate.Array::flatMap(arr, fn)– maps each element to an array and concatenates the results.Array::forEach(arr, fn)– executesfn(element)for each element (side effects only, returnsnull).Array::groupBy(arr, fn)– groups elements by the string key returned byfn(element).Array::partition(arr, fn)– splits into[matching, non-matching]based on predicate.
- Sorting:
Array::sort(arr)– returns a new sorted array (ascending order, homogeneous types only).Array::sortBy(arr, compareFn)– custom sort usingcompareFn(a, b)returning negative/zero/positive.
- Searching:
Array::indexOf(arr, value)– returnsOption::Some(index)orOption::None().Array::includes(arr, value)– returnstrueif the value exists in the array.
- Slicing and combining:
Array::slice(arr, start, end)– extracts a portion (supports negative indices).Array::take(arr, n)– returns the firstnelements.Array::drop(arr, n)– returns all elements after the firstn.Array::concat(arr1, arr2)– concatenates two arrays.Array::reverse(arr)– returns a new reversed array.Array::flatten(arr)– flattens one level of nested arrays.Array::unique(arr)– returns a new array with duplicates removed.
- Zipping:
Array::zip(arr1, arr2)– combines into array of pairs[[a1, b1], [a2, b2], ...].Array::unzip(arr)– splits array of pairs into[[a1, a2, ...], [b1, b2, ...]].
- Creation:
Array::range(start, end[, step])– creates an array of integers fromstarttoend(exclusive).Array::fill(value, count)– creates an array withcountcopies ofvalue.
- Utilities:
Array::isEmpty(arr)– returnstrueif the array has no elements.Array::len(arr)– returns the number of elements.
- These complement the lower-level builtins like
len,first,last,rest, andpush.
- Higher-order functions:
-
Obj
- Object (hash map) manipulation utilities:
Obj::keys(obj)– returns an array of all keys.Obj::values(obj)– returns an array of all values.Obj::entries(obj)– returns an array of[key, value]pairs.Obj::fromEntries(arr)– creates an object from an array of[key, value]pairs.Obj::has(obj, key)– returnstrueif the key exists.Obj::get(obj, key)– returnsOption::Some(value)orOption::None().Obj::set(obj, key, value)– returns a new object with the key set (immutable).Obj::delete(obj, key)– returns a new object with the key removed (immutable).Obj::merge(obj1, obj2)– returns a new object combining both (obj2 values override obj1).Obj::isEmpty(obj)– returnstrueif the object has no keys.Obj::len(obj)– returns the number of key-value pairs.
- Object (hash map) manipulation utilities:
-
String
- Basic utilities:
String::trim(s)– trims leading and trailing whitespace.String::toUpper(s)/String::toLower(s)– case conversion.String::split(s, sep)– splits into an array of strings (sep == ""splits into characters).String::join(arr, sep)– joins an array of strings with a separator.String::len(s)– returns the number of characters (not bytes).String::isEmpty(s)– returnstrueif the string has no characters.
- Searching:
String::contains(s, substr)– returnstrueifsubstris found.String::startsWith(s, prefix)/String::endsWith(s, suffix)– prefix/suffix checks.String::indexOf(s, substr)– returnsOption::Some(index)orOption::None().String::lastIndexOf(s, substr)– returns index of last occurrence.
- Slicing and manipulation:
String::slice(s, start, end)– extracts a substring (supports negative indices).String::replace(s, from, to)– replaces the first occurrence.String::replaceAll(s, from, to)– replaces all occurrences.String::repeat(s, count)– repeats the stringcounttimes.String::reverse(s)– reverses the string.String::padLeft(s, length, char)/String::padRight(s, length, char)– pads to target length.
- Character utilities:
String::chars(s)– returns an array of single-character strings.String::charCodeAt(s, index)– returns the Unicode code point at the index.String::charCodes(s)– returns an array of all character codes.String::fromCharCode(code)– creates a single-character string from a code point.String::fromCharCodes(arr)– creates a string from an array of code points.
- Basic utilities:
-
Math
- Basic operations:
Math::abs(x)– absolute value.Math::floor(x),Math::ceil(x),Math::round(x)– integer rounding.Math::min(a, b),Math::max(a, b)– minimum/maximum of two numbers.Math::pow(base, exp)– exponentiation.Math::sqrt(x)– square root.Math::sign(x)– returns -1, 0, or 1.Math::clamp(x, min, max)– constrains a value to a range.
- Trigonometry:
Math::sin(x),Math::cos(x),Math::tan(x)– basic trig (radians).Math::asin(x),Math::acos(x),Math::atan(x)– inverse trig.Math::atan2(y, x)– two-argument arctangent.Math::sinh(x),Math::cosh(x),Math::tanh(x)– hyperbolic functions.
- Logarithms and exponentials:
Math::log(x)– natural logarithm (base e).Math::log10(x)– base-10 logarithm.Math::log2(x)– base-2 logarithm.Math::exp(x)– e raised to the power x.
- Constants:
Math::PI()– π (3.14159...).Math::E()– Euler's number e (2.71828...).Math::TAU()– τ = 2π (6.28318...).
- Random numbers:
Math::random()– returns a random float in [0, 1).Math::randomInt(min, max)– returns a random integer in [min, max].
- Basic operations:
-
Time
- Current time:
Time::now()– returns the current Unix timestamp in milliseconds.Time::nowSecs()– returns the current Unix timestamp in seconds.
- Date/time components (all take a timestamp in milliseconds):
Time::year(ts),Time::month(ts),Time::day(ts)– date components (UTC).Time::hour(ts),Time::minute(ts),Time::second(ts)– time components (UTC).Time::dayOfWeek(ts)– returns 0-6 (Sunday = 0).
- Formatting and conversion:
Time::format(ts, formatStr)– formats using strftime syntax (e.g.,"%Y-%m-%d %H:%M:%S").Time::toObject(ts)– returns an object withyear,month,day,hour,minute,second,dayOfWeek.
- Utility:
Time::sleep(ms)– pauses execution for the specified milliseconds.
- Current time:
-
Sys
- Environment variables:
Sys::env()– returns an object with all environment variables.Sys::env(name)– returnsOption::Some(value)orOption::None()for a specific variable.Sys::setEnv(name, value)– sets an environment variable.
- Process information:
Sys::args()– returns an array of command-line arguments.Sys::cwd()– returns the current working directory.Sys::setCwd(path)– changes the current working directory.Sys::platform()– returns the OS name (e.g.,"macos","linux","windows").Sys::arch()– returns the CPU architecture (e.g.,"x86_64","aarch64").
- Process control:
Sys::exit(code)– exits the process with the given status code.Sys::exec(command)– executes a shell command, returnsResult::Ok({ code, stdout, stderr })orResult::Err(error).
- Environment variables:
-
HTTP
- HTTP client functions (all return
Result::Ok(response)orResult::Err(error)):HTTP::get(url[, options])– performs a GET request.HTTP::post(url, body[, options])– performs a POST request.HTTP::put(url, body[, options])– performs a PUT request.HTTP::delete(url[, options])– performs a DELETE request.HTTP::patch(url, body[, options])– performs a PATCH request.HTTP::head(url[, options])– performs a HEAD request.
- Options object can include:
headers– an object of HTTP headers.timeout– request timeout in milliseconds.
- Response object includes:
status– HTTP status code.body– response body as a string.headers– response headers as an object.
- HTTP client functions (all return
-
Fn
- Functional programming utilities:
Fn::identity(x)– returns its argument unchanged.Fn::constant(value)– returns a function that always returnsvalue.Fn::compose(f, g)– returnsfn(x) { f(g(x)) }(right-to-left composition).Fn::pipe(f, g)– returnsfn(x) { g(f(x)) }(left-to-right composition).Fn::apply(fn, argsArray)– callsfnwith arguments from an array.Fn::call(fn, ...args)– callsfnwith the provided arguments.Fn::negate(predicateFn)– returns a function that negates the predicate result.Fn::flip(fn)– returns a function with the first two arguments swapped.Fn::partial(fn, ...boundArgs)– returns a partially applied function.Fn::isCallable(value)– returnstrueif the value can be called as a function.
- Functional programming utilities:
-
Regex
- Regex helpers are available both as free functions and under the
Regexnamespace:Regex::isMatch(text, pattern)– boolean match test.Regex::find(text, pattern)– returnsOption::Some(match)orOption::None().Regex::replace(text, pattern, replacement)– returns a new string with replacements.Regex::match(text, pattern)– returnsOption::Some(arrayOfGroups)orOption::None().
- The free-function aliases (
regexIsMatch,regexFind,regexReplace,regexMatch) remain available for convenience.
- Regex helpers are available both as free functions and under the
-
File
- Low-level file operations exist as free functions (
file_open,file_read,file_write, etc.), but the preferred interface is theFilenamespace, which wraps results inResult:File::open(path, mode)– returnsResult::Ok(file)orResult::Err(error).File::read(file)– returnsResult::Ok(string)orResult::Err(error).File::write(file, string)– returnsResult::Ok(unit)orResult::Err(error).File::seek(file, offset, origin)– returnsResult::Ok(unit)orResult::Err(error).File::close(file)– returnsResult::Ok(unit)orResult::Err(error).
- Low-level file operations exist as free functions (
-
Json
- JSON interop helpers:
Json::parse(s)– parses a JSON string into Slang values, returningResult::Ok(value)orResult::Err(errorString).Json::stringify(value)– converts a Slang value back into a JSON string, returningResult::Ok(string)orResult::Err(errorString).
- JSON interop helpers:
-
Test
- The
Testnamespace provides basic assertion helpers designed for writing test scripts:Test::assert(condition)– fails ifconditionis false.Test::assertEq(expected, actual)– equality assertion.Test::assertNotEq(expected, actual)– inequality assertion.
- The
In addition to the namespaced modules above, a handful of helpers are exposed as top-level builtins:
- len(x) – length of a string or array.
- first(arr) / last(arr) / rest(arr) / push(arr, value) – basic array helpers.
- print(...args) – print values to stdout (used throughout the examples).
- debug(bool) – enable or disable Slang's internal debug logging.
Over time, more functionality may move into namespaced modules for better organization, but the existing free functions will continue to work for backwards compatibility.
// Array + Option + Result
let xs = [1, 2, 3, 4, 5];
let firstEven = Array::find(xs, fn(x) { x % 2 == 0; }); // Option::Some(2)
// String helpers
let raw = " hello world ";
let cleaned = String::toUpper(String::trim(raw)); // "HELLO WORLD"
// Math + Json
let v = { x: 3, y: 4, len: Math::sqrt(3 * 3 + 4 * 4) };
let json = Json::stringify(v);
let jsonStr = Result::unwrapOr(json, "ERR");
print(jsonStr); // {"x":3,"y":4,"len":5}
// Object manipulation
let user = { name: "Alice", age: 30 };
let updated = Obj::set(user, "email", "alice@example.com");
let keys = Obj::keys(updated); // ["name", "age", "email"]
// Functional programming
let double = fn(x) { x * 2 };
let addOne = fn(x) { x + 1 };
let doubleThenAdd = Fn::compose(addOne, double);
print(Fn::call(doubleThenAdd, 5)); // 11
// Time and dates
let now = Time::now();
let formatted = Time::format(now, "%Y-%m-%d %H:%M:%S");
print(formatted); // e.g., "2024-12-19 15:30:45"
// System interaction
let cwd = Sys::cwd();
let result = Sys::exec("echo hello");
let output = Result::unwrapOr(result, { stdout: "" });
print(String::trim(output.stdout)); // "hello"
// HTTP requests (requires network)
let response = HTTP::get("https://api.example.com/data");
if (Result::isOk(response)) {
let data = Result::unwrapOr(response, {});
print(data.body);
}
// Type checking
let value = 42;
if (Type::isNumber(value)) {
print("It's a number!");
}
print(Type::of(value)); // "integer"