My objective is to write a language which would have all the advantages of lisp, but easier to read and with fewer parentheses.
I am far from beeing the first to have tried to do it.
If you want to know more about it, you can visit this link
My goal is not to define a syntax to convert code to lisp-code and then execute it.
My goal is to process lists in a different way
I have made the eval2 function, which processes lists using triggers. Triggers are so general that they can handle functions, macros, but also variables, binary or unary operators, and much more. There is a notion of priority, and we do not need more parentheses than in any language different from lisp. The priority will not be defined once and for all. When you define what the + operator does, you will also define its priority over other operators.
In twinlisp, some operators are necessarily binary, some are unary... Here, it is not so. A symbol could correspond to anything, depending on what you do with it. The code will be written in common-lisp. It is not intended to be used, but to explain how triggers can process lists. Here, we will simply interpret code. However, I have also made a program using triggers, but to compile code. So, the aim is to write an eval2 function, which will evaluate lisp lists. A few triggers, which will be called by eval2 will also be entered.
A lisp interpretor needs to know what the arguments functions of a function or a macro are before to call it. With triggers, it is really different. In fact, the idea is to pass the whole list to process to the trigger, and it the trigger which will decide what to do with it. The trigger also gets what was previously evaluated. It returns a result, and the remaining list to evaluate. A trigger has a priority: The smaller it is, the more prioritary it is. A trigger also has a data. the trigger structure can simply be entered in common-lisp by:
(defstruct trigger func data prio)
the function func takes a result, a list, and the trigger itself. the data can be anything. prio is the priority, and it is a real number. The name trigger comes from the fact that triggers are used to trigger the call of a function. The eval2 function takes a list and a maximal priority. It returns the result and the remaining list to evaluate. It will call any trigger it encounters, and also call itself when it encounters a sublist. It will stop when it encounters a trigger whose priority is bigger than the maximal priority.
(defun eval2(f prio) (let ((result nil)) (loop (if (null f) ; if is has arrived to the end of the form, it returns (return-from eval2 (values result nil)) (let ((a (first f))) (if (symbolp a) (if (boundp a) (let ((trig (symbol-value a))) (if (< (trigger-prio trig) prio) ; tests the priority of the trigger (multiple-value-setq (result f) (funcall (trigger-func trig) result (rest f) trig)) (return-from eval2 (values result f)))) (error (format nil "unknown ~a" a))) (if (not (null result)) (error "two elements which are not symbols following") (progn (if (listp a) ; to evaluate sublists (setf result (eval2 a most-positive-single-float)) (setf result a)) (pop f)))))))))
This function alone is the core of the language.
Let's program binary operators: + * When called, a trigger corresponding to a binary operator will store the previous result sent by eval2. It will use eval2 to evaluate the remaining form, and call the function corresponding to the operator with the previous result and the new result. We could use a different trigger whith a different function for each operator. But I will rather write something general enough to handle any operator with a single function. So, there will be one trigger for each binary operator, but they will all use the function operator-fun. The data of each operator will be the lisp function to call.
(defun binary-operator-func (result f trig) (if (null f) (error "syntax error: nothing after a binary operator") (progn (multiple-value-setq (result-r f-r) (eval2 f (trigger-prio trig))) (values (funcall (trigger-data trig) result result-r) f-r)))); the data is the function corresponding to the operatorThe add-operator function will be used to enter a new trigger for an operator.
(defun add-binary-operator (symb func prio) (set symb (make-trigger :func #'binary-operator-func :data func :prio prio)))Now we can add the + and * operators:
(add-binary-operator '+ #'+ 4) (add-binary-operator '* #'* 3)We simply need to put a bigger priority to + than to * since * is prioritary over +
(eval2 '(2 * 4 + 2 * ( 5 + 3 )) most-positive-single-float)which will return 24 we use most-positive-single-float as a priority to send to eval2 beccause it is bigger than the one of any trigger. We will now see more precisely what happens when you evaluate the list '(2 * 3 + 4) with eval2 and most-positive-single-float as a priority. you can see it in common-lisp using (trace eval2) and (trace binary-operator-func)
1. Trace: (EVAL2 '(2 * 3 + 4) '3.4028235E38) 2. Trace: (OPERATOR-FUNC '2 '(3 + 4) '#S(TRIGGER :FUNC #detailed explanation::DATA # :PRIO 3)) 3. Trace: (EVAL2 '(3 + 4) '3) 3. Trace: EVAL2 ==> 3, (+ 4) 2. Trace: OPERATOR-FUNC ==> 6, (+ 4) 2. Trace: (OPERATOR-FUNC '6 '(4) '#S(TRIGGER :FUNC # :DATA # :PRIO 4)) 3. Trace: (EVAL2 '(4) '4) 3. Trace: EVAL2 ==> 4, NIL 2. Trace: OPERATOR-FUNC ==> 10, NIL 1. Trace: EVAL2 ==> 10, NIL
1. Trace: (EVAL2 '(2 * 3 + 4) '3.4028235E38)eval2 sees that the first element of f is 2, which is not a symbol. So, it simply affects 2 to the result. Then, it sees that the next element is *, which is a trigger. So, it calls its function operator-func, passing to it the previous result 2 and the remaining list '(3 + 4), and the trigger + itself.
2. Trace: (OPERATOR-FUNC '2 '(3 + 4) '#S(TRIGGER :FUNC #operator-func calls eval2 with the remaining list '(3 + 4), but with the priority of * as a maximum priority:DATA # :PRIO 3))
3. Trace: (EVAL2 '(3 + 4) '3)The eval2 function firstly affects 3 to the result. Then, since the priority of the trigger + is bigger than the maximum priority, it simply returns the result 3 and the remaing list '(+ 4)
3. Trace: EVAL2 ==> 3, (+ 4)Operator-func then applies the function * to the previous result 2 and what eval2 has returned, 3 So, it returns the 6 as a result and the remaining list '(+ 4) to eval2
2. Trace: OPERATOR-FUNC ==> 6, (+ 4)eval2 then calls the + trigger with 6 as a result and the remaining list '(4)
2. Trace: (OPERATOR-FUNC '6 '(4) '#S(TRIGGER :FUNC #It calls eval2 with the list '(4) and the priority of +:DATA # :PRIO 4))
3. Trace: (EVAL2 '(4) '4)eval2 simply returns 4 as a result and nil as the remaining list.
3. Trace: EVAL2 ==> 4, NILAnd the + trigger calls + with 4 and 6, and returns the result 10 to eval2 and nil as the remaining list.
2. Trace: OPERATOR-FUNC ==> 10, NILThen, eval2 returns 10
1. Trace: EVAL2 ==> 10, NIL
(defun variable-func (result f trig) (if (not (null result)) (error "syntax error") (values (trigger-data trig) f)))To enter variables more easily, we will use the lisp function:
(defun set-variable (symb value) (set symb (make-trigger :func #'variable-func :data value :prio 0)))We can now run: (set-variable 'a 3) (print (eval2 '(a * 4 + 3) most-positive-single-float)) This will print 11 We will now enter the trigger set-value to enter variables.
(setf set-value (make-trigger :func (lambda (gauche f trig) (let ((symb (first f)) (val)) (multiple-value-setq (val f) (eval2 (rest f) 100)) (set-variable symb val) (values nil f))) :prio 0))We can now use it with: (eval2 '(set-value a 4 + 2 * 3) most-positive-single-float)
(setf command-func (lambda (gauche f trig) (let ((val)) (multiple-value-setq (val f) (eval2 f 100)) (values (funcall (trigger-data trig) val) f)))) (setf print2 (make-trigger :func command-func :prio 0 :data #'print))The & trigger will be used as an equivalent of the ; in C to separate instructions
(setf & (make-trigger :func (lambda (gauche f trig) (let (val) (multiple-value-setq (val f) (eval2 f most-positive-single-float)) (values val f))) :prio 200))When set-value or print call eval2, eval2 must not evaluate the & trigger. This is why we put the priority 100 to set-value and print2 and 200 to & Now we can run:
(eval2 '(set-value a 5 + 6 & print2 a & print2 (a + 4) * 2 ) most-positive-single-float)