The Rulz Tutorial

Rulz and Rulz Language is Copyright © G.A.Jennings, 2015-2022.

This is language specification 2-1/2; last updated 24 June 2022.

This document is based on "The Python Tutorial" documentation (3.10.4) as a model, which means the order of chapters and stuff, and not a comparison of the two.

Rulz is a general purpose language with a machine language like format of one statement per line. It includes flow control, loops and subroutines. The language is minimalist with no use for braces, parenthesis, commas or semicolons; has no indent requirements; there is no operator precedence.

Rulz ain't "powerful" (you don't have to fill it up with diesel fuel) as it can't do anything beyond what a computer's CPU can do.

It is interpreted and works much like a shell program.

Whetting Your Appetite

Programming is premised on automation of tasks that a computer is capable of, such as manipulating files or reading/writing network/internet streams. Rulz is for all the basics.

Rulz has no GUI/GPU support as part of it's design – there are other tools more suited for that.

But if you want a language, not un-like a shell, that "does more for less", Rulz might be of interest to you.

Using the Rulz Interpreter

The Rulz interpreter is available for local use only (i.e. not to be installed by "root" or integrated into any OS's core file system, so that no system administrator will be required).

Rulz runs as every other command line program.

Rulz currently does not have an interactive mode.

Rulz source files need to be encoded for the OS it runs on and have no specific encoding requirements.

An Informal Introduction to Rulz

Rulz does not have an interactive mode but simple rules can be passed to execute rather than a source file, via the -rules=<rules> option, where <rules> is one or more rules separated by semicolons.

Semicolons are not part of the Rulz code and are only used for the -rules option and for comments in a Rulz source file.

Comments in Rulz begin with either # or ;. (Though the former is an operator and the latter are removed from the source file during the file load process.)

Using Rulz as a Calculator

aka. Basic Arithmetic


A Note is Required Here.

There is something basic about Rulz that should have been explained before this; that is that Rulz statements are single line only in the format:

<OP> [ARGS]

While other languages do v = 2, Rulz is "left handed", = v 2. Like Bash, assigning a variable is plain, only interpolation uses $.

The assignment operator is =. And arguments cannot have any operators. While Python (as most others) does this:

i = 2 + 2

Rulz has to do this:

= i 2
+= i 2

The Python single line assignment of i above is in actuality this:

i = 2
i = i + 2

As Python, as all languages, can only execute such a single source line assignment as two separate expressions. Compound expressions may look good, but do not make things run in parallel all by themselves. (Unless the 2 + 2 is converted to 4 by the compiler.)

Rulz has a "default variable" that operations apply to (^ is Rulz' print operator):

= 2
+= 2
^		            # prints 4

Spaces are not required if there is no ambiguity, like:

=i2
+=i2

Rulz doesn't care. (Just like math in Python can be i = 2 + 2 or i=2+2.)


Numbers

Arithmetic on the command line needs to store and print the results; this is by basic operations and a print statement.

(Here, the rules are multi-line but will be merged on the command line as shown after the first example.)

=2
+=2
^

Result is:

4

Which is run as:

-rules="=2;+=2;^"

Since there is one operator per line, sometimes a temporary variable will be needed when trying to "match" Python's examples. So, 50 – 5*6 is:

= 50
*= t 5 6
-= $t
^                   # prints 20

Dividing the result:

/= 4
^                   # 5

Operating on two numbers:

/= 8 5              # 1.6
/= 17 3             # 5.6666666666667
%= 17 3             # 2

Three or more numbers means one line per operator (5 * 3 + 2):

*= 5 3              # 15
+= 2                # 17

The power of operator is one of the mathematical operators, ^/:

^/ 5 2              # 5 squared, 25
^/ 2 7              # 2 to the power of 7, 128

Other mathematical operators include:

|/   abs    absolute value
>/   max    highest value
</   min    lowest value
e/   exp    exponent of e
[/   ceil   round up
]/   floor  round down
2/   sqrt   square root
?/   rand   random number

Variable names are all lowercase letters. Interpolation or as argument a $ is used.

= width 20
= height 5
*= height 9         # multiply height by 9
*= $height $width   # multiply height and width
^                   # 900

Non-existent variables are not an error but result in a typical "not defined" value, such as Python's None (though Rulz' is more like PHP's null), which for number operations is 0.

The print operator displays all arguments, sometimes formatting the output, like for arrays.

*= i 256 256
^ 'The var $i is' $i

Displays The var $i is 65536, followed by a newline. To print without a newline, use the modified print operator -^. To print just a newline there is /^.

Strings

Literal strings are barewords, single quoted and double quoted. These groups are identical:

^spam eggs
^'spam eggs'
^"spam eggs"
^'doesn\'t'
^"doesn't"
^'"Yes," they said.'
^"\"Yes,\" they said."

Rulz is less like Python in that single quoted strings only need to escape single quotes. (In Python terms, they are "raw".)

Rulz's string arguments can't be multi-line, there are operators for that ("heredocs", as well as special notation within source files).

Strings can be indexed as in Python and other languages, and in a few ways. (Rulz' barewords can have upper and/or lower case letters.[])

= w Rulz
^ $w:0              # first character, 'R'
^ $w:-1             # last character, 'z'

Or:

^ $[0]w             # first character, 'R'
^ $[-1]w            # last character, 'z'

Slices:

^ $w:0:2            # 'Ru'
^ $w:2:2            # 'uz'
^ $[0:2]w           # 'Ru'
^ $[-1:2]w          # 'zl'
^ $w:0,2            # 'Rl'
^ $[0,2,3]w         # 'Rlz'

Modifying strings has a similar syntax.

= [0]w r            # 'rulz'
= w:0 R             # 'Rulz'
= [0:2]w fo         # 'folz'

All out of range indexes and slices are handled gracefully.

While Rulz has builtins for lengths of all variables, including strings, string lengths are of the format:

^ $#w

Lists

Lists in Rulz are within parenthesis.

= v (1, 4, 9, 16, 25)

Indexes and slices and lengths are the same as strings (as strings are like a list of characters):

^ $[0]v             # 1
^ $[-1]v            # 25
^ $[-3:]v           # (9, 16, 25)

Some operators work on lists. To append a list to list the concatenation operator is used:

.= v (36, 49, 64, 81, 100)

List members can be assigned:

= v (1, 8, 27, 65, 125)
= [3]v 64
= v:3 64
= [-2]v 64
= v:-2 64

List members can be of any type, even lists, with the use of .. for ranges:

= v ('a', 'b', 'c', 'd', 'e', 'f', 'g')
= v ('a'..'g')
= v:0 ('1' '2' '3')
^ $v                # (('1', '2', '3'), 'b', 'c', 'd', 'e', 'f', 'g')

To "clear" a list it's just set to an empty list:

= v ()

The length of a list:

$#v

Rulz has a great many functions/builtins for lists.

First Steps Towards Programming

Rulz does conditionals and loops, of course. Like for Fibonacci series:

= a 0
= b 1
@until
^ $a
= t $a
= a $b
+= b $t
>? $a 10
@end

Which, as Python, results in:

0
1
1
2
3
5
8

Rulz @until is while false, as @while is while true.

The thing non-obvious there is that the loop is based on an "internal variable" that Rulz uses to store the result of the last operation, which is >?, the comparison operator "is greater than", described in the next section.

This "internal variable" is called the "R-value", for "result" or "return", depending on context. This variable is $0, and is usually not used directly. (This variable is a "special variable", not unlike Perl special variables, though $0 is not the same as Perl's.)

Loops (or any blocks) do not have to be indented (though they can be).

Loops in Rulz can not be nested. Which will be seen as a major flaw, but is deliberate. Nested loops (and blocks) are better off as subroutines.

The above program can also be:

= a,b 0,1
u@ >? $a 10
^ $a
= t,a $a,$b
+= b $t
@

Which shows the use of multiple assignment (=a,b0,1); aliases (u@ for @until); and compound operators.

To make the loop use @while the control rule is:

@while <? $a 10     # or w@

In Python, a and b are directly assigned with one line of ("expressions are evaluated before assignment"):

a, b = b, a+b

Trying with two lines is not the same:

a = b               # a just changed!
b = a+b             # so b gets wrong value
b = a+b             # b just changed!
a = b               # so a gets wrong value

without a temporary variable to hold a:

t = a
a = b
b = b+t

More Control Flow

if Operators

Basic conditional operators are ? for "if true" and ! for "if false". They operate on the previous operation, have two basic formats:

OP RULE
OP VAR RULE

Where RULE is executed or not depending on the result of the previous operator or of $VAR.

As loops, conditional operations without a variable, uses the R-value, $0. When Rulz starts, $0 is false.

? ^ "previous operation was true"
! ^ "previous operation was false"

If those two rules were in a program and run the message "... was false" would be printed. And there is an else operator, :, which will be executed or not depending on the "opposite" result of the test rule.

? rule              # if
: rule              # else
? rule              # if
:? rule             # else if

There is a difference between these two rule pairs and the previous two as explained later.

Conditional operators have modifiers which are used for comparison operators:

=?  equal
=!  not equal
<?  less than
<!  not less than
>?  greater than
>!  not greater than

Another format of a rule is with the "comma operator", which is a "pseudo operator", and used to mark multiple rules.

OP [ARGS] RULE , RULE

An example:

<? $x 0 ^ Negative changing to zero , = x 0
:? $x 0 ^ Zero
:? $x 1 ^ Single
:       ^ More

There is also a "switch/case" operator sequence.

There are alternative if/else operators, grouped loop operators:

@if [$var|rule]     # if $0 or $var or rule is true
<rules>
[@else [$var|rule]]
[<rules>]
@end

for Operator

The Rulz @for operator is similar to Python's for (and PHP's foreach).

= w ('cat' 'window' 'defenestrate')
@for $w
^ $_ $#_
@end
cat 3
window 6
defenestrate 12

This introduces another special variables, $_, which is similar to (sometimes) Python's _ (but is really more like Perl's $_).

Modifying a list during @for is fine. Modifying "past" data has no effect, growing or modifying "future" data should work as expected.

range

Rulz' has an argument construct for ranges but not an operator. A range argument is .. with a start and end (inclusive), as mentioned for lists above. But it can also be used without parentheses:

^ a..g              # (a b c d e f g)

And in a for loop; this prints all lowercase letters:

@for a..z
^ $_
@end

There are many list operators that are function-like. For example, to iterate over values (indices):

=( [VAR]            # values (0 1 2 3 4 5 6)

Rulz also supports hashes which are very similar...

break and continue Operators

Rulz' break and continue statements are operators. And since both are like a "goto" in operation (just "going to" a sole, or particular point in the loop), they are of the "jump" operator (.) group:

..                  # break
:.                  # continue

The .. operator is "break" within a loop, "return" within a subroutine and "exit" within a program.

Related are the operators for Perl-like "next" and "redo":

>.                  # next
<.                  # redo

noop Operator

There is a "do nothing operator" though is probably never needed, as empty loops and subroutines will also do nothing.

:                   # ain't doin nothin

Though it's pretty much worthless.

switch case

The switch case operators are based on loops. This example also introduces subroutines:

@switch $e
@case 400
^ "Bad request"
@case 404
^ "Not found"
@case 418
^ "I'm a teapot"
@case
^ "The Internet is broken!"
@end

A @case without an argument is the "default" case. These can be written as:

@switch $e
@case 400
    ^ "Bad request"
@case 404
    ^ "Not found"
@case 418
    ^ "I'm a teapot"
@case
    ^ "The Internet is broken!"
@end

Or:

@switch $e
@case 400 ^ "Bad request"
@case 404 ^ "Not found"
@case 418 ^ "I'm a teapot"
@case ^ "The Internet is broken!"
@end

The case statements do not require "break".

From now on this document strays away from Python examples and headings. This was not meant as a comparison of Rulz versus Python, as they are two totally different languages, made for two different purposes. The Python documentation was used as an exercise.

subroutines

Subroutine definitions are started with the subroutine name within bracket pairs. Definitions end at another subroutine definition or end of file. All basic variables in Rulz are of lowercase letters only, and so too for subroutine names.

[sub]
^ "Subroutine!"

Say you did not like the ^ operator, and you like Perl. You can do this.

[say]
^@_

The variable @_ is an array of all arguments (like Perl). Individual arguments (unlike Perl) are @0 through @9; the number of arguments is @#. To access more than nine arguments use @A to @Z (which should be more than enough for anybody).

An experimental variable, @*, concatenates @_ into a string.

Redefining a subroutine is not an error, the new subroutine replaces the previous.

A Rulz program's "top rules" make up the program and any subroutines simply follow. Subroutines are called by name:

hello Joe
[hello]
^ "Hello @_!"

A Rulz source file can have rules and/or subroutines. Subroutines follow rules, and as there is no "subroutine end" syntax, once a subroutine begins there can be only follow other subroutines (or data described below). A source file with just subroutines will not do anything but can be "included".

All variables are global but subroutines can have their own, as Perl's my and PHP's static. Subroutine arguments can be named and have default values assigned.

Subroutines do not return arguments. Generally, $0 is used, as it is set for the result of most operators and builtins. Since variables are globals, any variable will do.

There is a return operator, .., which takes an optional rule as an argument, which just runs the rule before the return.

[sub]
.. = sub

That just sets $0 to the bareword sub, though in that example the .. is superfluous.

A subroutine ends at any subsequent subroutine definition or end of file.

splat

Subroutines can take any number or type of variables. But a subroutine can save variables, have private and local variables, named arguments and define the type of arguments. This is known as "splat", and are considered "subroutine attributes".

Attributes are a letter, a colon, and variable names.

Special variables to be saved, and restored at subroutine end, like the common used $_ or $0, and a few others that control Rulz output.

[sub s:0_]
_ "saved!"
= sub
^ $0 $_             # "sub saved!"

The operator _ is an alias to = _, which sets $_ to it's argument.

Private variables (like Perl my) exist only within the subroutine. If a subroutine recurses it gets a new set of variables. Multiple variable names are separated by commas.

[sub p:a,b]

Local variables exist only within the subroutine but there is always only one instance (like PHP static, not like Perl local). If a subroutine recurses the variables retain their values.

[sub l:i]

Named arguments are like private variables but initialized to their matching parameter. (More or less variables than parameters do not cause errors.)

[sub a:s,len]

While that subroutine implies variables that are set, to add default values an assignment is used:

[sub p:a=0,b=1]

Non-assigned variables get the "default zero" for it's type when first accessed depending on context, which will be 0 for a number, (empty string) for strings, () (empty array) for arrays, false for booleans and null for others.

The t in "splat" is for how arguments are passed to a subroutine; Perl-like parameters in flat scalar array; or typical parameters preserving all argument types. For example:

sub (a b) 1 2

That is four arguments, a b 1 2, type p (the default, for Perl-like); or three arguments, array (a b) and numbers, 1 and 2, type a for "as-is".

Python's keyword arguments, special parameters, arbitrary arguments, etc., don't make sense.

Coding Style

Rulz has no coding style. By it's definition, things like indentation and braces are not part of this language. There are a few instances where an operator must be at the start of a line (no leading whitespace), and no rule can have embedded newlines. And as long as there is no ambiguity rules do not need spaces.

All user variables and subroutine name are lowercase letters only. Though there is no length limit, it is recommended to keep them short (one little bit of "style").

The design of the Rulz language, such as no indenting, loops and subroutines do not nest, etc., is purposeful, so that:

  1. Line length is short.
  2. Subroutine length is short.
  3. Source file length is short.

It's a simple language for simple purposes.

Data Structures

Rulz operators are character based, with one character indicative of the "group" (or category) of the operator type or function. And basic to Rulz operators a "prefix character" is used to define the operator.

List Operators

Creating (or assigning) a list (aka array) is near universal in that there is a begin character, followed by zero or more literals separated by a delimiter, followed by an end character.

$a = array(1,2,3);      # Perl, PHP
a := []int{1,2,3}       // Go
a = [1,2,3]             # Python
= a (1,2,3)             # Rulz

Again, this is not a comparison (nor judgement) of these languages – one is not "better" than any other, just different.

For lists the base operator character is [.

Operators are just like builtin functions in other languages. (Though "Object Oriented" languages like Python have a different syntax than C, Perl or PHP, they are all the same in functionality, including functions like append, push, sort, etc.)

Rulz just dispenses with parentheses and commas. So, instead of:

v = [1,2,3]
b = len(a)

it's:

= a (1,2,3)
*[ b $a

in which the variable b has the value of 3. (Admittedly, Rulz' length of list operator is weird in that it does away with the = as well.)

The list operators:

/[ [var] string [string] string to list (explode)
:[ [var] list [string]   join
~[ [var] list            values
%[ [var] list            keys
@[ [var] list            sort
\[ [var] list            reverse
+[ [var] list            push
-[ [var] list arg        pop
<[ [var] list            shift
>[ [var] list arg        unshift
*[ [var] list            count
[[ [var] list            last (element)
,[ [var] list off [len]  slice
![ var                   delete (element)
=[ [vars] list           list (like PHP's "list", or Perl's "my")

The length of an array is also an argument of $#a. Indices of an array are $[1]a or $a:1 (similarly, slices were mentioned previously). Elements can be deleted by ![ $[1]a.

Associative Arrays

Associative arrays are nearly the same as lists, with literals within braces, or within parentheses with (old time) assignment.

= a {aaa,1, bbb,2, ccc,3}
= a (aaa=1, bbb=2, ccc=3}

(The spaces are not necessary but used here for clarity.)

All operators are the same. Indexes are by name, $[aaa]a and $a:aaa. (Keys can be any string or number – some strings will require single or double quotes.)

Looping Arrays

The @for and @each operators are for (both types of) arrays. They are almost the same but for the special variables they use.

= (a b c)
@for
-^$_
# prints "a b c", $0 remains (a b c), $_ is 'c'
= a (a b c)
@for $a
-^$_
# prints "a b c", $0 not modified, $_ is 'c'
= a (a b c)
@for b $a
-^$b
# prints "a b c", $0 and $_ not modified, $b is 'c'

Regular arrays are associative arrays with numbers for indices.

= (a b c)
@each
-^$( $)
# prints "0 a 1 b 2 c", $0 remains (a b c), $_ not modified

That introduces two more special variables, $( and $). There are 33 such special variables (and totally based on Perl though a few differ in value) – the 32 punctuation characters and 0. As seen, $0 and $_ change frequently depending on context. The $( and $) variables are set by the @each operator.

= (a=1, b=2, c=3)
@each
-^$( $)
# prints "a 1 b 2 c 3"
= a (a=1, b=2, c=3)
@each $a
-^$( $)
@each k v $a
-^$k $v

Source Files

Rulz has nothing like modules, just source files as mentioned above. Nor is there any kind of "main" function. A Rulz file just runs from start to finish (top down). Execution terminates either by an explicit exit or no more rules.

A source file can be included by another by the include operator.

~ file
~ ~/path/file

The latter uses the Bash format for a user's home directory.

Output Formatting

The print operator displays all of it's arguments followed by a newline – without arguments it displays $0. Variables and ASCII escapes within double quotes are interpolated, and not so within single quotes.

When printed arrays are "flattened", the constants TRUE, FALSE and NULL are "1", "0" and "" (empty string) respectively, open file handles are kind of meaningful.

There are 6 other print operators:

-^                  print with no newline
\^                  print just a newline	
/^                  print escaping control characters
>^                  print to STDERR
%^                  print formatted
?^                  print types

The /^ operator converts the following as strings:

\b                  backspace
\e                  escape
\f                  form feed
\n                  newline
\r                  return
\t                  tab

Formatted print is like the C printf function – the exception being that arguments are before the format string (%^ $a $d "%s %d \n"). And there are a few new formats defined such as %T for "type of".

Reading and Writing Files

The open a file handle operator is & and it's various modifiers. As usual, the resulting handle can be in $_ or a specified variable name.

&  [fh] file        open for reading
-& [fh] file        open for writing
+& [fh] file        open for appending

Also as usual, these operators set $0 to TRUE on success and FALSE on error. (True and false in Rulz are like PHP's true and false. They are constants – and always uppercase as all constants are – and are booleans but equivalent to 1 and 0.)

The read operators are modified file operators, and results in the number of bytes read or FALSE on error. The read operators require the open handle in $_ or the given variable, and the result is in the specified variable name.

<& [$fh] var        read line
<& [$fh] var n      read n bytes
[& [$fh] var n      read n lines (as list)
(& [$fh] var        read line without EOL
{& [$fh] var        read character (same as <& [$fh] 1)
%& [$fh] var f      parse line according to format string f
/& [$fh] var r      parse line according to regular expression r

The write operator results in the number of bytes written or FALSE on error.

>& [$fh] $var       write $var with EOL
>& [$fh] $var n     write n bytes (of $var)
]& [$fh] $var n     write n lines (of $var as list)
)& [$fh] $var       write $var without EOL
}& [$fh] $var       write character (same as >& [$fh] 1)

There are several function-like operators that read or write files directly – into into $_ or specified variable.

<< [var] file       get (read) file as string 
(< [var] file       get file as list (of lines) with EOL
[< [var] file       get file as list without EOL
>> [$var] file      put (write) string to file
)> [$var] file      put list to file with
+> [$var] file      append string to file
-> [$var] file      prepend string to file

The "heredoc" is one of the few "multi-line" operators, <(, along with the "theredoc", which outputs to a file, >). (Both of which have "nowdoc" variants, (( and )).)

When putting a list to a file each member is one line.

The put, append and prepend file operators have a dual role with two arguments in that if the first argument is the name of a file that exists the file's contents will be used in place of the variable contents.

>> filea fileb      copy filea to fileb
+> filea fileb      append filea to fileb
-> filea fileb      prepend filea to fileb

There is one operator to read STDIN.

=< [var] [n]        read line or n characters from STDIN

The notation "EOL" here means end of line (character). Like Perl and Bash there is a special variable for the end of line character (or record seperator), which is $. and defaults to "\n".

Finally, a file can be read and output.

~< file             read file to STDOUT

There are also many operators for standard C functions such as stat(), tell(), seek(), etc.

Errors

The Rulz interpreter is quite forgiving, basically only "erring" on an invalid operator – a parse error. For example:

^ "is a string

That argument looks like an invalid string, but Rulz will see it as four arguments, a double quote, which is punctuation, and three barewords, which are strings.

The following causes an error as there is no valid operator:

% x
(-:0:""["% x",string]) missing OP in: "% x"

However, parse errors are not fatal errors (they are "complaints"). That rule just did not "do anything" because it did not have a valid operator and the interpreter complained about it.

Other runtime errors are for typical stuff like files not existing, trying to write to a read-only media, etc.

Rulz does not throw exceptions.

There is no "try", only "do".

Classes

Classes are bogus. Rulz don't have 'em.

Standard Library

Rulz has a simplistic "standard library" which is just a source file of subroutines that perform a few validations for some operators.

For example, the include operator, ~, will issue a diagnostic if the file to include does not exist. The library subroutine, inc will test for existence and not complain if not found. (Subroutines are always global, and they all can be re-defined.)

Otherwise, all Rulz has are it's many operators that work like other language's standard libraries.

That said, other languages have pretty cool features that Rulz version 2 just might emulate; like locales and templates for example.

This is the end of this document. And... It ain't all that great, is it? Not particularly... logical. It leaves 90% of the language undocumented! And it's nearly 1,000 lines long! sigh