aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2024-02-29 01:50:44 +0100
committerPaul Oliver <contact@pauloliver.dev>2024-02-29 01:50:44 +0100
commit2dc9d118efb64de6ea54a5a9eb4474f8e5ef3145 (patch)
tree74039957b10390da4875d676303a781bd0792e45
Initial commit
-rw-r--r--.gitignore10
-rw-r--r--Makefile32
-rw-r--r--README.md111
-rw-r--r--build/.keep0
-rw-r--r--include/evolver.h17
-rw-r--r--include/instset.h93
-rw-r--r--include/memory.h30
-rw-r--r--include/process.h40
-rw-r--r--include/salis.h22
-rw-r--r--include/types.h31
-rw-r--r--lib/.keep0
-rw-r--r--src/evolver.c125
-rw-r--r--src/instset.c56
-rw-r--r--src/memory.c227
-rw-r--r--src/process.c1071
-rw-r--r--src/salis.c98
-rw-r--r--tsalis/Makefile30
-rw-r--r--tsalis/README.md76
-rw-r--r--tsalis/bin/.keep0
-rw-r--r--tsalis/build/.keep0
-rw-r--r--tsalis/include/handler.h6
-rw-r--r--tsalis/include/printer.h35
-rw-r--r--tsalis/include/tsalis.h11
-rw-r--r--tsalis/src/handler.c317
-rw-r--r--tsalis/src/printer.c749
-rw-r--r--tsalis/src/tsalis.c118
26 files changed, 3305 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b7b88bd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+*
+!*.*
+!*/
+!Makefile
+!*/Makefile
+*.a
+*.d
+*.o
+*.sim
+*.exe
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e8f873a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,32 @@
+AR := ar
+CC := gcc
+LIB := lib/libsalis.a
+
+SOURCES := $(wildcard src/*.c)
+OBJECTS := $(patsubst src/%.c,build/%.o,$(SOURCES))
+DEPS := $(patsubst %.o,%.d,$(OBJECTS))
+
+LFLAGS := rs
+
+# uncomment for debug
+# OFLAGS := -ggdb
+
+# uncomment for release
+OFLAGS := -O3 -DNDEBUG
+
+CFLAGS := -Iinclude -c $(OFLAGS) -MMD -Wall -Wextra -std=c89 -pedantic-errors \
+ -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition
+
+all: $(OBJECTS)
+ $(AR) $(LFLAGS) $(LIB) $(OBJECTS)
+ $(MAKE) -C tsalis
+
+-include $(DEPS)
+
+$(OBJECTS): $(patsubst build/%.o,src/%.c,$@)
+ $(CC) $(CFLAGS) $(patsubst build/%.o,src/%.c,$@) -o $@
+
+clean:
+ -rm build/*
+ -rm $(LIB)
+ $(MAKE) clean -C tsalis
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6f90781
--- /dev/null
+++ b/README.md
@@ -0,0 +1,111 @@
+# SALIS: A-Life Simulator
+
+## Overview
+*SALIS* is my newest artificial life project. Mainly a re-take on Tom Ray's
+TIERRA simulation, but with my very own set of tweaks. Having a grasp on TIERRA
+will make understanding this simulation a lot easier.
+
+- [video about TIERRA](https://www.youtube.com/watch?v=Wl5rRGVD0QI)
+- [read about TIERRA](http://life.ou.edu/pubs/doc/index.html#What)
+
+For those that already know TIERRA, the main differences between it and SALIS
+are:
+- the replacement of templates with key-lock instruction pairs
+- the addition of a SEEKER POINTER to all organisms
+
+The seeker pointer is an attempt to bring extra spatial and temporal coherence
+to the simulation. Allocation, reads and writes will take more time when done
+between addresses that are far away, as a consequence of the SP having to
+travel those distances at a speed of 1 byte per update. In other words, in
+SALIS information can't travel faster than 1 byte per update (SALIS' speed of
+light, if you will).
+
+To watch an introductory video about *SALIS*
+[go here.](https://www.youtube.com/watch?v=jCFmOCvy6po)
+
+### Details
+- *SALIS* is an API, so an UI must be written to communicate with it
+- *SALIS* is written in C
+- *SALIS* must be compiled as a library (e.g. *libsalis.a*)
+
+### Organisms consist of
+- One or two associated memory blocks
+- One instruction pointer
+- One seeker pointer
+- Four registers
+- A stack of configurable size (default is 8)
+
+### Queue
+- Newborn organisms are placed on top of the queue
+- Organisms are killed at the bottom of the queue
+
+### Instruction set
+*SALIS*' organisms read a simple language similar to ASM. This language
+consists of 64 instructions, each with an associated name and symbol.
+Whenever an organism performs an invalid instruction it's considered a *fault*.
+To preserve robustness, faulty instructions are simply ignored by the
+organisms and their IPs are incremented to the next address.
+
+#### Faults
+- IP or SP reaching ends of memory
+- Perform a search or attempt a jump without a following key
+- Writing to an allocated (but not owned) or invalid address
+- Reading (loading) from an invalid address
+- SP on address non-adjacent to child memory block while allocating
+- Swapping, freeing or splitting when not owning 2 memory blocks
+- Dividing by zero
+
+#### Instruction set table
+|Name |Sym. |Val. |Description |
+|:--------|:-----|:-----|---------------------------------------------------------------------:|
+|NOOP | |00 |No operation. Does nothing. |
+|NOP0-3 |0-3 |01-04 |No operation. Modifies registers to be used (r0x to r3x). |
+|JMPB-F\* |() |05-06 |Jump to lock matching following key. |
+|ADRB-F\* |[] |07-08 |Search for lock matching following key and store on r[0]. |
+|IFNZ |? |09 |If r[0] is not zero, execute following instruction. Skip otherwise. |
+|ALLB-F\* |{} |10-11 |Allocate block of size stored on r[0]. Store its address on r[0]. |
+|BSWP\* |% |12 |Swap parent and child memory blocks. |
+|BCLR\* |\| |13 |Free child memory block. |
+|SPLT\* |$ |14 |Split. Child memory block becomes new organism. |
+|ADDN |+ |15 |Add (r[0] = r[1] + r[2]). |
+|SUBN |- |16 |Subtract (r[0] = r[1] - r[2]). |
+|MULN |\* |17 |Multiply (r[0] = r[1] \* r[2]). |
+|DIVN\* |/ |18 |Divide (r[0] = r[1] / r[2]). Faults if divisor is zero. |
+|INCN |^ |19 |Increment (r[0]++). |
+|DECN |v |20 |Decrement (r[0]--). |
+|NOTN |! |21 |Not (r[0] = !r[0]). |
+|SHFL |< |22 |Shift left by 1 (r[0] << 1). |
+|SHFR |> |23 |Shift right by 1 (r[0] >> 1). |
+|ZERO |z |24 |Put zero (r[0] = 0). |
+|UNIT |u |25 |Put one (r[0] = 1). |
+|PSHN |# |26 |Push r[0] to stack. |
+|POPN |~ |27 |Pop from stack into r[0]. |
+|LOAD\* |. |28 |Load instruction at address pointed by r[0] into r[1]. |
+|WRTE\* |: |29 |Write instruction on r[1] to address pointed by r[0]. |
+|DUPL |" |30 |Duplicate value on r[0] into r[1]. |
+|SWAP |x |31 |Swap values on r[0] and r[1]. |
+|KEYA-P |a-p |32-47 |Keys. |
+|LOKA-P |A-P |48-63 |Locks. |
+
+Instructions that may fault are marked with an asterisk (\*) on the table above.
+
+Instructions that modify values on registers may be followed by
+up to 3 register modifiers (r[0], r[1] and r[2]). By default, all registers
+are set to r0x.
+
+|Sample |r[0] |r[1] |r[2] |Meaning |
+|:-------|:----|:----|:----|-----------------------------------------:|
+|+123 |r1x |r2x |r3x |r1x = r2x + r3x |
+|-11 |r1x |r1x |r0x |r1x = r1x - r0x |
+|\* |r0x |r0x |r0x |r0x = r0x \* r0x |
+|!2 |r2x |--- |--- |r2x = !r2x |
+|x01 |r0x |r1x |--- |swap r0x and r1x |
+|]a1 |r1x |--- |--- |r1x = address of closest following LOKA |
+|[b |r0x |--- |--- |r0x = address of closest previous LOKB |
+
+## Building instructions
+You'll need nothing but a C compiler (C89). A sample makefile (Makefile)
+is provided for GNU Make. Just run `make` inside this directory and the salis
+library should compile as well as the tsalis executable. Feel free to edit
+the makefile as needed. Code should compile easily on all platforms and on all
+C89 compliant compilers. If you run into any difficulties, please let me know!
diff --git a/build/.keep b/build/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build/.keep
diff --git a/include/evolver.h b/include/evolver.h
new file mode 100644
index 0000000..15a4f64
--- /dev/null
+++ b/include/evolver.h
@@ -0,0 +1,17 @@
+#ifndef SALIS_EVOLVER_H
+#define SALIS_EVOLVER_H
+
+void se_init (void);
+void se_quit (void);
+void se_load (FILE *file);
+void se_save (FILE *file);
+
+sbool se_isInit (void);
+sword se_getLastAddress (void);
+sbyte se_getLastInst (void);
+sword se_getState (sword eidx);
+void se_setState (sword eidx, sword state);
+
+void se_cycle (void);
+
+#endif
diff --git a/include/instset.h b/include/instset.h
new file mode 100644
index 0000000..e819ff3
--- /dev/null
+++ b/include/instset.h
@@ -0,0 +1,93 @@
+#ifndef SALIS_INSTSET_H
+#define SALIS_INSTSET_H
+
+#define SINST_COUNT 64
+
+#define SINST_LIST \
+ SINST(SNOOP, ' ') \
+ SINST(SNOP0, '0') \
+ SINST(SNOP1, '1') \
+ SINST(SNOP2, '2') \
+ SINST(SNOP3, '3') \
+ \
+ SINST(SJMPB, '(') \
+ SINST(SJMPF, ')') \
+ SINST(SADRB, '[') \
+ SINST(SADRF, ']') \
+ SINST(SIFNZ, '?') \
+ \
+ SINST(SALLB, '{') \
+ SINST(SALLF, '}') \
+ SINST(SBSWP, '%') \
+ SINST(SBCLR, '|') \
+ SINST(SSPLT, '$') \
+ \
+ SINST(SADDN, '+') \
+ SINST(SSUBN, '-') \
+ SINST(SMULN, '*') \
+ SINST(SDIVN, '/') \
+ SINST(SINCN, '^') \
+ SINST(SDECN, 'v') \
+ SINST(SNOTN, '!') \
+ SINST(SSHFL, '<') \
+ SINST(SSHFR, '>') \
+ SINST(SZERO, 'z') \
+ SINST(SUNIT, 'u') \
+ \
+ SINST(SPSHN, '#') \
+ SINST(SPOPN, '~') \
+ \
+ SINST(SLOAD, '.') \
+ SINST(SWRTE, ':') \
+ SINST(SDUPL, '"') \
+ SINST(SSWAP, 'x') \
+ \
+ SINST(SKEYA, 'a') \
+ SINST(SKEYB, 'b') \
+ SINST(SKEYC, 'c') \
+ SINST(SKEYD, 'd') \
+ SINST(SKEYE, 'e') \
+ SINST(SKEYF, 'f') \
+ SINST(SKEYG, 'g') \
+ SINST(SKEYH, 'h') \
+ SINST(SKEYI, 'i') \
+ SINST(SKEYJ, 'j') \
+ SINST(SKEYK, 'k') \
+ SINST(SKEYL, 'l') \
+ SINST(SKEYM, 'm') \
+ SINST(SKEYN, 'n') \
+ SINST(SKEYO, 'o') \
+ SINST(SKEYP, 'p') \
+ \
+ SINST(SLOKA, 'A') \
+ SINST(SLOKB, 'B') \
+ SINST(SLOKC, 'C') \
+ SINST(SLOKD, 'D') \
+ SINST(SLOKE, 'E') \
+ SINST(SLOKF, 'F') \
+ SINST(SLOKG, 'G') \
+ SINST(SLOKH, 'H') \
+ SINST(SLOKI, 'I') \
+ SINST(SLOKJ, 'J') \
+ SINST(SLOKK, 'K') \
+ SINST(SLOKL, 'L') \
+ SINST(SLOKM, 'M') \
+ SINST(SLOKN, 'N') \
+ SINST(SLOKO, 'O') \
+ SILST(SLOKP, 'P')
+
+enum sinst {
+#define SINST(name, symb) name,
+#define SILST(name, symb) name
+ SINST_LIST
+#undef SINST
+#undef SILST
+};
+
+sbool si_isInst (sbyte inst);
+sbool si_isMod (sbyte inst);
+sbool si_isKey (sbyte inst);
+sbool si_isLock (sbyte inst);
+sbool si_keyLockMatch (sbyte key, sbyte lock);
+
+#endif
diff --git a/include/memory.h b/include/memory.h
new file mode 100644
index 0000000..a36a2ff
--- /dev/null
+++ b/include/memory.h
@@ -0,0 +1,30 @@
+#ifndef SALIS_MEMORY_H
+#define SALIS_MEMORY_H
+
+void sm_init (sword order);
+void sm_quit (void);
+void sm_load (FILE *file);
+void sm_save (FILE *file);
+
+sbool sm_isInit (void);
+sword sm_getOrder (void);
+sword sm_getSize (void);
+sword sm_getMemBlockCount (void);
+sword sm_getAllocated (void);
+sword sm_getCap (void);
+
+sbool sm_isOverCap (void);
+sbool sm_isValidAt (sword addr);
+sbool sm_isMemBlockStartAt (sword addr);
+sbool sm_isAllocatedAt (sword addr);
+
+void sm_setMemBlockStartAt (sword addr);
+void sm_unsetMemBlockStartAt (sword addr);
+void sm_allocateAt (sword addr);
+void sm_freeAt (sword addr);
+sbyte sm_getInstAt (sword addr);
+void sm_setInstAt (sword addr, sbyte inst);
+sbyte sm_getByteAt (sword addr);
+void sm_setByteAt (sword addr, sbyte byte);
+
+#endif
diff --git a/include/process.h b/include/process.h
new file mode 100644
index 0000000..35e1fdf
--- /dev/null
+++ b/include/process.h
@@ -0,0 +1,40 @@
+#ifndef SALIS_PROCESS_H
+#define SALIS_PROCESS_H
+
+#define SPROC_REG_COUNT 4
+#define SPROC_STACK_SIZE 8
+#define SPROC_ELEM_COUNT (6 + SPROC_REG_COUNT + SPROC_STACK_SIZE)
+
+typedef struct {
+ sword mb1a;
+ sword mb1s;
+ sword mb2a;
+ sword mb2s;
+
+ sword ip;
+ sword sp;
+
+ sword regs [SPROC_REG_COUNT];
+ sword stack [SPROC_STACK_SIZE];
+} SProc;
+
+void sp_init (void);
+void sp_quit (void);
+void sp_load (FILE *file);
+void sp_save (FILE *file);
+
+sbool sp_isInit (void);
+sword sp_getCount (void);
+sword sp_getCap (void);
+sword sp_getFirst (void);
+sword sp_getLast (void);
+
+sbool sp_isFree (sword pidx);
+SProc sp_getProc (sword pidx);
+void sp_setProc (sword pidx, SProc proc);
+
+void sp_create (sword addr, sword size);
+void sp_kill (void);
+void sp_cycle (void);
+
+#endif
diff --git a/include/salis.h b/include/salis.h
new file mode 100644
index 0000000..a9e2d3d
--- /dev/null
+++ b/include/salis.h
@@ -0,0 +1,22 @@
+#ifndef SALIS_H
+#define SALIS_H
+
+#include <stdio.h>
+#include <types.h>
+#include <instset.h>
+#include <memory.h>
+#include <process.h>
+#include <evolver.h>
+
+void s_init (sword order);
+void s_quit (void);
+void s_load (const char *fileName);
+void s_save (const char *fileName);
+
+sbool s_isInit (void);
+sword s_getCycle (void);
+sword s_getEpoch (void);
+
+void s_cycle (void);
+
+#endif
diff --git a/include/types.h b/include/types.h
new file mode 100644
index 0000000..3c0c3ff
--- /dev/null
+++ b/include/types.h
@@ -0,0 +1,31 @@
+#ifndef SALIS_TYPES_H
+#define SALIS_TYPES_H
+
+#include <limits.h>
+
+#define SWORD_MAX (0xffffffff)
+#define SBYTE_MAX (0xff)
+
+#if USHRT_MAX == SWORD_MAX
+ typedef unsigned short sword;
+#elif UINT_MAX == SWORD_MAX
+ typedef unsigned int sword;
+#elif ULONG_MAX == SWORD_MAX
+ typedef unsigned long sword;
+#elif
+ #error "Cannot define 32 bit unsigned int (sword)"
+#endif
+
+#if UCHAR_MAX == SBYTE_MAX
+ typedef unsigned char sbyte;
+#elif
+ #error "Cannot define 8 bit unsigned int (sbyte)"
+#endif
+
+typedef int sbool;
+
+#define SFALSE (0)
+#define STRUE (1)
+#define SNULL ((sword)-1)
+
+#endif
diff --git a/lib/.keep b/lib/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/.keep
diff --git a/src/evolver.c b/src/evolver.c
new file mode 100644
index 0000000..ba043a3
--- /dev/null
+++ b/src/evolver.c
@@ -0,0 +1,125 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "types.h"
+#include "instset.h"
+#include "memory.h"
+#include "evolver.h"
+
+sbool g_isInit;
+sword g_lastAddress;
+sbyte g_lastInst;
+sword g_state[4];
+
+void
+se_init(void)
+{
+ assert(!g_isInit);
+ srand((unsigned)time(NULL));
+ g_state[0] = rand();
+ g_state[1] = rand();
+ g_state[2] = rand();
+ g_state[3] = rand();
+ g_isInit = STRUE;
+}
+
+void
+se_quit(void)
+{
+ assert(g_isInit);
+ g_isInit = SFALSE;
+ g_lastAddress = 0;
+ g_lastInst = 0;
+ g_state[0] = 0;
+ g_state[1] = 0;
+ g_state[2] = 0;
+ g_state[3] = 0;
+}
+
+void
+se_load(FILE *file)
+{
+ assert(!g_isInit);
+ assert(file);
+ fread(&g_isInit, sizeof(sbool), 1, file);
+ fread(&g_lastAddress, sizeof(sword), 1, file);
+ fread(&g_lastInst, sizeof(sbyte), 1, file);
+ fread(g_state, sizeof(sword), 4, file);
+}
+
+void
+se_save(FILE *file)
+{
+ assert(g_isInit);
+ assert(file);
+ fwrite(&g_isInit, sizeof(sbool), 1, file);
+ fwrite(&g_lastAddress, sizeof(sword), 1, file);
+ fwrite(&g_lastInst, sizeof(sbyte), 1, file);
+ fwrite(g_state, sizeof(sword), 4, file);
+}
+
+sbool
+se_isInit(void)
+{
+ return g_isInit;
+}
+
+sword
+se_getLastAddress(void)
+{
+ return g_lastAddress;
+}
+
+sbyte
+se_getLastInst(void)
+{
+ assert(si_isInst(g_lastInst));
+ return g_lastInst;
+}
+
+sword
+se_getState(sword eidx)
+{
+ assert(eidx < 4);
+ return g_state[eidx];
+}
+
+void
+se_setState(sword eidx, sword state)
+{
+ assert(g_isInit);
+ assert(eidx < 4);
+ g_state[eidx] = state;
+}
+
+static sword
+generateRandomNumber(void)
+{
+ sword s;
+ sword t;
+ assert(g_isInit);
+ t = g_state[3];
+ t ^= t << 11;
+ t ^= t >> 8;
+ g_state[3] = g_state[2];
+ g_state[2] = g_state[1];
+ g_state[1] = g_state[0];
+ s = g_state[0];
+ t ^= s;
+ t ^= s >> 19;
+ g_state[0] = t;
+ return t;
+}
+
+void
+se_cycle(void)
+{
+ assert(g_isInit);
+ g_lastAddress = generateRandomNumber();
+ g_lastInst = (sbyte)(generateRandomNumber() % SINST_COUNT);
+
+ if (sm_isValidAt(g_lastAddress)) {
+ sm_setInstAt(g_lastAddress, (sbyte)g_lastInst);
+ }
+}
diff --git a/src/instset.c b/src/instset.c
new file mode 100644
index 0000000..1333ffb
--- /dev/null
+++ b/src/instset.c
@@ -0,0 +1,56 @@
+#include <assert.h>
+#include "types.h"
+#include "instset.h"
+
+sbool
+si_isInst(sbyte inst)
+{
+ return inst < SINST_COUNT;
+}
+
+static sbool
+isBetween(sbyte inst, sbyte lo, sbyte hi)
+{
+ assert(si_isInst(inst));
+ assert(lo < SINST_COUNT);
+ assert(hi < SINST_COUNT);
+
+ if (inst < lo) {
+ return SFALSE;
+ }
+
+ if (inst > hi) {
+ return SFALSE;
+ }
+
+ return STRUE;
+}
+
+sbool
+si_isMod(sbyte inst)
+{
+ assert(si_isInst(inst));
+ return isBetween(inst, SNOP0, SNOP3);
+}
+
+sbool
+si_isKey(sbyte inst)
+{
+ assert(si_isInst(inst));
+ return isBetween(inst, SKEYA, SKEYP);
+}
+
+sbool
+si_isLock(sbyte inst)
+{
+ assert(si_isInst(inst));
+ return isBetween(inst, SLOKA, SLOKP);
+}
+
+sbool
+si_keyLockMatch(sbyte key, sbyte lock)
+{
+ assert(si_isKey(key));
+ assert(si_isInst(lock));
+ return (key - SKEYA) == (lock - SLOKA);
+}
diff --git a/src/memory.c b/src/memory.c
new file mode 100644
index 0000000..4befd4f
--- /dev/null
+++ b/src/memory.c
@@ -0,0 +1,227 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "types.h"
+#include "instset.h"
+#include "memory.h"
+
+#define MEM_BLOCK_START_FLAG (0x80)
+#define ALLOCATED_FLAG (0x40)
+#define INSTRUCTION_MASK (0x3f)
+
+static sbool g_isInit;
+static sword g_order;
+static sword g_size;
+static sword g_memBlockCount;
+static sword g_allocated;
+static sword g_cap;
+static sbyte *g_data;
+
+void
+sm_init(sword order)
+{
+ assert(!g_isInit);
+ assert(INSTRUCTION_MASK == SINST_COUNT - 1);
+ g_isInit = STRUE;
+ g_order = order;
+ g_size = 1 << g_order;
+ g_cap = g_size / 2;
+ g_data = calloc(g_size, sizeof(sbyte));
+ assert(g_data);
+}
+
+void
+sm_quit(void)
+{
+ assert(g_isInit);
+ free(g_data);
+ g_isInit = SFALSE;
+ g_order = 0;
+ g_size = 0;
+ g_memBlockCount = 0;
+ g_allocated = 0;
+ g_cap = 0;
+ g_data = NULL;
+}
+
+void
+sm_load(FILE *file)
+{
+ assert(!g_isInit);
+ assert(file);
+ fread(&g_isInit, sizeof(sbool), 1, file);
+ fread(&g_order, sizeof(sword), 1, file);
+ fread(&g_size, sizeof(sword), 1, file);
+ fread(&g_memBlockCount, sizeof(sword), 1, file);
+ fread(&g_allocated, sizeof(sword), 1, file);
+ fread(&g_cap, sizeof(sword), 1, file);
+ g_data = calloc(g_size, sizeof(sbyte));
+ assert(g_data);
+ fread(g_data, sizeof(sbyte), g_size, file);
+}
+
+void
+sm_save(FILE *file)
+{
+ assert(g_isInit);
+ assert(file);
+ fwrite(&g_isInit, sizeof(sbool), 1, file);
+ fwrite(&g_order, sizeof(sword), 1, file);
+ fwrite(&g_size, sizeof(sword), 1, file);
+ fwrite(&g_memBlockCount, sizeof(sword), 1, file);
+ fwrite(&g_allocated, sizeof(sword), 1, file);
+ fwrite(&g_cap, sizeof(sword), 1, file);
+ fwrite(g_data, sizeof(sbyte), g_size, file);
+}
+
+sbool
+sm_isInit(void)
+{
+ return g_isInit;
+}
+
+sword
+sm_getOrder(void)
+{
+ return g_order;
+}
+
+sword
+sm_getSize(void)
+{
+ return g_size;
+}
+
+sword
+sm_getMemBlockCount(void)
+{
+ return g_memBlockCount;
+}
+
+sword
+sm_getAllocated(void)
+{
+ return g_allocated;
+}
+
+sword
+sm_getCap(void)
+{
+ return g_cap;
+}
+
+sbool
+sm_isOverCap(void)
+{
+ assert(g_isInit);
+ return g_allocated > g_cap;
+}
+
+sbool
+sm_isValidAt(sword addr)
+{
+ assert(g_isInit);
+ return addr < g_size;
+}
+
+sbool
+sm_isMemBlockStartAt(sword addr)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ return !!(g_data[addr] & MEM_BLOCK_START_FLAG);
+}
+
+sbool
+sm_isAllocatedAt(sword addr)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ return !!(g_data[addr] & ALLOCATED_FLAG);
+}
+
+void
+sm_setMemBlockStartAt(sword addr)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ assert(!sm_isMemBlockStartAt(addr));
+ g_data[addr] ^= MEM_BLOCK_START_FLAG;
+ g_memBlockCount++;
+ assert(sm_isMemBlockStartAt(addr));
+ assert(g_memBlockCount);
+ assert(g_memBlockCount <= g_size);
+}
+
+void
+sm_unsetMemBlockStartAt(sword addr)
+{
+ assert(g_isInit);
+ assert(g_memBlockCount);
+ assert(sm_isValidAt(addr));
+ assert(sm_isMemBlockStartAt(addr));
+ g_data[addr] ^= MEM_BLOCK_START_FLAG;
+ g_memBlockCount--;
+ assert(!sm_isMemBlockStartAt(addr));
+ assert(g_memBlockCount <= g_size);
+}
+
+void
+sm_allocateAt(sword addr)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ assert(!sm_isAllocatedAt(addr));
+ g_data[addr] ^= ALLOCATED_FLAG;
+ g_allocated++;
+ assert(sm_isAllocatedAt(addr));
+ assert(g_allocated);
+ assert(g_allocated <= g_size);
+}
+
+void
+sm_freeAt(sword addr)
+{
+ assert(g_isInit);
+ assert(g_allocated);
+ assert(sm_isValidAt(addr));
+ assert(sm_isAllocatedAt(addr));
+ g_data[addr] ^= ALLOCATED_FLAG;
+ g_allocated--;
+ assert(!sm_isAllocatedAt(addr));
+ assert(g_allocated <= g_size);
+}
+
+sbyte
+sm_getInstAt(sword addr)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ return g_data[addr] & INSTRUCTION_MASK;
+}
+
+void
+sm_setInstAt(sword addr, sbyte inst)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ assert(si_isInst(inst));
+ g_data[addr] &= (MEM_BLOCK_START_FLAG | ALLOCATED_FLAG);
+ g_data[addr] |= inst;
+}
+
+sbyte
+sm_getByteAt(sword addr)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ return g_data[addr];
+}
+
+void
+sm_setByteAt(sword addr, sbyte byte)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ g_data[addr] = byte;
+}
diff --git a/src/process.c b/src/process.c
new file mode 100644
index 0000000..e3d3c18
--- /dev/null
+++ b/src/process.c
@@ -0,0 +1,1071 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "types.h"
+#include "instset.h"
+#include "memory.h"
+#include "process.h"
+
+static sbool g_isInit;
+static sword g_count;
+static sword g_cap;
+static sword g_first;
+static sword g_last;
+static SProc *g_procs;
+
+void
+sp_init(void)
+{
+ assert(!g_isInit);
+ g_isInit = STRUE;
+ g_cap = 1;
+ g_first = SNULL;
+ g_last = SNULL;
+ g_procs = calloc(g_cap, sizeof(SProc));
+ assert(g_procs);
+}
+
+void
+sp_quit(void)
+{
+ assert(g_isInit);
+ free(g_procs);
+ g_isInit = SFALSE;
+ g_count = 0;
+ g_cap = 0;
+ g_first = 0;
+ g_last = 0;
+ g_procs = NULL;
+}
+
+void
+sp_load(FILE *file)
+{
+ assert(!g_isInit);
+ assert(file);
+ fread(&g_isInit, sizeof(sbool), 1, file);
+ fread(&g_count, sizeof(sword), 1, file);
+ fread(&g_cap, sizeof(sword), 1, file);
+ fread(&g_first, sizeof(sword), 1, file);
+ fread(&g_last, sizeof(sword), 1, file);
+ g_procs = calloc(g_cap, sizeof(SProc));
+ assert(g_procs);
+ fread(g_procs, sizeof(SProc), g_cap, file);
+}
+
+void
+sp_save(FILE *file)
+{
+assert(g_isInit);
+assert(file);
+fwrite(&g_isInit, sizeof(sbool), 1, file);
+fwrite(&g_count, sizeof(sword), 1, file);
+fwrite(&g_cap, sizeof(sword), 1, file);
+fwrite(&g_first, sizeof(sword), 1, file);
+fwrite(&g_last, sizeof(sword), 1, file);
+fwrite(g_procs, sizeof(SProc), g_cap, file);
+}
+
+sbool
+sp_isInit(void)
+{
+ return g_isInit;
+}
+
+sword
+sp_getCount(void)
+{
+ return g_count;
+}
+
+sword
+sp_getCap(void)
+{
+ return g_cap;
+}
+
+sword
+sp_getFirst(void)
+{
+ return g_first;
+}
+
+sword
+sp_getLast(void)
+{
+ return g_last;
+}
+
+sbool
+sp_isFree(sword pidx)
+{
+ sbool isFree;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ isFree = !(g_procs[pidx].mb1s);
+#ifndef NDEBUG
+
+ if (isFree) {
+ assert(!g_procs[pidx].mb1a);
+ assert(!g_procs[pidx].mb2a);
+ assert(!g_procs[pidx].mb2s);
+ }
+
+#endif
+ return isFree;
+}
+
+SProc
+sp_getProc(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ return g_procs[pidx];
+}
+
+void
+sp_setProc(sword pidx, SProc proc)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ g_procs[pidx] = proc;
+}
+
+static void
+reallocQueue(sword queueLock)
+{
+ sword newCap;
+ SProc *newQueue;
+ sword fidx;
+ sword bidx;
+ assert(g_isInit);
+ assert(g_count == g_cap);
+ assert(queueLock < g_cap);
+ newCap = g_cap * 2;
+ newQueue = calloc(newCap, sizeof(SProc));
+ assert(newQueue);
+ fidx = queueLock;
+ bidx = (queueLock - 1) % newCap;
+
+ /* copy all forward from lock */
+ while (STRUE) {
+ sword oldIdx = fidx % g_cap;
+ memcpy(&newQueue[fidx], &g_procs[oldIdx], sizeof(SProc));
+
+ if (oldIdx == g_last) {
+ g_last = fidx;
+ break;
+ } else {
+ fidx++;
+ }
+ }
+
+ if (queueLock != g_first) {
+ /* copy all backward from lock */
+ while (STRUE) {
+ sword oldIdx = bidx % g_cap;
+ memcpy(&newQueue[bidx], &g_procs[oldIdx], sizeof(SProc));
+
+ if (oldIdx == g_first) {
+ g_first = bidx;
+ break;
+ } else {
+ bidx--;
+ bidx %= newCap;
+ }
+ }
+ }
+
+ free(g_procs);
+ g_cap = newCap;
+ g_procs = newQueue;
+}
+
+static sword
+getNewProc(sword queueLock)
+{
+ assert(g_isInit);
+
+ if (g_count == g_cap) {
+ reallocQueue(queueLock);
+ }
+
+ g_count++;
+
+ if (g_count == 1) {
+ g_first = 0;
+ g_last = 0;
+ return 0;
+ } else {
+ g_last++;
+ g_last %= g_cap;
+ return g_last;
+ }
+}
+
+static void
+create(sword addr, sword size, sword queueLock, sbool allocate)
+{
+ sword pidx;
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ assert(sm_isValidAt(addr + size - 1));
+
+ if (allocate) {
+ sword offset;
+
+ for (offset = 0; offset < size; offset++) {
+ sword naddr = addr + offset;
+ assert(!sm_isAllocatedAt(naddr));
+ assert(!sm_isMemBlockStartAt(naddr));
+ sm_allocateAt(naddr);
+ }
+
+ sm_setMemBlockStartAt(addr);
+ }
+
+ pidx = getNewProc(queueLock);
+ g_procs[pidx].mb1a = addr;
+ g_procs[pidx].mb1s = size;
+ g_procs[pidx].ip = addr;
+ g_procs[pidx].sp = addr;
+}
+
+void
+sp_create(sword addr, sword size)
+{
+ assert(g_isInit);
+ assert(sm_isValidAt(addr));
+ assert(sm_isValidAt(addr + size - 1));
+ create(addr, size, 0, STRUE);
+}
+
+static void
+freeMemBlock(sword addr, sword size)
+{
+ sword offset;
+ assert(sm_isValidAt(addr));
+ assert(sm_isValidAt(addr + size - 1));
+ assert(size);
+ assert(sm_isMemBlockStartAt(addr));
+ sm_unsetMemBlockStartAt(addr);
+
+ for (offset = 0; offset < size; offset++) {
+ sword oaddr = addr + offset;
+ assert(sm_isValidAt(oaddr));
+ assert(sm_isAllocatedAt(oaddr));
+ assert(!sm_isMemBlockStartAt(oaddr));
+ sm_freeAt(oaddr);
+ }
+}
+
+static void
+freeMemOwnedBy(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ freeMemBlock(g_procs[pidx].mb1a, g_procs[pidx].mb1s);
+
+ if (g_procs[pidx].mb2s) {
+ assert(g_procs[pidx].mb1a != g_procs[pidx].mb2a);
+ freeMemBlock(g_procs[pidx].mb2a, g_procs[pidx].mb2s);
+ }
+}
+
+void
+sp_kill(void)
+{
+ assert(g_isInit);
+ assert(g_count);
+ assert(g_first != SNULL);
+ assert(g_last != SNULL);
+ assert(!sp_isFree(g_first));
+ freeMemOwnedBy(g_first);
+ memset(&g_procs[g_first], 0, sizeof(SProc));
+ g_count--;
+
+ if (g_first == g_last) {
+ g_first = SNULL;
+ g_last = SNULL;
+ return;
+ }
+
+ g_first++;
+ g_first %= g_cap;
+}
+
+static void
+incrementIP(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ g_procs[pidx].ip++;
+ g_procs[pidx].sp = g_procs[pidx].ip;
+}
+
+static sbool
+seek(sword pidx, sbool forward)
+{
+ sword nextAddr;
+ sbyte nextInst;
+ sbyte spInst;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ nextAddr = g_procs[pidx].ip + 1;
+
+ if (!sm_isValidAt(nextAddr)) {
+ incrementIP(pidx);
+ return SFALSE;
+ }
+
+ nextInst = sm_getInstAt(nextAddr);
+
+ if (!si_isKey(nextInst)) {
+ incrementIP(pidx);
+ return SFALSE;
+ }
+
+ spInst = sm_getInstAt(g_procs[pidx].sp);
+
+ if (si_keyLockMatch(nextInst, spInst)) {
+ return STRUE;
+ }
+
+ if (forward) {
+ g_procs[pidx].sp++;
+ } else {
+ g_procs[pidx].sp--;
+ }
+
+ return SFALSE;
+}
+
+static void
+jump(sword pidx)
+{
+#ifndef NDEBUG
+ sbyte nextInst;
+ sbyte spInst;
+#endif
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+#ifndef NDEBUG
+ nextInst = sm_getInstAt(g_procs[pidx].ip + 1);
+ spInst = sm_getInstAt(g_procs[pidx].sp);
+ assert(si_isKey(nextInst));
+ assert(si_isLock(spInst));
+ assert(si_keyLockMatch(nextInst, spInst));
+#endif
+ g_procs[pidx].ip = g_procs[pidx].sp;
+}
+
+static sword *
+getRegAddr(sword pidx, sword modAddr)
+{
+ sbyte modInst;
+ sbyte modOffset;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ assert(sm_isValidAt(modAddr));
+ modInst = sm_getInstAt(modAddr);
+ assert(si_isMod(modInst));
+ modOffset = modInst - SNOP0;
+ assert(modOffset < SPROC_REG_COUNT);
+ return &(g_procs[pidx].regs[modOffset]);
+}
+
+static void
+getRegAddrList(sword pidx, sword **regList, sword regCount, sbool offset)
+{
+ sword modAddr;
+ sbyte ridx;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ assert(regList);
+ assert(regCount);
+ assert(regCount < 4);
+
+ if (offset) {
+ modAddr = g_procs[pidx].ip + 2;
+ } else {
+ modAddr = g_procs[pidx].ip + 1;
+ }
+
+ for (ridx = 0; ridx < regCount; ridx++) {
+ regList[ridx] = &(g_procs[pidx].regs[0]);
+ }
+
+ for (ridx = 0; ridx < regCount; ridx++) {
+ sword modNext = modAddr + ridx;
+
+ if (!sm_isValidAt(modNext)) {
+ break;
+ } else {
+ sword modInst = sm_getInstAt(modNext);
+
+ if (!si_isMod(modInst)) {
+ break;
+ } else {
+ regList[ridx] = getRegAddr(pidx, modNext);
+ }
+ }
+ }
+}
+
+static void
+addr(sword pidx)
+{
+#ifndef NDEBUG
+ sbyte nextInst;
+ sbyte spInst;
+#endif
+ sword *reg;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+#ifndef NDEBUG
+ nextInst = sm_getInstAt(g_procs[pidx].ip + 1);
+ spInst = sm_getInstAt(g_procs[pidx].sp);
+ assert(si_isKey(nextInst));
+ assert(si_isLock(spInst));
+ assert(si_keyLockMatch(nextInst, spInst));
+#endif
+ getRegAddrList(pidx, &reg, 1, STRUE);
+ *reg = g_procs[pidx].sp;
+ incrementIP(pidx);
+}
+
+static void
+ifnz(sword pidx)
+{
+ sword *reg;
+ sword jumpMod;
+ sword nextAddr;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, &reg, 1, SFALSE);
+ nextAddr = g_procs[pidx].ip + 1;
+
+ if (sm_isValidAt(nextAddr)) {
+ sbyte nextInst = sm_getInstAt(nextAddr);
+ sbool nextInstIsMod = si_isMod(nextInst);
+
+ if (nextInstIsMod) {
+ jumpMod = 1;
+ } else {
+ jumpMod = 0;
+ }
+ } else {
+ jumpMod = 0;
+ }
+
+ if (*reg) {
+ /* execute next instruction */
+ g_procs[pidx].ip += (jumpMod + 1);
+ } else {
+ /* skip next instruction */
+ g_procs[pidx].ip += (jumpMod + 2);
+ }
+
+ g_procs[pidx].sp = g_procs[pidx].ip;
+}
+
+static void
+freeMemBlock2Of(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ assert(g_procs[pidx].mb2s);
+ freeMemBlock(g_procs[pidx].mb2a, g_procs[pidx].mb2s);
+ g_procs[pidx].mb2a = 0;
+ g_procs[pidx].mb2s = 0;
+}
+
+static void
+alloc(sword pidx, sbool forward)
+{
+ sword *regs[2];
+ sword blockSize;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, regs, 2, SFALSE);
+ blockSize = *regs[0];
+
+ /* check for possible errors */
+ /* ignore if requested block size is zero */
+ if (!blockSize) {
+ incrementIP(pidx);
+ return;
+ }
+
+ /* ignore if sp is not adjacent to already existing memory block */
+ if (g_procs[pidx].mb2s) {
+ sword correctAddr = g_procs[pidx].mb2a;
+
+ if (forward) {
+ correctAddr += g_procs[pidx].mb2s;
+ } else {
+ correctAddr--;
+ }
+
+ if (g_procs[pidx].sp != correctAddr) {
+ incrementIP(pidx);
+ return;
+ }
+ }
+
+ /* on successful allocation */
+ /* increment ip and save new block's address on register */
+ if (g_procs[pidx].mb2s == blockSize) {
+ incrementIP(pidx);
+ *regs[1] = g_procs[pidx].mb2a;
+ return;
+ }
+
+ /* handle block enlargement */
+ /* handle sp collision with allocated space */
+ if (sm_isAllocatedAt(g_procs[pidx].sp)) {
+ if (g_procs[pidx].mb2s) {
+ freeMemBlock2Of(pidx);
+ }
+
+ if (forward) {
+ g_procs[pidx].sp++;
+ } else {
+ g_procs[pidx].sp--;
+ }
+
+ return;
+ }
+
+ /* enlarge block when no collision occurs */
+ sm_allocateAt(g_procs[pidx].sp);
+
+ /* correct memory block start flag address */
+ if (!g_procs[pidx].mb2s) {
+ g_procs[pidx].mb2a = g_procs[pidx].sp;
+ sm_setMemBlockStartAt(g_procs[pidx].sp);
+ } else {
+ if (!forward) {
+ sm_unsetMemBlockStartAt(g_procs[pidx].mb2a);
+ g_procs[pidx].mb2a = g_procs[pidx].sp;
+ sm_setMemBlockStartAt(g_procs[pidx].mb2a);
+ }
+ }
+
+ g_procs[pidx].mb2s++;
+
+ /* move sp to next location */
+ if (forward) {
+ g_procs[pidx].sp++;
+ } else {
+ g_procs[pidx].sp--;
+ }
+}
+
+static void
+bswap(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+
+ if (g_procs[pidx].mb2s) {
+ sword addrTmp = g_procs[pidx].mb1a;
+ sword sizeTmp = g_procs[pidx].mb1s;
+ g_procs[pidx].mb1a = g_procs[pidx].mb2a;
+ g_procs[pidx].mb1s = g_procs[pidx].mb2s;
+ g_procs[pidx].mb2a = addrTmp;
+ g_procs[pidx].mb2s = sizeTmp;
+ }
+
+ incrementIP(pidx);
+}
+
+static void
+bclear(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+
+ if (g_procs[pidx].mb2s) {
+ freeMemBlock2Of(pidx);
+ }
+
+ incrementIP(pidx);
+}
+
+static void
+split(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+
+ if (g_procs[pidx].mb2s) {
+ create(g_procs[pidx].mb2a, g_procs[pidx].mb2s, pidx, SFALSE);
+ g_procs[pidx].mb2a = 0;
+ g_procs[pidx].mb2s = 0;
+ }
+
+ incrementIP(pidx);
+}
+
+static void
+r3op(sword pidx, sbyte inst)
+{
+ sword *regs[3];
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, regs, 3, SFALSE);
+
+ /* ignore when dividing by zero */
+ if ((inst == SDIVN) && (*regs[2] == 0)) {
+ incrementIP(pidx);
+ return;
+ }
+
+ switch (inst) {
+ case SADDN:
+ *regs[0] = *regs[1] + *regs[2];
+ break;
+
+ case SSUBN:
+ *regs[0] = *regs[1] - *regs[2];
+ break;
+
+ case SMULN:
+ *regs[0] = *regs[1] * *regs[2];
+ break;
+
+ case SDIVN:
+ assert(*regs[2]);
+ *regs[0] = *regs[1] / *regs[2];
+ break;
+
+ default:
+ assert(0);
+ }
+
+ incrementIP(pidx);
+}
+
+static void
+r1op(sword pidx, sbyte inst)
+{
+ sword *reg;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, &reg, 1, SFALSE);
+
+ switch (inst) {
+ case SINCN:
+ (*reg)++;
+ break;
+
+ case SDECN:
+ (*reg)--;
+ break;
+
+ case SNOTN:
+ *reg = !(*reg);
+ break;
+
+ case SSHFL:
+ *reg <<= 1;
+ break;
+
+ case SSHFR:
+ *reg >>= 1;
+ break;
+
+ case SZERO:
+ *reg = 0;
+ break;
+
+ case SUNIT:
+ *reg = 1;
+ break;
+
+ default:
+ assert(0);
+ }
+
+ incrementIP(pidx);
+}
+
+static void
+push(sword pidx)
+{
+ sword *reg;
+ sword sidx;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, &reg, 1, SFALSE);
+
+ for (sidx = SPROC_STACK_SIZE - 1; sidx; sidx--) {
+ g_procs[pidx].stack[sidx] = g_procs[pidx].stack[sidx - 1];
+ }
+
+ g_procs[pidx].stack[0] = *reg;
+ incrementIP(pidx);
+}
+
+static void
+pop(sword pidx)
+{
+ sword *reg;
+ sword sidx;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, &reg, 1, SFALSE);
+ *reg = g_procs[pidx].stack[0];
+
+ for (sidx = 1; sidx < SPROC_STACK_SIZE; sidx++) {
+ g_procs[pidx].stack[sidx - 1] = g_procs[pidx].stack[sidx];
+ }
+
+ g_procs[pidx].stack[SPROC_STACK_SIZE - 1] = 0;
+ incrementIP(pidx);
+}
+
+static void
+load(sword pidx)
+{
+ sword *regs[2];
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, regs, 2, SFALSE);
+
+ if (!sm_isValidAt(*regs[0])) {
+ incrementIP(pidx);
+ return;
+ }
+
+ if (g_procs[pidx].sp < *regs[0]) {
+ g_procs[pidx].sp++;
+ } else if (g_procs[pidx].sp > *regs[0]) {
+ g_procs[pidx].sp--;
+ } else {
+ *regs[1] = sm_getInstAt(*regs[0]);
+ incrementIP(pidx);
+ }
+}
+
+static sbool
+isWriteableBy(sword pidx, sword addr)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+
+ if (!sm_isValidAt(addr)) {
+ return SFALSE;
+ }
+
+ if (!sm_isAllocatedAt(addr)) {
+ return STRUE;
+ } else {
+ sword lo1 = g_procs[pidx].mb1a;
+ sword lo2 = g_procs[pidx].mb2a;
+ sword hi1 = lo1 + g_procs[pidx].mb1s;
+ sword hi2 = lo2 + g_procs[pidx].mb2s;
+ return (addr >= lo1 && addr < hi1) || (addr >= lo2 && addr < hi2);
+ }
+}
+
+static void
+write(sword pidx)
+{
+ sword *regs[2];
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, regs, 2, SFALSE);
+
+ if (!sm_isValidAt(*regs[0])) {
+ incrementIP(pidx);
+ return;
+ }
+
+ if (!si_isInst(*regs[1])) {
+ incrementIP(pidx);
+ return;
+ }
+
+ if (g_procs[pidx].sp < *regs[0]) {
+ g_procs[pidx].sp++;
+ } else if (g_procs[pidx].sp > *regs[0]) {
+ g_procs[pidx].sp--;
+ } else {
+ if (isWriteableBy(pidx, *regs[0])) {
+ sm_setInstAt(*regs[0], *regs[1]);
+ }
+
+ incrementIP(pidx);
+ }
+}
+
+static void
+r2op(sword pidx, sbyte inst)
+{
+ sword *regs[2];
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ getRegAddrList(pidx, regs, 2, SFALSE);
+
+ switch (inst) {
+ case SDUPL:
+ *regs[1] = *regs[0];
+ break;
+
+ case SSWAP: {
+ sword temp = *regs[0];
+ *regs[0] = *regs[1];
+ *regs[1] = temp;
+ break;
+ }
+
+ default:
+ assert(0);
+ }
+
+ incrementIP(pidx);
+}
+
+static void
+cycle(sword pidx)
+{
+ sbyte inst;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+
+ if (!sm_isValidAt(g_procs[pidx].ip) || !sm_isValidAt(g_procs[pidx].sp)) {
+ incrementIP(pidx);
+ return;
+ }
+
+ inst = sm_getInstAt(g_procs[pidx].ip);
+
+ switch (inst) {
+ case SJMPB:
+ if (seek(pidx, SFALSE)) {
+ jump(pidx);
+ }
+
+ break;
+
+ case SJMPF:
+ if (seek(pidx, STRUE)) {
+ jump(pidx);
+ }
+
+ break;
+
+ case SADRB:
+ if (seek(pidx, SFALSE)) {
+ addr(pidx);
+ }
+
+ break;
+
+ case SADRF:
+ if (seek(pidx, STRUE)) {
+ addr(pidx);
+ }
+
+ break;
+
+ case SIFNZ:
+ ifnz(pidx);
+ break;
+
+ case SALLB:
+ alloc(pidx, SFALSE);
+ break;
+
+ case SALLF:
+ alloc(pidx, STRUE);
+ break;
+
+ case SBSWP:
+ bswap(pidx);
+ break;
+
+ case SBCLR:
+ bclear(pidx);
+ break;
+
+ case SSPLT:
+ split(pidx);
+ break;
+
+ case SADDN:
+ case SSUBN:
+ case SMULN:
+ case SDIVN:
+ r3op(pidx, inst);
+ break;
+
+ case SINCN:
+ case SDECN:
+ case SNOTN:
+ case SSHFL:
+ case SSHFR:
+ case SZERO:
+ case SUNIT:
+ r1op(pidx, inst);
+ break;
+
+ case SPSHN:
+ push(pidx);
+ break;
+
+ case SPOPN:
+ pop(pidx);
+ break;
+
+ case SLOAD:
+ load(pidx);
+ break;
+
+ case SWRTE:
+ write(pidx);
+ break;
+
+ case SDUPL:
+ case SSWAP:
+ r2op(pidx, inst);
+ break;
+
+ default:
+ incrementIP(pidx);
+ }
+}
+
+#ifndef NDEBUG
+
+static void
+isFreeValid(sword pidx)
+{
+ sword *element;
+ sword eidx;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(sp_isFree(pidx));
+ element = (sword *)&g_procs[pidx];
+
+ for (eidx = 0; eidx < SPROC_ELEM_COUNT; eidx++) {
+ assert(!(*element));
+ element++;
+ }
+}
+
+static void
+isUsedValid(sword pidx)
+{
+ sword offset;
+ assert(g_isInit);
+ assert(pidx < g_cap);
+ assert(!sp_isFree(pidx));
+ assert(sm_isMemBlockStartAt(g_procs[pidx].mb1a));
+
+ if (g_procs[pidx].mb2s) {
+ assert(sm_isMemBlockStartAt(g_procs[pidx].mb2a));
+ }
+
+ for (offset = 0; offset < g_procs[pidx].mb1s; offset++) {
+ sword addr = g_procs[pidx].mb1a + offset;
+ assert(sm_isValidAt(addr));
+ assert(sm_isAllocatedAt(addr));
+ }
+
+ for (offset = 0; offset < g_procs[pidx].mb2s; offset++) {
+ sword addr = g_procs[pidx].mb2a + offset;
+ assert(sm_isValidAt(addr));
+ assert(sm_isAllocatedAt(addr));
+ }
+}
+
+static void
+isValid(sword pidx)
+{
+ assert(g_isInit);
+ assert(pidx < g_cap);
+
+ if (sp_isFree(pidx)) {
+ isFreeValid(pidx);
+ } else {
+ isUsedValid(pidx);
+ }
+}
+
+#endif
+
+void
+sp_cycle(void)
+{
+#ifndef NDEBUG
+ sword pidx;
+#endif
+ assert(g_isInit);
+#ifndef NDEBUG
+
+ /* check for validity before cycle */
+ for (pidx = 0; pidx < sp_getCap(); pidx++) {
+ isValid(pidx);
+ }
+
+#endif
+
+ if (sp_getCount()) {
+ sword qidx = sp_getLast();
+
+ /* cycle all procs */
+ while (STRUE) {
+ cycle(qidx);
+
+ if (qidx == sp_getFirst()) {
+ break;
+ } else {
+ qidx--;
+ qidx %= sp_getCap();
+ }
+ }
+
+ /* kill procs if memory is over capacity */
+ while (sm_isOverCap()) {
+ sp_kill();
+ }
+ } else {
+ sp_create(0, 1);
+ }
+
+#ifndef NDEBUG
+
+ /* check for validity after cycle */
+ for (pidx = 0; pidx < sp_getCap(); pidx++) {
+ isValid(pidx);
+ }
+
+#endif
+}
diff --git a/src/salis.c b/src/salis.c
new file mode 100644
index 0000000..c57c60e
--- /dev/null
+++ b/src/salis.c
@@ -0,0 +1,98 @@
+#include <assert.h>
+#include <stdlib.h>
+#include "salis.h"
+
+static sbool g_isInit;
+static sword g_cycle;
+static sword g_epoch;
+
+void
+s_init(sword order)
+{
+ assert(!g_isInit);
+ sm_init(order);
+ se_init();
+ sp_init();
+ g_isInit = STRUE;
+}
+
+void
+s_quit(void)
+{
+ assert(g_isInit);
+ sp_quit();
+ se_quit();
+ sm_quit();
+ g_isInit = SFALSE;
+ g_cycle = 0;
+ g_epoch = 0;
+}
+
+void
+s_load(const char *fileName)
+{
+ FILE *file;
+ assert(!g_isInit);
+ assert(fileName);
+ file = fopen(fileName, "rb");
+ assert(file);
+ fread(&g_isInit, sizeof(sbool), 1, file);
+ fread(&g_cycle, sizeof(sword), 1, file);
+ fread(&g_epoch, sizeof(sword), 1, file);
+ sm_load(file);
+ se_load(file);
+ sp_load(file);
+ fclose(file);
+}
+
+void
+s_save(const char *fileName)
+{
+ FILE *file;
+ assert(g_isInit);
+ assert(fileName);
+ file = fopen(fileName, "wb");
+ assert(file);
+ fwrite(&g_isInit, sizeof(sbool), 1, file);
+ fwrite(&g_cycle, sizeof(sword), 1, file);
+ fwrite(&g_epoch, sizeof(sword), 1, file);
+ sm_save(file);
+ se_save(file);
+ sp_save(file);
+ fclose(file);
+}
+
+sbool
+s_isInit(void)
+{
+ return g_isInit;
+}
+
+sword
+s_getCycle(void)
+{
+ return g_cycle;
+}
+
+sword
+s_getEpoch(void)
+{
+ return g_epoch;
+}
+
+void
+s_cycle(void)
+{
+ assert(g_isInit);
+ assert(sm_isInit());
+ assert(sp_isInit());
+ assert(se_isInit());
+ g_cycle++;
+
+ if (!g_cycle) {
+ g_epoch++;
+ }
+
+ se_cycle();
+ sp_cycle();
+}
diff --git a/tsalis/Makefile b/tsalis/Makefile
new file mode 100644
index 0000000..c393350
--- /dev/null
+++ b/tsalis/Makefile
@@ -0,0 +1,30 @@
+CC := gcc
+BIN := bin/tsalis
+
+SOURCES := $(wildcard src/*.c)
+OBJECTS := $(patsubst src/%.c,build/%.o,$(SOURCES))
+DEPS := $(patsubst %.o,%.d,$(OBJECTS))
+
+LFLAGS := -L ../lib -lsalis -lncurses
+
+# uncomment for debug
+# OFLAGS := -ggdb
+
+# uncomment for release
+OFLAGS := -O3 -DNDEBUG
+
+CFLAGS := -Iinclude -I../include -c $(OFLAGS) -MMD -Wall -Wextra -std=c89 \
+ -pedantic-errors -Wmissing-prototypes -Wstrict-prototypes \
+ -Wold-style-definition
+
+all: $(OBJECTS)
+ $(CC) $(OBJECTS) $(LFLAGS) -o $(BIN)
+
+-include $(DEPS)
+
+$(OBJECTS): $(patsubst build/%.o,src/%.c,$@)
+ $(CC) $(CFLAGS) $(patsubst build/%.o,src/%.c,$@) -o $@
+
+clean:
+ -rm build/*
+ -rm $(BIN)
diff --git a/tsalis/README.md b/tsalis/README.md
new file mode 100644
index 0000000..214e3b9
--- /dev/null
+++ b/tsalis/README.md
@@ -0,0 +1,76 @@
+# TSALIS
+*TSALIS* is a text user interface (TUI) designed to communicate with *SALIS*.
+Its only dependencies are ncurses and the *SALIS* library itself. It should be
+portable enough and should run easily in any terminal environment.
+
+## Building instructions
+You'll need nothing but a C compiler (C89). You must build the program and link
+it with *SALIS* (e.g. *libsalis.a*) and ncurses. A sample makefile
+(Makefile) is provided for GNU Make. You can also just run the `make` command
+inside the salis directory and it will automate the building and linking of
+both the library and this application. Feel free to edit both makefiles as
+needed. If you run into any difficulties, please let me know!
+
+## List of commands
+### Command-line arguments
+You may run *TSALIS* from the terminal in any of these three ways (arguments
+are being represented by *XX*). Note that, upon exit, *SALIS* automatically
+generates a save (by default called *defsim*). This save file may be freely
+renamed (any name 10 characters or shorter) and reloaded as needed.
+
+|Arguments |Action |
+|:--------------|------------------------------------------------------------------------------:|
+|tsalis |If file *defsim* exists in directory, loads simulation from that file. |
+|tsalis |If file *defsim* does not exist, creates new simulation (memory size 2^16). |
+|tsalis n*XX* |Creates new simulation with memory size 2^*XX*. |
+|tsalis l*XX* |Loads simulation from file named *XX*. |
+
+### Keyboard commands
+|Key |Action |
+|:--------------|------------------------------------------------------:|
+|Left arrow |Previous page |
+|Right arrow |Next page |
+|wasd |Scroll (PROCESS and WORLD page) |
+|W |Scroll to top (PROCESS and WORLD page) |
+|A |Scroll to left (PROCESS page) |
+|zx |Zoom in/out (WORLD page) |
+|op |Select previous/next organism |
+|g |Toggle data/gene view (PROCESS page) |
+|c |Open console (pauses simulation) |
+|Space |Run/pause simulation |
+|jl |Select first/last organism |
+|k |Go to selected organism (PROCESS and WORLD page) |
+|Numbers (1-0) |Cycle simulation (1 = 1, 2 = 2, 3 = 4, 4 = 8, ...) |
+
+### Console commands
+The console opens up when 'c' is pressed. Commands, with their respective
+parameters separated by underscores, may be written in order to modify or
+control some aspects of the simulation. Parameters here are represented by
+*XX*.
+
+|Command |Param. 1 |Param. 2 |Action |
+|:-----------|:-----------|:-----------|---------------------------------------------------------------------:|
+|q |--- |--- |Save and quit simulation. |
+|i*XX*\_*XX* |address |instructions|Writes given instructions into address. |
+|c*XX*\_*XX* |address |file name |Compiles given file into address. |
+|n*XX*\_*XX* |address |size |Initializes organism of given size into address. |
+|k |--- |--- |Kills organism at bottom of queue (first organism). |
+|m*XX* |address |--- |Scroll/move (PROCESS and WORLD page) to given process/address. |
+|p*XX* |process id |--- |Select given process. |
+|s |--- |--- |Save simulation. |
+|r*XX* |name |--- |Rename simulation (will be automatically saved to this name on exit). |
+|a*XX* |interval |--- |Set simulation's auto-save interval. |
+
+### Legend
+In WORLD view, as well as in PROCESS view (when gene mode is selected), each
+cell is colored according to the following legend:
+
+|Background color |Meaning |
+|:----------------|---------------------------------------:|
+|BLUE |Non-allocated cell |
+|CYAN |Allocated cell |
+|WHITE |Start of memory block |
+|YELLOW |Main memory block of selected organism |
+|GREEN |Child memory block of selected organism |
+|MAGENTA |SP of selected organism |
+|RED |IP of selected organism |
diff --git a/tsalis/bin/.keep b/tsalis/bin/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tsalis/bin/.keep
diff --git a/tsalis/build/.keep b/tsalis/build/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tsalis/build/.keep
diff --git a/tsalis/include/handler.h b/tsalis/include/handler.h
new file mode 100644
index 0000000..9bb4b0e
--- /dev/null
+++ b/tsalis/include/handler.h
@@ -0,0 +1,6 @@
+#ifndef TSALIS_HANDLER_H
+#define TSALIS_HANDLER_H
+
+void tsh_handleEvent (int event);
+
+#endif
diff --git a/tsalis/include/printer.h b/tsalis/include/printer.h
new file mode 100644
index 0000000..5356594
--- /dev/null
+++ b/tsalis/include/printer.h
@@ -0,0 +1,35 @@
+#ifndef TSALIS_PRINTER_H
+#define TSALIS_PRINTER_H
+
+extern const int PROC_ELEMENT_COUNT;
+
+extern int g_currentPage;
+extern sword g_selectedProcess;
+extern sbool g_processShowGenes;
+extern sword g_processVertScroll;
+extern sword g_processDataScroll;
+extern sword g_processGeneScroll;
+extern sword g_worldPos;
+extern sword g_worldZoom;
+
+void tsp_init (void);
+void tsp_quit (void);
+void tsp_onResize (void);
+void tsp_prevPage (void);
+void tsp_nextPage (void);
+void tsp_scrollUp (void);
+void tsp_scrollDown (void);
+void tsp_scrollLeft (void);
+void tsp_scrollRight (void);
+void tsp_scrollToTop (void);
+void tsp_scrollToLeft (void);
+void tsp_zoomIn (void);
+void tsp_zoomOut (void);
+void tsp_prevOrganism (void);
+void tsp_nextOrganism (void);
+void tsp_gotoSelectedProc (void);
+void tsp_selectProcess (sword proc);
+void tsp_moveTo (sword loc);
+void tsp_printData (void);
+
+#endif
diff --git a/tsalis/include/tsalis.h b/tsalis/include/tsalis.h
new file mode 100644
index 0000000..d60b7c7
--- /dev/null
+++ b/tsalis/include/tsalis.h
@@ -0,0 +1,11 @@
+#ifndef TSALIS_H
+#define TSALIS_H
+
+#define NAME_MAX_SIZE 10
+
+extern sbool g_exit;
+extern sbool g_running;
+extern sword g_autoSaveInterval;
+extern char g_simName[];
+
+#endif
diff --git a/tsalis/src/handler.c b/tsalis/src/handler.c
new file mode 100644
index 0000000..21aa706
--- /dev/null
+++ b/tsalis/src/handler.c
@@ -0,0 +1,317 @@
+#include <stdlib.h>
+#include <string.h>
+#include <curses.h>
+#include <salis.h>
+#include "printer.h"
+#include "handler.h"
+#include "tsalis.h"
+
+#define CONSOLE_INPUT_LENGTH 64
+
+static sbyte
+symbolToInst(char charSymbol)
+{
+#define SINST(name, symbol) case symbol: return name;
+#define SILST(name, symbol) case symbol: return name;
+
+ switch (charSymbol) {
+ SINST_LIST
+ }
+
+#undef SINST
+#undef SILST
+ return (sbyte)(-1);
+}
+
+static void
+writeInstructions(const char *command)
+{
+ sword addr = atoi(command);
+ char *symbol = strchr(command, '_');
+
+ if (symbol) {
+ symbol++;
+
+ while (sm_isValidAt(addr) && *symbol) {
+ sbyte svalue = symbolToInst(*symbol);
+
+ if (si_isInst(svalue)) {
+ sm_setInstAt(addr, svalue);
+ } else {
+ sm_setInstAt(addr, 0);
+ }
+
+ addr++;
+ symbol++;
+ }
+ }
+}
+
+static void
+compileGenome(const char *command)
+{
+ sword addr = atoi(command);
+ char *fileName = strchr(command, '_');
+
+ if (fileName) {
+ FILE *file = fopen(fileName + 1, "r");
+
+ if (file) {
+ while (addr < sm_getSize()) {
+ int symbol = fgetc(file);
+ sbyte svalue = symbolToInst((char)symbol);
+
+ if (symbol == EOF) {
+ break;
+ }
+
+ if (si_isInst(svalue)) {
+ sm_setInstAt(addr, svalue);
+ } else {
+ sm_setInstAt(addr, 0);
+ }
+
+ addr++;
+ }
+ }
+ }
+}
+
+static void
+createOrganism(const char *command)
+{
+ sword addr = atoi(command);
+ char *sizep = strchr(command, '_');
+
+ if (sizep) {
+ sword size = atoi(sizep + 1);
+
+ if (sm_isValidAt(addr) && sm_isValidAt(addr + size - 1)) {
+ sword offset;
+
+ for (offset = 0; offset < size; offset++) {
+ if (sm_isAllocatedAt(addr + offset)) {
+ return;
+ }
+ }
+
+ sp_create(addr, size);
+ }
+ }
+}
+
+static void
+killOrganism(void)
+{
+ sp_kill();
+}
+
+static void
+moveTo(const char *command)
+{
+ sword loc = atoi(command);
+ tsp_moveTo(loc);
+}
+
+static void
+selectProc(const char *command)
+{
+ sword proc = atoi(command);
+ tsp_selectProcess(proc);
+}
+
+static void
+saveSim(void)
+{
+ s_save(g_simName);
+}
+
+static void
+renameSim(const char *name)
+{
+ if (strlen(name) <= NAME_MAX_SIZE) {
+ strncpy(g_simName, name, NAME_MAX_SIZE);
+ }
+}
+
+static void
+setAutoSave(const char *command)
+{
+ g_autoSaveInterval = atoi(command);
+}
+
+static void
+clearConsoleLine(void)
+{
+ move(LINES - 1, 0);
+ clrtoeol();
+}
+
+static void
+runConsole(void)
+{
+ char command[CONSOLE_INPUT_LENGTH] = {0};
+ clearConsoleLine();
+ echo();
+ mvprintw(LINES - 1, 1, "$ ");
+ curs_set(TRUE);
+ getnstr(command, CONSOLE_INPUT_LENGTH - 1);
+ curs_set(FALSE);
+ noecho();
+ clearConsoleLine();
+
+ switch (command[0]) {
+ case 'q':
+ g_exit = STRUE;
+ break;
+
+ case 'i':
+ writeInstructions(&command[1]);
+ break;
+
+ case 'c':
+ compileGenome(&command[1]);
+ break;
+
+ case 'n':
+ createOrganism(&command[1]);
+ break;
+
+ case 'k':
+ killOrganism();
+ break;
+
+ case 'm':
+ moveTo(&command[1]);
+ break;
+
+ case 'p':
+ selectProc(&command[1]);
+ break;
+
+ case 's':
+ saveSim();
+ break;
+
+ case 'r':
+ renameSim(&command[1]);
+ break;
+
+ case 'a':
+ setAutoSave(&command[1]);
+ break;
+ }
+}
+
+void
+tsh_handleEvent(int event)
+{
+ switch (event) {
+ case KEY_RESIZE:
+ tsp_onResize();
+ break;
+
+ case KEY_LEFT:
+ tsp_prevPage();
+ break;
+
+ case KEY_RIGHT:
+ tsp_nextPage();
+ break;
+
+ case 'w':
+ tsp_scrollDown();
+ break;
+
+ case 'a':
+ tsp_scrollLeft();
+ break;
+
+ case 's':
+ tsp_scrollUp();
+ break;
+
+ case 'd':
+ tsp_scrollRight();
+ break;
+
+ case 'W':
+ tsp_scrollToTop();
+ break;
+
+ case 'A':
+ tsp_scrollToLeft();
+ break;
+
+ case 'z':
+ tsp_zoomIn();
+ break;
+
+ case 'x':
+ tsp_zoomOut();
+ break;
+
+ case 'o':
+ tsp_prevOrganism();
+ break;
+
+ case 'p':
+ tsp_nextOrganism();
+ break;
+
+ case 'g':
+ g_processShowGenes = !g_processShowGenes;
+ break;
+
+ case 'c':
+ g_running = SFALSE;
+ nodelay(stdscr, SFALSE);
+ tsp_printData();
+ runConsole();
+ break;
+
+ case ' ':
+ g_running = !g_running;
+ nodelay(stdscr, g_running);
+ break;
+
+ case 'j':
+ if (sp_getCount()) {
+ g_selectedProcess = sp_getFirst();
+ }
+
+ break;
+
+ case 'l':
+ if (sp_getCount()) {
+ g_selectedProcess = sp_getLast();
+ }
+
+ break;
+
+ case 'k':
+ tsp_gotoSelectedProc();
+ break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ if (!g_running) {
+ int power = ((event - '0') ? (event - '0') : 10) - 1;
+ int cycles = 1 << power;
+
+ while (cycles--) {
+ s_cycle();
+ }
+ }
+
+ break;
+ }
+}
diff --git a/tsalis/src/printer.c b/tsalis/src/printer.c
new file mode 100644
index 0000000..a3f60b6
--- /dev/null
+++ b/tsalis/src/printer.c
@@ -0,0 +1,749 @@
+#include <stdarg.h>
+#include <curses.h>
+#include <salis.h>
+#include "printer.h"
+#include "tsalis.h"
+
+enum {
+ PAIR_NORMAL = 1,
+ PAIR_HEADER = 2,
+ PAIR_SELECTED_PROC = 3,
+ PAIR_FREE_CELL = 4,
+ PAIR_ALLOC_CELL = 5,
+ PAIR_MEM_BLOCK_START = 6,
+ PAIR_MEM_BLOCK_2 = 7,
+ PAIR_MEM_BLOCK_1 = 8,
+ PAIR_SELECTED_SP = 9,
+ PAIR_SELECTED_IP = 10
+};
+
+enum {
+ PAGE_MEMORY,
+ PAGE_EVOLVER,
+ PAGE_PROCESS,
+ PAGE_WORLD,
+ PAGE_COUNT
+};
+
+const char *g_instNames[] = {
+#define SINST(name, symbol) #name,
+#define SILST(name, symbol) #name
+ SINST_LIST
+#undef SINST
+#undef SILST
+};
+
+const char g_instSymbols[] = {
+#define SINST(name, symbol) symbol,
+#define SILST(name, symbol) symbol
+ SINST_LIST
+#undef SINST
+#undef SILST
+};
+
+const char *g_procElems[] = {
+ "mb1a",
+ "mb1s",
+ "mb2a",
+ "mb2s",
+ "ip",
+ "sp",
+ "reg[0]",
+ "reg[1]",
+ "reg[2]",
+ "reg[3]",
+ "stack[0]",
+ "stack[1]",
+ "stack[2]",
+ "stack[3]",
+ "stack[4]",
+ "stack[5]",
+ "stack[6]",
+ "stack[7]"
+};
+
+const int PROC_ELEMENT_COUNT = (sizeof(g_procElems) / sizeof(*g_procElems));
+const int DATA_WIDTH = 25;
+
+int g_currentPage;
+sword g_selectedProcess;
+sbool g_processShowGenes;
+sword g_processVertScroll;
+sword g_processDataScroll;
+sword g_processGeneScroll;
+sword g_worldPos;
+sword g_worldZoom;
+sword g_worldLineWidth;
+sword g_worldLineCoverage;
+sword g_worldArea;
+
+static void
+adjustWorld(void)
+{
+ g_worldLineWidth = COLS - DATA_WIDTH;
+ g_worldLineCoverage = g_worldLineWidth * g_worldZoom;
+ g_worldArea = LINES * g_worldLineCoverage;
+
+ while ((g_worldArea > (sm_getSize() * 2)) && (g_worldZoom > 1)) {
+ g_worldZoom /= 2;
+ }
+}
+
+void
+tsp_init(void)
+{
+ initscr();
+ cbreak();
+ noecho();
+ curs_set(0);
+ keypad(stdscr, TRUE);
+ start_color();
+ init_pair(PAIR_NORMAL, COLOR_WHITE, COLOR_BLACK);
+ init_pair(PAIR_HEADER, COLOR_CYAN, COLOR_BLACK);
+ init_pair(PAIR_SELECTED_PROC, COLOR_YELLOW, COLOR_BLACK);
+ init_pair(PAIR_FREE_CELL, COLOR_CYAN, COLOR_BLUE);
+ init_pair(PAIR_ALLOC_CELL, COLOR_BLUE, COLOR_CYAN);
+ init_pair(PAIR_MEM_BLOCK_START, COLOR_BLUE, COLOR_WHITE);
+ init_pair(PAIR_MEM_BLOCK_1, COLOR_BLACK, COLOR_YELLOW);
+ init_pair(PAIR_MEM_BLOCK_2, COLOR_BLACK, COLOR_GREEN);
+ init_pair(PAIR_SELECTED_SP, COLOR_BLACK, COLOR_MAGENTA);
+ init_pair(PAIR_SELECTED_IP, COLOR_BLACK, COLOR_RED);
+ g_worldZoom = 1;
+ adjustWorld();
+}
+
+void
+tsp_quit(void)
+{
+ endwin();
+}
+
+void
+tsp_onResize(void)
+{
+ clear();
+ adjustWorld();
+}
+
+void
+tsp_prevPage(void)
+{
+ clear();
+ g_currentPage += (PAGE_COUNT - 1);
+ g_currentPage %= PAGE_COUNT;
+}
+
+void
+tsp_nextPage(void)
+{
+ clear();
+ g_currentPage++;
+ g_currentPage %= PAGE_COUNT;
+}
+
+void
+tsp_scrollUp(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ if (g_processVertScroll) {
+ g_processVertScroll--;
+ }
+
+ break;
+
+ case PAGE_WORLD:
+ if (g_worldPos >= g_worldLineCoverage) {
+ g_worldPos -= g_worldLineCoverage;
+ }
+
+ break;
+ }
+
+ refresh();
+}
+
+void
+tsp_scrollDown(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ if (g_processVertScroll < (sp_getCap() - 1)) {
+ g_processVertScroll++;
+ }
+
+ break;
+
+ case PAGE_WORLD:
+ if ((g_worldPos + g_worldLineCoverage) < sm_getSize()) {
+ g_worldPos += g_worldLineCoverage;
+ }
+
+ break;
+ }
+
+ refresh();
+}
+
+void
+tsp_scrollLeft(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ if (g_processShowGenes) {
+ if (g_processGeneScroll) {
+ g_processGeneScroll--;
+ }
+ } else {
+ if (g_processDataScroll) {
+ g_processDataScroll--;
+ }
+ }
+
+ break;
+
+ case PAGE_WORLD:
+ if (g_worldPos >= g_worldZoom) {
+ g_worldPos -= g_worldZoom;
+ } else {
+ g_worldPos = 0;
+ }
+
+ break;
+ }
+
+ refresh();
+}
+
+void
+tsp_scrollRight(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ if (g_processShowGenes) {
+ g_processGeneScroll++;
+ } else {
+ if (g_processDataScroll < (sword)(PROC_ELEMENT_COUNT - 1)) {
+ g_processDataScroll++;
+ }
+ }
+
+ break;
+
+ case PAGE_WORLD:
+ if ((g_worldPos + g_worldZoom) < sm_getSize()) {
+ g_worldPos += g_worldZoom;
+ }
+
+ break;
+ }
+
+ refresh();
+}
+
+void
+tsp_scrollToTop(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ g_processVertScroll = 0;
+ break;
+
+ case PAGE_WORLD:
+ g_worldPos = 0;
+ break;
+ }
+}
+
+void
+tsp_scrollToLeft(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ if (g_processShowGenes) {
+ g_processGeneScroll = 0;
+ } else {
+ g_processDataScroll = 0;
+ }
+
+ break;
+ }
+}
+
+void
+tsp_zoomIn(void)
+{
+ if (g_currentPage == PAGE_WORLD) {
+ if (g_worldZoom > 1) {
+ g_worldZoom /= 2;
+ }
+
+ adjustWorld();
+ }
+}
+
+void
+tsp_zoomOut(void)
+{
+ if (g_currentPage == PAGE_WORLD) {
+ if (g_worldArea < sm_getSize()) {
+ g_worldZoom *= 2;
+ refresh();
+ }
+
+ adjustWorld();
+ }
+}
+
+void
+tsp_prevOrganism(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ case PAGE_WORLD:
+ if (g_selectedProcess < sp_getCap()) {
+ g_selectedProcess += (sp_getCap() - 1);
+ g_selectedProcess %= sp_getCap();
+ }
+
+ break;
+ }
+
+ refresh();
+}
+
+void
+tsp_nextOrganism(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ case PAGE_WORLD:
+ if (g_selectedProcess < sp_getCap()) {
+ g_selectedProcess++;
+ g_selectedProcess %= sp_getCap();
+ }
+
+ break;
+ }
+
+ refresh();
+}
+
+void
+tsp_gotoSelectedProc(void)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ g_processVertScroll = g_selectedProcess;
+ break;
+
+ case PAGE_WORLD:
+ g_worldPos = sp_getProc(g_selectedProcess).mb1a;
+ break;
+ }
+}
+
+void
+tsp_selectProcess(sword proc)
+{
+ if (proc < sp_getCap()) {
+ g_selectedProcess = proc;
+ }
+}
+
+void
+tsp_moveTo(sword loc)
+{
+ switch (g_currentPage) {
+ case PAGE_PROCESS:
+ if (loc < sp_getCap()) {
+ g_processVertScroll = loc;
+ }
+
+ break;
+
+ case PAGE_WORLD:
+ if (loc < sm_getSize()) {
+ g_worldPos = loc;
+ }
+
+ break;
+ }
+}
+
+static void
+printWidget(int line, const char *format, ...)
+{
+ if (line < LINES) {
+ va_list args;
+ char dataLine[24];
+ va_start(args, format);
+ vsprintf(dataLine, format, args);
+ mvprintw(line, 1, "%.*s", COLS - 1, dataLine);
+ va_end(args);
+ }
+}
+
+static void
+printHeader(int line, const char *string)
+{
+ attron(COLOR_PAIR(PAIR_HEADER));
+ printWidget(line, string);
+ standend();
+}
+
+#define PHEADER(label) printHeader((*line)++, label)
+#define PWIDGET(label, data) printWidget((*line)++, "%-10s : %10u", label, data)
+#define PSIDGET(label, data) printWidget((*line)++, "%-10s : %10s", label, data)
+#define INCREMENT_LINE (*line)++
+
+static void
+printMemoryPage(int *line)
+{
+ PHEADER("MEMORY");
+ PWIDGET("order", sm_getOrder());
+ PWIDGET("size", sm_getSize());
+ PWIDGET("blocks", sm_getMemBlockCount());
+ PWIDGET("alloc", sm_getAllocated());
+ PWIDGET("cap", sm_getCap());
+}
+
+static void
+printEvolverPage(int *line)
+{
+ PHEADER("EVOLVER");
+ PWIDGET("lastAddr", se_getLastAddress());
+ PSIDGET("lastInst", g_instNames[se_getLastInst()] + 1);
+ PWIDGET("state0", se_getState(0));
+ PWIDGET("state1", se_getState(1));
+ PWIDGET("state2", se_getState(2));
+ PWIDGET("state3", se_getState(3));
+}
+
+static void
+printField(int y, int x, const char *field, sbool lalign)
+{
+ if ((y < LINES) && (x < COLS)) {
+ if (lalign) {
+ mvprintw(y, x, "%-.*s", COLS - x, field);
+ } else {
+ mvprintw(y, x, "%.*s", COLS - x, field);
+ }
+ }
+}
+
+static void
+printSingleProcessGenome(int line, sword pidx)
+{
+ char sidx[11];
+ SProc proc = sp_getProc(pidx);
+ sword gidx = g_processGeneScroll;
+ int xpos = 14;
+
+ if (pidx == g_selectedProcess) {
+ attron(COLOR_PAIR(PAIR_SELECTED_PROC));
+ } else if (!sp_isFree(pidx)) {
+ attron(COLOR_PAIR(PAIR_HEADER));
+ }
+
+ sprintf(sidx, "%-10u |", pidx);
+ printField(line, 1, sidx, STRUE);
+ move(line, xpos);
+
+ while ((gidx < proc.mb1s) && (xpos < COLS)) {
+ sword gaddr = proc.mb1a + gidx;
+
+ if (gaddr == proc.ip) {
+ attron(COLOR_PAIR(PAIR_SELECTED_IP));
+ } else if (gaddr == proc.sp) {
+ attron(COLOR_PAIR(PAIR_SELECTED_SP));
+ } else {
+ attron(COLOR_PAIR(PAIR_MEM_BLOCK_1));
+ }
+
+ addch(g_instSymbols[sm_getInstAt(gaddr)]);
+ gidx++;
+ xpos++;
+ }
+
+ if (proc.mb1s < g_processGeneScroll) {
+ gidx = g_processGeneScroll - proc.mb1s;
+ } else {
+ gidx = 0;
+ }
+
+ while ((gidx < proc.mb2s) && (xpos < COLS)) {
+ sword gaddr = proc.mb2a + gidx;
+
+ if (gaddr == proc.ip) {
+ attron(COLOR_PAIR(PAIR_SELECTED_IP));
+ } else if (gaddr == proc.sp) {
+ attron(COLOR_PAIR(PAIR_SELECTED_SP));
+ } else {
+ attron(COLOR_PAIR(PAIR_MEM_BLOCK_2));
+ }
+
+ addch(g_instSymbols[sm_getInstAt(gaddr)]);
+ gidx++;
+ xpos++;
+ }
+
+ standend();
+}
+
+static void
+printProcessGenes(int *line)
+{
+ sword pidx = g_processVertScroll;
+ attron(COLOR_PAIR(PAIR_HEADER));
+ printField(*line, 1, "pidx", STRUE);
+
+ standend();
+ INCREMENT_LINE;
+
+ while ((*line < LINES) && (pidx < sp_getCap())) {
+ printSingleProcessGenome(*line, pidx);
+ INCREMENT_LINE;
+ pidx++;
+ }
+
+ standend();
+}
+
+static void
+printSingleProcessData(int line, sword pidx)
+{
+ char sidx[11];
+ int eidx = g_processDataScroll;
+ int xpos = 12;
+ SProc proc = sp_getProc(pidx);
+ sword *data = (sword *)&proc;
+
+ if (pidx == g_selectedProcess) {
+ attron(COLOR_PAIR(PAIR_SELECTED_PROC));
+ } else if (!sp_isFree(pidx)) {
+ attron(COLOR_PAIR(PAIR_HEADER));
+ }
+
+ sprintf(sidx, "%u", pidx);
+ printField(line, 1, sidx, STRUE);
+
+ while (eidx < PROC_ELEMENT_COUNT) {
+ char element[13];
+ sprintf(element, "| %10u", data[eidx]);
+ printField(line, xpos, element, SFALSE);
+ eidx++;
+ xpos += 13;
+ }
+
+ standend();
+}
+
+static void
+printProcessData(int *line)
+{
+ sword pidx = g_processVertScroll;
+ int eidx = g_processDataScroll;
+ int xpos = 12;
+ attron(COLOR_PAIR(PAIR_HEADER));
+ printField(*line, 1, "pidx", STRUE);
+
+ while (eidx < PROC_ELEMENT_COUNT) {
+ char element[13];
+ sprintf(element, "| %10s", g_procElems[eidx]);
+ printField(*line, xpos, element, SFALSE);
+ eidx++;
+ xpos += 13;
+ }
+
+ standend();
+ INCREMENT_LINE;
+
+ while ((*line < LINES) && (pidx < sp_getCap())) {
+ printSingleProcessData(*line, pidx);
+ INCREMENT_LINE;
+ pidx++;
+ }
+
+ standend();
+}
+
+static void
+printProcessPage(int *line)
+{
+ int cline;
+ sbool fnull = (sp_getFirst() == (sword) - 1);
+ sbool lnull = (sp_getLast() == (sword) - 1);
+ PHEADER("PROCESS");
+ PWIDGET("count", sp_getCount());
+ PWIDGET("cap", sp_getCap());
+ fnull ? PSIDGET("first", "---") : PWIDGET("first", sp_getFirst());
+ lnull ? PSIDGET("last", "---") : PWIDGET("last", sp_getLast());
+ PWIDGET("selected", g_selectedProcess);
+ INCREMENT_LINE;
+
+ for (cline = *line; cline < LINES; cline++) {
+ move(cline, 0);
+ clrtoeol();
+ }
+
+ if (g_processShowGenes) {
+ printProcessGenes(line);
+ } else {
+ printProcessData(line);
+ }
+}
+
+static int
+getColorOf(sword addr)
+{
+ if (!sp_isFree(g_selectedProcess)) {
+ SProc proc = sp_getProc(g_selectedProcess);
+
+ if (addr == proc.ip) {
+ return PAIR_SELECTED_IP;
+ } else if (addr == proc.sp) {
+ return PAIR_SELECTED_SP;
+ } else if ((addr >= proc.mb1a) && (addr < (proc.mb1a + proc.mb1s))) {
+ return PAIR_MEM_BLOCK_1;
+ } else if ((addr >= proc.mb2a) && (addr < (proc.mb2a + proc.mb2s))) {
+ return PAIR_MEM_BLOCK_2;
+ }
+ }
+
+ if (sm_isMemBlockStartAt(addr)) {
+ return PAIR_MEM_BLOCK_START;
+ } else if (sm_isAllocatedAt(addr)) {
+ return PAIR_ALLOC_CELL;
+ } else {
+ return PAIR_FREE_CELL;
+ }
+}
+
+static void
+printWorld(void)
+{
+ sword y;
+
+ for (y = 0; y < (sword)LINES; y++) {
+ sword x;
+
+ for (x = 0; x < g_worldLineWidth; x++) {
+ sword addr = g_worldPos + (((y * g_worldLineWidth) + x) * g_worldZoom);
+ sbool atEnd = !sm_isValidAt(addr);
+ int xpos = DATA_WIDTH + x;
+
+ if (atEnd) {
+ mvaddch(y, xpos, ' ');
+ continue;
+ }
+
+ if (g_worldZoom == 1) {
+ char symbol = g_instSymbols[sm_getInstAt(addr)];
+ attron(COLOR_PAIR(getColorOf(addr)));
+ mvaddch(y, xpos, symbol);
+ } else {
+ sword offset;
+ char symbol;
+ sword instSum = 0;
+ int color = PAIR_FREE_CELL;
+
+ for (offset = 0; offset < g_worldZoom; offset++) {
+ int testColor;
+ sword offsetAddr = addr + offset;
+
+ if (!sm_isValidAt(offsetAddr)) {
+ break;
+ }
+
+ instSum += sm_getInstAt(offsetAddr);
+ testColor = getColorOf(offsetAddr);
+
+ if (testColor > color) {
+ color = testColor;
+ }
+ }
+
+ instSum /= g_worldZoom;
+
+ if (!instSum) {
+ symbol = ' ';
+ } else if (instSum < 32) {
+ symbol = '-';
+ } else {
+ symbol = '=';
+ }
+
+ attron(COLOR_PAIR(color));
+ mvaddch(y, xpos, symbol);
+ }
+
+ standend();
+ }
+ }
+}
+
+static void
+printWorldPage(int *line)
+{
+ int eidx;
+ SProc proc = sp_getProc(g_selectedProcess);
+ sword *data = (sword *)&proc;
+ PHEADER("WORLD");
+ PWIDGET("pos", g_worldPos);
+ PWIDGET("zoom", g_worldZoom);
+ PWIDGET("selected", g_selectedProcess);
+ INCREMENT_LINE;
+ PHEADER("SELECTED");
+
+ if (!sp_isFree(g_selectedProcess)) {
+ attron(COLOR_PAIR(PAIR_SELECTED_PROC));
+ }
+
+ for (eidx = 0; eidx < PROC_ELEMENT_COUNT; eidx++) {
+ PWIDGET(g_procElems[eidx], data[eidx]);
+ }
+
+ standend();
+ printWorld();
+}
+
+void
+tsp_printData(void)
+{
+ int linev = 1;
+ int *line = &linev;
+ PHEADER("SALIS");
+ PSIDGET("name", g_simName);
+ PSIDGET("state", g_running ? "running" : "paused");
+
+ if (g_autoSaveInterval) {
+ PWIDGET("autosave", g_autoSaveInterval);
+ } else {
+ PSIDGET("autosave", "---");
+ }
+
+ PWIDGET("cycle", s_getCycle());
+ PWIDGET("epoch", s_getEpoch());
+ INCREMENT_LINE;
+
+ switch (g_currentPage) {
+ case PAGE_MEMORY:
+ printMemoryPage(line);
+ break;
+
+ case PAGE_EVOLVER:
+ printEvolverPage(line);
+ break;
+
+ case PAGE_PROCESS:
+ printProcessPage(line);
+ break;
+
+ case PAGE_WORLD:
+ printWorldPage(line);
+ break;
+ }
+
+ refresh();
+}
diff --git a/tsalis/src/tsalis.c b/tsalis/src/tsalis.c
new file mode 100644
index 0000000..ab24c61
--- /dev/null
+++ b/tsalis/src/tsalis.c
@@ -0,0 +1,118 @@
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <curses.h>
+#include <salis.h>
+#include "printer.h"
+#include "handler.h"
+#include "tsalis.h"
+
+#define DEFAULT_ORDER 16
+
+sbool g_exit;
+sbool g_running;
+sword g_autoSaveInterval;
+char g_simName[NAME_MAX_SIZE + 1] = "def.sim";
+
+static void
+onDefault(void)
+{
+ FILE *testFile = fopen(g_simName, "r");
+
+ if (testFile) {
+ fclose(testFile);
+ s_load(g_simName);
+ } else {
+ s_init(DEFAULT_ORDER);
+ }
+}
+
+static void
+onLoad(const char *fileName)
+{
+ FILE *testFile;
+
+ if (strlen(fileName) > NAME_MAX_SIZE) {
+ fputs("ERROR: File name too long", stderr);
+ exit(1);
+ }
+
+ strncpy(g_simName, fileName, NAME_MAX_SIZE);
+ testFile = fopen(g_simName, "r");
+
+ if (testFile) {
+ fclose(testFile);
+ s_load(g_simName);
+ } else {
+ fputs("ERROR: File does not exist", stderr);
+ exit(1);
+ }
+}
+
+static void
+init(int argc, char **argv)
+{
+ if (argc == 1) {
+ onDefault();
+ } else if (argc == 2) {
+ char cmd = argv[1][0];
+ char *val = &argv[1][1];
+
+ if (cmd == 'n') {
+ s_init(atoi(val));
+ } else if (cmd == 'l') {
+ onLoad(val);
+ } else {
+ fputs("ERROR: Incorrect arguments", stderr);
+ exit(1);
+ }
+ } else {
+ fputs("ERROR: Incorrect argument count", stderr);
+ exit(1);
+ }
+
+ tsp_init();
+}
+
+static void
+exec(void)
+{
+ while (!g_exit) {
+ if (g_running) {
+ clock_t beg = clock();
+ clock_t end;
+ float delay;
+
+ do {
+ s_cycle();
+
+ if (g_autoSaveInterval && !(s_getCycle() % g_autoSaveInterval)) {
+ s_save(g_simName);
+ }
+
+ end = clock();
+ delay = (float)(end - beg) / CLOCKS_PER_SEC;
+ } while (delay < (1.0 / 60.0));
+ }
+
+ tsp_printData();
+ tsh_handleEvent(getch());
+ }
+}
+
+static void
+quit(void)
+{
+ tsp_quit();
+ s_save(g_simName);
+ s_quit();
+}
+
+int
+main(int argc, char **argv)
+{
+ init(argc, argv);
+ exec();
+ quit();
+ return 0;
+}