version 0.3 v0.3
authorMart Lubbers <mart@martlubbers.net>
Wed, 27 Jan 2016 20:38:30 +0000 (21:38 +0100)
committerMart Lubbers <mart@martlubbers.net>
Wed, 27 Jan 2016 20:38:30 +0000 (21:38 +0100)
.gitignore
Clean System Files/Makefile
Clean System Files/readLine.c
Makefile
README.md
ReadLine.dcl
ReadLine.icl
test.icl

index f7c3c6c..0abc0fe 100644 (file)
@@ -1,3 +1,4 @@
 test
 *.abc
 *.o
+readline.history
index 154cf00..8a2abe6 100644 (file)
@@ -1,7 +1,8 @@
-CFLAGS=-Wall -pedantic
-LDFLAGS=-lreadline
+CFLAGS:=-Wall -pedantic -std=c99
+LDFLAGS:=-lreadline
+OBJS:=readLine.o
 
-all: readLine.o
+all: $(OBJS)
 
 clean:
-       $(RM) -v readLine.o
+       $(RM) -v $(OBJS)
index b72d31a..c73bc06 100644 (file)
@@ -7,8 +7,11 @@
 #include "Clean.h"
 
 static char *cs_answer = (char *)NULL;
+HISTORY_STATE *history_state = NULL;
 
-char *cleanStringToCString(CleanString s){
+//Helper functions
+char *cleanStringToCString(CleanString s)
+{
        unsigned long len = CleanStringLength(s);
        char *cs = (char *)malloc(len+1);
        if(cs == NULL){
@@ -20,7 +23,13 @@ char *cleanStringToCString(CleanString s){
        return cs;
 }
 
-void cleanReadLine(CleanString prompt, int history, CleanString *result)
+void cleanSetReadLineName(CleanString name)
+{
+       rl_readline_name = cleanStringToCString(name);
+}
+
+//Readline functions
+void cleanReadLine(CleanString prompt, int history, CleanString *result, int *eof)
 {
        char *cs_prompt = cleanStringToCString(prompt);
 
@@ -34,59 +43,205 @@ void cleanReadLine(CleanString prompt, int history, CleanString *result)
        if(cs_answer && *cs_answer && history){
                add_history(cs_answer);
        }
-
+       
+       *eof = 0;
        if(!cs_answer){ //In case of an EOF
-               CleanStringVariable(answer, 1);
-               *result = (CleanString) answer;
-               memcpy(CleanStringCharacters(answer), "", 1);
-               CleanStringLength(answer) = 1;
-       } else { //In case of a proper response
-               CleanStringVariable(answer, strlen(cs_answer));
-               *result = (CleanString) answer;
-               memcpy(CleanStringCharacters(answer), cs_answer, strlen(cs_answer));
-               CleanStringLength(answer) = strlen(cs_answer);
+               cs_answer = (char *)malloc(1);
+               cs_answer[0] = '\0';
+               *eof = 1;
        }
+       CleanStringVariable(answer, strlen(cs_answer));
+       *result = (CleanString) answer;
+       memcpy(CleanStringCharacters(answer), cs_answer, strlen(cs_answer));
+       CleanStringLength(answer) = strlen(cs_answer);
 }
 
-void cleanSetReadLineName(CleanString name){
-       rl_readline_name = cleanStringToCString(name);
+//History Functions
+//Initializing History and State Management
+void cleanUsingHistory()
+{
+       using_history();
 }
 
-void cleanUsingHistory(){
-       using_history();
+void cleanGetState(int *offset, int *num, int *flags)
+{
+       *offset = history_get_history_state()->offset;
+       *num = history_get_history_state()->length;
+       *flags = history_get_history_state()->flags;
+}
+
+void cleanGetHistoryItem(int num, CleanString *line, CleanString *timestamp)
+{
+       char *cs_line = history_get_history_state()->entries[num]->line;
+       char *cs_stamp = history_get_history_state()->entries[num]->timestamp;
+
+       CleanStringVariable(cleanLine, strlen(cs_line));
+       *line = (CleanString) cleanLine;
+       memcpy(CleanStringCharacters(cleanLine), cs_line, strlen(cs_line));
+       CleanStringLength(cleanLine) = strlen(cs_line);
+
+       CleanStringVariable(cleanTimestamp, strlen(cs_stamp));
+       *timestamp = (CleanString) cleanTimestamp;
+       memcpy(CleanStringCharacters(cleanTimestamp), cs_stamp, strlen(cs_stamp));
+       CleanStringLength(cleanTimestamp) = strlen(cs_stamp);
+}
+
+void cleanInitNewHistoryState(int offset, int flags, int num)
+{
+       if(history_state != NULL){
+               //we should test if we can recursively free the object
+               free(history_state);
+       }
+       history_state = (HISTORY_STATE *)malloc(sizeof(HISTORY_STATE));
+       if(history_state == NULL){
+               printf("Not enough memory...\n");
+               exit(1);
+       }
+       HIST_ENTRY **entries = (HIST_ENTRY **)malloc(sizeof(HIST_ENTRY*)*num);
+       if(entries == NULL){
+               printf("Not enough memory...\n");
+               exit(1);
+       }
+       for(int i = 0; i<num; i++){
+               entries[i] = (HIST_ENTRY *)malloc(sizeof(HIST_ENTRY));
+       }
+       history_state->offset = offset;
+       history_state->entries = entries;
+       history_state->length = num;
+       history_state->size = num;
+       history_state->flags = 0;
+}
+
+void cleanSetNewHistoryEntry(int i, CleanString line, CleanString timestamp)
+{
+       char *cs_line = cleanStringToCString(line);
+       char *cs_timestamp = cleanStringToCString(timestamp);
+       history_state->entries[i]->line = cs_line;
+       history_state->entries[i]->timestamp = cs_timestamp;
+}
+
+void cleanCommitSetHistory(void)
+{
+       history_set_history_state(history_state);
+}
+
+//History List Management
+void cleanAddHistoryTime(CleanString timestamp)
+{
+       char *cs_timestamp = cleanStringToCString(timestamp);
+       add_history_time(cs_timestamp);
+       free(cs_timestamp);
 }
 
-void cleanAddHistory(CleanString entry){
+void cleanAddHistory(CleanString entry)
+{
        char *cs_entry = cleanStringToCString(entry);
        add_history(cs_entry);
        free(cs_entry);
 }
 
-void cleanClearHistory(){
+void cleanRemoveHistory(int which, CleanString *line, CleanString *timestamp)
+{
+       HIST_ENTRY *entry = remove_history(which);
+       char *cs_line =  entry->line;
+       char *cs_stamp = entry->timestamp;
+
+       CleanStringVariable(cleanLine, strlen(cs_line));
+       *line = (CleanString) cleanLine;
+       memcpy(CleanStringCharacters(cleanLine), cs_line, strlen(cs_line));
+       CleanStringLength(cleanLine) = strlen(cs_line);
+
+       CleanStringVariable(cleanTimestamp, strlen(cs_stamp));
+       *timestamp = (CleanString) cleanTimestamp;
+       memcpy(CleanStringCharacters(cleanTimestamp), cs_stamp, strlen(cs_stamp));
+       CleanStringLength(cleanTimestamp) = strlen(cs_stamp);
+
+       histdata_t tofree = free_history_entry(entry);
+       free(tofree);
+}
+
+void cleanReplaceHistoryEntry(
+               int which, CleanString l, CleanString *line, CleanString *timestamp)
+{
+       char *cs_l = cleanStringToCString(l);
+       HIST_ENTRY *entry = replace_history_entry(which, cs_l, NULL);
+       free(cs_l);
+       if(entry == NULL){
+               printf("invalid which\n");
+       }
+
+       char *cs_line =  entry->line;
+       char *cs_stamp = entry->timestamp;
+
+       CleanStringVariable(cleanLine, strlen(cs_line));
+       *line = (CleanString) cleanLine;
+       memcpy(CleanStringCharacters(cleanLine), cs_line, strlen(cs_line));
+       CleanStringLength(cleanLine) = strlen(cs_line);
+
+       CleanStringVariable(cleanTimestamp, strlen(cs_stamp));
+       *timestamp = (CleanString) cleanTimestamp;
+       memcpy(CleanStringCharacters(cleanTimestamp), cs_stamp, strlen(cs_stamp));
+       CleanStringLength(cleanTimestamp) = strlen(cs_stamp);
+
+       histdata_t tofree = free_history_entry(entry);
+       free(tofree);
+}
+
+
+void cleanClearHistory()
+{
        clear_history();
 }
 
-int cleanHistorySearch(CleanString s, int dir){
+void cleanStifleHistory(int max)
+{
+       stifle_history(max);
+}
+
+int cleanUnstifleHistory()
+{
+       return unstifle_history();
+}
+
+int cleanHistoryIsStifled()
+{
+       return history_is_stifled();
+}
+
+//Information About the History List
+int cleanHistoryTotalBytes()
+{
+       return history_total_bytes();
+}
+
+//Moving Around the History List
+
+//Searching the History List
+int cleanHistorySearch(CleanString s, int dir)
+{
        char *cs_s = cleanStringToCString(s);
        int ret = history_search(cs_s, dir);
        free(cs_s);
        return ret;
 }
 
-int cleanHistorySearchPrefix(CleanString s, int dir){
+int cleanHistorySearchPrefix(CleanString s, int dir)
+{
        char *cs_s = cleanStringToCString(s);
        int ret = history_search_prefix(cs_s, dir);
        free(cs_s);
        return ret;
 }
 
-int cleanHistorySearchPos(CleanString s, int dir, int pos){
+int cleanHistorySearchPos(CleanString s, int dir, int pos)
+{
        char *cs_s = cleanStringToCString(s);
        int ret = history_search_pos(cs_s, dir, pos);
        free(cs_s);
        return ret;
 }
 
+//Managing the History File
 int cleanReadHistory(CleanString path)
 {
        char *cs_path = cleanStringToCString(path);
@@ -126,4 +281,3 @@ int cleanHistoryTruncateFile(CleanString path, int nlines)
        free(cs_path);
        return errno;
 }
-
index 46b3d7b..e3cfbd2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,19 @@
-SHELL:=/bin/bash
-CLM=clm
-CLMFLAGS=-nt -l -lreadline
+CLM:=clm
+CLMFLAGS:=-nt -l -lreadline
+LIBRARYDIR:=Clean\ System\ Files
+BINARIES:=test
 
-all: test
+.PHONY: readline clean
+
+all: readline $(BINARIES)
+
+readline:
+       $(MAKE) -C $(LIBRARYDIR)
 
 %: %.icl
        $(CLM) $(CLMFLAGS) $(basename $<) -o $@
 
 clean:
-       $(RM) -v test Clean\ System\ Files/{ReadLine,test}.*
+       $(MAKE) -C $(LIBRARYDIR) clean
+       $(RM) -v $(BINARIES)
+       $(RM) -v $(addprefix $(LIBRARYDIR)/,$(addsuffix .*,$(BINARIES)))
index 1f12c6e..9a5e329 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Clean readline Version 0.2
+Clean readline Version 0.3
 ==========================
 
 ReadLine is a Clean library that allows interactive console programs to use the
@@ -26,6 +26,12 @@ functions as close as possible
 
 ###Changelog
 
+- 0.3 (2016-01-26)
+  - Better build mechanism
+  - Information about the history list functions implemented, not thoroughly
+    tested though.
+  - Fixed issue #1
+  - History state can now be written as well as read
 - 0.2 (2016-01-25)
   - History file operations added
   - EOF is now properly handled
@@ -35,11 +41,8 @@ functions as close as possible
 
 ###Todo
 
-- Access to the history datastructure
-- Access to the current location
+- Use builtin Maybe if available, otherwise fall back on own Maybe
 - Complete history api implementation
-- Embed the readline library in the object files in such a way that no special
-  compiler flag is needed.
 - Control over tabcompletion, right now it completes on filenames.
 - Check possibilities for Windows/Mac
 
index ecc58bf..7828e88 100644 (file)
@@ -1,21 +1,49 @@
 definition module ReadLine
 
+import StdClass
+
+:: Maybe a = Nothing | Just a
+:: HistoryItem = {line :: String, timestamp :: String}
+:: HistoryState = {entries :: [HistoryItem], offset :: Int, flags :: Int}
+
+instance toString HistoryItem
+instance toString HistoryState
+
+//Maybe functions
+isNothing :: !(Maybe .x) -> Bool
+isJust    :: !(Maybe .x) -> Bool
+fromJust  :: !(Maybe .x) -> .x
+
+//Non-library functions
+setReadLineName :: !String !*env -> *env
+
 //Readline
-readLine :: !String !Bool !*env -> (!String, !*env)
-setReadLineName :: !String !*env -> !*env
+readLine :: !String !Bool !*env -> (!Maybe String, !*env)
 
 //Initializing History and State Management
 //Note that this HAS to be executed when you want to add entries when the
 //history has not been used
-usingHistory :: !*env -> !*env
+usingHistory :: !*env -> *env
+historyGetHistoryState :: !*env -> (!HistoryState, !*env)
+historySetHistoryState :: !HistoryState !*env -> *env
 
 //History List Management
-addHistory :: !String !*env -> !*env
-clearHistory :: !*env -> !*env
-//TODO some more functions
+addHistory :: !String !*env -> *env
+addHistoryTime :: !String !*env -> *env
+removeHistory :: !Int !*env -> (!HistoryItem, !*env)
+replaceHistoryEntry :: !Int !String !*env -> (!HistoryItem, !*env)
+clearHistory :: !*env -> *env
+stifleHistory :: !Int !*env -> *env
+unstifleHistory :: !*env -> *env
+historyIsStifled :: !*env -> (!Int, !*env)
 
 //Information About the History List
-//TODO
+historyList :: !*env -> (![HistoryItem], !*env)
+whereHistory :: !*env -> (!Int, !*env)
+currentHistory :: !*env -> (!Maybe HistoryItem, !*env)
+historyGet :: !Int !*env -> (!Maybe HistoryItem, !*env)
+historyGetTime :: HistoryItem -> String
+historyTotalBytes :: !*env -> (!Int, !*env)
 
 //Moving Around the History List
 //TODO
index 4bb84d1..4faf3ba 100644 (file)
@@ -4,77 +4,221 @@ import StdEnv
 
 import code from "readLine.o"
 
+//Maybe
+isNothing :: !(Maybe .x) -> Bool
+isNothing Nothing = True
+isNothing _ = False
+
+isJust    :: !(Maybe .x) -> Bool
+isJust Nothing = False
+isJust _ = True
+
+fromJust  :: !(Maybe .x) -> .x
+fromJust (Just x) = x
+
+instance toString HistoryItem where
+       toString {line,timestamp} = line +++ " (" +++ timestamp +++ ")"
+instance toString HistoryState where
+       toString {entries,offset,flags} = "[" +++ toS entries +++ "]\n" +++ 
+                       "offset: " +++ toString offset +++ "\nflags: " +++ toString flags
+               where
+                       toS :: [HistoryItem] -> String
+                       toS [] = "--empty--"
+                       toS [x] = toString x
+                       toS [x:xs] = toString x +++ ","
+
 //Readline
-readLine :: !String !Bool !*env -> (!String, !*env)
-readLine s h e = code {
-                       ccall cleanReadLine "SI:S:A"
-       }
+readLine :: !String !Bool !*env -> (!Maybe String, !*env)
+readLine s h e
+# (s, eof, e) = readLineEof s h e
+= (if eof Nothing (Just s), e)
+       where
+               readLineEof :: !String !Bool !*env -> (!String, !Bool, !*env)
+               readLineEof s h e = code {
+                               ccall cleanReadLine "SI:VSI:A"
+                       }
 
-setReadLineName :: !String !*env -> !*env
-setReadLineName s e = code inline {
-                       ccall cleanSetReadLineName "S-"
+setReadLineName :: !String !*env -> *env
+setReadLineName s e = code {
+               ccall cleanSetReadLineName "S:V:A"
        }
 
 //Initializing History and State Management
-usingHistory :: !*env -> !*env
-usingHistory e = code inline {
-                       ccall cleanUsingHistory "-"
+usingHistory :: !*env -> *env
+usingHistory e = code {
+               ccall cleanUsingHistory ":V:A"
        }
 
+historyGetHistoryState :: !*env -> (!HistoryState, !*env)
+historyGetHistoryState e
+# (offset, num, flags, e) = getState e
+# (entries, e) = getItems num e
+= ({HistoryState | entries=entries, offset=offset, flags=flags}, e)
+       where
+               getState :: !*env -> (!Int, !Int, !Int, !*env)
+               getState e= code {
+                               ccall cleanGetState ":VIII:A"
+                       }
+               getItems :: !Int !*env -> (![HistoryItem], !*env)
+               getItems 0 e = ([], e)
+               getItems i e
+               # (line, timestamp, e) = getItem (i-1) e
+               # (rest, e) = getItems (i-1) e
+               = ([{line=line,timestamp=timestamp}:rest], e)
+
+               getItem :: !Int !*env -> (!String, !String, !*env)
+               getItem i e = code {
+                               ccall cleanGetHistoryItem "I:VSS:A"
+                       }
+               
+
+historySetHistoryState :: !HistoryState !*env -> *env
+historySetHistoryState {entries,offset,flags} e
+# e = initNewHistoryState offset flags (length entries) e
+# e = setItems entries 0 e
+= commit e
+       where
+               initNewHistoryState :: !Int !Int !Int !*env -> *env
+               initNewHistoryState o f l e = code {
+                               ccall cleanInitNewHistoryState "III:V:A"
+                       }
+               setItems :: ![HistoryItem] !Int !*env -> *env
+               setItems [] _ e = e
+               setItems [x:xs] i e
+               # e = setItem i x.line x.timestamp e
+               = setItems xs (i+1) e
+
+               setItem :: !Int !String !String !*env -> *env
+               setItem i l t e = code {
+                               ccall cleanSetNewHistoryEntry "ISS:V:A"
+                       }
+               
+               commit :: !*env -> *env
+               commit e = code {
+                               ccall cleanCommitSetHistory ":V:A"
+                       }
+
 //History List Management
-addHistory :: !String !*env -> !*env
-addHistory s e = code inline {
-                       ccall cleanAddHistory "S-"
+addHistory :: !String !*env -> *env
+addHistory s e = code {
+               ccall cleanAddHistory "S:V:A"
        }
 
-clearHistory :: !*env -> !*env
-clearHistory e = code inline{
-                       ccall cleanClearHistory "-"
+addHistoryTime :: !String !*env -> *env
+addHistoryTime s e = code {
+               ccall cleanAddHistoryTime "S:V:A"
+       }
+
+removeHistory :: !Int !*env -> (!HistoryItem, !*env)
+removeHistory i e
+# (line, timestamp, e) = removeHistoryItem i e
+= ({HistoryItem | line=line, timestamp=timestamp}, e)
+       where
+               removeHistoryItem :: !Int !*env -> (!String, !String, !*env)
+               removeHistoryItem i e = code {
+                               ccall cleanRemoveHistory "I:VSS:A"
+                       }
+
+replaceHistoryEntry :: !Int !String !*env -> (!HistoryItem, !*env)
+replaceHistoryEntry i s e
+# (line, timestamp, e) = replaceItem i s e
+= ({HistoryItem | line=line, timestamp=timestamp}, e)
+       where
+               replaceItem :: !Int !String !*env -> (!String, !String, !*env)
+               replaceItem i s e = code {
+                               ccall cleanReplaceHistoryEntry "IS:VSS:A"
+                       }
+
+clearHistory :: !*env -> *env
+clearHistory e = code {
+               ccall cleanClearHistory ":V:A"
+       }
+
+stifleHistory :: !Int !*env -> *env
+stifleHistory i e = code {
+               ccall cleanStifleHistory "I:V:A"
+       }
+
+unstifleHistory :: !*env -> *env
+unstifleHistory e = code {
+               ccall cleanUnstifleHistory ":V:A"
+       }
+
+historyIsStifled :: !*env -> (!Int, !*env)
+historyIsStifled e = code {
+               ccall cleanHistoryIsStifled ":I:A"
        }
 
 //Information About the History List
+historyList :: !*env -> (![HistoryItem], !*env)
+historyList e
+# ({entries,offset,flags}, e) = historyGetHistoryState e
+= (entries, e)
+
+whereHistory :: !*env -> (!Int, !*env)
+whereHistory e
+# ({entries,offset,flags}, e) = historyGetHistoryState e
+= (offset, e)
+
+currentHistory :: !*env -> (!Maybe HistoryItem, !*env)
+currentHistory e
+# ({entries,offset,flags}, e) = historyGetHistoryState e
+= (if (isEmpty entries) Nothing (Just (entries!!offset)), e)
+
+historyGet :: !Int !*env -> (!Maybe HistoryItem, !*env)
+historyGet i e
+# ({entries,offset,flags}, e) = historyGetHistoryState e
+= (if (isEmpty entries) Nothing (Just (entries!!i)), e)
+
+historyGetTime :: HistoryItem -> String
+historyGetTime {line,timestamp} = timestamp
+
+historyTotalBytes :: !*env -> (!Int, !*env)
+historyTotalBytes e = code {
+               ccall cleanHistoryTotalBytes ":I:A"
+       }
 
 //Moving Around the History List
 
 //Searching the History List
 historySearch :: !String !Int !*env-> (!Int, !*env)
 historySearch s i e = code {
-                       ccall cleanHistorySearch "SI:I:A"
+               ccall cleanHistorySearch "SI:I:A"
        }
 
 historySearchPrefix :: !String !Int !*env-> (!Int, !*env)
 historySearchPrefix s i e = code {
-                       ccall cleanHistorySearchPrefix "SI:I:A"
+               ccall cleanHistorySearchPrefix "SI:I:A"
        }
        
 historySearchPos :: !String !Int !Int !*env-> (!Int, !*env)
 historySearchPos s i1 i2 e = code {
-                       ccall cleanHistorySearchPos "SI:I:A"
+               ccall cleanHistorySearchPos "SI:I:A"
        }
        
 
 //Managing the History File
 readHistory :: !String !*env -> (!Bool, !*env)
 readHistory s e = code {
-                       ccall cleanReadHistory "S:I:A"
+               ccall cleanReadHistory "S:I:A"
        }
 
 readHistoryRange :: !String !Int !Int !*env -> (!Bool, !*env)
 readHistoryRange s i1 i2 e = code {
-                       ccall cleanReadHistoryRange "SII:I:A"
+               ccall cleanReadHistoryRange "SII:I:A"
        }
 
 writeHistory :: !String !*env -> (!Bool, !*env)
 writeHistory s e = code {
-                       ccall cleanWriteHistory "S:I:A"
+               ccall cleanWriteHistory "S:I:A"
        }
 
 appendHistory :: !Int !String !*env -> (!Bool, !*env)
 appendHistory i s e = code {
-                       ccall cleanWriteHistory "IS:I:A"
+               ccall cleanWriteHistory "IS:I:A"
        }
                
 historyTruncateFile :: !String !Int !*env -> (!Bool, !*env)
 historyTruncateFile i s e = code {
-                       ccall cleanWriteHistory "SI:I:A"
+               ccall cleanWriteHistory "SI:I:A"
        }
index 729c126..6dd1f5a 100644 (file)
--- a/test.icl
+++ b/test.icl
@@ -3,16 +3,39 @@ module test
 import StdEnv
 import ReadLine
 
-Start :: *World -> (Int, String, *World)
+testHistory :: HistoryState
+testHistory = {HistoryState | 
+       entries=[{HistoryItem | line="custom", timestamp=""}],
+       offset=1, flags=0}
+
+Start :: *World -> (String, *World)
 Start w
-# (f, w) = stdio w
 #! w = setReadLineName "Test program" w
 #! w = usingHistory w
 #! (_, w) = readHistory "readline.history" w
-#! w = addHistory "testentry" w
+#! w = addHistory "testentry1" w
+#! w = addHistoryTime "time1" w
+#! w = addHistory "testentry2" w
+#! w = addHistoryTime "time2" w
+#! (hi, w) = removeHistory 1 w
+#! (hy, w) = replaceHistoryEntry 1 "replacement" w
 #! (s, w) = readLine "first prompt: " True w
 #! (s, w) = readLine "uparrow should word with history: " False w
 #! (_, w) = writeHistory "readline.history" w
 #! (i, w) = historySearch "testentry" -1 w
-//#! w = clearHistory w
-= (i, s, w)
+#! (_, w) = historyList w
+#! (_, w) = whereHistory w
+#! (_, w) = currentHistory w
+#! (_, w) = historyGet 1 w
+#! (_, w) = historyTotalBytes w
+#! w = stifleHistory 2 w
+#! (i, w) = historyIsStifled w
+#! w = unstifleHistory w
+#! (i, w) = historyIsStifled w
+#! (oh, w) = historyGetHistoryState w
+#! w = historySetHistoryState testHistory w
+#! (h, w) = historyGetHistoryState w
+#! w = clearHistory w
+= (
+       toString h,
+       w)