tomhrr / dale

Lisp-flavoured C
BSD 3-Clause "New" or "Revised" License
1.03k stars 48 forks source link

Problems writing complex macros #122

Closed porky11 closed 8 years ago

porky11 commented 8 years ago

I wrote a macro for easier defining c-functions.

(def defc (macro extern (name linkage cname type args)
  (let ((arg-list \ (arg-list-names mc args))
        (fdef \ (qq (uq cname) (uql arg-list))))
    (print fdef) ;;debug, print the form
    (printf "\n")
    (qq do
      (def (uq cname) (fn extern-c (uq type) (uq args)))
      (def (uq name) (fn (uq linkage) (uq type) (uq args)
        ((uq cname) (uql arg-list))))))))

It contains a function, that convert a list like this ((a int) (b (p void))) to a list like that (a b) (so extracting the function names) I think I should use std.macros.walk-nodes, but I'm not sure, how to do it. I tried this

(def arg-list-names (fn extern (retval (p DNode)) ((mc (p MContext)) (form (p DNode)))
  (setf retval (copy mc true form))
  (def last-node (var auto \ (nullptr (p DNode))))
  (walk-nodes (@ retval) mc (cast last-node (p void))
    (fn int ((form (p DNode)) (mc (p MContext)) (data (p void)))
      (def last-node (var auto (p (p DNode)) (cast data (p (p DNode)))))
      (if (= 0 (@:@ form is-list)) 0
        (do (setf (:@ form is-list) 0)
            (setf (:@ form token-str) (@:@ (@:@ form list-node) token-str))
            (if (not (null (@ last-node)))
                (setf (:@ (@ last-node) next-node) form)
                0)
            (setf last-node form)
            0))))))

which causes segfault. I think, I have to link the nodes correctly, because when removing everything concerning last-node, i just get the first argument back. It seems weird to me, that DNodes, that are not lists, contain a next-node.

Maybe the documentation on macros (i.e. walk-nodes and the DNode struct) should be improved, or macros added to simplify macro creation.

tomhrr commented 8 years ago

The specific problem with arg-list-names is that it's attempting to dereference last-node when last-node may be null, at least when I attempt to use it with a form like (defc eg' extern eg int ((a int) (b int))). I don't think walk-nodes is appropriate in this case, because it's not clear that recursion is required. The following should work as expected:

(using-namespace std.macros
  (def defc (macro extern (name linkage cname type args)
    (let ((arg-list  \ (nullptr DNode))
          (arg-list' \ (nullptr DNode))
          (args'     \ (@:@ args list-node)))
      (while (not (null args'))
        (let ((name-node \ (copy mc false (@:@ args' list-node))))
          (if (null arg-list')
              (do (setv arg-list' name-node)
                  (setv arg-list arg-list'))
              (do (setf (:@ arg-list' next-node) name-node)
                  (setv arg-list' (@:@ arg-list next-node))))
          (setv args' (@:@ args' next-node))))
      (let ((fdef \ (qq (uq cname) (uql arg-list))))
        (print fdef) ;;debug, print the form
        (printf "\n")
        (qq do
          (def (uq cname) (fn extern-c (uq type) (uq args)))
          (def (uq name) (fn (uq linkage) (uq type) (uq args)
            ((uq cname) (uql arg-list))))))))))

The current DNode structure, where each node can potentially be a list node, appears to be confusing. Whether to change that to be more CL-like needs further thought; in the meantime, the documentation for qq has been updated to be more accurate.

If you have any ideas around a set of macros for simplifying macro authorship, that would be much appreciated.

porky11 commented 8 years ago

I think, I wrote this issue, before I really understood, how macros work. I first thought, the DNodes are similar to lists in lisp, but it's a bit different. So after understanding this, it should not be a problem