__________________________

                                               CLEANING UP THE RPN CALC

                                                                 lro
                                          __________________________


                                                       <2019-01-07 Mon>


Table of Contents
_________________

Current architecture
Saving user functions


So Last post I alluded that I was going to clean things things up a bit
and add a way to save any user define functions.

Lets have a general look at how things work at the moment, then go from
there.


Current architecture
====================

 At the moment, when the main loop encounters a function, it calls
 /run-func/ which checks if the `func' is the dictionary, and if it is
 run it otherwise print an error. And when the function is called, if
 its a primitive, the function is /rpn-func/, which takes a scheme
 function and how many arguments it needs, then /pop/s the appropriate
 number of arguments and calls the function on tose arguments and
 /push/es the result to the stack that is then returned. If a user
 defined function is called, then the list of functions is executed
 using /run-func/, and the altered stack passed along to the next
 function in the list.

 ,----
 |       +----------+    number   +------------+  list   +----------+
 |   +---|   push   |<------------| Main loop  |-------->| new-func |----|
 |   |   +----------+                     +------------+         +----------+    |
 |   |                             ^    |   ^ ^                          |
 |   +-----------------------------+    |   | |--------------------------|
 |                                      |   |
 |                                      |   |
 |                               symbol |   ------|
 |                                      |         |
 |                                 +----------+   |
 |                                 | run-func |   |
 |                                 +----------+   |
 |                                      |         |
 |                                      |         |
 |                                  +------+      |
 |                                  | func |      |
 |                                  +------+      |
 |                                      |         |
 |                                      |         |
 |                                 +----------+   |
 |                                 | rpn-func |----
 |                                 +----------+
 `----

 <file:rpn-calc-func-call-diagram.png>

 So its not terribly complex, but I would prefer if calling a function
 didn't jump thorugh so many hoops. But it isn't really that much
 abstraction. If I wanted to remove, I could write /rpn-func/ as a
 macro that expanded to a lambda that was juct the appropiate
 /let-values/ for many arguments the function took.

 For example:

 ,----
 | (rpn-func + 2)
 `----

 Would expand too:

 ,----
 | (lambda (stack dict)
 |   (let*-values (((var1 stack) (pop stack))
 |                               ((var2 stack) (pop stack)))
 |       (push (+ var1 var2) stack)))
 `----

 Which would be awesome, but I don't know if it's worth it. For now I
 think i'll just keep things as they are with regards to function
 calls. I don't want to use macros to much, I think it would be best to
 try and keep them to a minimum, so I'm not going to build `init-dict'
 at compile time either, maybe in my next phlog post as a little detour
 I can go ham on the macros, both as practice and something interesting
 to phlog about.


Saving user functions
=====================

 Looking at how I add user functions to the dictionary, it would be
 pretty easy to add some code to /new-func/ that writes the user
 functions to a file like /user-funcs/ and just write the lists to
 it. Then when we start the main loop, we can call a function that
 finds this file, and loads all the functions into the dictionary at
 start up. We can abuse/use read again by using /parameterize/ to make
 the standard input the /user-funcs/ file and add all the functions at
 startup using /new-func/.

 Unfortunately, there is no standard way in R7RS to append to a file,
 what bullshit.

 So we have some options:
 1. read through the whole /user-funcs/ to get to the end. (slow)
 2. just overwrite the file everytime we save. (stupid)
 3. read /user-funcs/ in at startup, save to a string, and append new
    functions to that string, and saves that string to the file.

 We will go with number 3.

 So firstly we need a function to add a new user func to the
 /user-funcs/ string. I have used /parameterize/ so that I can use
 /display/ which will print our newlines correctly so that "your-funcs"
 has each function on a line. It then _overwrites_ the file with the
 old funcs and the new func, and returns that string as well.

 ,----
 | (define (add-user-func list user-funcs file)
 |   (let ((func-to-add (list-as-string list)))
 |     (parameterize ((current-output-port (open-output-file file)))
 |       (let ((new-user-funcs (string-append user-funcs func-to-add "\n")))
 |             (display new-user-funcs)
 |             (close-output-port (current-output-port))
 |             new-user-funcs))))
 `----

 I needed a function that converted lists to a string, because schemes
 /list->string/ just converts a list of chars to one string.

 ,----
 | (define (list-as-string list)
 |   (parameterize ((current-output-port (open-output-string)))
 |     (write list)
 |     (get-output-string (current-output-port))))
 `----

 Now we can enter new functions to our save file, which is held in a
 variable.

 ,----
 | (define funcs-file "your-funcs")
 `----

 And now we need functions to retrieve the functions from the save file
 at the start of the main loop.

 ,----
 | (define (load-funcs-from-file-dict file dict)
 |   (with-input-from-file file
 |     (lambda ()
 |       (let loop ((input (read))
 |                              (dict dict))
 |             (if (eof-object? input)
 |               dict
 |               (loop (read) (new-func input dict)))))))
 |
 | (define (load-funcs-from-file-str file)
 |   (with-input-from-file file
 |     (lambda ()
 |       (let loop ((next-str (read-string 10))
 |                              (str ""))
 |             (if (eof-object? next-str)
 |               str
 |               (loop (read-string 10) (string-append str next-str)))))))
 `----

 And our new main loop

 ,----
 | (let loop ((input (read))
 |                (stack '())
 |                (dict (load-funcs-from-file-dict funcs-file init-dict))
 |                (user-funcs (load-funcs-from-file-str funcs-file)))
 |   (cond
 |    ((number? input) (loop (read) (push input stack) dict user-funcs))
 |    ((list? input) (let ((user-funcs (add-user-func input user-funcs funcs-file)))
 |                                     (loop (read) stack (new-func input dict) user-funcs)))
 |    ((symbol? input) (loop (read) (run-func input dict stack) dict user-funcs))
 |    (else (begin
 |                (display "ERROR not valid input: ")
 |                (display input)
 |                (newline)
 |                (loop (read) stack dict)))))
 `----

 I also found a bud in my /update-alist/ function, it added an extra
 layer of parenthesis when updating functions.

 ,----
 | (define (update-alist key new-val alist)
 |   (let ((index (index-in-alist key alist)))
 |     (list-set! alist index (cons key new-val))
 |     alist))
 `----

 So thats it for now, if anyone has any feature suggestions or patches
 or bug reports, feel free to email me, [email protected].

 Next time we might do some macro programming, or just actually go
 through and use this thing and do some cool RPN maths programming,
 we'll see.