1

I have problem with macros in my lisp interpreter writtein in JavaScript. the problem is in this code:

(define log (. console "log"))

(define (alist->object alist)
  "(alist->object alist)

   Function convert alist pairs to JavaScript object."
  (if (pair? alist)
      ((. alist "toObject"))))


(define (klist->alist klist)
  "(klist->alist klist)

   Function convert klist in form (:foo 10 :bar 20) into alist
   in form ((foo . 10) (bar . 20))."
  (let iter ((klist klist) (result '()))
    (if (null? klist)
        result
        (if (and (pair? klist) (pair? (cdr klist)) (key? (car klist)))
            (begin
              (log ":::" (cadr klist))
              (log "data" (. (cadr klist) "data"))
              (iter (cddr klist) (cons (cons (key->string (car klist)) (cadr klist)) result)))))))




(define (make-empty-object)
  (alist->object '()))

(define empty-object (make-empty-object))

(define klist->object (pipe klist->alist alist->object))

;; main function that give problems
(define (make-tags expr)
  (log "make-tags" expr)
  `(h ,(key->string (car expr))
      ,(klist->object (cadr expr))
      ,(if (not (null? (cddr expr)))
           (if (and (pair? (caddr expr)) (let ((s (caaddr expr))) (and (symbol? s) (eq? s 'list))))
               `(list->array (list ,@(map make-tags (cdaddr expr))))
               (caddr expr)))))


(define-macro (with-tags expr)
  (make-tags expr))

I call this macro using this code:

(define (view state actions)
  (with-tags (:div ()
                   (list (:h1 () (value (cdr (assoc 'count (. state "counter")))))
                         (:button (:onclick (lambda () (--> actions (down 1)))) "-")
                         (:button (:onclick (lambda () (--> actions (up 1)))) "+")))))

which should expand to almost the same code:

(define (view state actions)
  (h "div" (make-empty-object)
     (list->array (list
                   (h "h1" (make-empty-object) (value (cdr (assoc 'count (. state "counter")))))
                   (h "button" (klist->object `(:onclick ,(lambda () (--> actions (down 1))))) "-")
                   (h "button" (klist->object `(:onclick ,(lambda () (--> actions (up 1))))) "+")))))

This function works. I have problem with expanded code using my macro that call the main function, don't know how LIPS should behave when it find:

(:onclick (lambda () (--> actions (down 1))))

inside code and you try to process it like this:

,(klist->object (cadr expr))

Right now my lisp works that lambda is marked as data (have data flag set to true this is a hack to prevent of recursive evaluation of some code from macros) and klist->object function get lambda code as list, instead of function.

How this should work in Scheme or Common Lisp? Should klist->object get function object (lambda get evaluated) or list structure with lambda as first symbol? If second then how I sould write my function and macro to evaluate lambda should I use eval (kind of hack to me).

Sorry don't know how to test this, with more bug free LISP.

EDIT:

I've tried to apply the hint from @jkiiski in guile (because in my lisp it was not working)

;; -*- sheme -*-

(define nil '())

(define (key? symbol)
  "(key? symbol)

   Function check if symbol is key symbol, have colon as first character."
  (and (symbol? symbol) (eq? ":" (substring (symbol->string symbol) 0 1))))

(define (key->string symbol)
  "(key->string symbol)

   If symbol is key it convert that to string - remove colon."
  (if (key? symbol)
      (substring (symbol->string symbol) 1)))


(define (pair-map fn seq-list)
  "(seq-map fn list)

   Function call fn argument for pairs in a list and return combined list with
   values returned from function fn. It work like the map but take two items from list"
  (let iter ((seq-list seq-list) (result '()))
(if (null? seq-list)
    result
    (if (and (pair? seq-list) (pair? (cdr seq-list)))
        (let* ((first (car seq-list))
               (second (cadr seq-list))
               (value (fn first second)))
          (if (null? value)
              (iter (cddr seq-list) result)
              (iter (cddr seq-list) (cons value result))))))))


(define (klist->alist klist)
  "(klist->alist klist)

   Function convert klist in form (:foo 10 :bar 20) into alist
   in form ((foo . 10) (bar . 20))."
  (pair-map (lambda (first second)
              (if (key? first)
                  (cons (key->string first) second))) klist))

(define (h props . rest)
  (display props)
  (display rest)
  (cons (cons 'props props) (cons (cons 'rest rest) nil)))


(define (make-tags expr)
  `(h ,(key->string (car expr))
      (klist->alist (list ,@(cadr expr)))
      ,(if (not (null? (cddr expr)))
           (if (and (pair? (caddr expr)) (let ((s (caaddr expr))) (and (symbol? s) (eq? s 'list))))
               `(list->array (list ,@(map make-tags (cdaddr expr))))
               (caddr expr)))))


(define-macro (with-tags expr)
  (make-tags expr))

(define state '((count . 10)))

(define xxx (with-tags (:div ()
                             (list (:h1 () (cdr (assoc 'count state)))
                                   (:button (:onclick (lambda () (display "down"))) "-")
                                   (:button (:onclick (lambda () (display "up"))) "+")))))

but got error:

ERROR: Unbound variable: :onclick

I've found solution for my lisp, Here is code:

(define (pair-map fn seq-list)
  "(seq-map fn list)

   Function call fn argument for pairs in a list and return combined list with
   values returned from function fn. It work like the map but take two items from list"
  (let iter ((seq-list seq-list) (result '()))
    (if (null? seq-list)
        result
        (if (and (pair? seq-list) (pair? (cdr seq-list)))
            (let* ((first (car seq-list))
                   (second (cadr seq-list))
                   (value (fn first second)))
              (if (null? value)
                  (iter (cddr seq-list) result)
                  (iter (cddr seq-list) (cons value result))))))))

(define (make-tags expr)
  (log "make-tags" expr)
  `(h ,(key->string (car expr))
      (alist->object (quasiquote
                      ;; create alist with unquote for values and keys as strings
                      ,@(pair-map (lambda (car cdr)
                                    (cons (cons (key->string car) (list 'unquote cdr))))
                                  (cadr expr))))
      ,(if (not (null? (cddr expr)))
           (if (and (pair? (caddr expr)) (let ((s (caaddr expr))) (and (symbol? s) (eq? s 'list))))
               `(list->array (list ,@(map make-tags (cdaddr expr))))
               (caddr expr)))))

So in my code I'm writing some kind of meta macro I'm writing quasiquote as list that will get evaluated the same as if I use in my original code:

(klist->object `(:onclick ,(lambda () (--> actions (down 1)))))

I'm using alist->object and new function pair-map, so I can unquote the value and convert key symbol to string.

is this how it should be implemented in scheme? not sure If I need to fix my lisp or macros are working correctly there.

10
  • 1
    You're using a comma to evaluate the lambda-expression, so it should be a function object just like if you had written (list :onclick (lambda ...)) (quoting the :onclick if it's not self-evaluating in your lisp). Commented Apr 20, 2019 at 15:45
  • @jkiiski in my macro that don't use comma for lambda (,(lambda ...), it's in original code that I want to try to rewrite as macro it's ,(klist->object (cadr expr)) and it get the lambda as list structure. Commented Apr 20, 2019 at 16:01
  • So problem is that the macro isn't currently expanding to the example expansion you gave? Looking at the make-tags-function, it seems that you're calling klist->object at macroexpansion-time, rather than returning it. If I understand you correctly, you would want it to be something like ´(h ... (klist->object (list ,@(cadr expr))) ...) (that's supposed to be a backtick at the start, but I don't know how to write one in a comment here). Commented Apr 20, 2019 at 16:22
  • @jkiiski tried to apply your solution to guile scheme, because in my code it was not working, found a fix for my lisp but want also know how this should be implemented in scheme. (code for guile in collapsed snippet). Commented Apr 20, 2019 at 17:01
  • Keywords in Guile are written #:onclick rather than :onclick (I think there's a way to make it accept the latter too, but I'm not that familiar with Guile/Scheme), so it treats :onclick as a variable rather than a self-evaluating keyword. Commented Apr 20, 2019 at 17:16

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.