I'm currently working on a small program where I was in need of carrying what the programmer entered along until a later point of execution.
I'll first state the problem I wanted to solve, as I may be approaching this in the wrong way in shen.
I'm doing a small test framework for my future needs. Two requirements that I have are that the tests that the user enters are not run right away and that, in case of some failure, I will be able to show the user what was evaluated.
This means that I both need to remember the ast and need it to be evaluated at a later point in time, possibly more than one time at different time points t.
For example, from the current iteration, a failure may look like this:
```
(5-) (testing "Trivial Tests" (= 10 20) (= (* 10 (+ 20 30)) 10))
(6-) (run-tests)
Testing: Trivial Tests
Failed: Expected 10 // 10 But Got 20 // 20 Instead
Failed: Expected (* 10 (+ 20 30)) // 500 But Got 10 // 10 Instead
testing.done
```
As you see from the example I need to remember what the ast the user entered was.
I couldn't find a direct way to do this in shen as there isn't a direct quote/dequote mechanism.
The obvious first step is probably to have a macro, so that you have access to the ast at some point.
The problem is that you cannot directly return the ast itself, as, correctly, shen will evaluate it. For example:
```
(11-)(defmacro ast-macro [ast|Args] -> Args)
ast-macro
(12-) (ast 10)
10
(13-) (ast (= 10 20))
false
(14-) (ast (= 10 20) (= 20 30))
The function SHEN::|false| is undefined.
```
For example, in `(14-)`, the shen reader will see something like `[[= 10 20] [= 20 30]]` and evaluate it as `((= 10 20) (= 20 30)) -> (false (= 20 30) -> exception`.
To bring an ast along, it is thus needed to present it in a way in which the shen reader will not need to evaluate it right away.
This might be done, for example, with an abstraction:
```
(defmacro ast-macro [ast|Args] -> [/. _ (/. _ Args)])
ast-macro
(31-) (((ast (= 10 20) (= 20 30)) _) _)
[[= 10 20] [= 20 30]]
```
By calling the abstraction ( or a function too ) at reading time we will have access to the ast which we can then carry along.
From my understanding, the abstraction will contain the ast and return a closure whose insides are opaque to the reader and that will evaluate to itself, such that its contained value will be accessible later.
This is a bit unsightly and I'm not really sure this is the easiest way to write this, but the other things I tried haven't really worked.
For example, building an ast that builds the abstraction will not work, Args seems to have been reduced at least once by that time, such that the following is incorrect:
```
(32-)(defmacro ast-macro [ast|Args] -> [/. _ Args])
ast-macro
(33-) ((ast (= 10 20) (= 20 30)) _)
The function SHEN::|false| is undefined.
```
Similarly, trying to return the abstraction directly results in an error, albeit I'm not sure about the actual explanation for this and if it is correct that this is the case:
```
(34-) (defmacro ast-macro [ast|Args] -> (/. _ Args))
ast-macro
(35-) ((ast (= 10 20) (= 20 30)) _)
; in: #<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100347F48B}> '_
; (#<CLOSURE (LAMBDA (SHEN::_) :IN SHEN::|ast-macro|) {100347F48B}> 'SHEN::_)
;
; caught ERROR:
; illegal function call
;
; compilation unit finished
; caught 1 ERROR condition
Execution of a form compiled with errors.
Form:
(#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100347F48B}> '_)
Compile-time error:
illegal function call
(36-) (ast (= 10 20) (= 20 30))
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10034A295B}>
```
As I said, I'm not sure about why this happens but I feel it might have something to do with the reader expecting a list of symbols or something like that.
If we try to enclose the lambda in a "list" it won't work but if we actually build a list with the abstraction inside, the computation will go trough:
```
(37-) (defmacro ast-macro [ast|Args] -> [(/. _ Args)])
ast-macro
(38-) (ast (= 10 20) (= 20 30))
#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {1003514CBB}>
(39-) ((ast (= 10 20) (= 20 30)) _)
; in: #<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100351FEEB}> '_
; (#<CLOSURE (LAMBDA (SHEN::_) :IN SHEN::|ast-macro|) {100351FEEB}> 'SHEN::_)
;
; caught ERROR:
; illegal function call
;
; compilation unit finished
; caught 1 ERROR condition
Execution of a form compiled with errors.
Form:
(#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {100351FEEB}> '_)
Compile-time error:
illegal function call
(40-) (defmacro ast-macro [ast|Args] -> [cons (/. _ Args) []])
ast-macro
(41-) ((ast (= 10 20) (= 20 30)) _)
The value
(#<CLOSURE (LAMBDA (_) :IN |ast-macro|) {10035B413B}>)
is not of type
(OR FUNCTION SYMBOL)
when binding FUNCTION
(42-) ((hd (ast (= 10 20) (= 20 30))) _)
[[= 10 20] [= 20 30]]
```
All of those forms, tough, aren't particularly appreciable, as the ast will have to be extracted by providing a unused argument to the abstraction. There might be a way to avoid this by instead compiling a 0-arity function in a null package that returns the ast for us and have the reader call it, but I haven't really tried that way as I feel the complexity is not worth it.
The last example actually shows us that there is another way to produce the ast.
The reader evaluates a list of symbol as code, thus when we simply return the ast it will not remain in list form.
A way to avoid this is to actually have the reader evaluate code that builds the list itself.
For example:
```
(51-) (define to-cons
[] -> []
[X|Xs] -> [cons (to-cons X) (to-cons Xs)]
X -> X)
to-cons
(52-) (defmacro ast-macro [ast|Args] -> (to-cons Args))
ast-macro
(53-) (ast (= 10 20) (= 20 30))
[[= 10 20] [= 20 30]]
(54-) (ast (= (* 20 30) 20) (= 20 30) (do (output "something") (* 20 (* 30 (+ 50 10)))))
[[= [* 20 30] 20] [= 20 30] [do [shen.prhush "something" stoutput] [* 20 [* 30 [+ 50 10]]]]]
```
Each ast is normalized to it's cons form, such that the reader only sees the concatenations of a series of symbol instead of evaluable forms.
This is the simplest and cleanest way I've found, as it avoids the needs for non-intuitive retrievals of the ast and provides for a simple macro.
My actual use case is quite more complex than this, as I have various requirements as compiling the tests only once and providing for user defined setups and teardowns and so on.
Nonetheless, the basic problem is still that of bringing the non evaluated ast along.
Thus my questions are:
What is the best way to accomplish this in shen?
Are there some edge cases that I cannot see in my currently used solution?
What is the reason for the, seemingly idiosyncratic, behavior of the various abstraction versions of ast?
Is there some apparent misunderstanding of mine on the internal workings of shen from how I approached this?
Best Regards,
Di Sera Luca