Iris
Interesting Random Instruction Sets (IRIS) is a language authored by User:Montywest in 2012.
Philosophy & rationale
Iris is named for the author's wife & much MUCH better half. While the name originally refers to "arco iris" (rainbow), the English reference to the iris flower (Iris refers to a genus of several hundred plant species) makes sense with the biological inspiration for the language. It was invented because the author doesn't know how genetic algorithms work. The fact that the author is an aspiring English teacher (spelling errors not withstanding) rather than a computer scientist might count in his favour when others note how he's reinventing the wheel for the Nth time.
The author thought that it'd be neat to be able to make code that would produce something interesting even with random changes. So, there are no possible errors in the code (that is if the implementation doesn't conk out on non-numeric input). Iris is a context dependent language. The same "bit" of code may serve several independent functions. It could indicate a control structure, a reference, a value, an operator (logical or arithmetic) or metadata for a "function" call. This code reuse means code that's difficult to read but compact and extremely malleable. Whatever comes in produces legitimate output - even NULL output's fine.
A framework that produces small random changes to source may, with careful selective pressures, produce useful results. Imagine an evolved Fibonacci generator or sudoku solver. Imagine selecting for shorter code, faster code, or other attributes.
Iris is a potentially self-modifying source language. As of Iris 0.1, the source is static, but any part of a program can reference modifiable data and use that for source. The result is a dynamic program in which various parts can change dramatically. A function call can become a call for a completely different function, and what was a value may become a function call in itself. This sort of seeming "random" functionality may naively mimic the way our own genetic code operates.
Language structure
As of Iris 0.1, the language is written entirely with numbers. There are two built in data structures: Lists A and R (for Register). List A contains the program itself. List R contains input, is used to manipulate data, and is used for output.
A pointer traverses list A from beginning to end. It can be moved to other locations in A, allowing loops. When the pointer reaches the end of A, the program ends and list R is output.
Here is an annotated example of a single statement: 0,1,1,1,2,2,1,0
0 assignment (arithmetic) This is the function call 1 element 1 in list R (0 indexed list) will get the result 1 there is one operator in the statement These are function metadata 1 the next number is a value to be used in the expression 2 2 is the number 2 the next number is a reference in R for the value to use in the expression 1 element 1 in R is used in the expression 0 addition operator These are the function guts. The above is equivalent to R[1] = R[1] + 2
So, each statement consists of the function call, followed by metadata, followed by the functions mechanics. The entire program consists of such calls.
Syntax
Entirely dependent on context.
There are three functions: Assignment, Flow (Control), and Go. (The last two are named for the rhyme)
Assignment
0,1,1,1,2,2,1,0
Function Call | Reference to assignee | Number of Operators in expression | expression data | 0 1 1 1,2,2,1,0 0 assignment (arithmetic) This is the function call 1 element 1 in R list (0 indexed list) will get the result 1 there is one operator in the statement These are function metadata 1 the next number is a value to be used in the expression 2 2 is the number 2 the next number is a reference in R for the value to use in the expression 1 element 1 in R is used in the expression 0 addition operator These are the function guts. The above is equivalent to R[1] = R[1] + 2
In the expression data 1,2 = just use the number 2. 2,1 means use R[1] to get the next number. 0 means '+'
Look at the next statement: 3,1,1,5,2,0,1,0
3 assignment (arithmetic) This is the function call 1 element 1 in R list (0 indexed list) will get the result 1 there is one operator in the statement These are function metadata 5 the next number is a value to be used in the expression 2 2 is the number 0 the next number is a reference in R for the value to use in the expression 1 element 1 in R is used in the expression 0 addition operator These are the function guts. The above is equivalent to R[1] = R[1] + 2
Look closely at the above two Assignment examples. Do you see any differences? In the 1st, 4th, and 6th lines, the numbers are different, yet the code is the same. Why? Well, since we want to be able to use randomly generated numbers for source-code, we need to be able to convert them to a limited number of choices: Modulus to the rescue! N % 3 will always produce 0, 1, or 2. This translates to Assignment, Go, and Flow respectively.
The other two changes are simple 0 and 1 (even and odd) choices. These allow for using either data from the source-code, or from list R.
Let's look at the same statement with a very REAL difference:
0,1,1,1,2,2,1,-12
0 assignment (arithmetic) This is the function call 1 element 1 in R list (0 indexed list) will get the result 1 there is one operator in the statement These are function metadata 1 the next number is a value to be used in the expression 2 2 is the number 2 the next number is a reference in R for the value to use in the expression 1 element 1 in R is used in the expression -12 what's this? These are the function guts. The above is equivalent to R[1] = R[1] ? 2
The results MIGHT be the same, but that -12 puts a monkey wrench in the calculations. Why? Well, in most circumstances (except where negative numbers are useful) a negative number gets used as a reference to list R. E.g., the above used the number from R[12] (absolute value of -12) to get the operator to use in the expression. If R[12] isn't defined, then we get '0' and that's '+' which works out well enough. If R[12] were '3' we'd get a '/' operator.
In cases which a value is being gathered, we use a separate bit of code to decide whether the value comes from A or R. Negative numbers have use as values.
You may notice a number that indicates "number of operators." This is needed, because the interpreter does not know in advance the structure of the expression. When there are more than one operator in an expression, the interpreter has to decide whether a number refers to a value or operator. Let's look at the next partial example:
0, call the function 0, R[0] getting a new value! 3, 3 operators! Now, in Reverse Polish Notation (RPN) the first two data are numbers, so we do the usual: 1, 12, number 12 2, 0, whatever number R[0] contains Now's the tricky part... 1, Oh! The next number is referring to a value! 2, Oh! That value comes from R 3, It's from R[3]
So, that's three values in a row, and we have two operators to get in there somewhere. If the next number's a value, then the remaining will automatically be operators... let's see!
0, It's an operator! 8, But what operator is it? Remember, we use modulus to turn random numbers into limited choices. Well, this time it's 8%6 = 2 which is *! which means R[3] * 12 is part of the expression.
That's enough example for now, let's look at . . .
Flow
Flow is IF, WHILE, and FOR all in one.
2,1,1,10,1,1,12,6,0,0,
Flow call | Iteration value | Number of calls to make | Skipto | Condition Expression | 2 1 1 10 1,1,12,6,0,0
2 Tell interpreter that this is a Flow statement 1 The Flow statement iterates once 1 There is one statement that will run if the conditions are met 10 The pointer gets moved 10 bits forward if the condition is not met, or we're done. 1 the number of logical operators in the condition expression (look familiar?) 1 the next value is from list A 12 the value is 12 6 the next value references list R 0 R[0] holds the value 0 the logical operator is '=='
The above is equivalent to R[0] == 12. If true, then 1 statement following the condition runs. Otherwise the pointer is moved forward according to the skipto value above.
The iteration value of 1 turns this Flow statement into an IF statement. If the iteration value were '0' then it would be treated as a While statement. An iteration value of 2 or above makes it a For statement with a condition attached.
By the way, the condition statements are Reverse Polish Notation as well. Uses the same routines in the Perl implementation.
Go
Go is like a GOTO & GOSUB This call either sets the pointer to a new location permanently or for a specified number of function calls.
1,23,0
Go call | Location to go to | Number of Calls before returning (0 = no return) 1 23 0
The above statement sends the pointer to list A's element 23, where it will advance as if nothing had ever happened.
Function schematics
The left hand is a short description of a piece of code (one number in length per line) The right hand is a detailed description of the piece of code.
Code value | Description of code meaning | Notes |
---|---|---|
1, 4, 7, . . . | Function call Go | N%3 = 1 |
any value | Location | -N maps to R * |
any value | Iterations | -N maps to R *
the resultant value is the number of function calls made from the above location before the pointer is returned to its old location 0 = pointer doesn't return |
Code value | Description of code meaning | Notes |
---|---|---|
0, 3, 6, . . . | Function call Assignment | N%3 = 0 |
any value | R reference to receive result | -N maps to R * |
-- expression (identical to conditional part of Flow()) -- | ||
any value | Number of operators |
-N maps to R * if equal to 0, the next value is the result |
-- first two parts of the expression are values -- | ||
any value | choose source | Odd number = next value is used
Even number = next value is an R reference |
value | ||
-- The above two actions are repeated one more time -- | ||
any value | Value or operator |
N%2 = 0 ? it's an operator N%2 = 1 ? it's a value if value, use the above value scheme |
any value | operator | -N maps to R * |
*** there must be one fewer operators then values. The final part of the expression must be an operator. |
Function call Flow N%3 = 2 iterations -N maps to R * number of times the condition runs 0 = while loop 1 or more = for loop. when condition is false, loop ends. number of calls -N maps to R * after N function calls, the loop starts over skipto -N maps to R * skipto = skipto + pointer pointer will = skipto once Flow() is finished. -- expression (identical to what's in Assignment()) -- Number of operators -N maps to R * if equal to 0, the next value is the result -- first two parts of the expression are values -- choose source Odd number = next value is used Even number = next value is an R reference value -- The above two actions are repeated one more time -- Value or operator N%2 = 0 ? it's an operator N%2 = 1 ? it's a value if value, use the above value scheme operator -N maps to R * *** there must be one fewer operators then values. The final part of the expression must be an operator.
- When -N maps to R, list R actually gets the absolute value of -N and uses it as a reference. E.g., if -3 is the initial value, then R[3] is the source of the value to be used in the code.
Lists (partial) of numbers and there meanings
Function Calls: 0 = Assignment 1 = Go 2 = Flow
In Assignment: Operators: 0 + 1 - 2 * 3 / 4 ** 5 % In Condition statements: Operators: 0 == 1 < 2 > 3 <= 4 >= 5 != 6 and 7 or 8 xor
Ending the program
There are several ways to end a program,
- Divide by 0.
- Produce a number that the interpreter doesn't handle without resorting to scientific notation.
- Send the pointer to an undefined location in list A.
All such methods output list R.
Notes regarding syntax
The only implementation as of this writing is in Perl (listed at the end of this article). As such, there may be idiosyncrasies that creep in to the process that have influenced the development of Iris.
- if referring to an array in Perl, 1.01 is the same as 1. Thus, -.1 is seen as a negative number (remember that negative numbers are often used to refer to list R), but the array sees a 0.
- Reverse Polish notation normally treats 3 2 - as 3 - 2, but the current implementation treats it as 2 - 3 because of the way the interpreter is evaluating the expression.
Examples
The famouse Fibonacci sequence
Comma delimited form: 0,0,0,1,5,0,3,0,1,0,0,4,0,1,1,2,3000,4,1000,0,1,1,0,1,1,1,1,2,0,1,0,2,1,1,2,2,0,1,0,-.1,1,2,-1,2,-2,0,0,1,2,0,1,1,0,
Using Perl style with annotation:
my @A #list A = ( #init: 0, #assignment call 0,0,1,5, # R[0] = 5 0, #assign 3,0,1,0, # R[3] = 0 0, #assign 4,0,1,1, # R[4] = 1 2, #Flow call 3000, #iterations --> that's lots of numbers! 4, #number of function calls (those four assignment calls following...) 1000, #skipto effectively ends the program when this is done 0,1,1, #condition statement. returns a '1' which is "true" 0, #assignment 1, 1, 1,1, 2,0, 1, #R[1] = R[0] - 1 0, #assignment 2, 1, 1,2, 2,0, 1, #R[2] = R[0] - 2 0, #assignment -.1, 1, 2,-1, 2,-2, 0, #R[R[0]] = R[R[2]] + R[R[1]] 0, #assignment 0, 1, 2,0, 1,1, 0, #R[0] = R[0] +1 );
And, here is the output based on the Perl implementation:
97|96|95|0|1|1|2|3|5|8|13|21|34|55|89|144|233|377|610|987|1597| 2584|4181|6765|10946|17711|28657|46368|75025|121393|196418| 317811|514229|832040|1346269|2178309|3524578|5702887|9227465| 14930352|24157817|39088169|63245986|102334155|165580141|267914296| 433494437|701408733|1134903170|1836311903|2971215073|4807526976| 7778742049|12586269025|20365011074|32951280099|53316291173| 86267571272|139583862445|225851433717|365435296162|591286729879| 956722026041|1548008755920|2504730781961|4052739537881|6557470319842| 10610209857723|17167680177565|27777890035288|44945570212853| 72723460248141|117669030460994|190392490709135|308061521170129| 498454011879264|806515533049393|1304969544928657|2111485077978050| 3416454622906707|5527939700884757|8944394323791464|14472334024676221| 23416728348467685|37889062373143906|61305790721611591| 99194853094755497|160500643816367088|259695496911122585| 420196140727489673|679891637638612258|1100087778366101931| 1779979416004714189|2880067194370816120|4660046610375530309| 7540113804746346429|12200160415121876738| dump_ message is big-ass number
The first three numbers "97|96|95" are used in the expressions to calculate the sequence. starting at R[3] "0" you get the actual sequence.
The dump_ message indicated that an expression came up with a number in scientific notation. Iris 0.1 terminates at that point and writes out list R.
Implementation
(needs work)
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my @R = qw//; my @A = ( #embedded Fibonacci Sequence code. #init: 0, #asn 0,0,1,5, 0, 3,0,1,0, 0, 4,0,1,1, 2, #Flow() 3000, #iterations 4, #num events 1000, #skipto 0,1,1, 0, 1, 1, 1,1, 2,0, 1, 0, 2, 1, 1,2, 2,0, 1, 0, -.1, 1, 2,-1, 2,-2, 0, 0, 0, 1, 2,0, 1,1, 0, ); my $ptr = 0; my $limit = 10000; #kill infinite loops etc. while (1){ event(); } sub event { $limit--; dump_("LIMIT REACHED") unless $limit; my ($choice) = access_A(); $choice = $choice%3; if ($choice == 0) { assignment() } if ($choice == 1){ go() } if ($choice == 2){ flow() } } sub dump_{ my $debug = shift; print map{$_,"|"}@R; print "\ndump_ message is $debug\n"; die; } sub access_A { my $value; my $no_R = shift; #if this has a value, then we do not convert negative numbers from A to R references. This is needed for expressions. my $int = shift; defined $A[$ptr]? $value = $A[$ptr]: dump_('end'); $ptr++; unless ($no_R){ $value =$R [abs($value)] if $value < 0; #to get to R[0] this way, use -.1 or some similar value } $value = int($value) if $int; return ($value); } sub access_R{ my $ref = shift; my $value; (defined $R[$ref])? ($value = $R[$ref]): ($value = 0); $value } sub flow{ # iterations give us if() for() and while() loops all in one. # iterations = 0 gives us a while from the conditional # iterations = 1 gives us an if from conditional # iterations = >1 gives us a for loop from conditional. # with a conditional of (1) (like while (1)) you end # up with a classic for loop # # # my $iterations = access_A(0,'int'); my $num_events = access_A(0,'int'); $num_events = 1 unless $num_events; my $skipto = access_A(0,'int'); $skipto += $ptr; my $conditional_ptr = $ptr; if ($iterations){ for (1 .. $iterations){ if (conditional()){ for (1 .. $num_events){event()} }else{last} $ptr = $conditional_ptr; } }else{ while (conditional()){ for (1 .. $num_events){event()} $ptr = $conditional_ptr; } } $ptr = $skipto; } sub conditional{ my @operators = qw/== < > != <= >= and or xor/; my $truthiness = expression(\@operators); return $truthiness } sub assignment{ my @operators = qw/+ - * \/ ** %/; my $left_val_ref = access_A(); my $result = expression(\@operators); $R[$left_val_ref] = $result; } ####____ # sub expression has several routines associated with it: # rpn() # get_operator() # get_value() # val_or_operator() # # sub expression { my $operator_list = shift; my $o = access_A(0,'int');#num of ops if ($o == 0){ return get_value(); } my @a = (get_value(),get_value()); #start with two numbers my $v = $o -1; #with the two numbers above.. we reduce the value count while ($o){ if ($o == $v){ push @a, get_value();$v--; next }elsif ($v){ if (val_or_operator()){ push @a, get_value(); $v--; }else{ push @a, get_operator($operator_list); $o--; } next } push @a, get_operator($operator_list); $o--; } my $result = rpn(\@a); return $result; } sub get_operator { my $operator_list = shift; my $op = access_A(); my $operator = $operator_list->[$op % ($#$operator_list+1)]; return $operator; } sub get_value{ #in which we learn weather or not the next value is a reference to @R # then we return the result # # we don't use the "negative numbers are R refs," becuase negative nums # are kinda useful in arithmetic... go figure. my $val = access_A(); # $val is the choice between either an A value or an R value ($val% 2)? ($val = access_A("no R")): ($val = access_R(access_A())); # $val is now an arithmetic value defined($val)?return $val:return 0; } sub val_or_operator{ my $choice = access_A(); return $choice%2; } sub rpn{ my $a = shift; my @stack; for (@$a){ (/\d/)? (push @stack, $_): (push @stack, eval ((pop @stack)." $_ ".(pop @stack))); dump_("big-ass number") if ($stack[$#stack] =~/e/); #too big is too big. dump_(" DIV by ZERO is a BIG problem") if ($@=~ /Illegal/); #honestly don't need to deal with div by 0 ($stack[$#stack] = 0) unless (defined $stack[$#stack]); $stack[$#stack] = 0 if $stack[$#stack] == -0; #wierd... but I have to do it. } return $stack[0]; } # the above ends selections of routines for expression() ###__________ sub go { my $loc = access_A(); my $iterations = access_A(0,'int'); unless ($iterations){ $ptr = $loc; return } my $temp = $ptr; $ptr = $loc; for (0 .. $iterations){ event() } $ptr = $temp; }
The Future
Iris x.x may include some changes such as a list for output. Maybe lists R and A will be merged into one list, making this a self modifying language.
It would be trivial to include an ELSE to Flow. Maybe some form of CASE statement.
In building the Fibonacci generator, the inability to use expressions as list references has made life less easy. Perhaps this should change.
Why 0.1 and not 0.0 or 1.0? Iris 0 was different from 0.1 in that Flow did not have a skipto bit. Instead, the Flow statement was to run once to find the end of the function call. It was a pain. The use of the skipto bit is in line with the philosophy of Iris. Once the condition loop is finished, the pointer is sent to a new location of code to start again. When will it deserve a 1 designation?