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.

jcubic
  • 61,973
  • 54
  • 229
  • 402
  • 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). – jkiiski Apr 20 '19 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. – jcubic Apr 20 '19 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). – jkiiski Apr 20 '19 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). – jcubic Apr 20 '19 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. – jkiiski Apr 20 '19 at 17:16
  • @jkiiski they are not keywords in my lisp they are normal symbols that are evaluated to variables if used outside quote. – jcubic Apr 20 '19 at 17:28
  • Just using a straight Scheme would help us to actually understand your code, since Scheme's semantics are known. Your language probably isn't and it might be hard to put a description of how your language might work into a Stackoverflow question. – Rainer Joswig Apr 21 '19 at 07:27
  • It's a bit difficult to understand. Example: What is `(null? cddr)`supposed to do? – Rainer Joswig Apr 21 '19 at 07:39
  • @RainerJoswig it suppose to be `(null? (cddr xxx))` in edit there is my attempt in guile scheme (in collapsed stack snippet). – jcubic Apr 21 '19 at 08:27
  • @RainerJoswig you just found error in my code that (not (null? cddr)) will always be true since function is never null. – jcubic Apr 21 '19 at 08:29

0 Answers0