Introduction
This document is a work-in-progress and is not normative. It only applies to version 4 of the Rant language.
Found an error? Want something improved? Submit an issue or a pull request!
This documentation is a reference for the language features, standard library, and major runtime features of version 4.x of Rant. While every effort is made to make the documentation as approachable as possible, it is not primarily intended to be a beginner's guide to the language.
Scope
This documentation is divided into the following chapters:
- Language features: Overview of Rant as a language.
- Runtime features: Overview of features specific to the runtime.
- Standard library: Reference for the functions found in the Rant Standard Library.
- Glossary: Familiarize yourself with Rant's terminology.
Other helpful resources
For API documentation, see docs.rs.
For a step-by-step beginner's guide to Rant, see the Getting Started page on the Rant website.
Language features
Text
In Rant, text is considered as a combination of two types of tokens: fragments and whitespace.
Any string of text is a valid Rant program.
Fragments
A fragment is defined in Rant as any sequence of characters that:
- contains no whitespace,
- contains no reserved characters (e.g. braces, brackets, etc.)
Rant will output any fragment verbatim.
# Prints "Hello"
Hello
Whitespace
All sequences of whitespace in a Rant program are normalized to a single space by default.
Rant is very selective about the whitespace it prints. By default, whitespace included in a Rant program is only printed if it is between any two of the following:
- Fragments
- Numbers
- Getters
- Blocks containing any of the above
- Blocks that satisfy the aforementioned requirement
All other whitespace (including line breaks) is ignored. Escaped whitespace (\t
, \s
, etc.) are exempt from this rule.
TODO: Add examples
Hinting
Because Rant's whitespace rules are quite strict, it is sometimes necessary to give the compiler additional information about the structure of your output.
This can be achieved by adding hints (symbolized by a '
) to your code.
A hint informs the compiler that the next element of code will print to the output, and that the whitespace between it and another printing element should be included.
The following elements are implicitly hinted:
- Numbers
- Variable getters
- Blocks containing at least one fragment, number, getter, hinted element, or nested block that meets this requirement
The act of a block inheriting a hint from its contents is known as "taking the hint."
# Implicitly hinted (block contains fragments)
The coin landed on {Heads|Tails}.
# Explicitly hint the function call so the compiler knows it prints something
Your lucky number is '[num:1;100].
Sinking
If you want to suppress the output of a code element, you can sink it with the _
operator.
Sinking an element has the opposite effect of hinting: it will be marked as "non-printing" and any whitespace between it and another non-printing element will also be ignored.
Only fragments and numbers cannot be sinked; the _
character will be interpreted as text in such contexts.
Blocks that are sinked cannot take a hint.
{Now you see me...} # Prints like normal
_{Now you don't.} # Prints nothing
Goodbye _{cruel} world! # Prints "Goodbye world!"
_Fragment # Prints "_Fragment", as fragments cannot be sinked
String literals
If you want to treat any text as a single fragment, you can wrap it in a string literal using double quotes:
" This text is indented"
They even work over several lines:
"This
text
has
several
lines"
Blocks
A block is a unit of Rant code subdivided into zero or more parts.
Syntax
A block is defined by a set of braces containing zero or more sections of Rant code separated by vertical pipes (|
). Here are a few examples:
{} # Empty block
{A} # Block with one element
{A|B} # Block with multiple elements
Use cases
Blocks serve several purposes in Rant, ranging from simple element selection to collections and even loops. They can contain any kind of Rant code—even other blocks.
Item selection
In their simplest form, blocks resolve a single element from their body and add it to the output.
# Randomly prints "Heads" or "Tails" with uniform probability
{Heads|Tails}
By default, blocks select a random element with uniform probability, but the selection strategy can be customized if needed using a selector.
Collection generation
Blocks can be used to easily generate lists and maps with conditional, repeating, or probabilistic elements.
# Evaluates to (A; B; C) or (A; D; E)
(A) { (B; C) | (D; E) }
# Evaluates to (1; 2; 3; 4; 5; 6; 7; 8; 9; 10)
[rep:10]{ ([step]) }
Entanglement
A group of blocks can be "entangled" so that they all coordinate their selections.
# Create a selector and store it in a variable
<$sync = [mksel:one]>
# Both blocks use the `sync` selector, so they're entangled
[sel:<sync>]{Dogs|Cats} say \"[sel:<sync>]{woof|meow}!\"
##
Possible outputs:
- Dogs say "woof!"
- Cats say "meow!"
##
Variable scope
Blocks also act as scopes for local variables. Any variables created inside of a block are destroyed immediately after the block resolves.
Stored blocks
Blocks can also be stored in variables for later use. To treat a block like a value instead of resolving, you need to prefix it with <>
(known as the "defer operator").
If the block requires a hint or sink, the <>
must appear after it.
<$cool-block = <>{Ohhh yeah!}>
Then you can resolve it later with the [resolve]
function:
[resolve: <cool-block>] # "Ohhh yeah!"
Blocks are similar to closures when used this way, but only in that they contain code you can store and run later. Stored blocks make several guarantees that closures and functions cannot:
- They never require arguments
- They never capture variables
- They will always consume attributes
Restrictions on function bodies and dynamic keys
Blocks used as function bodies and dynamic accessor keys are "linear" blocks: they can only contain one element and never consume attributes. Attempting to use a multi-element block in these contexts will cause a compiler error.
This design consideration prevents function calls and accessors from unintentionally consuming attributes unless the user explicitly requests it by adding an inner block.
Functions
Functions contain code that can be executed arbitrarily. The Rant standard library includes several native functions, but you can also define your own.
Calling functions
Function calls use the following syntax:
# Function with no arguments
[func-name]
# Function with one argument
[func-name: arg1]
# Function with multiple arguments, delimited by ';'
[func-name: arg1; arg2]
Shadowed functions are still callable
The situation may occasionally arise where you accidentally (or intentionally) define a non-function variable with the same name as a function from a parent scope (e.g. a stdlib function) and then try to call it:
<$rep = "not a function">
[rep:10] # Wait a sec...
[sep:\n]
{
# ...
}
Some people might (understandably) assume that this would crash the program, but this code actually still works!
When this happens, Rant will perform what is known as function percolation: the runtime will search each parent scope up to the global scope until it finds a function with the same name, and then call it as normal.
Function percolation only applies to function calls, so getters will still correctly retrieve the new variable instead of the function.
Defining functions
To define a function and assign it to a variable, the syntax is as follows:
# Define a parameterless function named `say-hello` that prints "Hello"
[$say-hello] {
Hello
}
# Define a function `greet` with parameter `name` that prints a custom greeting
[$greet: name] {
Hello, <name>!
}
# Define a function `flip-coin` that returns the value of either `heads` and `tails`
[$flip-coin: heads; tails] {
{<heads>|<tails>}
}
Optional parameters
A function parameter can be marked as optional using the ?
symbol.
When the argument is omitted for an optional parameter, it will default to ~
.
Please note that any optional parameters must appear after all required parameters, and before any variadic parameter.
# Generates a map for a pet with a name and species (defaults to "dog")
[$gen-pet: name; species?] {
@(
name = <name>|
species = [alt: <species>; dog]
)
}
Variadic parameters
Functions support variadic parameters with the special symbols *
and +
.
A *
parameter is optional and defaults to an empty list, while a +
parameter is required and must contain at least one element.
Functions may only have up to one variadic parameter, and it must appear last in the signature.
[$how-many:items*] {
[len:<items>]
}
[how-many: foo; bar; baz] # Outputs "3"
Closures
It is sometimes necessary to pass a function as a value rather than define it with a name, so that it can be called elsewhere later on. This can be done using closures, which are an implementation of anonymous functions with the ability to "capture" external variables.
Basic closure syntax with no parameters consists of a ?
symbol inside brackets preceding the function body.
# Creates a closure and places it on the output
[?]{Hello from closure!}
Parameterized closures
If you want to add parameters to a closure, specify the parameter names as you would with a normal function inside the closure's signature:
[?: param1; param2]{ ... }
Calling anonymous functions
Calling an anonymous function uses the same syntax as regular function calls, but the function name is replaced by the !
symbol followed by an expression that provides the function (or closure) you want to call.
# Define a function that returns a parameterless box
[$get-anon-func] {
[?]{Hello from anonymous function}
}
[![get-anon-func]] # "Hello from anonymous function"
# Define a function that returns a box with a single parameter
[$get-greet-func] {
[?:name]{Hello, <name>!}
}
[![get-greet-func]:Rant] # "Hello, Rant!"
Behavior of captured variables
As mentioned previously, a closure can access variables defined outside of its body. When this happens, it "captures" a reference to that variable for use inside the function. As a result, changes made to a captured variable persist between calls.
Variable capturing can also be used to maintain a persistent state within a closure instance: even if the original variable falls out of scope, the closure still keeps it alive.
# Create a function with a persistent state
{
<$foo-num = 1>
# Define a function [next-foo] in the parent scope
[$^next-foo] {
# Modify `foo-num` from inside the function
foo <$n = <foo-num>; foo-num = [add: <foo-num>; 1]; n>
}
} # `foo-num` falls out of scope here, but [next-foo] can still access it
# Call [next-foo] multiple times
[rep:4][sep:\n]
{
[next-foo]
}
##
Output:
foo 1
foo 2
foo 3
foo 4
##
Limitations on variable capture
Capturing is currently only supported on variables accessed locally from the function body. Descoped and explicit global accessors do not capture variables.
Argument spreading
Argument spreading enables you to deconstruct a list argument into multiple individual arguments.
Rant exposes this functionality through two spread operators: parametric spread (*
), and temporal spread (**
).
Parametric spread
The parametric spread operator lets you use a list to pass multiple arguments at once to a single function call. This operation is referred to in Rant as "parametric spreading" or simply "spreading."
Spreading is mostly useful for cases where the contents of a list need to be passed to a variadic parameter, but it will work on any other parameter (required, optional, or variadic).
There are no restrictions on the position of a spread within an argument list; you can add them to the start, end, middle, or even have multiple spreads sprinkled throughout. As long as the final argument list meets the requirements of the target function, it "just works."
Argument spreading is performed by prefixing an argument with *
, as seen below:
[$concat-args: a*] {
[join: \s; <a>]
}
<$extras = (baz; qux)>
[concat-args: foo; bar; * <extras>; boo]
# Same as calling [concat-args: foo; bar; baz; qux; boo]
Attempting to spread a non-list will simply pass the value as a normal argument.
Temporal spread
Rant has a second spread operatior, the temporal spread operator (**
), which behaves differently from a parametric spread.
Instead of spreading across parameters, it spreads across time itself: the target function is called once for each value in the temporally spread list argument (known as a "temporal argument"), passing in the current list item as the argument value. This essentially turns the function call into a loop.
This is best explained with the following example:
# Simple function that prints tab-separated values with a newline at the end
[%println: cols*] {
[join: <cols>; \t]\n
}
<%items = (foo; bar; baz)>
NORMAL SPREAD:\n
[println: * <items>]\n
# -> [println: foo; bar; baz]
TEMPORAL SPREAD:\n
[println: ** <items>]\n
# -> [println: foo]
# -> [println: bar]
# -> [println: baz]
This produces the following output:
NORMAL SPREAD:
foo bar baz
TEMPORAL SPREAD:
foo
bar
baz
As with parametric spreading, using a temporal spread on a non-list argument simply passes it as a regular argument.
Multiple temporal arguments
A function call can have more than one temporal argument. When this happens, Rant will call the function once for each possible combination of values from all temporal arguments.
When iterating through the combinations, Rant will increment the selection for the leftmost temporal argument first. When it rolls back over to the first item, the selection of the second temporal argument is incremented, then the third, and so on. The total number of iterations will be equal to the product of the lengths of all temporal arguments.
The following example demonstrates how this works:
# Print out every combination (disgusting or not) of two lists of seasonings
[concat: **(salt; pepper; sugar); \t; **(cinnamon; cilantro; basil; cloves); \n]
This produces the following output:
salt cinnamon
pepper cinnamon
sugar cinnamon
salt cilantro
pepper cilantro
sugar cilantro
salt basil
pepper basil
sugar basil
salt cloves
pepper cloves
sugar cloves
Temporal argument synchronization
In some cases it may be desirable to perform an operation only on elements with matching indices from two or more lists (often called a "zip" operation). Thankfully, this is easy to do with temporal arguments by adding a label to the temporal spread operators you want to synchronize.
To add a label to a temporal spread operator, simply add any sequence of alphanumeric characters, understores, and hyphens between the two *
characters, like this:
[concat: *a* (1; 2; 3); *a* (A; B; C); \n]
Doing this produces only 3 iterations:
1A
2B
3C
Function composition
When function calls are nested inside each other, they can be difficult to read and understand at a glance:
[e: [d: [c: [b: [a: 1]; 2]; 3]; 4]; 5] # Welcome to bracket hell
An alternative way to write nested function calls is by using iterative (rather than recursive) syntax-- a method known as function composition.
By using function composition, the same expression above can be rewritten with only a single pair of brackets:
[a: 1 |> b: 2 |> c: 3 |> d: 4 |> e: 5] # Much more readable!
In a composed function, the output of one function is fed into the next. By default, the output is inserted as the first argument;
however, this can be changed by passing the composition value []
as an argument:
[$get-zipper] {
{<add>|<sub>}
}
# Pass the output of [get-zipper] as the third argument for [zip]
[get-zipper |> zip: (1; 2; 3); (4; 5; 6); []]
If the composition value is a function
, you can call it directly using an anonymous call:
[$get-math-func] {
{<add>|<mul>|<sub>|<div>}
}
# Get a random math function and call it, passing in two numbers
[get-math-func |> ![]: 3.0; 2.0]
Behavior with temporal arguments
Temporal arguments in composed function calls behave differently than those in nested function calls.
To illustrate this, suppose we have a call to [foo]
that passes its return value into [bar]
.
In the nested scenario, a temporal argument in [foo]
will not duplicate the call to [bar]
:
[bar: [foo: **(1; 2; 3)]]
# -> [bar: [foo: 1][foo: 2][foo: 3]]
In the composed scenario, the temporal argument in [foo]
duplicates the entire call chain:
[foo: **(1; 2; 3) |> bar]
# -> [bar: [foo: 1]]
# -> [bar: [foo: 2]]
# -> [bar: [foo: 3]]
Comments
Comments are used to annotate code without affecting its behavior. This is useful for adding descriptions to your code that offer further explanation of the functionality.
There are two types of comments in Rant: single-line and multi-line.
Single-line comments
A single-line comment begins with the #
character and terminates at the end of the current line.
They do not have to start at the beginning of a line.
# This is a comment
{A|B|C} # This is an inline comment
Multi-line comments
A multi-line comment can span multiple lines and is enclosed by two #
characters on each end:
##
This is
a multi-line
comment
##
Escape sequences
It will, of course, be necessary to sometimes print reserved characters, or characters that cannot be typed on a standard keyboard. To this end, Rant offers escape sequences.
Character escape sequences
Any reserved (and most non-reserved) characters can be printed by prefixing each one with a backslash character:
\{This text will print with braces around it\}
Special escape sequences
Certain characters are not interpreted verbatim when escaped, and instead have special meaning. Here's a list of them:
Escape sequence | Output character |
---|---|
\r | Carriage return (U+000D) |
\n | Line feed (U+000A) |
\s | Space (U+0020) |
\t | Tab (U+0009) |
\0 | Null character (U+0000) |
\xXX | UTF-8 character code [See below] |
Unicode escape sequences
Escape sequences can also be used to print characters by their Unicode code point in hexadecimal format:
\xE4 # 'ä'
Data types
Rant has 10 built-in data types:
Type name | Description | Pass type |
---|---|---|
string | Sequence of UTF-8 characters | by-value |
int | 64-bit signed integer | by-value |
float | 64-bit double-precision float | by-value |
bool | Boolean value | by-value |
function | Function/closure | by-ref |
list | List of values | by-ref |
map | String-keyed collection of values | by-ref |
block | Stored block | by-ref |
special | Handle to internal runtime data, such as a selector | by-ref |
empty | Unit type representing "null" value | by-value |
The empty
type
To represent the lack of a value, Rant has the empty
type, which has only one possible value, represented by the token ~
.
<$nothing = ~> # i.e. <$nothing>
[type:<nothing>] # empty
Boolean values
The two boolean values are represented by the keywords true
and false
.
These keywords can still be used as fragments when not used alone, as their string representations are simply "true"
and "false"
;
however, if they are passed on their own and need to be strings, you will need to put them in a string literal.
Type inference for expressions
In order to resolve type ambiguities, Rant makes a few basic assumptions:
Integers
Any number token without a decimal place becomes an int
.
[type:123] # int
Floats
Any number token with a decimal place becomes a float
.
[type:123.0] # float
Top-level fragments or whitespace
Any expression containing top-level text (i.e. not in a collection) evaluates to a string.
Whitespace at the start or end of an expression is ignored.
# Since there are fragments and whitespace, it's a string
[type:99 bottles of beer] # string
# Since there is whitespace between the numbers, the value is still a string
[type:99 99] # string
Multiple values in a sequence
A sequence will evaluate to a string if the following are true:
- There are multiple values in the expression
- At least one value is non-empty
- The sequence is not all lists or all maps
When these conditions are met, all values printed by the sequences are converted to their string representations and concatenated.
# Even though they are all integer tokens, they are treated as text
[type:10 20 30 40 50] # string
Lists
A sequence that is entirely made of lists will return a single list containing the concatenation of the input lists.
(1; 2) (3; 4) # same as (1; 2; 3; 4)
This rule also applies when the sequence contains any expression that returns a list:
[rep:10] {
([step])
}
# returns (1; 2; 3; 4; 5; 6; 7; 8; 9; 10)
Maps
A sequence that is entirely made of maps will return a single map containing the key/value pairs of the input maps. Any duplicate keys are overwritten.
<%my-map =
@(a = 1)
@(b = 2)
>
# returns @(a = 1; b = 2)
As with list sequences, this also applies with expressions that return maps:
[rep:3]{
@(item_{[step]}) = [step]
}
# returns @(item_1 = 1; item_2 = 2; item_3 = 3)
Empties
Expressions containing only empties evaluate to the empty value.
Expressions containing nothing also evaluate to the empty value.
[type:~~] # empty
[type:~] # empty
[type:] # empty
Variables and accessors
Variables enable you to store and retrieve values.
Rant is a dynamically-typed language, meaning that variables have no set type; for example, you can initialize a variable with an integer, and change it later on to a string.
Accessors
Accessors are the central means of reading and writing data in Rant, and are denoted by a pair of angle brackets.
They have several uses:
- defining new variables
- getting/setting variable values
- accessing elements in collections
Accessors have three subtypes: definitions, setters, and getters.
Definitions
A definition creates a new variable in the current scope.
They are denoted by placing a $
symbol before the variable name.
It is optional to assign a value in a definition.
You can leave the assignment part out and it will be initialized to the empty value (~
).
# Define a variable `name` but leave it empty (value is `~`)
<$name>
# Define a variable `name` and assign it the string "Nick"
<$name = Nick>
Setters
A setter modifies an existing variable or value.
# Define a variable
<$name = Bob>
# Overwrite value on existing variable `name`
<name = Susan>
Along with setting variables, setters can also write to specific elements of collections.
<$numbers = (1; 2; 3)>
<numbers/0 = 4> # list is now (4; 2; 3)
Getters
A getter retrieves some value and prints it to the output.
Attempting to retrieve a variable that does not exist causes a runtime error.
# Get value of `name` (note the lack of '$')
<$name = Robin>
My name is <name>.\n # Prints "My name is Robin."
Providing a default value when a getter fails
If a getter fails (due to a missing variable, index, or key), the user may sometimes want to provide a default value to fall back on instead of raising an error. To do this, a fallback expression can be added to the end of the getter.
A fallback expression only runs if the data requested by the associated getter is not found; otherwise, it is ignored completely.
Example
# Store a value to use as a fallback
<$fallback = "I don't exist!">
{
# Define a variable `foo`
<$foo = "I exist!">
# Get `foo` with fallback
<foo ? <fallback>> # -> "I exist!"
}\n
# Getting `foo` again out of scope will trigger the fallback
<foo ? <fallback>> # -> "I don't exist!"
# Getting `foo` without a fallback here would crash the program
<foo> # error
Multi-part accessors
To aid readability, Rant also allows you to place several access operations in a single accessor block. Simply end each operation with a semicolon; the final semicolon is optional and may be omitted.
<$first-name = John; $last-name = Smith; $full-name = <first-name>\s<last-name>;>
Anonymous accessors
In order to access a key or index on a value, it normally must be stored in a variable; however, anonymous accessors remove this requirement by accepting a raw value instead of a variable name.
To create an anonymous accessor, replace the variable name in the accessor with !
followed by a single expression
(block, collection, accessor, function call, etc.) and write the rest of your access path as normal.
Both getters and setters can be made anonymous; however, anonymous setters must include at least one index or key.
Example 1
# Create two lists
<$list1 = (1;2;3;4;5;6;7;8)>
<$list2 = (a;b;c;d;e;f;g;h)>
# Create a function that returns one of two lists
[$get-either-list]{{<list1>|<list2>}}
# Call [get-random-list] and get the 4th item (index 3) of the returned list
<![get-either-list]/3> # Returns either '4' or 'd'
Example 2
# Get the first letter of a string literal
<!"hello"/0> # Returns 'h'
Variable scope
Variables only live as long as the expression or block in which they are defined.
Blocks, function arguments, setter expressions, dynamic keys, and function bodies are all examples of variable scopes. As soon as a scope is finished running, all variables defined within it are discarded.
Child scopes
Child scopes inherit variables from parent scopes. In addition, they may define their own variables.
{
<$a = 1>
{
<$b = 2>
[add: <a>; <b>] # Outputs "3"
}
}
Shadowing
Variables in a parent scope can be temporarily hidden ("shadowed") by defining a variable of the same name in a child scope.
When the child variable goes out of scope, the shadowed parent variable will once again become accessible.
# Define variable `a` in parent scope
<$a = foo>
a is <a>\n
{
# Define another variable `a` in child scope
# Parent variable is not affected
<$a = bar>
a is <a>\n
}
# Parent variable takes over after child scope exits
a is <a>
##
Output:
a is foo
a is bar
a is foo
##
The 'descope' operator
If a child scope shadows a parent variable and you want to access the parent variable explicitly, you can use the descope operator.
To do this, prefix the variable name with a ^
character.
<$a = foo>
{
<$a = bar>
Shadowed: <a>\n
Descoped: <^a>\n
}
##
Output:
Shadowed: bar
Descoped: foo
##
Adding more than one ^
character in a row will skip up to that many scopes.
These operations are called "n-descopes", where n is the number of scopes skipped:
For example, <^^foo>
is a 2-descope, <^^^foo>
is a 3-descope, and so on.
# Define `test`
<$test = foo>
{
# Shadow `test`
<$test = bar>
{
# Shadow `test` again
<$test = baz>
<^^test> <^test> <test>
}
}
## Output: "foo bar baz"
While the compiler imposes no explicit limit on descope depth, use cases for large descope depths are rare and it is recommended to avoid them when possible.
# An example of a 360-descope.
<^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
foo>
Explicitly accessing global variables
The top level of a Rant program implicitly defines a local variable scope; because of this, top-level variables will only persist for the duration of the program.
To explicitly access a variable in the global scope, prefix the path with the /
character.
Similarly to the descope operator, this method also allows you to override shadowing on globals.
<$foo = Hello from program scope!>
<$/foo = Hello from global scope!>
Local: <foo>\n
Global: </foo>\n
##
Output:
Local: Hello from program scope!
Global: Hello from global scope!
##
Constants
Constants are much like variables in that they are scoped the same way and store a value. Unlike variables, however, constants are immutable: they can only be assigned within a definition, and cannot be redefined.
The syntax to define a constant is simple: where variable definitions use $
, constant definitions use %
:
# Variable definition
<$mutable-value = 123>
# Constant definition
<%constant-value = 123>
<mutable-value = 456> # works as expected
<constant-value = 456> # error
Constants can still be shadowed by child scopes.
<%foo = 123>
{
<$foo = 456> # this is valid since we're not reassigning ^foo
}
By-ref constants have interior mutability
Storing a by-ref type, such as a list or map, in a constant does not prevent its contents from being changed; it only guarantees that the reference stored by the constant cannot change.
# Create a constant list
<%special-list = (1; 2; 3)>
# Modifying the contents is still allowed
[push: <special-list>; 4] # list now contains (1; 2; 3; 4)
# Swapping out the list itself is not allowed
<special-list = (7; 8; 9)> # error!
Collections
Rant's variable system has two collection types: lists and maps.
Lists
Lists are resizable arrays of values. They may contain multiple types of values.
Lists are initialized using a pair of parentheses containing the list elements, separated by semicolons:
# A pair of parentheses is treated like an empty list
<$empty-list = ()>
# Initialize a list with one empty element
<$one-empty = (~)>
# Create a list with two empty elements
<$two-empty = (;)> # Short for (~;~)
# Create a list of numbers
<$lucky-numbers = (1; 2; 3; 5; 7; 11; 13)>
# Create a list of lists
<$important-lists = ((A; B; C); (D; E; F))>
List indexing
List indices start at 0 and can be accessed by adding the index to the accessor path.
You can also use negative indices to access items relative to the end of a list, starting from -1 and going down.
# Create a list
<$list = (1;2;3;4;5;6;7;8;9)>
# Get first item
<list/0>\n # 1
# Get second item
<list/1>\n # 2
# Get last item
<list/-1>\n # 9
# Get second to last item
<list/-2>\n # 8
# Change third item from 3 to -3
<list/2 = -3>
<list/2> # -3
Maps
Maps are un-ordered collections of key-value pairs, where each key is unique. Map keys are always strings: even if you try to use a non-string as a map key, it will be automatically converted to a string first.
Maps use similar syntax to lists, but with some extra requirements:
- The
@
symbol must appear before the opening(
to identify the collection as a map. - Each element must be a key-value pair, with the key and value separated by an
=
symbol.
Map keys come in two flavors:
- Static keys: Evaluated at compile-time. They must be identifiers or string literals.
- Dynamic keys: Evaluated at run-time. They must be blocks.
# Create an empty map
<$empty-map = @()>
# Create a map with various value types
<$secret-key = occupation>
<$person = @(
name = John;
age = 25;
"favorite color" = {red|green|blue};
{<secret-key>} = programmer
)>
Retrieving values from collections
Variable accessors can access individual elements in lists and maps by using the access operator /
.
<$person = @(
name = Bob;
age = 30;
hobbies = (programming;hiking)
)>
name = <person/name>\n
age = <person/age>\n
hobbies = [join:,\s;<person/hobbies>]
##
Output:
name = Bob
age = 30
hobbies = programming, hiking
##
Additionally, a variable access path does not need to be made out of entirely constants. You can use a block to resolve to a key or index.
Map example
<$my-map = @(
a = foo; b = bar; c = baz
)>
<my-map/{{a|b|c}}>
# Outputs "foo", "bar", or "baz"
List example
<$my-list = (foo;bar;baz)>
<my-list/0> # "foo"
<my-list/1> # "bar"
<my-list/2> # "baz"
<my-list/{[num:0;2]}> # "foo", "bar", or "baz"
Collection auto-concatenation
Multiple collections of the same type are automatically concatenated or merged when printed alone in the same sequence. This significantly reduces the amount of boilerplate required when generating collections with varying amount of elements, or elements that are conditionally included.
Examples
# List auto-concatenation
[assert-eq: (A)(B); (A; B)]
# Probabilistic list generation
<%strange-list = { (A; B) | (C; D) } { (E; F) | (G; H) }>
##
<strange-list> will be one of:
* (A; B; E; F)
* (A; B; G; H)
* (C; D; E; F)
* (C; D; G; H)
##
# Automatically combine two maps
<%data =
@(
foo = 1
)
@(
bar = 2
)
>
[assert-eq: <data/foo?>; 1]
[assert-eq: <data/bar?>; 2]
Runtime features
This section deals with features specific to the Rant runtime.
Attributes
Attributes are special runtime settings that modify block behavior, such as how many times a block repeats or how it selects which element to run.
Attributes are set by calling the standard library's attribute functions and stored in the program's attribute stack. Each frame of the attribute stack stores a full set of attributes.
When a block resolves, it consumes attributes from the topmost attribute frame and replaces them with their default values.
Frames can be added to and removed from the attribute stack using [push-attrs]
and [pop-attrs]
.
Repetitions
By setting the repetitions attribute, you can control how many times the next encountered block will run.
This attribute is set with the [rep]
function.
Example
[rep:10]{[step]\n}
# Output:
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10
Separator
The separator attribute controls what is printed between each block repetition.
It is set using the [sep]
function.
Example
[rep:4][sep:" and "]
It just keeps {going}...
# Output:
# It just keeps going and going and going and going...
Selector
The selector attribute controls how Rant chooses which branch of a block to take. It does this using a special state machine object, which must be created separately but can be shared between blocks to coordinate their behavior.
Example
# Print every element of the block in a random order with no duplicates
<$s=[mksel:deck]> # Create a "deck" selector
[sel:<s>] # Apply the selector
[rep:all] # Set repetitions
[sep:,\s] # Set separator
{A|B|C|D|E|F|G|H}
# Output
# F, C, E, G, B, H, D, A
Pipe
The pipe attribute allows the user to supply a function (known as the "pipe function") that is called in place of each iteration of the block. The pipe function accepts the current block element as a callback parameter, which it can then call to produce output for that iteration.
This is extremely useful for applying filters or post-processing to a block at a per-iteration level.
[rep: all]
[sep: \n]
[pipe: [?:elem] { [elem]! }] # Just adds an exclamation point
[mksel: forward |> sel]
{
One | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten
}
Formatters
Rant can passively apply various filters to program output.
The following formatters are currently supported:
- Capitalization
- Numbers
- Whitespace normalization
Case formatter
The case formatter can automatically adjust capitalization of output text according to a variety of filter presets.
Number formatter
The number formatter controls how Rant converts numeric values (integers and floats) to strings in the program output.
Whitespace formatter
The whitespace formatter controls how Rant normalizes printable whitespace found in programs.
Modules
Rant supports basic dependency management via a module system. Modules are libraries of Rant functions that you can write and load into other Rant programs for use. Each module resides in its own source file.
Writing modules
A module is simply a Rant program that returns a map containing the module's contents. Similar to a regular Rant program, modules are expected have the .rant
file extension.
Below is a basic example of a very simple module:
# lol.rant: Laughter Generation Module
<$module = @()>
[$module/cackle: len?] {
[rep:[alt:<len>;2]]{h{a|e|ee|ue|aa|o}}!!!
}
<module>
Importing and using modules
A module can be imported using the [require]
function.
For this example, we'll import the lol
module previously shown.
# Looks for `lol.rant` and imports it as map variable `lol`.
# You don't need to specify the file extension.
[require: lol]
# Call the `[cackle]` function from the module
[lol/cackle: 16]
# --> haahoheehehehuehohoheehahohaaheehoheehaa!!!
Imported modules are cached by the Rant context running the program. If you import the same module twice, it will be fetched from cache.
Compiler errors in modules
Rant needs to compile modules before they can be imported. If a module fails to compile, a runtime error will be triggered with the compiler error list.
Where Rant looks for modules
When you run [require]
, Rant looks for the module in the following locations in-order:
- The requesting program's file location
- Skipped if the program was not loaded from a file
- The local modules path
- Defaults to the current working directory, but the host app can reconfigure it
- The global modules path
- Set by the
RANT_MODULES_PATH
environment variable - Can be disabled by the host app
- Set by the
If Rant cannot locate a module with a matching path and name, it will trigger a runtime error.
Relative paths in [require]
The [require]
function can also accept a relative path to a module.
This makes it possible to access modules in subfolders of any of the module search locations.
For example, if your application has a rant_modules
subfolder in its main directory, you can import modules from it like this:
# Imports `my-module`
[require:rant_modules/my-module]
Rant Standard Library
The Rant Standard Library contains many useful and some essential functions that are natively implemented in the Rant runtime itself.
Categories
Below is a list of broad categories that standard library features fall under:
General functions
Functions which fall under no specific category or are widely useful go here.
Attribute/Control flow functions
Functions for performing conditional branching, setting attributes, and other forms of control flow.
Collection functions
Functions that read from, manipulate, or create new collections.
Generator functions
Functions for generating various types of values, often using randomization.
Formatting functions
Functions for changing the output formatting.
String functions
Functions for working with strings.
Boolean functions
Functions for performing boolean algebra.
Comparison functions
Functions for comparing values to each other.
Math functions
Various arithmetic functions.
Conversion functions
Functions for converting values to other types.
Verification functions
Functions for testing values against various criteria.
Assertion functions
Functions for verifying program state and function output.
Constants
Special constant values, such as the language version in use.
Standard Library: General functions
[alt: a; b+]
→ any
or empty
Prints the first argument that isn't the empty value ~
. If all arguments are ~
, prints ~
.
[call: func; args]
→ any
or empty
Calls the function func
with the argument list args
.
Errors
Causes a runtime error if either of the following are true:
func
isn't a functionargs
isn't a list
[concat: values*]
→ any*
Prints the provided arguments in order.
[copy: value]
→ any
Returns a shallow clone of value
.
[either: condition; true-val; false-val]
→ any
Returns true-val
if condition
is true
, or false-val
if condition
is false
.
condition
must be of type bool
.
[fork: seed?]
Forks (overrides) the current RNG with a new RNG seeded by both the old RNG's seed and the specified seed. If no seed is provided, a random one is generated.
The seed value must be an int
or string
.
Errors
Causes a runtime error if seed
is neither an int
nor string
.
Example
# Entangles {yee|woo} with {haw|hoo}, i.e. forces them both to pick the same index
[fork:a]{yee|woo}[unfork]-
[fork:a]{haw|hoo}[unfork]!
# Output is either "yee-haw!" or "woo-hoo!"
[nop: args*]
Does absolutely nothing. Intended as a convenience function for use as a default/placeholder callback.
[require: module-path]
Imports the module at the specified relative path and assigns it to a local variable with a name matching the file name.
Rant requires module files to have the .rant
extension in order to load them; as such, it is not necessary to supply the file extension in the path.
Example
# Import module `my-module`
[require: my-module]
# Call `hello-world` function in `my-module`
[my-module/hello-world]
[resolve: block]
→ any
or empty
Resolves the specified block.
Errors
Causes a runtime error if the provided value is not a block.
[seed]
→ int
Returns the seed value of the currently active runtime RNG.
[type: value]
→ string
Returns the name of value
's type. The type name can be any of the following:
string
float
int
bool
function
list
map
special
empty
[unfork]
Removes the last RNG created by [fork]
and resumes use of the previous RNG.
Standard Library: Attributes & Control flow
[break: value?]
Exits the current repeater, optionally overwriting the iteration output with value
.
Errors
Causes a runtime error if called outside of a repeater.
[continue: value?]
Interrupts execution of the current repeater iteration and proceeds with the next one, optionally overwriting the iteration output with value
.
Errors
Causes a runtime error if called outside of a repeater.
[count-attrs]
→ int
Prints the current size of the attribute frame stack.
[else]
Marks the next block as conditional and causes it to resolve iff the last block was conditional and its condition evaluated to false.
[else-if: condition]
Marks the next block as conditional and causes it to resolve iff the following are true:
condition
is true- The last block was conditional and its condition evaluated to false
[if: condition]
Marks the next block as conditional and causes it to resolve iff condition
is true.
[mksel: selector-mode]
→ special
Creates and returns a selector with the specified mode.
Selector modes
Mode name | Description |
---|---|
random | Selects a random element each time. |
one | Selects the same, random element each time. |
forward | Selects in a wrapping sequence from first to last. |
reverse | Selects in a wrapping reverse sequence from last to first. |
deck | Selects each element once in a random sequence, then reshuffles. |
deck-loop | Selects each element once in a wrapping random sequence, without reshuffling. |
deck-clamp | Selects each element once in a random sequence, repeating the final element. |
forward-clamp | Selects from first to last, then repeats the last element. |
reverse-clamp | Selects from last to first, then repeats the first element. |
ping | Selects from first to last, switching directions when a boundary is reached. |
pong | Selects from last to first, switching directions when a boundary is reached. |
no-double | Selects a random element each time, ensuring the same element never repeats twice in a row. |
[pipe: pipe-func]
Sets the pipe function for the next block.
The function passed to pipe-func
must accept a single parameter (the element callback) in order to run properly.
[push-attrs]
Pushes a new attribute frame onto the attribute frame stack, overriding the previous one.
[pop-attrs]
Removes the topmost attribute frame from the attribute frame stack, handing control back to the previous one.
Errors
Popping the last attribute frame results in a runtime error.
[rep: reps]
Sets the repetition count for the next block to reps
.
The value of reps
must either be a non-negative int
or one of the special modes listed below.
Special rep modes
Mode name | Description |
---|---|
once | Run the block only once. (default behavior) |
forever | Repeat the next block until explicitly stopped. |
all | Repeat as many times as there are elements in the next block. |
Examples
# Print the fragment 3 times
[rep:3]{ha}
# hahaha
# Print the fragment between 3 and 8 times
[rep:[num:3;8]]{ha}
# hahahaha
# hahahahahahaha
# hahaha
# hahahahaha
# ...
[return: value?]
Returns from the current function, optionally overriding the function's output with a value.
[sel: selector]
Sets the selector for the next block.
The selector
parameter can accept either a selector state object created via [mksel]
, or a selector mode string (see [mksel]
documentation for available modes).
Examples
# Pass in an existing selector object so its state persists between uses
<%fwd = [mksel:forward]>
[sel:<fwd>]
[rep:all]
[sep:\s]
{
A | B | C | D
}
# Create a temporary selector using [mksel]
[sel:[mksel:forward]]
[rep:all]
[sep:\s]
{
A | B | C | D
}
# Automatically create a temporary selector from a mode name
[sel:forward]
[rep:all]
[sep:\s]
{
A | B | C | D
}
[sep: separator]
Sets the separator value for the next block to separator
.
The value of separator
may be a string, number, or function.
If it is a function, it will be called separately for each usage and its return value will be printed.
Examples
# Print comma-separated list of numbers 1-10
[rep:10][sep:,\s]{[step]}
##
Output:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
##
# Print list of numbers 1-10 separated by random sequence of spaces and newlines
[rep:10][sep:*{\n|\s}]{[step]}
##
Output:
1 2 3
4 5 6 7 8
9
10
##
Standard Library: Collection functions
[assoc: keys; values]
→ map
Creates a map from a list of keys and a list of values.
Each key in keys
will be matched with the value at the same index in values
.
Raises a runtime error if the lists are not the same length.
Example
# Generate a map of the "Big Five" personality traits
# with random rating values that add up to 50
<$personality =
[assoc:
(ope; con; ext; agr; neu);
[shred: 50; 5; [rand: 1; 10]] # Use random variance to reduce trait deviation
]
>
# Print the values
[whitespace-fmt:verbatim]
"Openness to experience:" <personality/ope>\n
"Conscientiousness:" <personality/con>\n
"Extraversion:" <personality/ext>\n
"Agreeableness:" <personality/agr>\n
"Neuroticism:" <personality/neu>\n
##
EXAMPLE OUTPUT:
Openness to experience: 7
Conscientiousness: 11
Extraversion: 5
Agreeableness: 12
Neuroticism: 15
##
[clear: collection]
Removes all elements from a list or map.
Errors
Causes a runtime error if collection
is not a list or map.
[filter: list; predicate]
→ list
Runs a predicate function against all items in a list and returns another list containing only the values that the predicate returned true
on.
The predicate function must accept a single parameter and return a bool
value.
Examples
Filter a list of numbers by only those divisible by 3
<$numbers = (1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12)>
<$multiples-of-three = [filter: <numbers>; [?:x] { [is-factor: <x>; 3] }]>
[join: ,\s; <multiples-of-three>]
# -> 3, 6, 9, 12
Filter a list of numbers by only odd numbers
<$numbers = (1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12)>
# `is-odd` is a function, so we can simply pass it in as the predicate!
<$odd-numbers = [filter: <numbers>; <is-odd>]>
[join: ,\s; <odd-numbers>]
# -> 1, 3, 5, 7, 9, 11
Filter a list of words to only those that are 3 letters or less
<$words = [split: "the quick brown fox jumps over the lazy dog";\s]>
<$short-words = [filter: <words>; [?:word] { [le: [len: <word>]; 3] }]>
[join: \s; <short-words>]
# -> the fox the dog
[index-of: list; element]
→ int
or empty
Returns the index of the first occurrence of element
in list
.
If no match is found, returns ~
.
Examples
<$letters = (A; A; B; C; C; D; E)>
[index-of: <letters>; A |> assert-eq: 0]
[index-of: <letters>; C |> assert-eq: 3]
[index-of: <letters>; E |> assert-eq: 6]
[index-of: <letters>; F |> assert-eq: ~]
[join: separator; list]
→ any*
Prints the elements of a list in order separated by the separator
value.
[last-index-of: list; element]
→ any
Returns the index of the last occurrence of element
in list
.
If no match is found, returns ~
.
<$letters = (A; A; B; C; C; D; E)>
[last-index-of: <letters>; A |> assert-eq: 1]
[last-index-of: <letters>; C |> assert-eq: 4]
[last-index-of: <letters>; E |> assert-eq: 6]
[last-index-of: <letters>; F |> assert-eq: ~]
[map: list; map-func]
→ list
Applies a function to each item in a list and returns another list with the results in the same order.
The predicate function must accept a single parameter, but can return anything.
Example
# Multiple each element of a list by 10
<$numbers = (1; 2; 3; 4; 5; 6; 7; 8; 9; 10)>
<$tens = [map: <numbers>; [?:x] { [mul: <x>; 10] }]>
[join: ,\s; <tens>]
# -> 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
[oxford-join: comma; conj; comma-conj; list]
→ any*
A variant of the join
function for conveniently formatting comma-separated lists.
Three distinct separator values are required:
comma
: the comma value, which separates items except for the last twoconj
: the conjunction value, which is only used to separate items in pairs (lists of 2)comma-conj
: the comma-conjunction value, which separates the final two values
These arguments can be used to configure several aspects of list formatting-- namely, the inclusion of the Oxford comma or the choice of conjunction separating the final two items.
The separator values are applied as follows:
- Lists of 1 item use no separators.
- Lists of 2 items use
conj
to separate the two items. - Lists of 3 or more items separate the final two items with
comma-conj
; all others usecomma
.
Examples
Print lists with Oxford comma
<$numbers = ()>
[rep: 5][sep: \n]
{
[push: <numbers>; [step]]
[oxford-join: ,\s; \sand\s; ,\sand\s; <numbers>]
}
##
OUTPUT:
1
1 and 2
1, 2, and 3
1, 2, 3, and 4
1, 2, 3, 4, and 5
##
Print lists without Oxford comma
<$numbers = ()>
[rep: 5][sep: \n]
{
[push: <numbers>; [step]]
[oxford-join: ,\s; \sand\s; \sand\s; <numbers>]
}
##
OUTPUT:
1
1 and 2
1, 2 and 3
1, 2, 3 and 4
1, 2, 3, 4 and 5
##
[push: list; value]
Appends a value to the end of a list.
[pop: list]
→ any
Removes the last value from a list and prints it.
[insert: collection; value; pos]
Inserts value
into a list or map at the position pos
.
If collection
is a list, pos
must be an int
.
If collection
is a map, pos
may be any non-empty value.
Errors
Causes a runtime error if any of the following are true:
collection
is not a list or mappos
is an unsupported type for the provided collectioncollection
is a list andpos
is out of range
[len: obj]
→ int
Prints the length of obj
.
For strings, this is the number of bytes; for lists and maps, this is the number of elements. All other value types give a length of 1.
[remove: collection; pos]
Removes the value at the pos
from a list or map.
If collection
is a list, pos
must be an int
.
If collection
is a map, pos
may be any non-empty value.
Errors
Causes a runtime error if any of the following are true:
collection
is not a list or mappos
is an unsupported type for the provided collectioncollection
is a list andpos
is out of range
[shuffle: list]
Shuffles the elements of a list in-place.
Example
# Shuffles a list of letters and concatenates them into a single string
<$letters = (A;B;C;D;E;F;G;H;I;J;K;L)>
[shuffle: <letters>]
[join: ; <letters>]
# ~> GKIBCHLEJADF
[shuffled: list]
→ list
Creates a shuffled copy of a list.
Example
# Shuffle the words in a string
<$message = "the quick brown fox jumps over he lazy dog">
[join: \s; [shuffled: [split: <message>; \s]]]
# ~> jumps fox quick dog lazy the brown the over
[sift: list; target-size]
Removes random elements from a list in-place until the number of elements in the list reaches target-size
.
If the number of elements in the list is less than or equal to target-size
, this function does nothing.
Example
# Remove a random element from a list and print the contents at each iteration
<$list = [split: "Sphinx of black quartz, judge my vow."; \s]>
[rep: [len: <list>]]
[sep: \n]
{
# Print the current list contents
[join: \s; <list>]
# Sift the list to n - 1
[sift: <list>; [sub: [len: <list>]; 1]]
}
##
EXAMPLE OUTPUT:
Sphinx of black quartz, judge my vow.
Sphinx of black quartz, my vow.
Sphinx of black quartz, vow.
Sphinx of black vow.
Sphinx black vow.
Sphinx vow.
vow.
##
[sifted: list; target-size]
→ list
Returns a copy of a list with random elements removed until the number of elements in the list copy reaches target-size
.
If the number of elements in the list is less than or equal to target-size
, this function simply returns an exact copy of the original list.
Example
# Create a random subset of abilities for a character
<$char-traits = (
berserk; xray-vision; speaks-to-bees;
vampirism; flying; telekinesis;
many-legs; high-jump; bee-allergy
)>
<$npc = @(
name = "Foo Bar";
traits = [sifted: <char-traits>; 2];
)>
# Print character info
<npc/name>: '[join: ,\s; <npc/traits>]
# ~> Foo Bar: speaks-to-bees, many-legs
[squish: list; target-size]
Merges random adjacent elements in a list using addition until the number of elements in the list reaches target-size
.
If the number of elements in the list is less than or equal to target-size
, this function does nothing.
Example
# Merge random items in a number list
<$numbers = (100; 100; 100; 100; 100; 100; 100; 100; 100; 100)>
# Print the original list
Before: '[join: ,\s; <numbers>]\n
# Squish the list down to 5 elements
[squish: <numbers>; 5]
# Print the modified list
After: '[join: ,\s; <numbers>]\n
##
EXAMPLE OUTPUT:
Before: 100, 100, 100, 100, 100, 100, 100, 100, 100, 100
After: 100, 200, 100, 400, 200
##
[squished: list; target-size]
→ list
Returns a copy of a list with random adjacent elements merged using addition until the number of elements in the list copy reaches target-size
.
If the number of elements in the list is less than or equal to target-size
, this function simply returns an exact copy of the original list.
[sort: list]
Sorts the elements of a list in-place in ascending order.
[sorted: list]
→ list
Creates a copy of a list with its elements sorted in ascending order.
[sum: list]
→ any
Adds the elements of a list together from left to right and prints the result.
[take: collection; pos]
→ any
Removes the value at pos
from a list or map and prints it.
If collection
is a list
, pos
must be an int
.
If collection
is a map
, pos
may be any non-empty value.
Errors
Causes a runtime error if any of the following are true:
collection
is not a list or mappos
is an unsupported type for the provided collectioncollection
is a list andpos
is out of range
Errors
Causes a runtime error if collection
is a list and the pos
is out of range.
[translate: list; map]
→ list
Matches each item in a list to a map and returns a list with the corresponding map values. Values that have no corresponding key in the map are passed through as-is.
Example
# Constructs a substitution cipher function
[$make-cipher: alphabet] {
<
$letters = [split: <alphabet>];
$sub-letters = [shuffled: <letters>];
$cipher = [assoc: <letters>; <sub-letters>];
$cipher-rev = [assoc: <sub-letters>; <letters>];
>
# Return cipher functions
@(
encode = [?: message] {
[sum: [translate: [split: <message>]; <cipher>]]
};
decode = [?: message] {
[sum: [translate: [split: <message>]; <cipher-rev>]]
};
)
}
# Create a cipher and encode/decode a message
<$cipher = [make-cipher: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"]>
<$secret-message = "The quick brown fox jumps over the lazy dog.">
<$encoded-message = [cipher/encode: <secret-message>]>
<$decoded-message = [cipher/decode: <encoded-message>]>
# Finally, print the result
Original: \"<secret-message>\"\n
Encoded: \"<encoded-message>\"\n
Decoded: \"<decoded-message>\"\n
##
EXAMPLE OUTPUT:
Original: "The quick brown fox jumps over the lazy dog."
Encoded: "kWj KGvaF QcDiq HDx CGpMJ DOjc AWj Tsyt fDN."
Decoded: "The quick brown fox jumps over the lazy dog."
##
[zip: list-a; list-b; zip-func]
→ list
Returns a new list that combines each pair of items from the two input lists using the specified function. The lists do not need to be the same length; if there is a difference, it will be made up with empty values.
The zip-func
must accept two parameters.
Examples
# Dot product
[$dot: a; b] {
[zip: <a>; <b>; <mul> |> sum]
}
[dot: (1; 2; 3); (4; 5; 6)] # 32
Standard Library: Generators
[dig: count?]
→ string
Prints a uniformly random decimal digit. If count
is specified, repeats count
times.
# Generate a random 32-character decimal string
[dig:32] # ~> 01952208554533821061510695429126
[digh: count?]
→ string
Prints a uniformly random lowercase hexadecimal digit. If count
is specified, repeats count
times.
Example
# Generate a random 32-character hex string
[digh:32] # ~> f4e5bef31ea02eac22f220e68e837587
[dignz: count?]
→ string
Prints a uniformly random non-zero decimal digit. If count
is specified, repeats count
times.
# Generate a random 32-character decimal string without zeros
[dignz:32] # ~> 92558761934966287236132511739511
[maybe: p?]
→ bool
Returns a bool
value with p
probability of being true.
p
must be either a float
or empty
. If omitted, it will default to 0.5
.
[rand: a; b]
→ int
Prints a random integer with uniform distribution between a
and b
(both inclusive).
Example
You roll the dice and get '[rand:1;6] and '[rand:1;6].
# You roll the dice and get 2 and 5.
[randf: a; b]
→ float
Prints a random float with uniform distribution between a
(inclusive) and b
(exclusive).
[rand-list: a; b; n]
→ list
Prints a list of n
random integers with uniform distribution between a
and b
(both inclusive).
Example
# 2-dice roll
<$roll = [rand-list: 1; 6; 2]>
You rolled '[join: \sand\s; <roll>] for a total of '[sum: <roll>].
# ~> You rolled 5 and 3 for a total of 8.
[randf-list: a; b; n]
→ list
Prints a list of n
random floats with uniform distribution between a
(inclusive) and b
(exclusive).
[shred: input; count; variance]
→ list
Generates a list of count
random numbers that vary from each other by variance
, and whose sum equals input
.
All three arguments must be numbers, and count
must be an int
.
Errors
Causes a runtime error if:
input
,count
, orvariance
is not a numbercount
is zero or negative
Example
# Generate and print a list of 5 random numbers that add up to 1000
<$parts = [shred:1000;5;200]>
[join:" + ";<parts>] = [sum:<parts>]
##
Output:
170 + 378 + 83 + 189 + 180 = 1000
##
Standard Library: Formatting
[whitespace-fmt: mode?; custom-value?]
→ string
or empty
Gets or sets the whitespace normalization mode. This does not affect whitespace in escape sequences and string literals.
Passing no arguments to this function will simply get the current mode.
Formatting modes
Mode | Description |
---|---|
default | (Default) Normalizes all whitespace to a single ASCII space character (0x20). |
verbatim | Prints all printable whitespace as it appears in the code. |
ignore-all | Ignores all printable whitespace. |
custom | Replaces all whitespace sequences with the custom-value argument. |
Example
[whitespace-fmt: default] The quick brown fox jumps over the lazy dog.
# Output: The quick brown fox jumps over the lazy dog.
[whitespace-fmt: verbatim] The quick brown fox jumps over the lazy dog.
# Output: The quick brown fox jumps over the lazy dog.
[whitespace-fmt: ignore-all] The quick brown fox jumps over the lazy dog.
# Output: Thequickbrownfoxjumpsoverthelazydog.
[whitespace-fmt: custom; ...\s] The quick brown fox jumps over the lazy dog.
# Output: The... quick... brown... fox... jumps... over... the... lazy... dog.
Standard Library: String functions
[lines: str]
→ list
Splits string str
by line feed characters (0x0A, \n
) and returns a list of the results.
[lower: str]
→ string
Converts string str
to lowercase and returns the result.
[split: text; sep?]
→ list
Splits the input text by sep
into a list of strings. If sep
is omitted, splits into characters.
[seg: text; size]
→ list
Segments the input text into a list of strings of size
length.
[upper: str]
→ string
Converts string str
to uppercase and returns the result.
Standard Library: Boolean functions
[and: a; b; c*]
→ bool
Performs a short-circuiting boolean AND on the operands and returns the result.
[not: a]
→ bool
Returns the inverse of the input boolean value.
[or: a; b; c*]
→ bool
Performs a short-circuiting boolean OR on the operands and returns the result.
[xor: a; b]
→ bool
Performs a boolean XOR on the operands and returns the result.
Comparison
[eq: lhs; rhs]
→ bool
Returns true
if lhs
and rhs
are equal.
[ge: lhs; rhs]
→ bool
Returns true
if lhs
is greater than or equal to rhs
.
[gt: lhs; rhs]
→ bool
Returns true
if lhs
is greater than rhs
.
[le: lhs; rhs]
→ bool
Returns true
if lhs
is less than or equal to rhs
.
[lt: lhs; rhs]
→ bool
Returns true
if lhs
is less than rhs
.
[neq: lhs; rhs]
→ bool
Returns true
if lhs
and rhs
are not equal.
Standard Library: Math functions
[abs: num]
→ int
or float
Calculates the absolute value of num
.
Errors
Raises an error if num
is an integer and the absolute value overflows.
[add: lhs; rhs]
→ any
Adds two values.
[ceil: val]
→ int
Gets the smallest integer that is greater than or equal to val
.
val
must be a float
.
[div: lhs; rhs]
→ any
Divides two values.
[floor: val]
→ int
Gets the largest integer that is less than or equal to val
.
val
must be a float
.
[frac: val]
→ float
Gets the fractional part of val
.
val
must be a float
.
[max: values+]
→ any
Returns the largest value in values
.
Any elements of type list
in values
will be expanded to their individual elements
before the maximum value is calculated.
Examples
# Arguments can be single values
[max: 3; 2 |> assert-eq: 3]
# Lists are treated as their individual elements
[max: (3; 2) |> assert-eq: 3]
# Even alongside single values, lists are still expanded!
[max: 3; (4; -2; 0; 10); 6 |> assert-eq: 10]
[min: values+]
→ any
Returns the smallest value in values
.
Any elements of type list
in values
will be expanded to their individual elements
before the minimum value is calculated.
Examples
# Arguments can be single values
[min: 3; 2 |> assert-eq: 2]
# Lists are treated as their individual elements
[min: (3; 2) |> assert-eq: 2]
# Even alongside single values, lists are still expanded!
[min: 3; (4; -2; 0; 10); 6 |> assert-eq: -2]
[mod: lhs; rhs]
→ any
Gets the modulus of two values.
[mul: lhs; rhs]
→ any
Multiplies two values.
[mul-add: lhs; rhs; add]
→ any
Multiplies two values, then adds another value to the result.
[neg: n]
→ any
Negates a value.
[recip: n]
→ any
Gets the reciprocal of a value.
[sub: lhs; rhs]
→ any
Subtracts two values.
[sin: x]
→ float
Calculates the sine of x
(in radians).
[cos: x]
→ float
Calculates the cosine of x
(in radians).
[tan: x]
→ float
Calculates the tangent of x
(in radians).
[asin: x]
→ float
Calculates the arcsine (in radians) of x
.
[acos: x]
→ float
Calculates the arccosine (in radians) of x
.
[atan: x]
→ float
Calculates the arctangent (in radians) of x
.
[atan2: y; x]
→ float
Calculates the four-quadrant arctangent (in radians) of y / x
.
Notes
Returns 0 if x
or y
is 0.
[sqrt: x]
→ float
Calculates the square root of x
.
[pow: x; y]
Raises x
to the power of y
. Both x
and y
can be int
or float
.
Errors
Raises an overflow error if a x
is an int
and x ^ y
causes an overflow.
Standard Library: Conversion functions
[float: value]
→ float
or empty
Attempts to convert value
to a float
value and returns the result.
If the conversion fails, returns empty
value.
[int: value]
→ int
or empty
Attempts to convert value
to an int
value and returns the reuslt.
If the conversion fails, returns empty
value.
[string: value]
→ string
or empty
Attempts to convert value
to a string
value and returns the result.
If the conversion fails, returns empty
value.
Standard Library: Verification functions
[is-any: value]
→ bool
Returns true
if value
is any non-empty
type.
[is-between: value; a; b]
→ bool
Returns true
if value
is bewteen a
and b
(both inclusive).
[is-bool: value]
→ bool
Returns true
if value
is of type bool
.
[is-empty: value]
→ bool
Returns true
if value
is ~
.
[is-even: number]
→ bool
Returns true
if number
is an even number.
[is-float: value]
→ bool
Returns true
if value
is of type float
.
[is-int: value]
→ bool
Returns true
if value
is of type int
.
[is-nan: value]
→ bool
Returns true
if value
is of type float
and equal to NaN (Not a Number).
[is-number: value]
→ bool
Returns true
if value
is of type int
or float
.
[is-odd: number]
→ bool
Returns true
if number
is an odd number.
[is-string: value]
→ bool
Returns true
if value
is of type string
.
Assertion
[assert: condition; message?]
Asserts that condition
is true.
If condition
is false
, raises a runtime error; if true
, does nothing.
The message displayed in the runtime error can be customized by passing a string to message
.
Examples
# does absolutely nothing
[assert: true]
# [assertion error] assertion failed: condition was false
[assert: false]
# [assertion error] ooooops!
[assert: false; "ooooops!"]
[assert-eq: actual; expected; message?]
Asserts that expected
and actual
are equal.
Like [assert]
, a custom error message can be provided.
[assert-neq: actual; unexpected; message?]
Asserts that unexpected
and actual
are not equal.
Like [assert]
, a custom error message can be provided.
Standard Library: Constants
RANT_VERSION
string
The version of the Rant language currently being used.
Glossary
Accessor
Accessors are program elements that read or write values. They are the primary means of interacting with variables.
There are three types of accessors: getters, setters, and definitions.
Access path
An access path is used to access variables or items in collections. They consist of /
-delimited series of identifiers, keys, and indices.
Associative block
Associative blocks are a special block type dividing each element into left- and right-hand sides.
The left-hand side (LHS) associaties some value (usually a weight or case value) with the element, while the right-hand side (RHS) contains the code associated with the element.
This block type is typically used for conditional or weighted branching.
Attribute
Attributes modify block behavior, such as how many times a block repeats or how it selects elements.
Attributes can be set through attribute functions.
Attribute frame
An attribute frame is a full set of all available attributes. A program's attribute stack always contains at least one frame.
Attribute functions
A function that reads or writes an attribute.
Attribute stack
Each program maintains an "attribute stack", which stores and provides attributes to blocks. When a block runs, it consumes the attributes in the topmost frame of the attribute stack, replacing them with their default values.
The user can add and remove frames from the attribute stack to preserve attributes for later use.
Block
A block is a section of a Rant program subdivided into zero or more parts. Blocks act as variable scopes and can be combined with various constructs to produce a wide variety of behaviors.
Closure
A closure is a function that captures one or more variables from its environment.
Construct
A construct is a group of one or more runtime variables can be used to interact with the Rant runtime and customize various aspects of program behavior.
Definition
A definition is an accessor type that creates a new variable.
Descoping
Descoping is a type of variable access that explicitly retrieves a variable from a parent scope, overriding shadowing.
Dynamic key
A dynamic key is an access path key, consisting of a single-element block, that must be resolved before the containing path can be read. The block resolves to the final key or index that will be inserted into the path.
Formatter
A formatter is a runtime component that passively changes the output in some way.
Fragment
A fragment is a sequence of any non-whitespace, non-reserved characters found in a Rant program.
Function percolation
Function percolation is the ability of function calls to access functions from parent scopes despite shadowing by non-function variables. It is an implicit form of descoping.
Getter
A getter is an accessor type that retrieves a value from a variable or collection.
Hint
A hint is a compile-time operation that informs the Rant compiler that the next program element is expected to print to the output.
Identifier
A name assigned to a variable or module.
Identifiers enforce specific formatting requirements to ensure consistency:
- Must contain at least one alphanumeric character.
- Must only contain alphanumeric characters, underscores, and hyphens.
List
A list is an ordered collection of values, accessible by index.
Map
A map is a collection of key-value pairs, also known as an "associative array".
Module
A module is a library of Rant functions that can be loaded into another program using the [require]
function.
Repeater
A block that can iterate multiple times. Repeaters are created by calling [rep]
.
Resolution
Resolution refers to the process of producing an output from a block, after which the block is referred to as 'resolved.'
Resolver
The component of the Rant runtime that handles block resolution.
Shadowing
A variable is shadowed (hidden) when it is overridden by a variable of the same name in a child scope.
Setter
A setter is an accessor type that writes to a variable or collection.
Sink
A sink is an operation that suppresses the output of a program element while still allowing its execution.