Syntax
Comments
#
is a comment, but only if followed by a space. My editor makes it easy to comment / uncomment lines of iogii if I just tell it to use ruby syntax for .iog
files.
Literals
Integers are any continuous sequence of digits. Leading 0's are a separate number.
0050 → 0 0 50
↗️
Chars are a '
followed by a single character. \
can escape \
, n
(newline), 0
(null character), x00
for any two digit hexadecimal.
'\x0a → '\n'
↗️
Strings are enclosed in ""
, the last "
can be omitted if it is the last thing in your program. \
can also be used to escape "
.
Identifiers ([A-Za-z][A-Za-z0-9]*
) will be broken into individual characters if there is no op or variable by that name.
>
matching
This section assumes you have already read the intro on looping and functions.
End brackets (>
) match with loop ops (i iterate
, e expand
, f foldr
, and m meld
) like parenthesis. Think of iefm
as left brackets that all match with >
as the right bracket.
If end brackets are missing, they are implicitly inserted at the first valid location. i e f m
can use values from left, they are normal syntactic elements.
The value before >
is passed to the matching i e f m
op. The result of the op is returned after the >
.
It would be rare to need nested loops that require two >
s. But understanding the matching algorithm could be useful in such a case:
- Start at the rightmost
>
- Try to match it against leftmost
i e f m
- If it doesn't match (not valid because it would return > 1 value), then try the next
i e f m
- If it doesn't match (not valid because it would return > 1 value), then try the next
- Once a match is found, match the next
>
- Try to match it against leftmost
When developing code with nested loops, I recommend explicitly using >
until you are ready to optimize, then [try to] make them implicit.
Note that folds would always be invalid if there were only unary ops applied to the arg, so they will not implicitly close until there has been a binary op (or trinary). It is still possible to write completely jibberish folds, but detecting them would require looking at type level information, while bracket matching is done purely before this.
0 foldr succ 1,2,3 + → 9
↗️
The fold does not terminate after succ
, but an iterate
would.
0 iterate succ 1,2,3 + → [1,3,5]
↗️
>,
You can proceed >
with any number of ,
s. These end brackets skip matching as many i e f m
s as there are ,
s. This is necessary if you wish to use an outer loop's var inside and inner loop's function.
For example, suppose we wanted to create a sequence where each number is the sum of the numbers 1 to previous number (without using the sum op). Sum can be written as a fold from 0 (meld):
meld 5 countTo + > → 15
↗️
And to write a sequence it would be:
[initial value] iterate [function of loop var]
But our function needs to place that loop var inside the meld (it was a 5 in our example):
2 iterate meld [iterate loop var] countTo + end_meld end_iterate
So how do we do this? iterate
represents where the loop var goes, so just put it where it goes!
meld 2 iterate countTo + [end meld] [end iterate]
But the [end iterate]
is last and it matches with the inner loop. That is what >,
is for...
meld 2 iterate countTo + > >, → [2,3,6,21,231,...
↗️
The >,
was used on the last loop end because bracket matching starts with the last >
.
Note that the other >
isn't needed and we could have written this shortly as:
m2i}+>, → [2,3,6,21,231,...
↗️
This example is a bit contrived because we could have just used the sum
op, but also because addition is commutative, so we actually could have swapped the order of the args which would have allowed us to write this without >,
. But not all operations are commutative so this will sometimes be necessary.
Difference between function loop op and meta op functions
Loop ops and meta ops (mdup
/ mpeek
) both take in a function after the op.
But meta ops can never match with >
, they always implicitly close (because this would almost never be useful compared to using =
, but could cause extra >
s to be needed).
Loop ops and meta ops implicitly close in reverse order they are created. This makes them behave more intuitively although such an assumption is not actually required in iogii.
Meta ops ignore loop ops for matching closing purposes, but meta ops are not ignored by loop ops. This difference is because loop op endings must be calculated after parsing is complete since they could be explicitly ended. Whereas meta ops affect the stack size and their effects must be known while parsing is happing. (Another way of thinking about it is just that meta ops are parsed before loop ops are implicitly or explicitly matched - loop ops are just regular ops to them).
5 mdup iterate + → [5,10,15...
↗️
(because mdup
closes imediately after the iterate
), but:
1 5 iterate mdup + → ERROR: 1:5 (iterate) unterminated function
↗️
(because iterate
doesn't terminate immediately after mdup
, mdup
pulls in another value to +
on then the dupped value is place with no more ops after it for iterate
to bring it back down to one).
Input Placement
As mentioned in IO input placement can always be done implicitly by rotating your program so that the input would come first (then omit it). If you are using input multiple times, you have choices about which one you can make implicit.
Implicit Values
If an op uses more args than there are items on the stack, it creates implicit
args as needed (that is an actual op name you can use to simulate the effect as needed too).
If you have not set the implicit value using unmatched >
then the implicit value is the value yielded by the innermost loop function you are in when it is consumed. (Which for the main program it is the input).
In this example the implicit value is used in the main program so it is equivalent to input again:
4
*
16
↗️
In this example, iterate consumes the input as its starting value, then the function multiplies the loop variable by itself (since the implicit value is used which is that same loop variable).
4
iterate * >
4
16
256...
↗️
If the input is empty and used as auto input, then it is removed and only implicit values are placed.
4 iterate * >
4
16
256...
↗️
If you are using input and implicit values, implicits are created as needed after your program is unrotated. So for example if this was your program:
implicit implict [extra values] input [main program]
You could have written it as:
[main program] [extra values]
But note that [extra values]
cannot consume those implicits because there is no way to distinguish that from consuming the result of the main program.
$
$
is the value of the innermost loop function it is in. (Which is input if present, implicit value otherwise).
4 iterate $ * >
4
16
256...
↗️
Use in main program:
4
$$*
16
↗️
Here the implicit value is set to 4 and no input is present:
4> del $$*
16
↗️
Unmatched End Brackets
If an end bracket cannot be matched, then it will act like a unary op that sets the implicit value with its arg (see below to learn more about the implicit value).
5> ++ → 15
↗️
You can set the implicit value multiple times. Each unmatched >
sets any implicit values used between it and the next unmatched >
. This wraps around to the beginning, so a single unmatched >
sets the implicit value everywhere in the program.
3>*>+ → 18
↗️
Other ways to use values multiple times
Postfix is perfect at representing expression trees, but what about when you need to use a value more than once (which would technically be a directed acyclic graph (DAG) not a tree)?
If that value is the loop value, you can just use $
.
Or if it just so happens to be used at the left hand side of the tree you could set the implicit value and use it implicitly.
If it is used twice and only simple manipulation is done before combining the two uses, you can use one of dup
, mdup
, peek
, or mpeek
. These correspond to the 4 cases the S combinator (Sxyz = xz(yz)
), for each of x
or y
being the identity function.
They can't be used to handle all cases because they don't take in arbitrarily complex functions as args (doing so would require >
which would then not be shorter than iogii's other method, see below).
mdup ;
and mpeek !
ops take a function just like looping ops, but this function does not match with >
's so it can only handle simple cases because it always ends implicitly. >
would not be useful anyway since this would be the same number of characters as using set next var (see below). And this increases the chances of a loop function needing and extra >
because the loop function's >
got matched with one of these ops instead.
set
set
is assignment, the token following the set
has the value before the set
assigned to it. This overrides any other meaning of that token for the entire program (both before and after the set
).
1 2+ set foo foo → 3 3
↗️
let
is a set
followed by a del
.
These have long names because they are intended only for debugging since there is a shorter way to set vars...
Set Next Var
=
saves the top of the stack to the next available var. The next available var is a capital letter A-Z chosen in a special way so that you never overwrite an op that you want to use. How can it do that?! Find the largest gap between used letters and choose the next one. Technically it is the largest difference between starting runs of used letters (so that adding the use of the next var does not change the selection process).
So if you don't use any capital letter ops then the first use of =
will save to A
, and the next to B
.
5= A + → 10
↗️
These values can be used before they are set since iogii is really just a bunch of timeless static definitions.
A 5= + → 10
↗️
If you use capital letter ops A
, C
and W
now the largest gap is between C
and W
so the first use of =
will save to D
.
If you use so many =
that you fill the largest gap completely it will not work, so there is a limit to how many times you can use this.
Note that you MUST use the saved value (otherwise it will think that an op you used is actually a variable being used).
Sometimes it can be tedious to find the largest gap yourself, you could use iogii to automatically find it by using a bunch of =
s - more than there are consecutive capital letters (which will cause an error saying which var is unused).
CWA === → ERROR: 1:6 (=) Sets register 'D' but it is never used
↗️
Set Next Vars are powerful but I recommend using set
as you develop your program and only switching to them at the end, since it would be tedious if you frequently change which capital letter ops you use in your code.