npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

rule-lang-programming-language

v2.0.0

Published

An esoteric matching language with bizarre control flow

Readme

RuleLang Docs

RuleLang is an esoteric matching language with bizarre control flow.

# fizzbuzz
begin >> 1
num as x if x <= 100 & mod(x 15) = 0 >> [ add(x 1) !print("FizzBuzz")]
num as x if x <= 100 & mod(x 3) = 0 >> [ add(x 1) !print("Fizz")] 
num as x if x <= 100 & mod(x 5) = 0 >> [ add(x 1) !print("Buzz")] 
num as x if x <= 100 >> [ add(x 1) !print(x)] 
num !> [] # Remove the last number from the record
          # Although not necessary, this is good practice

[!NOTE] The code examples are highlighted in Python, so they might not be entirely accurate, but they are easier to understand.

Instillation for CLI

npm i rule-lang-programming-language

Running Files via CLI

# .rul is the file extention for a RuleLang program
rule my_file.rul

The Record

Everything in RuleLang is built on the record, which is the only true storage for values. The record is a list of values. You can easily add values to the record, remove values, or loop throughout the record.

Comments

# This is a comment
#[
 Comments can be multi-
 line
]#

Values

There are five different types of primitive data types also known as values (for simplicity).

Numbers (num)

If a number is a decimal number less than 1, it must be prepended with a 0. Negative numbers can prepended with - and no space in between characters.

1 
1.2 
0.4
-0.6

Strings (str)

Strings are naturally multi-line, but they can be typed on a single line using escape codes.

"Hello, World"
"Multi-
Line
String"
# Escape Codes:
#   \" -> "
#   \\ -> \
#   \n -> new line 

Booleans (bool)

A boolean is either true (has value) or false (does not have value).

true
false

Terms (term)

A term is a word that starts with a capital letter and only contains alphanumeric characters and underlines.

Loop
My_Term
LOOP_TO_100

Nil (nil)

This value represents a failed result or no return value from a function.

nil

Rules

Rules are the main control flow of RuleLang. Each rule consists of a pattern that will be matched against the record. When a successful match occurs, the rule's scope(s) will be evaluated in a way determined by the match operator(s). All the parts of a rule will be explained below, and several examples tie together these concepts at the end.

Pattern

Think of a pattern as a key to the lock. Only a specific grove sequence will open the lock. Similarly, only a specific pattern will match the record.

The RuleLang program is a big rule scope, a sequence of rules that continue attempting to match against the record until no more patterns match. The simplest patterns are begin and end. The begin pattern will automatically match when entering the rule scope before all other patterns attempt to match, and the end pattern will match when leaving the rule scope after all other patterns cease to match.

All other patterns are custom patterns that repeatably attempt to match against the record until there are no more matches.

Pattern Values

Custom patterns are made of a sequence of pattern values. If each pattern value matches the record value it's compared to, a successful match has taken place!

A pattern value can simply be any value:

false           # Matches a `false` in the record
"Hello, World!" # Matches a `Hello, World!` in the record

A pattern value can also match any value of a type:

num             # Matches any number in the record, like `5` or `2`
str             # Matches any string in the record, like `Hello` or `World`
term            # Matches any term in the record, like `Loop` or `If`
bool            # Matches any boolean in the record, like `true` or `false`
any             # Matches any value in the record of any type

Patterns can have multiple pattern values in a sequence.

1 2          # Matches a `1` followed by a `2` in the record
true any str # Matches a `true` followed by any value, and then a string
Logical Pattern Operators

There are several pattern operators that can alter a pattern value.

Not

The not operator (!) matches anything but the following pattern value.

!1 # Matches any value but a `1` in the record, like `3` or `My_Term`

!str # Matches any value but a string in the record, like `1` or `false`

[!WARNING] You cannot have a sequence of nots, !!!!7 is an error.

Or

The or operator (|) matches one group of pattern values or the other group of pattern values. Each side of the or operator must have the same number of pattern values.

1 | 2      # Matches `1` or `2` in the record
num | str  # Matches any number or string in the record
1 2 | 3    # Error! The left group has 2 pattern values, and the right has 1

Parenthesis can be used to make more complex patterns.

1 2 | 3 4    # Matches `1` followed by `2`, or `3` followed by a `4` in the record

# We can wrap the "or" in parenthesis to limit the pattern groups

1 (2 | 3) 4  # Matches `1`, followed by a `2` or `3`, followed by a 4 in the record

The or operator can be chained, so several ors can be used at once.

1 | 2 | 3     # Matches `1` or `2` or `3` number in the record

# This simplifies to (1 | (2 | 3))

Rule Operator and Scope

The second part of a rule, after the pattern, is the rule operator(s) and accompanying scope(s). There are two different kinds of scopes: rule scopes, a list of rules, and value scopes, a list of values, both of which are surrounded by square brackets ([ ... ]).

[!TIP] A value scope could also be a single value without square brackets. Both of these are value scopes: [ 1 2 3 ] and 1

When a pattern successfully matches, the pattern will be removed from the record and the rule operator will determine what happens to the result of the scope.

End Pushing Match (>>)

The end-pushing match rule operator (>>) will add all values within the scope to the end of the record.

begin >> [ 1 2 ]
end >> [ 3 4 ]

# Record Result: [ 1 2 3 4 ]
Beginning Pushing Match (<<)

The beginning-pushing match rule operator (<<) will add all values within the scope to the start of the record.

begin << [ 3 4 ]
end << [ 1 2 ]

# Record Result: [ 1 2 3 4 ]
Removing Match (!>)

The removing match rule operator (!>) will not add any values from the scope to the record.

begin !> [ 1 2 ]

# Record Result: [ ]
Replacing Match (->)

The replacing match rule operator (->) will add the values from the scope to the record at the index of where the pattern was removed. Therefore, the pattern in the record is replaced by the scope.

begin >> [ 1 2 3 4 ]
2 3 -> [ 5 ] # When a `2` followed by a `3` is matched, replace it with `5`
# Record Result: [ 1 5 4]

# The custom rule can omit the square brackets because it is a single value:

begin >> [ 1 2 3 4 ]
2 3 -> 5 # When a `2` followed by a `3` is matched, replace it with `5`
# Record Result: [ 1 5 4]

[!WARNING] This match operator does not work with the begin or end rules because there are no patterns to replace.

Rule Match (=>)

The rule match operator (=>) will enter a new rule scope and match within the scope until no more matches exist.

begin >> [ 1 2 3 ]
# Current Record: [ 1 2 3 ]
end => [
 begin >> [ 4 5 6 ]
    # Current Record: [ 1 2 3 4 5 6 ]
    5 -> "five" # Match a `5` to replace with `five`
    # Current Record: [ 1 2 3 4 "five" 6 ]
 end >> [ 7 8 9 ]
]
# Final Record: [ 1 2 3 4 "five" 6 7 8 9 ]
Additional Value Scope Matching Information

All of the value scope matching operators (>>, <<, !>, and =>) evaluate the scope entirely before the values are added to the record. This way, matches like the beginning-pushing match don't push values in a reverse order:

begin << [ 3 4 ]
end << [ 1 2 ]
# Values pushed as the entire scope at once: [ 1 2 3 4 ]
# Values pushed separately: [ 2 1 4 3 ]

See how the end rule pushes the 1 to the start of the record, then the 2. This seems complicated and unnecessary because it is. So RuleLang doesn't work this way.

[!NOTE] This can lead to several minor annoyances like the function empty, which empties the record when evaluated. begin >> [ 1 2 3 empty() ] does not empty the record, because 1, 2, and 3 are not added to the record until the scope finishes evaluating. Therefore, you can use rule chaining: begin >> [ 1 2 3 ] !> empty()

Rule Chaining

Each rule scope can have up to one begin and one end rule, but there are an unlimited number of custom rules in between. A rule is not just limited to a single rule operator and scope. You can chain rule operators and scopes so they evaluate sequentially, however, you can only use a replacing match (->) as the start of a chain (the first match operator).

begin >> [ 1 2 3 ] # Current Record: [ 1 2 3 ]
 >> [ 4 5 6 ] # Current Record: [ 1 2 3 4 5 6 ]
 => [
        5 -> "five" # Match a `5` to replace with `five`
        # Current Record: [ 1 2 3 4 "five" 6 ]
 ]
 >> [ 7 8 9 ]
# Final Record: [ 1 2 3 4 "five" 6 7 8 9 ]

[!NOTE] The replacing match (->) can only be first and once because otherwise, other scopes could push values to the record messing up where the replacing replaces values. For example begin >> [ 1 2 3 4 ] 2 << 0 -> "two". Should the result be [ 0 1 "two 3 4 ]or[ 0 "two" 2 3 4 ]`?

In-Scope Not

The in-scope not operator (!) can be used within a value scope to NOT add the following value to the record.

begin >> [ 1 2 3 !4 !5 ] # [ 1 2 3 ]

Although the same result can be found using rule chaining, this simplifies the program and is mostly beneficial for functions.

begin >> [ 1 2 3 !print(4) ] # [ 1 2 3 ] and displays `4` in the console
end >> !print(5) # this is the same as `end !> print(5)`

Sequence of Rule Matching

When a rule scope is entered, if there is a begin rule, it will be evaluated first. Then, all custom rules attempt to evaluate until there are no more matches. These custom rules attempt to match first down rules, then across the record.

Rule matching follows this sequence:

  1. A pointer points towards the first value in the record. The pointer always points to the value in the record that will be compared to the first pattern value in the pattern.
  2. Rules are descended. Sequentially, each rule's pattern will try to match against the record.
  3. If a pattern matches, the rule's scope evaluates. Then, return to 1.
  4. If no rule matched, slide the pointer over to the next record value, going across. If there are no more record values, no more matches will occur so the end rule will evaluate, then the rule scope will be exited. Otherwise, there was a new record value, so return to 2 to go down.

Here are several examples to demonstrate the sequence:

Single Rule (across)

begin >> [ 2 1 2 2 1 2 ]
1 2 -> "three"

Initially, the record and pointer will look like the following:

Record  [ 2 1 2 2 1 2 ]
Pointer   ^

First, we go down the rules, which is easy because we just have one. The pattern 1 2 will attempt to match against the record at the index of the pointer, resulting in no match.

Record  [ 2 1 2 2 1 2 ]
Pointer   ^
Pattern   1 2
Result: No Match

Because no matches took place going down, we go across. The pointer will slide over one value in the record, and we'll attempt to make another match going down our rules. In this case, there is a match, so 1 2 is replaced by "three".

Record  [ 2 1 2 2 1 2 ]
Pointer     ^
Pattern     1 2
Result: Match
New Record: [ 2 "three" 2 1 2 ]

A pattern matched, so the pointer reverts back to the start of the record.

Record  [ 2 "three" 2 1 2 ]
Pointer   ^

Now, we continue going down and across until another pattern is matched.

Record  [ 2 "three" 2 1 2 ]
Pointer   ^
Pattern   1 2
Result: No Match

Record  [ 2 "three" 2 1 2 ]
Pointer     ^
Pattern     1 2
Result: No Match

Record  [ 2 "three" 2 1 2 ]
Pointer             ^
Pattern             1 2
Result: No Match

Record  [ 2 "three" 2 1 2 ]
Pointer               ^
Pattern               1 2
Result: Match
New Record: [ 2 "three" 2 "three" ]

Because a pattern was matched, the pointer goes back to the start of the record and we continue attempting to match. However, the down and across methods yield no more matches, so the rule scope is exited.

Record  [ 2 "three" 2 "three" ]
Pointer   ^
Pattern   1 2
Result: No Match

Record  [ 2 "three" 2 "three" ]
Pointer     ^
Pattern     1 2
Result: No Match

Record  [ 2 "three" 2 "three" ]
Pointer             ^
Pattern             1 2
Result: No Match

Record  [ 2 "three" 2 "three" ]
Pointer               ^
Pattern               1 2
Result: No Match

Multiple Rules (down then across)

begin >> [ 1 1 2 4 2 1 ]
1 1 -> 2
1 2 -> 3
2 2 -> 4
4 4 -> 8

This program has four custom rules, which we will label: ① 1 1 -> 21 2 -> 32 2 -> 44 4 -> 8 Initially, the record and pointer will look like the following:

Record  [ 1 1 2 4 2 1 ]
Pointer   ^

First, we go down the rules. ① attempts to match and succeeds, replacing 1 1 with 2.

Record   [ 1 1 2 4 2 1 ]
Pointer    ^
Pattern ① 1 1
Result: Match
New Record: [ 2 2 4 2 1 ]

Because a match was found, the pointer moves to the beginning of the record, and rules are matched going down again. ① and ② both fail to match, but ③ succeeds, replacing 2 2 with 4.

Record   [ 2 2 4 2 1 ]
Pointer    ^
Pattern ① 1 1
Result: No Match

Record   [ 2 2 4 2 1 ]
Pointer    ^
Pattern ② 1 2
Result: No Match

Record   [ 2 2 4 2 1 ]
Pointer    ^
Pattern ③ 2 2
Result: Match
New Record: [ 4 4 2 1 ]

A match was found, so we start with the pointer at the beginning of the record, going down again. ①, ②, and ③ all fail to match, but ④ succeeds, replacing 4 4 with 8.

Record   [ 4 4 2 1 ]
Pointer    ^
Pattern ① 1 1
Result: No Match

Record   [ 4 4 2 1 ]
Pointer    ^
Pattern ② 1 2
Result: No Match

Record   [ 4 4 2 1 ]
Pointer    ^
Pattern ③ 2 2
Result: No Match

Record   [ 4 4 2 1 ]
Pointer    ^
Pattern ④ 4 4
Result: Match
New Record: [ 8 2 1 ]

For the final time, a match took place, so we start by going down with the pointer at the beginning of the record. ①, ②, ③, and ④ all fail to match, so the pointer slides over a value in the record. ①, ②, ③, and ④ all fail to match again, causing the pointer to slide an additional time. Finally, ①, ②, ③, and ④ all fail to match at the end of the record, so matching ceases and the scope is exited

Record   [ 8 2 1 ]
Pointer    ^
Patterns ①, ②,  ③, and ④
Result: All Fail

Record   [ 8 2 1 ]
Pointer      ^
Patterns ①, ②,  ③, and ④
Result: All Fail

Record   [ 8 2 1 ]
Pointer        ^
Patterns ①, ②,  ③, and ④
Result: All Fail

Multiple Rule Scopes

begin >> [ 1 2 Three_Ones ]
1 => [
 num -> "NUMBER"
]
Three_Ones -> [ 1 1 1 ]

This program has three custom rules, which we will label: ① 1 => [ ... ]Three_Ones -> [ 1 1 1 ]num -> "NUMBER" Initially, the record and pointer will look like the following:

Record  [ 1 2 Three_Ones ]
Pointer   ^

First, we go down the rules, which causes ① to match, removing 1 from the record.

Record  [ 1 2 Three_Ones ]
Pointer   ^
Pattern   1
Result: Match
New Record: [ 2 Three_Ones ]

Now, a new rule scope is entered with only rule ③. The pointer goes to the start of the record, and matching begins. ③ will match with the first item in the record, replacing it with "NUMBER". Then, after the pointer shifts a couple of times, no more matches occur, so the scope is exited.

Record  [ 2 Three_Ones ]
Pointer   ^
Pattern   num
Result: Match
New Record: [ "NUMBER" Three_Ones ]

Record  [ "NUMBER" Three_Ones ]
Pointer   ^
Pattern   num
Result: No Match

Record  [ "NUMBER" Three_Ones ]
Pointer            ^
Pattern            num
Result: No Match

Because a rule was just matched, the method repeats with the pointer at the start in the main rule scope. Neither ① nor ② match going down, so the pointer slides, going across. ① doesn't match again, but ② does, replacing Three_Ones with 1 1 1.

Record  [ "NUMBER" Three_Ones ]
Pointer   ^
Pattern   ①, and ②
Result: Both Fail

Record  [ "NUMBER" Three_Ones ]
Pointer            ^
Pattern            1
Result: No Match

Record  [ "NUMBER" Three_Ones ]
Pointer            ^
Pattern            Three_Ones
Result: Match
New Record: [ "NUMBER" 1 1 1 ]

A rule was matched, so we restart going down. Both ① and ② fail before the pointer shifts and ① matches. 1 is removed from the record, and the rule scope is entered.

Record  [ "NUMBER" 1 1 1 ]
Pointer   ^
Pattern   ①, and ②
Result: Both Fail

Record  [ "NUMBER" 1 1 1 ]
Pointer            ^
Pattern            1
Result: Match
New Record: [ "NUMBER" 1 1 ]

Now rule ③ attempts to match, eventually succeeding twice before the scope is exited.

Record  [ "NUMBER" 1 1 ]
Pointer   ^
Pattern   num
Result: No Match

Record  [ "NUMBER" 1 1 ]
Pointer            ^
Pattern            num
Result: Match
New Record: [ "NUMBER" "NUMBER" 1 ]

Record  [ "NUMBER" "NUMBER" 1 ]
Pointer   ^
Pattern   num
Result: No Match

Record  [ "NUMBER" "NUMBER" 1 ]
Pointer            ^
Pattern            num
Result: No Match

Record  [ "NUMBER" "NUMBER" 1 ]
Pointer                     ^
Pattern                     num
Result: Match
New Record: [ "NUMBER" "NUMBER" "NUMBER" ]

Record  [ "NUMBER" "NUMBER" "NUMBER" ]
Pointer   ^
Pattern   num
Result: No Match

Record  [ "NUMBER" "NUMBER" "NUMBER" ]
Pointer            ^
Pattern            num
Result: No Match

Record  [ "NUMBER" "NUMBER" "NUMBER" ]
Pointer                     ^
Pattern                     num
Result: No Match

Patterns ① and ② will attempt to match against the record and fail three times with the pointer shifting each time. After the third attempt, no matches have taken place and the pointer is at the end of the record, so the program ends.

Variables

Variables provide a way to bind a matched value to use later in conditions or value scopes. Variables are declared directly following a pattern value and assigned to a variable name that follows the criteria:

  • Starts with a lowercase alphabetical character
  • Contains only alphanumeric and underscore (_) characters This way, there is a distinction between terms (uppercase start) and variables (lowercase start).

The following example binds a number to the variable my_number. When the pattern is matched my_number will be assigned to the record value it matched with. In this case, my_number is set to 1. Then, the variable is used within the scope.

begin >> [ Type 1 ]
Type num as my_num -> [ my_num "is a number" ]

# Record Result [ 1 "is a number" ]

Multiple variables can be declared in the same rule.

begin >> [ Type 1 2 ]
Type num as num_1 num as num_2 -> [ num_1 "and" num_2 "are both numbers" ]

# Record Result [ 1 "and 2 "are both numbers" ]

This can be streamlined by using parenthesis around the variable names.

begin >> [ Type 1 2 ]
Type num num as (num_1 num_2) -> [ num_1 "and" num_2 "are both numbers" ]

# Record Result [ 1 "and 2 "are both numbers" ]

A variable cannot be defined in the middle of an or expression, and an or cannot be used on the same level (group (...)) as a variable declaration.

num as x | "one" >> x # Error!
"one" | (num as x) >> x # Error!
"one" | num as x >> x # Fine: ("one" | num) as x

Variables are scoped, so when entering a new value scope, previous variables are still available. Once the scope is exited, the variable disappears.

begin >> [MakeVar 1 2]

MakeVar num as x num as y => [ # x=1 y=2
   begin >> [MakeVar 3 4]
   MakeVar num as x num as z => [ # x=3 z=4
        begin >> 1
        1 if x = 3 >> [x y z] # [ 3, 2, 4]
   ] # Exit inner scope, so x=3 and z=4 are no longer available
   end >> [x y] # x=1 y=2
]

Conditions

Conditions are an optional addition to patterns to make them more terse and concise. Conditions are checked after a pattern value match occurs. If the condition evaluates to true, meaning it has value, a match occurs, otherwise, the match does not happen.

  • Numbers have value when they are non-zero
  • Strings have value when they are not empty
  • Booleans have value when they are true
  • Terms always have value
  • Nils never have value
Conditional and Logical Operators

Several operators work within conditions.

Greater Than (num > num)

Greater than compares if the left operand is greater than the right operand and returns true if it is, and false otherwise.

1 > 2 # false
8 > 1 # true 
1 > 1 # false
Less Than (num < num)

Less than compares if the left operand is less than the right operand and returns true if it is, and false otherwise.

1 < 2 # true
8 < 1 # false 
1 < 1 # false
Greater Than Or Equal To (num >= num)

Greater than or equal to compares if the left operand is greater than or equal to the right operand and returns true if it is, and false otherwise.

1 >= 2 # false
8 >= 1 # true 
1 >= 1 # true
Less Than Or Equal To (num <= num)

Less than or equal to compares if the left operand is less than or equal to the right operand and returns true if it is, and false otherwise.

1 <= 2 # true
8 <= 1 # false 
1 <= 1 # true
Equal (num = num)

Equal compares if the left operand is the same type and value as the right operand and returns true if it is, and false otherwise.

1 = 1    # true
"1" = 1  # false
2 = 1    # false
Not Equal (num != num)

Not equal compares if the left operand is not the same type or value as the right operand and returns true if it's not, and false otherwise.

1 != 1    # false
"1" != 1  # true
2 != 1    # true
Not (! any)

Not is a unary operator. If the right operand has value return false, otherwise return true.

!1       # false
!0       # true
!My_Term # false
!nil     # true
Or (any | any)

Or is a binary operator. If the left operand has value, the left operand is returned, otherwise the right operand is returned. The or operator is short-circuiting, meaning the right operator will not be evaluated unless necessary.

1 | 2   # 1
0 | 1   # 1
5 | 0   # 5
nil | 0 # 0
And (any & any)

And is a binary operator. If the left operand has value, the right operand is returned, otherwise the left operand is returned. The and operator is short-circuiting, meaning the right operator will not be evaluated unless necessary.

1 | 2   # 2
0 | 1   # 0
5 | 0   # 0
nil | 0 # nil
Operator Precedence

There are four levels of precedence for expression operators that determine which operations evaluate first:

  1. Unary Not (!)
  2. Conditionals (>, <, >=, <=, =, and !=)
  3. Logical And (&)
  4. Logical Or (|)
3 > 2 & 5 <= 5 | 7 < 6 & !(4 >= 4) # true
Equivalent expression order:
((3 > 2) & (5 <= 5)) | ((7 < 6) & (!(4 >= 4)))  # true

Parenthesis can be used to evaluate subexpressions before others.

1 & !3 >= -1            # ERROR: left operand for `>=` is not a number

This can be fixed using parenthesis
(1 & !3) >= -1         # true
Conditions in Rules

Conditions can also include variables, making the syntax more concise.

1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 -> "Number Between 1 and 10"
# With a condition:
num as x if x >= 1 & x <= 10 ->  "Number Between 1 and 10"

The following program uses conditions to sort a record of numbers from least to greatest.

begin >> [ 1 4 1 -8 4 2 7 ]
(num num) as (x y) if x > y -> [ y x ]

Functions

Functions provide more features in RuleLang. They are all pre-made and follow the same naming convention as variables

  • Starts with a lowercase alphabetical character
  • Contains only alphanumeric and underscore (_) characters Functions take in several parameters and return a result. For example, the print function takes a parameter, displays it in the console, and returns nil.
# Display all values in the record
begin >> [ 1 2 3 4 ]
any as x !> print(x)

Functions can have multiple parameters, like the add function, which returns the sum of two numbers. These parameters are separated by whitespace.

# Sum all the numbers in the record
begin >> [ 1 2 3 4 ]
num num as (x y) -> add(x y)

Some functions are lazy, meaning the parameters are not evaluated unless necessary. This allows functions to do some interesting logic, like the when function that has three parameters, a condition, a has value parameter 'scope', and a has no value parameter 'scope'. If the condition has value, the has value parameter will be evaluated, otherwise, the has no value parameter will be evaluated. Because the function is lazy, only one of the 'scopes' is ever evaluated when the function evaluates. This function can be read like: when(condition has value, evaluate parameter2, otherwise evaluate parameter3).

# Print all values in the record that have value
begin >> [ 1 0 3 nil false true ]
any as x !> when(x print(x) print("No value"))
# Console: 1 "No value" 3 "No value" "No value" true

Safe and Unsafe

Some functions are designated unsafe meaning they cannot be used within rule conditions or replacing match scopes because they manipulate the record or mess up the pattern matching in some way. For example, the push function adds a value to the end of the record. If it were used in a condition, it could easily result in an infinite loop

begin >> [ 1 ]
num as x if push(x) | true !> [] 

String Formatting

Some functions have string formatting where additional arguments are formatted and written into placeholders within the string. Placeholders have the form %[flags][width][.precision][!] where all components in square brackets are optional.

An explanation of each of the components:

  • flags: A sequence of the following characters
    • >: Makes the output right-justified by adding any padding spaces to the left instead of to the right
    • ^: Makes the output center-justified where padding is evenly distributed on either side (an extra space defaults to the left)
    • +: Causes positive numbers to be prefixed with +
    • _: Prefixes a space to positive numbers (so digits can be lined up with the digits of negative numbers)
    • ,: Groups digits (like by thousands, based on user's locale)
  • width: A whole number specifying the minimum number of characters that the output should occupy. If necessary, extra spaces will default to the right of the content (unless the > flag is used)
  • .precision: A . followed by a whole number which indicates how many decimal digits to show in the formatted data
  • !: Optional delimiter
def pi := 3.14159265359
def mil := 1000000

def s1 := format("PI: %" pi) # becomes "PI: 3.14159265359"
def s2 := format("PI: %.2" pi) # becomes "PI: 3.14"
def s3 := format("PI: %_.3" pi) # becomes "PI:  3.141"
def s4 := format("Million: %," mil) # becomes "Million: 1,000,000"
def s5 := format("PI: %.2 | Million: %" pi mil) # becomes "PI: 3.14 | Million: 1000000"

def widthStr := format("| %5 |\n| %^5 |\n| %>5 |" 1 2 3) 
# becomes: 
# | 1     |
# |   2   |
# |     3 |

A delimiter is used to include placeholder characters without including them in the placeholder:

def s := format("%!, %!!" "Hello" "World") # becomes "Hello, World!"

To display a % character, you can use the code %%. The format function creates a formatted string:

def s := format("%!%%" 0.56) # becomes "0.56%"

Libraries

RuleLand has several built-in libraries that extend functionality. Unlike standard library functions, you need to import these functions before you use them. You can import the full library and all functions within, or you can specify which functions to import. Regardless, imports must be at the top of the file.

import string                             # Imports all functions from the string library
import [ math_floor math_ceil ] from math # Imports only the math_floor and math_ceil functions from the math library

begin >> [ math_floor(3.4) math_ceil(3.4) str_uppercase("Hello, World!") ]
any as x !> print(x) # 3, 4, "HELLO, WORLD!"

Global Variables

Global variables can be defined at the top of a file below imports.

import [ math_pi ] from math

def myVarName := 4
def text := "Hello, World"
def pi := math_pi()

begin >> [ myVarName text pi ] # [ 4, "Hello, World", 3.141592653589793 ]

Function Docs

Standard Library

  • print(...messages:any) -> nil: Displays all messages in the console joined by spaces.
  • printf(template:str ...parameters:any) -> nil: Displays the formatted string template with the parameters inserted in placeholders.
  • input(prompt:str) -> str [UNSAFE]: Displays prompt and waits until user enters input, returning that input.
  • wait(delay:num) -> nil [UNSAFE]: Delays delay milliseconds.
  • type(value:any) -> str: Returns the type of value as a string.
  • less(left:num, right:num) -> bool: Returns true if left is less than right, otherwise false.
  • greater(left:num, right:num) -> bool: Returns true if left is greater than right, otherwise false.
  • less_or_equal(left:num, right:num) -> bool: Returns true if left is less than or equal to right, otherwise false.
  • greater_or_equal(left:num, right:num) -> bool: Returns true if left is greater than or equal to right, otherwise false.
  • equal(left:any, right:any) -> bool: Returns true if left is the same value and type as right, otherwise false.
  • not_equal(left:any, right:any) -> bool: Returns true if left is not the same value or not the same type as right, otherwise false.
  • add(left:num, right:num) -> num: Returns the sum of left and right.
  • sub(left:num, right:num) -> num: Returns the difference between left and right.
  • mult(left:num, right:num) -> num: Returns the product of left and right.
  • div(left:num, right:num) -> num: Returns the division of left by right.
  • floor_div(left:num, right:num) -> num: Returns the floor division of left by right.
  • mod(left:num, right:num) -> num: Returns the remainder of left divided by right.
  • random(x:num, y:num) -> num: Returns a random integer within the range [x,y] such that both x and y are both inclusive.
  • when(condition:any, true_val:any, false_val:any) -> any [LAZY]: Evaluate and returns true_val if condition has value, otherwise evaluate and returns false_val.
  • or(left:any, right:any) -> any [LAZY]: Evaluates left. If it is true, return left, otherwise evaluate and return right.
  • and(left:any, right:any) -> any [LAZY]: Evaluates left. If it is false, return left, otherwise evaluate and return right.
  • not(value:any) -> bool: Returns true if value is has value, otherwise false.
  • empty() -> nil [UNSAFE]: Clears the record.
  • size() -> num: Returns the size of the record.
  • length(str:str) -> num: Returns the length of str.
  • format(template:str ...parameters:any) -> str: Returns the formatted string template with the parameters inserted in placeholders.
  • join(left:str, right:str ...strs:str) -> str: Returns the concatenation of left and right and any additional string parameter.
  • join_with(left:str, right:str, combiner:str) -> str: Returns left, combiner, and right concatenated together.
  • is_str(value:any) -> bool: Returns true if value is a string, otherwise false.
  • is_num(value:any) -> bool: Returns true if value is a number, otherwise false.
  • is_term(value:any) -> bool: Returns true if value is a term, otherwise false.
  • is_bool(value:any) -> bool: Returns true if value is a boolean, otherwise false.
  • is_nil(value:any) -> bool: Returns true if value is nil, otherwise false.
  • to_term(str:str) -> term: Converts str to a term if possible, otherwise returns nil.
  • to_str(value:any) -> str: Converts value to a string.
  • to_num(str:str) -> num: Converts str to a number if possible, otherwise returns nil.
  • get(index:num) -> any: Returns the value at index in the record. Indexes start at 1 and the record is also indexed negatively with -1 as the final index.
  • push(...values:any) -> any [UNSAFE]: Appends values to the end of the record and returns nil.
  • push_begin(...values:any) -> any [UNSAFE]: Prepends values to the beginning of the record and returns nil.
  • pop() -> any [UNSAFE]: Removes and returns the last value in the record. If the record is empty, returns nil.
  • pop_begin() -> any [UNSAFE]: Removes and returns the first value in the record. If the record is empty, returns nil.
  • insert(value:any, index:num) -> any [UNSAFE]: Inserts value at the given index in the record and returns it. If index is out of range, throws an error.
  • reverse() -> nil [UNSAFE]: Reverses the order of values in the record and returns nil.

Math

import math
  • math_pi() -> num: Returns PI (3.141592653589793).
  • math_e() -> num: Returns Euler's number (2.718281828459045).
  • math_floor(value:num) -> num: Returns the largest integer less than or equal to value.
  • math_ceil(value:num) -> num: Returns the smallest integer greater than or equal to value.
  • math_round(value:num) -> num: Returns value rounded to the nearest integer.
  • math_sqrt(value:num) -> num | nil: Returns the square root of value, or nil if the result is impossible.
  • math_pow(base:num, exp:num) -> num | nil: Returns base raised to the exponent exp, or nil if the result is impossible.
  • math_log(value:num) -> num | nil: Returns the natural logarithm of value, or nil if the result is undefined.
  • math_log2(value:num) -> num | nil: Returns the base-2 logarithm of value, or nil if the result is undefined.
  • math_log10(value:num) -> num | nil: Returns the base-10 logarithm of value, or nil if the result is undefined.
  • math_abs(value:num) -> num: Returns the absolute value of value.
  • math_sin(radians:num) -> num: Returns the sine of a radian angle radians.
  • math_cos(radians:num) -> num: Returns the cosine of a radian angle radians.
  • math_tan(radians:num) -> num: Returns the tangent of a radian angle radians.
  • math_min(val1:num val2:num ...nums:num) -> num: Returns the minimum value of all number parameters.
  • math_max(val1:num val2:num ...nums:num) -> num: Returns the maximum value of all number parameters.

String

import string
  • str_lowercase(str:str) -> str: Returns str converted to lowercase.
  • str_uppercase(str:str) -> str: Returns str converted to uppercase.
  • str_trim(str:str) -> str: Returns str with leading and trailing whitespace removed.
  • str_split(str:str, delimiter:str) -> nil [UNSAFE]: Splits str by delimiter and appends the resulting substrings as new values to the record. Returns nil.
  • str_get_char(str:str index:num) -> str: Gets the character at at index within str and returns it as a 1-character string.
  • str_substr(str:str i:num j:num) -> str: Returns the substring of str, all the characters from index i to j inclusive.
  • str_starts_with(str:str start:str) -> bool: Returns true if str starts with the string start, otherwise false.
  • str_ends_with(str:str end:str) -> bool: Returns true if str ends with the string end, otherwise false.
  • str_contains(str:str substr:str) -> bool: Returns true if str contains the substring substr, otherwise false.
  • str_index_of(str:str substr:str) -> num: Returns the index of the start of the first occurrence of substr within str, or -1 if the string does not include substr.
  • str_last_index_of(str:str substr:str) -> num: Returns the index of the start of the last occurrence of substr within str, or -1 if the string does not include substr.
  • str_replace(str:str searchStr:str replaceStr:str) -> str: Returns str with the first occurrence of searchStr replaced with replaceStr.
  • str_char_code(char:str) -> num: Returns the unicode number representation of the 1-character string char.
  • str_char_code(charCode:num) -> str: Returns the text representation of the unicode code charCode as a string.
import file
  • file_read(path:str) -> str: Returns the contents of the file at path.
  • file_write(path:str content:str) -> nil: Writes content to the file at path. If the file already exists, its contents are overwritten.
  • file_append(path:str content:str) -> nil: Appends content to the end of the file at path. If the file does not exists, it will make a new file first.
  • file_prepend(path:str content:str) -> nil: Prepends content to the start of the file at path. If the file does not exists, it will make a new file first.
  • file_remove(path:str) -> nil: Deletes the file at path.
  • file_rename(fromPath:str toPath:str) -> nil: Renames the file at fromPath to toPath.
  • file_exists(path:str) -> bool: Returns true if the file at path exists. Otherwise it returns false.

Error Codes

Lexing (Scanning) errors

  • E100001: Unexpected token
  • E100002: Unterminated string

Parsing Errors

  • E200001: Unexpected token
  • E200002: Expected `[` to start the rule scope
  • E200003: Expected `]` to end the rule scope
  • E200004: Expected `]` to end the value scope
  • E200005: Expected identifier for global variable name
  • E200006: Expected value or scope after match operator `myMatchOperator``
  • E200007:
    • Replacing match operator (`->`) is invalid for the `begin` pattern
    • Replacing match operator (`->`) is invalid for the `end` pattern
  • E200008:
    • Expected match operator after `begin` pattern
    • Expected match operator after `end` pattern
  • E200009: Expected `)` to end the function call
  • E200010: Expected value after `!` in the value scope
  • E200011: Expected expression after `if`
  • E200012: Expected rule operator after the pattern
  • E200013: Expected pattern value after `!` in the pattern, not a group
  • E200014: Expected pattern value in the pattern group
  • E200015: Expected `)` to end the pattern group
  • E200016: Expected pattern value after `!` in pattern
  • E200017: The `|` pattern operator cannot be combined with `as` within the same group
  • E200018: Expected pattern value(s) to the right of the `|` pattern operator
  • E200019: The left side of the `|` pattern operator must have the same number of pattern values as right side
  • E200020: Cannot use `as` in the middle of the `|` condition
  • E200021: Expected variable name(s) in `as` group
  • E200022: Expected `)` to end `as` group
  • E200023: Expected variable name or group of variable names after `as`
  • E200024: Variable `myVariableName` is already declared in the pattern
  • E200025: Too many variables for the number of pattern values
  • E200026: Expected value after `!` operator
  • E200027: Expected expression after `(`
  • E200028: Expected `)` to end expression
  • E200029: Expected expression after `myExpressionOperator` expression operator
  • E200030: Expected library name or [ in import statement
  • E200031: Expected ] to close function group in import statement
  • E200032: Expected from after function group in import statement
  • E200033: Expected library name after from in import statement
  • E200034: Multiple imports to library `libraryName`
  • E200035: Library `libraryName` does not exist
  • E200036: Function `importedFunctionName` not found in `myLibrary` library
  • E200037: Duplicate function `myFunction` import
  • E200038: Expected := for global variable definition
  • E200039: Expected value for global variable definition
  • E200040: Duplicate global variable, `myVar` has already been defined

Runtime Errors

  • E300001: Left operand of `myExpressionOperator` operator must be a number
  • E300002: Right operand of `myExpressionOperator` operator must be a number
  • E300003: Function `myCalledFunction` does not exist
  • E300004: Function `myCalledFunction` is not a safe function and cannot be used in expressions or replacing value scopes (`-> [ ... ]`)
  • E300005: Invalid number of parameters, function `myCalledFunction` must have x parameters
  • E300006: Parameter x of `myCalledFunction` function must be a `parameterType` type
  • E300007: Variable `myVariable` is not defined

Library Errors

  • E400001: Parameter for `myCalledFunction` function must be an integer
  • E400002: `x` is out of range for `myCalledFunction` function, the record has y values
  • E400003: Index \index` is out of range for string
  • E400004: Parameter for `myCalledFunction` function must be a 1-character string
  • E400005: Cannot read file `myPath`
  • E400006: Cannot write to file `myPath`
  • E400007: Cannot append to file `myPath`
  • E400008: Cannot prepend to file `myPath`
  • E400009: Cannot remove file `myPath`
  • E4000010: Cannot rename file `fromPath` to `toPath``
  • E4000011: Too few parameters for the placeholders in the format string
  • E4000012: Too many parameters for the placeholders in the format string