Rulz Programming Language

This is the second-1/2 version of the language specification — a formal version numbering value has not been defined. This language is still evolving. The "proof of concept" was written in PHP and a formal executable is being written in Go.

What Rulz Is

Rulz –- pronounced "rules" -– 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 of traditional braces, parentheses or commas.

The Rulz Language is Copyright © G.A.Jennings, 2015-2020. (It is still evolving and subject to change, though at this point in only minor ways.)

Introduction

This document introduces a new programming language. Though yet to be described formally, the language has an assembly language structure and uses PHP-like variables and types, and borrows features from Bash and Perl.

A statement is called a "rule". A group of "rules" in a subroutine is called a "Rule". Together, rules and Rules make up a Rulz program.

Reference: • OperatorsVariablesData TypesSubroutines

More Details: • LexerRegex Example

Index

Introduction

Hello World

Here is the ubiquitous "Hello World" program in Rulz.

^Hello World

The ^ is the "print operator" which is like echo in Bash, outputting all arguments, separated by spaces, terminated with a newline. It also shows that, like Bash and Perl, barewords — identifiers of letters only — are treated as strings. To use any other characters quotes need to be used.

^"Hello, World!"

Otherwise the punctuation characters will be treated as separate arguments:

^Hello, World!           prints as 'Hello , World !'

Left-handedness

Rulz language's most unique syntax is that operators are always "first on the line" and there is always only one (and therefore there is no operator precedence). See #Operators.

For example, assignment is =var argument and not var=argument. As in Bash, variable assignment does not include the $. Here is the "Hello World" program with assigning a variable.

=name 'Hello World'
^$name
This is known as "prefix notation" for operations, but Rulz syntax is more "prefix statement notation".

Spaces

Spaces are needed only in the case of ambiguous arguments. These two rules are parsed identically as above.

= name'Hello World'
^ $name

For clarity, most examples here use spaces (though it's kind of a convention to not use a space after the leading operator if it is unambiguous).

Naming Conventions

This document uses some identifiers in particular ways.

rule                    a Rulz language statement
Rule                    two or more rules together or a subroutine
var                     a variable name to be set
val                     a literal value (number, string, etc.)
$var                    a variable
argument|arg            a literal, variable or function name

Basic Syntax

The basic format of a rule is:

<[operator][builtin|subroutine|label]>[arguments]

Where:

operator                arithmetic, conditional, or other operator
builtin                 builtin command or function
subroutine              a named set of one or more rules; called a Rule
label                   a placeholder for the "jump" operator
arguments               zero or more arguments (usually) separated by spaces

Currently supported operators are: conditionals, assignment, comparison, arithmeti, bitwise, execution, call, logical, string, list and a jump operator.

(And a few others like percent and some mathematical operators for mathematical functions.)

Rulz implements many functions as operators for regular expressions, file I/O, file and type tests and others; not unlike Perl's binding operator (or Perl's named unary operators using two characters rather than names).

"While we usually think of quotes as literal values, in Perl they function as operators, providing various kinds of interpolating and pattern matching capabilities." - PerlOp

Builtins

Builtins are defined by the language and are similar to basic functions provided by Bash, Perl and PHP.

Similar to Perl, many have default values for arguments and/or return value (see #Variables).

Some builtins are also referred to as commands or functions, depending on their category, (with the acronym BCF). Subroutines can create new builtins, even redefining them - see rulzsub.

Types

The following types are supported: boolean, integer, float, string, list, hash (arrays) and the PHP types NULL and handle (resource).

Types and examples.

boolean                 TRUE FALSE
integer                 123 0x1A 1Ah 033 0b0001 0001b
float                   .123 1e4
string                  "interpolated" 'non-interpolated' bareword
list                    (1,2,3) (a..z) ($a,$b)
hash                    (a=1,b=2,c=1) (a=$a,b=$b)

Rulz introduces several other pseudo-types such as file and argument — which are similar to barewords. See rulzdata.

Variables

Basic variables are like $var where "var" is one or more lowercase letters, and, PHP-like, can be assigned any type. Variable assignment is Bash-like with the $ not used.

Like Bash and Perl all variables are global and there are "my" and "local" attributes for subroutines.

There are Perl-like special variables of the format: $[0-9[:punct:]]. The special variables $0 and $_, differ from their Perl counterparts and are used as defaults for most Operators and Builtins.

The variable $0 is called the "return value", or "r-value",and contains the result of function calls and is the default for most operators and commands.

The variable $_ is called the "function value", or "f-value", and is the default argument for some operators and functions.

Operators

Comments

;                       line ignored before parsing
#                       line ignored after parsing
##                      start of multi-line comment, ends at the next ## or end of Rule

Internally, #, ## comments are implemented as operators — # and ; comments can trail a rule.

No Op

:                       ignored

(A useless thing. Not even needed to make an empty subroutine or block.)

Conditional Operators

There are two conditional operators. In their simplest for they test the $0 special variable.

? rule                  run rule if previous rule ($0) was true
! rule                  run rule if previous rule ($0) was false

The logical operations are "true if true" and "true if false".

There are two conditions to test a variable, running the rule if the variable is true or not:

? $var rule             run rule if $var true
! $var rule             run rule if $var false

The combination of these two rules are the reduction of the syntax of these two PHP statements.

if ($var) statement;
if (!$var) statement;

The conditional operators have "compound forms".

<?!> [operator [$var]] rule

See #Compound Conditionals.

And there are ternary versions based on a variable.

? $var rule rule
! $var rule rule

And for result of a rule.

? rule rule rule
! rule rule rule

These are not a "ternary assignment operator" as in other languages. For Rulz, the ternary construct is a form of an if/else block; as if PHP or Perl were able to do this:

(expression) ? statement : statement;

But of course it has to be like this:

if (expression) {
  statement;
} else {
  statement;
}

True vs. False

Rulz implements the PHP boolean type, TRUE and FALSE. The Conditional Operators are "loose" comparisons, with 0, "0", empty string and empty list evaluating false, with all other values evaluating true.

Assignment Operators

The assignment operator is = and their "combined operator" equivalents. Assignment without an argument is a special case which "resets" $0; with one argument it assigns $0; with two arguments the first is a variable name to assign.

=                       reset $0 (to NULL)
= argument              set $0 to argument
= var argument          set $var to argument

It is important to note that the last two examples above are how nearly all operators work.

There are "compound" versions for multiple variables.

= a,b,c 1,2,3

Without values, compound variables are set to 0.

= i,j

With just one value the variables are all set to it. This is same as above.

= i,j 0

To set some and default some, use a comma.

= i,j 1,

Un-Assignment Operator

!= var [vars]           unset $var and all following variables

Logical Operators

The logical operators set $0 with the result of the operation.

!! [argument]           true if argument or $_ not true
|| [arguments]          true if any argument or $_ true
&& [arguments]          true if all arguments or $_ true
^^ argument [argument]  true if either argument or argument and $_ true but not both

The Perl Logical Defined-Or is supported.

// var argument         result is $var if defined else argument

In addition, there are two named operators that operate a little different.

or argument [arguments] result is the first argument that is found true
and argument [arguments] result is the first argument if all are true

And two that are unique to Rulz.

is argument [argument]  true if argument or argument and $_ are of the same type
non argument [argument] true if argument or argument and $_ are not of the same type

Arithmetic Operators

These are combined assignment operators; if a variable is undefined it is set to 0 first — or "" in the case concatenation (which is grouped here for its syntax).

+= argument             add to $0
-= argument             subtract from $0
*= argument             multiply $0
/= argument             divide $0
%= argument             modulo $0
.= argument             concatenate $0
+= var argument         add to $var
-= var argument         subtract from $var
*= var argument         multiply $var
/= var argument         divide $var
%= var argument         modulo $var
.= var argument         concatenate $var

An easy way to remember these two character operators are to think of them as "modified assignment operators", with the operation character, +, -, *, etc., modifying the = operator — in the "left-handed" way.

When used with a variable name and two arguments the result is like other language arithmetic.

+= var arg arg          $var = arg + arg
-= var arg arg          $var = arg - arg
*= var arg arg          $var = arg * arg
/= var arg arg          $var = arg / arg
%= var arg arg          $var = arg % arg
.= var arg arg          $var = arg . arg

See also #List Assignment Operations.

Increment and Decrement Operators

Since increment and decrement operators in most langages are just a syntactical shortcut — the underlying principle is adding or subtracting one — Rulz simply has one more version of add and subtract.

+=                      increment $0
-=                      decrement $0
+= var                  increment $var
-= var                  decrement $var

(So, there actually are no increment and decrement operators! Only addtition and subtraction for that's all increment and decrement are!)

Constant Assignment Operators

There are two ways to create constants — one for enumerations and one for values.

,= A B C                A = 1; B = 2; C = 3
:= A 1 B 2 C 3          same

The latter can also have extra spaces.

:= A 1  B 2  C 3        same

Or it can have none.

:=A1B2C3                same

Exponentiation Operator

The exponentiation operator works just like assignment operators but stands outside them by not having a trailing =.

** arg                  $0 = $0 ** arg
** arg arg              $0 = arg ** arg
** var arg              $var = $var ** arg
** var arg arg          $var = arg ** arg

See also #Mathematical Operators.

Bitwise Operators

These operators are listed by number of arguments.

The unary not of bits takes zero or one arguments. If none, $0 is a not of itself, else it is not of the variable.

~= [var]                result is Not of bits

The xor and shift operators take one or two arguments, with the result in $0 or a variable respectively.

^= argument [argument]  result is Xor of bits
<= argument [argument]  result is bit shift left
>= argument [argument]  result is bit shift right

The and and or operators can take from one to many arguments.

&= argument [arguments] result is And of bits
|= argument [arguments] result is Or of bits

With one or two values, the result is in $0 (with one value the result is the binary operation with $0 (itself) as the first operand).

Examples

&= 1                    $0 is $0 And 1
&= $a $b                $0 is $a And $b

With a variable name the operations are the same:

&= a 1                  $a is $0 And 1
&= c $a $b              $c is $a And $b

With three or more arguments, the result is the operator applied to all values.

|= 1 2 4 8              $0 is 15
|= var 1 2 4 8          $var is 15

Comparison Operators

These operators set the result in $0, and take one or two arguments. If one, the comparison is against $_; if two, the comparison is between the first and second arguments.

=? argument [argument]  true if equal
=! argument [argument]  true if not equal
<? argument [argument]  true if less than
<! argument [argument]  true if not less than
>? argument [argument]  true if greater than
>! argument [argument]  true if not greater than

These are "modified conditional operators", with the test type character, =, < and >, modifying the ? and ! operators.

See #Compound Conditionals.

List Assignment Operations

The #Assignment Operators can be used with lists.

+= list value           add value to each list element
+= list list            add each element of second to each element of first

All basic arithmetic operations are similarly supported.

Concatenation is either push (append) or merge.

.= list value           push value onto list
.= list list            merge second list into first list

There are also several operators dedicated to lists.

List Bitwise Operations

Bitwise operations also can be applied to lists in a unique way — with just one argument $_ will be used as if it were the second.

&= [list] list          union
|= [list] list          replace
^= [list] list          unique
~= [list] list          intersect
<= [var] list           shift
>= [var|val] list       unshift

See also #List Operators.

List Comparison Operators

=? list [list]          true if have same key/value pairs
=! list [list]          true if not the same

Evaluation Operators

Evaluation operators mimic the PHP isset and the Perl defined functions.

?? var                  isset (defined)
?! var                  not isset (not defined)
&? var                  defined [subroutine]
&! var                  not defined [subroutine]

Percent Operators

The percent operators are function-like.

=% argument [argument]  percentage of
/% argument [argument]  percentage against
+% argument [argument]  percentage add
-% argument [argument]  percentage from
*% argument [argument]  percentage between

Mathematical Operators

The Mathematical operators are function-like.

v/ argument             square root
2/ argument             square
1/ argument             reciprocal
a/ argument             absolute
^/ argument             exponent

They work like #String Operators defined below. (There are many more.)

File Operators

The file I/O operators mimic basic file functions. The $0 variable is TRUE on success or FALSE on error.

&  [fh] file            open file for reading, handle in $_ or $fh
-& [fh] file            open file for writing, handle in $_ or $fh
+& [fh] file            open file for appending, handle in $_ or $fh

The read operators returns the number of bytes read or FALSE on error.

<& [$fh] var            read line from $fh or $_ to $var
<& [$fh] var number     read number bytes from $fh or $_ to $var
[& [$fh] var            read line from $fh or $_ to $var without EOL
{& [$fh] var            read character from $fh or $_ to $var
(& [$fh] var format     parse line from $fh or $_ to $var according to format 
/& [$fh] var pattern    parse line from $fh or $_ to $var according to regular expression

The write operators returns the number of bytes written or FALSE on error.

>& [$fh] $var           write $var to $fh or $_ with EOL
]& [$fh] $var           write $var to $fh or $_ without EOL
}& [$fh] $var           write character $var to $fh or $_
)& [$fh] var format     write $var to $fh or $_ according to format

Other file operators.

@& [$fh]                eof test
.& [$fh]                close
%& [$fh]                stat
=& [$fh] number         seek
,& [$fh]                rewind
?& [$fh]                tell
:+ [$fh]                truncate

With some file name versions.

%& file                 stat
^& file                 touch
!& file                 delete
|& file file            rename/move

The use of $_ as a default for the file handle is odd as many other operators can subsequently and inadvertently overwrite $_. Using $_ is recommended only for short sequences of code.

See also #Test Operators.

There are several function-like operators that read or write files directly.

<< [var] file           get file as string into $_ or $var
[< [var] file           get file as list into $_ or $var with EOL
(< [var] file           get file as list into $_ or $var without EOL
>> [$var] file          put $var or $_ as string to file
]> [$var] file          put $var or $_ as list to file with EOL
)> [$var] file          put $var or $_ as list to file without EOL
+> [$var] file          append $var or $_ as string to file
-> [$var] file          prepend $var or $_ as string to file

Special variable $0 is set to FALSE for error, TRUE for success; except that read operators return the number of bytes read, and write operators return the numbers of bytes written on success.

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 file
+> filea fileb          append file
-> filea fileb          prepend file

Finally, a file can be read and output.

~< file                 output file

(To add one last thing, Perl's documentation categorizes some functions as "operators". For Rulz, most functions are operators.)

STDIN

There are three operators to read STDIN directly (without having to "open" it).

_< [var]                read line from STDIN to $var or $_
-< [var]                read line from STDIN to $var or $_ without EOL
,< [var]                read character from STDIN to $var or $_

(These are not satisfying looking operators and probably will change.)

Load Rules Operator

A Rule file can be included with the include operator.

.< file                 load

The file being included will not be run and any rules outside of subroutines will be ignored — see rulzsub. What this means is that only subroutines will be loaded.

Unlike PHP and Perl, Rulz subroutines can be redefined — any existing subroutine of the same name will be replaced.

There are Rulz builtins for running Rulz programs. See Functions.

Execution Operator

The execution operator is like PHP or Bash, returning the program's output in $0, or NULL if error.

`whoami`

Or used as an argument:

=var `whoami`

Call Operator

The & (function) operator is for using a function return value as an argument.

This Rule calls time with the return in $0 which is passed to date.

time
date "M d, Y" $0

The & operator turns functions into arguments.

date "M d, Y" &time

Otherwise, time would be seen as a bareword.

Search and Replace Operators

/pattern/               search $_ for pattern
/pattern/ $var          search $var for pattern
/pattern/text/          search $_ and replace with text
/pattern/text/ var      search $var and replace with text

As with variable assignment, the variable name for replace is without the $.

After a successful match, the following special variables are set:

$#                      number of matches
$&                      the string that matched
$1                      the first subexpression match; $2 the 2nd, etc.

For search, $0 is set to the number of matches; for replace, the number of replacements.

These are full PCRE strings but with one extension; the /a modifier turns search into the equivalent of PHP and Golang "all" version of a match.

Jump Operator and Labels

:label                  define label
. label                 jump to label
. 3                     jump 3 lines
.. [rule]               exit Rule or program; running optional rule

A jump operator can be preceded by a conditional operator:

? . label               conditional jump to label
! ..                    conditional exit

Variables can be used for jump conditions:

=n 3                    assign $n
. $n                    jump $n lines
=l label                assign $l
. $l                    jump to label $l

Jumps can only be positive or "into the future".

Loop Operators

@do                     while (1)
<rules>
@end
@each [$var]            foreach on $0 or $var (as hash)
<rules>
@end
@for [$var]             foreach on $0 or $var (as list)
<rules>
@end
@until [$var|rule]      while $0 is false or while [$var|rule] is false
<rules>
@end
@while [$var|rule]      while $0 true or while [$var|rule] is true
<rules>
@end

With @for the special variable $_ is set to the array value. With @each the special variable $( is set to the array key, and $) the array value.

These operators cannot be nested.

The following operators can control the loop:

.. [rule]               break
>. [rule]               next
<. [rule]               redo

(These are "modified" jump operators.)

The @until and @while operators have one line versions.

@while <$var|rule> <rule>

With the caveat that for a conditional <rule> the loop condition is $0, which means that the loop will continue or not based on either <rule> setting $0 at some point.

With the loop conditional a variable, the <rule> argument has to set the variable to continue or stop the loop, either through a subroutine or a builtin.

If Else Operators

These are not blocks in the sense of PHP or Perl; but are similar.

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

These operators cannot nest nor can be in a loop.

Not being able to nest loops/blocks is a minor thing as there are subroutines.

Test Operators

Test Operators, based on Bash conditional operators, set $0 with the result of a test on a file — TRUE or FALSE. If the test argument is missing $_ will be used.

f- [file]               file exists
d- [file]               file exists and is a directory
r- [file]               file is readable
w- [file]               file is writable

The following "tests" will return a value other than TRUE for success.

s- [file]               file size
p- [file]               file permissions
t- [file]               file type
g- [file]               file group
a- [file]               file access time
c- [file]               file change time
m- [file]               file modification time

The type test operators test the type of it's argument or $_.

S- [argument]           is string
I- [argument]           is integer
D- [argument]           is double
A- [argument]           is list or hash
N- [argument]           is numeric
B- [argument]           is boolean
U- [argument]           is null
T- [argument]           is true (PHP like)
Z- [argument]           is empty (PHP like)

These return a value other than TRUE.

L- [argument]           integer length if string or count if list
Y- [argument]           string indicating type (PHP like)

The tests are "backward" compared to Perl and Bash, which do -f. But they simply adhere to the "left handedness" of the Rulz language — the - (dash) is the base test operator which always requires a letter modifier.

Type Operators

Type Operators are extensions to Test Operators on types, and are similar to PCRE generic character types escapes, POSIX notation for character classes, or PHP ctype functions.

Again, $_ is used if no argument and the boolean result is in $0.

u? [argument]           is uppercase
l? [argument]           is lowercase
d? [argument]           is decimal
s? [argument]           is whitespace 
w? [argument]           is word character
n? [argument]           is alpha numeric
a? [argument]           is alpha
c? [argument]           is control
g? [argument]           is graph
p? [argument]           is printable
v? [argument]           is punctuation
x? [argument]           is hexadecimal

All have an ! version to negate the test. The test is a string and not just a single character. (Though they look it, these are not considered modified conditional operators.)

String Operators

The string operator extensions perform function-like operations on strings. These can be either operators or arguments. As an operator the default is the "return" setting $_. However, missing "main" arguments will default to use $_ as well.

u~ [count] [arg]        to upper case
l~ [count] [arg]        ro lower case
n~ [arg]                length
q~ [arg]                quote meta 
i~ offset [len] [arg]   index (substr)
s~ string [off] [arg]   string occurrence
S~ string [off] [arg]   string occurrence (case insensitive)
p~ string [off] [arg]   string position
P~ string [off] [arg]   string position (case insensitive)
c~ [arg]                chop (PHP like)
C~ [arg]                chomp (Perl like)
t~ [string] [arg]       trim
T~ [string] [arg]       trim right
L~ [string] [arg]       trim left
f~ format arg [args]    sprintf
F~ format arg [args]    sscanf
h~ [arg]                htmlentities
H~ [arg]                htmlspecialchars
x~ [string] [arg]       explode
w~ [len] [string] [arg] wordwrap
r~ string string [arg]  string replace
R~ string string [arg]  string replace (case insensitive)
e~ [arg]                urlencode
E~ [arg]                urldecode
y~ [string] [arg]       crypt
g~ string [arg]         preg split
b~ [string] [arg]       basename
d~ [arg]                dirname
Rulz makes decisions on argument defaults and types based on the number of arguments.

Examples.

t~
t~ $var
= var t~ $var
u~ 1 \$var
The latter,uppercase first, is to modify a variable by reference.

List Operators

List operators perform Perl/PHP functions. Most create (or modify) lists.

/[ [var] string [string] split
:[ [var] list [string]  join
~[ [var] list           values
%[ [var] list           keys
@[ [var] list           sort
\[ [var] list           reverse
<[ [var] list           shift
>[ [var] list           unshift
-[ [var] list           pop
+[ [var] list           push
,[ [var] list off [len] slice
([ [var] list string    extract
^[ [var] list list      difference

Without var the results are in $_. For @[ and [ list can be a reference to a list.

The ([ operator extracts lists out of a list using string as the "boundary" character.

= a ('a',',','b','c')
([ b $a ,               $b = (('a'),('b','c'))

The ^[ operator returns an array containing all the entries from the first list that are not present in the second list.

Operators that act upon a list.

+[ [var] list           sum
-[ [var] list           subtract
*[ [var] list           product

Operators that act upon a list or a hash.

#[ [var] <list|hash>    count
=[ [var] <list|hash>    index/key exists (boolean)
?[ [var] <list|hash>    value exists (boolean)
[[ [var] <list|hash>    value exists (search) returns key
*[ [var] <list|hash>    random value

See also #List Assignment Operations, #List Bitwise Operations and #List Interpolation.

List Operator Alternatives

j[                      join
v[                      values
k[                      keys
s[                      sort
r[                      reverse
p[                      pop
P[                      push
l[                      split
L[                      slice
d[                      difference
i[                      index/key exists
e[                      value exists (boolean)
E[                      value exists (search)

Heredoc

"Here-documents" are supported, with slightly unique syntax.

<<<
Here, here documents are multi-line print statements.
They include interpolation and escapes (\$foo = $foo).
And they end with the string '>>>' on a line by itself.
Identifiers are not used.

Lines are concatenated with spaces; blank lines are 
replaced by a newline.

  * Leading whitespace is preserved...

...the trailing newline is not. For that, end in a blank.

>>>
<<< var
This heredoc is assigned to a variable.
>>>

The ending ">>>" ignores leading and trailing whitespace.

By adding a single quote (') the result is a nowdoc.

<<<'
A "nowdoc" does not interpolate $vars and does not convert 
escapes like \\ or even \'. (Which is kind of Perl-like. I 
think.)
>>>

The default is actually as if a double quote (") was used. By adding two quotes, single or double, all lines are terminated by a newline.

<<<""
1) Line 1.
2) Line 2.
3) Line 3.
>>>

If a heredoc is last in a program or subroutine the ending ">>>" would not be required.

Filedoc

An extension to heredoc is to output text to a file. This uses the operator >>> and a file name, with the text ending on <<<.

=title "Rulz Programming Language"
>>>"" head.htm
<!DOCTYPE html>
<html>
<head>
<title>$title</title>
</head>
<<<

All the aspaects of heredoc applies.

Compound Conditionals

The #Conditional Operators can test some operators — basically combining two rules in one.

? <rule> <rule>
! f- .. ^$_ not found
! f- $f .. ^$f not found
! &error.log .. ^could not open log file

Similar to basing the conditional test on a variable, here, the conditional test is a rule and only those with operators that set $0 will work as expected.

! += v ^foo

That rule will always increment $v but will only print foo if $0 "is false".

! += ^foo

That rule will always increment $0 but will only print foo if $0 "was false".

Even though a builtin might set $0, they will not work this way.

? function $v ^foo

The ^foo will get passed to the function (as two arguments), which will be called is $0 true. This is where the comma psuedo-operator comes in.

? function $v , ^foo

So, ^foo will run based on the return of function $v.

A limited switch construct can be simulated with comparison operators.

= n &getname
=? $n value &value
=? $n comment &comment

Calling subroutines in this case.

With regular expressions it's a less visually impaired way.

= n &getname
? /value/ $n &value
? /comment/ $n &comment

For an example of parsing program options see rulzsub.

Whitespace

Leading and trailing spaces and tabs are ignored. Operators and arguments can be separated by spaces or tabs.

? func
= foo 10
! ++ foo
>? 10
= f ~/*
The latter rule's argument is a file glob.

The execution operator ignores leading and trailing spaces within it.

If there is no ambiguity between operator and argument(s) spaces are not required at all.

?func
=foo10
!++foo
>?10
=f ~/*
The latter rule's space is required else it would be = f~ /*
They hurt you at home and they hit you at school.
They hate you if you're clever and they despise a fool.
'Til you're so fucking crazy you can't follow their rules.
  - John Lennon