Amelia
Amelia:
Allele Mediated Expressive List Interpretation Algorithms
Amelia's Monstrously Enervating List Interpretative Acts
Amelia's Manipilatively Esoteric Listy Instigative Activity
Amelia's Messy Esoteric Language Is Anguishing
Anti-Modular Enlightened Loopy Architecture
Overview
Amelia is the progeny of Iris. (Its namesake is the progeny of Iris's namesake as of March 22, 2013.) Its design philosophy mirrors Iris: no syntax errors, no premature exiting, and a simple structure. Programs are designed to be produced by random mutation and non-random selection.
Amelia, however, has important differences. There are only two function calls: move pointer & calculate expression. These calls are clearly separated (using a semi-colon in the current version).
With a clear separation between functions, it's much easier to try create programs via mutation and selection.
What it loses: Iris allowed for any element in the program to be modified by register values. Amelia only allows expression elements and where to put results to be modified by register values. Iris allows a snippet of code to be used several times as several different things (function call, value, etc.). Amelia's components are single function.
The program structure carries the genetic metaphore into its operation. A function is a "gene"; each component of which is a tripplet of values called a codon. Each value is called a base. The first codon defines the function call, the remaining codons are an expression which result is used as a truth test or gets assigned to a register. Each "level" (gene, codon, base) can also be modified when creating new programs. These modifications can vary depending on the level.
Syntax
The specifications of this language call for one or more sets of numbers. These sets are called genes. The numbers are comma delimited. The sets are semi-colon delimited. The program in its entirety is called a chromosome.
The instructions (genes) are executed through an instruction pointer that advances by one gene after each execution.
There are two types of genes. The first type (MOD) modifies the value in a given register. The second type (MOV) moves the intruction pointer to a new location.
A gene is read in tripplets (codons). A gene may have a few numbers left over after dividing up the codons. Those extras are discarded.
After the first tripplet in a gene is used to call MOV or MOD, the remaining tripplets are an expression. In the case of MOD, the result of the expression is assigned to a register. In the case of MOV, the expression is a truth value. If true, then the instruction pointer is moved to a new location on the chromosome. If there are fewer than three tripplets in the expression, the first tripplet is the expression result. If there isn't enough to have even one tripplet, then the expression result is 0.
Expressions are prefix (polish) notation.
If there are fewer than three codons in an expression, the first codon is parsed and returned as a value.
The chromosome loops perpetually, so the interpreter should terminate in a reasonable number of gene executions.
To exit a program, one may either execute an expression that divides by 0 or move the execution pointer to the current gene.
Comments are preceeded by a colon.
Syntax tables
Schematic | Example | Description | |
---|---|---|---|
Function Call | Codon | 24, | Calls MOD. This is an assignment statement. |
45, | The register that gets the new value. | ||
12, | Just states that the 45 is a register and not a reference to another register. | ||
Expression | Codon | 6, | |
7, | |||
8, | |||
… | … | ||
Codon | 0, | ||
3, | |||
4,; |
Value position | Value meaning | Note |
---|---|---|
n1 (number at position 1 of codon) | Function call. If n1 % 2 == 0 then call MOD | Even numbers always call MOD. Odd numbers call MOV. |
n2 (number at position 2 of codon) | Register to change according to attached expression | The implementation uses a single array for registers |
n3 (number at position 3 of codon) | if n3%2 == 0 then register[n2] = expression result
if n3%2 == 1 then register[register[n2]] = expression result |
There isn't much to say here. |
Value position | Value meaning | Notes |
---|---|---|
n1 | Function call. If n1 % 2 == 1 then call MOV | Even numbers always call MOD. Odd numbers call MOV. |
n2 | Move the pointer to Gene n2 | Self explanatory. |
n3 | How many function calls (genes) before going to the gene following the current one.
If this value is 0 then there is no returning to the following gene. |
when n3 > 0, the current location is saved, the pointer goes to new location (gene n2), and n3 calls are made before returning. When n3 == 0 we start over at the new location (gene n2)
Think of this as a GOTO or GOSUB statement if n3 == 0 or 1 respectively |
Value position | Value meaning | Notes |
---|---|---|
n1 | if #values -1 <= #operators then n3 is a value
if #values > 1/2 of total #operators and #values for the expression then n3 is an operator if n1%2 == 0 then n3 is a value if n1%2 == 1 then n3 is an operator |
Since the values and operators are (potentially) randomly built, there has to be a way to guarantee a legitimate expression to evaluate.
This decision set (and other bits) guarantees that proper expression. |
n2 | if n2%3 == 0 then n3 is a value or operator
if n2%3 == 1 then register[n3] is a value or operator if n2%3 == 2 then register[register[n3]] is a value or operator |
|
n3 | operator or value | When an operator for a MOD expression:
n3 % 6 == 0 then + 1 then - 2 then * 3 then / 4 then ** 5 then % When an operator for a MOV expression: n3 % 9 == 0 then == 1 then > 2 then < 3 then >= 4 then <= 5 then != 6 then and 7 then or 8 then not 9 then xor |
Examples
Fibonacci:
The output of the following code is 26 24 23 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711
The fourth number is the start of the sequence. The first number 26 refers to the register about to be written to. (Register 26 endes up never being written to.) The second and third numbers 24 and 23 refer to the registers which hold the previous two numbers in the sequence.
0,0,0, :assigns a value to register 0, 0,5,0,; :the value is 5 0,3,0, : register 3 0,0,0,; : gets a 0 0,4,0, : register 4 0,1,0,; : gets a 1 0,1,0, : register 1 gets 0,1,0, : 1 0,0,1, : register[0] 0,1,0,; : - : - register[0] 1 (prefix notation) 0,2,0, : register 2 gets 0,1,0, : 1 0,1,1, : register[1] 0,1,0,; : - : - register[1] 1 (prefix notation) 0,0,1, : location referenced by register [0] 0,1,2, : value in register[2] is a reference to 0,2,2, : another register (this changes during run) 0,0,0,; : + : this produces each value in the : Fibonacci sequence 0,0,0, : add 1 to register[0] 0,0,1, 0,1,0, 0,0,0,; 1,7,0, : go to gene 7 0,25,1,; : if register[25] has a value : (other than 0) : this is gene 7 (infinite loop) : this is one way to end the program 1,3,0, :go to gene 3 1,1,0,; :if (1) :this is how we iterate ################## : this following is alternate way to terminate a program 0,0,0, : (gene 9) assign a divide by zero 0,0,0, 0,0,0, 0,3,0,; : / 0 0 (illegal devide by 0 - :ends program and outputs)
Implementation
#!/usr/bin/perl #use warnings; use strict; use Data::Dumper; { #closure for death count my $still_alive = 1000; sub deathcount{ my $opt = shift; return $still_alive if $opt; $still_alive--; finish ("counted down\n") unless ($still_alive); } } { #closure for genome my $file = "sequence.ama"; my $string = get_file($file); my @C = get_chromasome($string); sub Access_C{ my $access_type = shift; #gene, codon, base, size my $ptr = shift; #which gene return map { [@$_] }@{$C[$ptr]} if $access_type eq "gene"; #gene return $#C if $access_type eq "size"; } } { #closure for register my @R = qw//; sub Access_R{ my $access_type = shift; my $register_location = shift; $register_location = abs $register_location; my $value = shift; $R[$register_location] = $value if $access_type eq "write"; return $R[$register_location] if $access_type eq "read"; } sub output{ for (@R){ print "$_ "; } } } wrapper(0); sub wrapper{ my $ptr = shift; #genome pointer my $i = 1 + deathcount("return");#iterator for top level call() while (1) { $ptr = call ($ptr, $i); #ptr is changed to call()'s return value if we #ever get to this point } } sub get_file{ my $infile = shift; my $string; open (my $fh, "<", $infile) or die "nopen $infile, $!"; while (<$fh>){ chomp; s/:.*//; #comments s/(\d)\s/$1,/g; #space to comma s/(\d);/$1,;/g; # forgottn comma before ; s/\s//g; #get rid of space s/,,+/,/g; #get rid of duplicate commas $string .= $_ } return $string; } sub finish{ my $message = shift; print "$message \nthe end. let's print output:\n"; output (); die } sub get_chromasome{ my $Genome = shift;#string my @C = map { [ map{ [ /(-?\d+\.?\d*),/g # the parens excise the commas ] } /-?\d+\.?\d*,-?\d+\.?\d*,-?\d+\.?\d*,/g ] } split /;/, $Genome; for (@C){ if ($#$_ > 1) { unless ($#$_ % 2){ splice @$_, -1, 1 } } } @C # @C[gene[codon[base,base,base]]] } sub call{ my $ptr = shift; my $i = shift; my $alt_ptr = -1; for (1 .. $i){ deathcount(); #count down to process death my @g = Access_C("gene",$ptr); my $call = shift $g[0]; #1st base of 1st codon my $modcall = $call % 2; ($call % 2)?($alt_ptr = MOV(\@g,$ptr)):(MOD(\@g)); (return $alt_ptr) if ($alt_ptr >= 0); ($ptr == Access_C("size")) ? $ptr = 0 : $ptr++;#circular chromasome } return $alt_ptr; #if alt_ptr >= 0 #alt_ptr is used to reset ptr permanently. #the recursive calls to call() are unwound in favor of alt_ptr } sub MOD{ my $g = shift; #gene my ($loc,$reftype) = @{(shift @$g)}; my @ops = qw/+ - * \/ ** %/; $loc = abs($loc); $loc = Access_R("read",$loc) if ($reftype%2); $loc = 0 unless $loc; Access_R("write",$loc,expression_processor($g,\@ops)); } sub MOV{ my $g = shift; my $ptr = shift; #needed to check for infi-loop my @ops = qw/== > < >= <= != and or not xor/; my ($loc,$i) = @{(shift @$g)}; $loc = abs $loc; $loc = 0 if $loc > Access_C("size"); $i = abs $i; if (expression_processor($g, \@ops)){ (finish ("pointer collision")) if ($loc == $ptr); return $loc unless $i; return call($loc,$i); } return -1 } sub filter_v_or_o{ my ($v,$o,$total_bits,$orig) = @_; (return 0) if ($v-1 <= $o); (return 1) if ($v >= ($total_bits / 2)); return ($orig % 2) } sub expression_processor{ #build a polish notation expression from triplets. my $g = shift; #gene my $ops = shift; #operators my ($v,$o) = 0; #init counting vars my $total_bits = $#$g; my @expression; return 0 unless @$g; while (@$g){ my ($v_or_o, $data, $ref_type) = @{(shift @$g)}; $v_or_o = filter_v_or_o($v,$o,$total_bits,$v_or_o); $ref_type = ($ref_type % 3); ($data = Access_R("read",$data)) if ($ref_type == 1); ($data = Access_R("read",Access_R("read",$data))) if ($ref_type == 2); $data = 0 unless $data; ($data = $ops->[$data % ($#$ops +1)]) if ($v_or_o); ($v_or_o)?($o++):($v++); unshift @expression, $data; } return solve(\@expression) } sub solve{ my $exp = shift; my $a = shift @$exp; ($a=~/\d/)? (return $a) : ($a = eval(solve($exp) . " $a " . solve($exp))); (finish("termination with n/0\n")) if ($@=~ /Illegal/); return $a; }