Amelia

From Esolang
Jump to navigation Jump to search

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

Gene structure
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,;
Function call MOD
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.


Function call MOV
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


expression contents (per codon)
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;
  }