From 8026138b9c5c89cc6ddb776d3562bcbf6338d033 Mon Sep 17 00:00:00 2001
From: Simon Brooke <simon@journeyman.cc>
Date: Sat, 7 Jan 2017 10:48:28 +0000
Subject: [PATCH] Added primitive unit testing.

---
 src/conspage.c             |   6 --
 src/init.c                 |  10 +--
 src/print.c                |   6 +-
 src/read.c                 | 149 ++++++++++++++++++++++++-------------
 unit-tests.sh              |  33 ++++++++
 unit-tests/empty-string.sh |  13 ++++
 unit-tests/fred.sh         |  13 ++++
 unit-tests/nil.sh          |  13 ++++
 8 files changed, 180 insertions(+), 63 deletions(-)
 create mode 100755 unit-tests.sh
 create mode 100644 unit-tests/empty-string.sh
 create mode 100644 unit-tests/fred.sh
 create mode 100644 unit-tests/nil.sh

diff --git a/src/conspage.c b/src/conspage.c
index 77187b9..22a53e3 100644
--- a/src/conspage.c
+++ b/src/conspage.c
@@ -152,13 +152,7 @@ struct cons_pointer allocate_cell( char* tag) {
     if ( strncmp( &cell->tag.bytes[0], FREETAG, TAGLENGTH) == 0) {
       freelist = cell->payload.free.cdr;
 
-      fprintf( stderr, "Before: %c%c%c%c (%d)\n",
-	       cell->tag.bytes[0], cell->tag.bytes[1], cell->tag.bytes[2],
-	       cell->tag.bytes[3], cell->tag.value);
       strncpy( &cell->tag.bytes[0], tag, 4);
-      fprintf( stderr, "After: %c%c%c%c (%d)\n",
-	       cell->tag.bytes[0], cell->tag.bytes[1], cell->tag.bytes[2],
-	       cell->tag.bytes[3], cell->tag.value);
     
       cell->count = 0;
       cell->payload.cons.car = NIL;
diff --git a/src/init.c b/src/init.c
index f3d442d..98ece93 100644
--- a/src/init.c
+++ b/src/init.c
@@ -18,17 +18,15 @@
 #include "read.h"
 
 int main (int argc, char *argv[]) {
-  printf( "Post scarcity software environment version %s\n", VERSION);
+  // printf( "Post scarcity software environment version %s\n", VERSION);
   initialise_cons_pages();
 
-  printf( "Ready\n>> ");
-
   struct cons_pointer input = read( stdin);
-  inc_ref( input);
-  printf( "\n:: ");
+  //inc_ref( input);
+  //printf( "\n:: ");
   print( stdout, input);
 
-  dump_pages(stdout);
+  // dump_pages(stdout);
   // printf( "Tag2uint(\"FREE\") = %d\n", tag2uint("FREE"));
   
   return(0);
diff --git a/src/print.c b/src/print.c
index 6e0452e..f26514a 100644
--- a/src/print.c
+++ b/src/print.c
@@ -39,7 +39,11 @@ void print( FILE* output, struct cons_pointer pointer) {
     for (struct cons_pointer p = pointer; stringp( p);
 	 p = pointer2cell( p).payload.string.cdr) {
       // TODO: That's potentially a UTF character, needs more handling.
-      fprintf( output, "%c", (char)pointer2cell( p).payload.string.character);
+      char c = (char)pointer2cell( p).payload.string.character;
+
+      if ( c != '\0') {
+	fprintf( output, "%c", c);
+      }
     }
     fputc( '"', output);
   } else if ( check_tag( pointer, TRUETAG)) {
diff --git a/src/read.c b/src/read.c
index a5236f1..2354420 100644
--- a/src/read.c
+++ b/src/read.c
@@ -9,6 +9,7 @@
  */
 
 #include <ctype.h>
+#include <stdbool.h>
 #include <stdio.h>
 
 #include "consspaceobject.h"
@@ -21,67 +22,29 @@
    lists
    Can't read atoms because I don't yet know what an atom is or how it's stored. */
 
-/**
- * read a number from this input stream, given this initial character.
- */
-struct cons_pointer read_number( FILE* input, char initial) {
-  int accumulator = 0;
-  char c;
-  
-  for (c = initial; isdigit( c); c = fgetc( input)) {
-    int digitvalue = (int)c - (int)'0';
-    accumulator = accumulator * 10 + digitvalue;
-  }
-
-  /* push back the character read which was not a digit */
-  fputc( c, input);
-
-  return make_integer( accumulator);
-}
-
-
-struct cons_pointer read_list( FILE* input) {
-  struct cons_pointer car = read( input);
-  struct cons_pointer cdr = NIL;
-
-  char c = fgetc( input);
-
-  if ( c != ')' ) {
-    cdr = read_list( input);
-  }
-
-  return make_cons( car, cdr);
-}
-
-
-struct cons_pointer read_string( FILE* input) {
-  struct cons_pointer cdr = NIL;
-  struct cons_pointer result= NIL;
-  
-  char c = fgetc( input);
-
-  if ( c != '"' ) {
-    result = make_string( c, read_string( input));
-  }
-
-  return result;
-}
+struct cons_pointer read_number( FILE* input, char initial);
+struct cons_pointer read_list( FILE* input, char initial);
+struct cons_pointer read_string( FILE* input, char initial);
 
 
 /**
- * read the next object on this input stream and return a cons_pointer to it.
+ * Read the next object on this input stream and return a cons_pointer to it,
+ * treating this initial character as the first character of the object
+ * representation.
  */
-struct cons_pointer read( FILE* input) {
+struct cons_pointer read_continuation( FILE* input, char initial) {
   struct cons_pointer result = NIL;
 
   char c;
 
-  for (c = fgetc( input); isblank( c); c = fgetc( input));
+  for (c = initial; c == '\0' || isblank( c); c = fgetc( input));
   
   switch( c) {
-  case '(' : result = read_list(input);
+  case '(' :
+  case ')':
+    result = read_list(input, fgetc( input));
     break;
-  case '"': result = read_string(input);
+  case '"': result = read_string(input, fgetc( input));
     break;
   case '0':
   case '1':
@@ -102,6 +65,92 @@ struct cons_pointer read( FILE* input) {
 
   return result;
 }
+
+
+/**
+ * read a number from this input stream, given this initial character.
+ */
+struct cons_pointer read_number( FILE* input, char initial) {
+  int accumulator = 0;
+  int places_of_decimals = 0;
+  bool seen_period = false;
+  char c;
+
+  fprintf( stderr, "read_number starting '%c' (%d)\n", initial, initial);
+  
+  for (c = initial; isdigit( c); c = fgetc( input)) {
+    if ( c == '.') {
+      seen_period = true;
+    } else {
+      accumulator = accumulator * 10 + ((int)c - (int)'0');
+
+      if ( seen_period) {
+	places_of_decimals ++;
+      }
+    }
+  }
+
+  /* push back the character read which was not a digit */
+  fputc( c, input);
+
+  return make_integer( accumulator);
+}
+
+
+/**
+ * Read a list from this input stream, which no longer contains the opening
+ * left parenthesis.
+ */
+struct cons_pointer read_list( FILE* input, char initial) {
+  struct cons_pointer cdr = NIL;
+  struct cons_pointer result= NIL;
+
+  fprintf( stderr, "read_list starting '%c' (%d)\n", initial, initial);
+  
+  if ( initial != ')' ) {
+    struct cons_pointer car = read_continuation( input, initial);
+    cdr = read_list( input, fgetc( input));
+    result = make_cons( car, cdr);
+  }
+
+  return result;
+}
+
+
+/** 
+ * Read a string from this input stream, which no longer contains the opening
+ * double quote. Note that there is (for now) a problem with the list
+ * representation of a string, which is that there's no obvious representation of
+ * an empty string.
+ */
+struct cons_pointer read_string( FILE* input, char initial) {
+  struct cons_pointer cdr = NIL;
+  struct cons_pointer result;
+
+  fprintf( stderr, "read_string starting '%c' (%d)\n", initial, initial);
+
+  switch ( initial) {
+  case '\0':
+    result = make_string( initial, NIL);
+    break;
+  case '"':
+    result = make_string( '\0', NIL);
+    break;
+  default:
+    result = make_string( initial, read_string( input, fgetc( input)));
+    break;
+  }
+
+  return result;
+}
+
+
+/**
+ * Read the next object on this input stream and return a cons_pointer to it.
+ */
+struct cons_pointer read( FILE* input) {
+  return read_continuation( input, '\0');
+}
     
 
 
diff --git a/unit-tests.sh b/unit-tests.sh
new file mode 100755
index 0000000..da99fa6
--- /dev/null
+++ b/unit-tests.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Early stage unit test runner for post-scarcity software environment.
+# Uses bash to run every file in the unit-tests subdirectory; expects these
+# to return 0 on success, anything else on fail.
+
+# (c) 2017 Simon Brooke <simon@journeyman.cc>
+# Licensed under GPL version 2.0, or, at your option, any later version.
+
+tests=0
+pass=0
+fail=0
+
+for file in unit-tests/*
+do
+    echo ${file}
+    bash ${file}
+
+    if [ $? -eq 0 ]
+    then
+	pass=$((${pass} + 1))
+    else
+	fail=$((${fail} + 1))
+    fi
+
+    tests=$((${tests} + 1))
+done
+
+echo
+echo "Tested ${tests}, passed ${pass}, failed ${fail}"
+
+exit ${fail}
+
diff --git a/unit-tests/empty-string.sh b/unit-tests/empty-string.sh
new file mode 100644
index 0000000..c8e947a
--- /dev/null
+++ b/unit-tests/empty-string.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+expected="\"\""
+actual=`echo '""' | target/psse 2> /dev/null`
+
+if [ "$expected" = "$actual" ]
+then
+    echo "OK"
+    exit 0
+else
+    echo "Expected '$expected', got '$actual'"
+    exit 1
+fi
diff --git a/unit-tests/fred.sh b/unit-tests/fred.sh
new file mode 100644
index 0000000..2be3096
--- /dev/null
+++ b/unit-tests/fred.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+expected=\"Fred\"
+actual=`echo '"Fred"' | target/psse 2> /dev/null`
+
+if [ "$expected" = "$actual" ]
+then
+    echo "OK"
+    exit 0
+else
+    echo "Expected '$expected', got '$actual'"
+    exit 1
+fi
diff --git a/unit-tests/nil.sh b/unit-tests/nil.sh
new file mode 100644
index 0000000..249c51b
--- /dev/null
+++ b/unit-tests/nil.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+expected=NIL
+actual=`echo '()' | target/psse 2> /dev/null`
+
+if [ "${expected}" = "${actual}" ]
+then
+    echo "OK"
+    exit 0
+else
+    echo "Expected '${expected}', got '${actual}'"
+    exit 1
+fi