Arc Forumnew | comments | leaders | submitlogin
5 points by rocketnia 3671 days ago | link | parent

For a step-by-step look at things...

  (mac somemacro (someobj)
    `(if ,someobj!field t nil))
When we enter any code at the REPL, it causes three phases to occur: Reading, compilation, and execution.[1]

The read phase processes the ( ) ` , symbols and gets this data structure of cons lists and symbols:

  (mac somemacro (someobj)
    (quasiquote (if (unquote someobj!field) t nil)))
Note that someobj!field is a single symbol whose name contains an exclamation point character.

At this point you can probably see the problem already. What you may have expected was ((unquote someobj) (quote field)), but what we got was (unquote someobj!field). This is technically because Arc doesn't implement ssyntax at the reader level; instead it uses Racket's reader without modification, and then it processes the ssyntax in the next phase.

Even though the issue should already be clear, I'm going to go through the rest of the process to illustrate macroexpansion.

At this point the compilation phase starts. It expands the (mac ...) macro call, and then it re-processes whatever that macro expands to. As it goes along, at some point it also processes the (quasiquote ...) special form, and it expands the someobj!field syntax. The result of expanding someobj!field is (someobj 'field), and since this isn't a special form or macro, it's compiled as a function call.

The overall result is Racket code. In case it helps show what's going on, I just went to http://tryarc.org/ and ran the following command:

  ($ (ac '(macro somemacro (someobj) `(if ,someobj!field t nil)) (list)))
This produced a bunch of Racket code, which looks like this if I format it nicely:

  ((lambda ()
     (ar-funcall3 _sref _sig (quote (someobj)) (quote somemacro))
     ((lambda ()
        (if (not (ar-false? (ar-funcall1 _bound (quote somemacro))))
          ((lambda ()
             (ar-funcall2 _disp "*** redefining " (ar-funcall0 _stderr))
             (ar-funcall2 _disp (quote somemacro) (ar-funcall0 _stderr))
             (ar-funcall2 _disp #\newline (ar-funcall0 _stderr))))
          (quote nil))
        (begin
          (let ((zz
                  (ar-funcall2 _annotate (quote mac)
                    (let ((| somemacro|
                            (lambda (someobj)
                              (quasiquote
                                (if (unquote (ar-funcall1 someobj (quote field)))
                                  t
                                  nil)))))
                      | somemacro|))))
            (namespace-set-variable-value! (quote _somemacro) zz)
            zz))))))
Personally, I never think about the raw Racket code. Instead I pretend the result of compilation is Arc code again, just without any macro calls or ssyntax:

  ((fn ()
     (sref sig (quote (someobj)) (quote somemacro))
     ((fn ()
        (if (bound (quote somemacro))
          ((fn ()
             (disp "*** redefining " (stderr))
             (disp (quote somemacro) (stderr))
             (disp #\newline (stderr))))
          (quote nil))
        (assign somemacro
          (annotate (quote mac)
            (fn (someobj)
              (quasiquote
                (if (unquote (someobj (quote field)))
                  t
                  nil)))))))))
Either way, you can see the original (if ... t nil) is still in there somewhere. :)

Finally, this Racket code is executed. It modifies the global environment via Racket's namespace-set-variable-value! and puts a macro there. The macro is simply stored as a tagged value where the tag is 'mac and the content is a function. Then the result of execution is printed to the REPL as "#(tagged mac #<procedure: somemacro>)", and the REPL prompt appears again.

Later on, we execute the following command:

  (somemacro oo)
The reader parses this to make this s-expression:

  (somemacro oo)
Then the compilation phase starts. It starts to expand the (somemacro ...) macro call. To do this, it invokes the macro implementation we defined earlier. It passes in this list of s-expressions:

  (oo)
The macro's implementation is the function that resulted from this Racket code:

  (lambda (someobj)
    (quasiquote
      (if (unquote (ar-funcall1 someobj (quote field)))
        t
        nil)))
Or alternately, in my imagination, it's the result of this macroless Arc code:

  (fn (someobj)
    (quasiquote
      (if (unquote (someobj (quote field)))
        t
        nil)))
When this function is called, someobj's value is the symbol named "oo". When we try to call the symbol, we get an error.

The compilation phase terminates prematurely, and it displays the error on the console. The execution phase is skipped, since there's nothing to execute. Then the REPL prompt appears again.

I hope this gives people a good picture of the semantics of macroexpansion and ssyntax.

[1] Technically we might add printing and looping phases to get a full Read-Eval-Print-Loop. The eval step of the REPL does compilation followed by execution.

---

As I said above, the confusing point is probably that the reader doesn't give the result that you might expect when it sees ",someobj!field". On the one hand, this is a technical limitation of the fact that Arc uses Racket's reader which doesn't process ssyntax. On the other hand, I think it's debatable if this interpretation of the syntax is better or worse than the alternative.