Separate Compilation using Compile-Time Macro Dependencies (a hack) 
Author Message
 Separate Compilation using Compile-Time Macro Dependencies (a hack)

[So this is somewhat long and conversational. So what?! At least it ain't
 another {*filter*}y Perl script!!]

I recently sent a query to the MIT Scheme list (comp.lang.scheme.c) asking
about the care and feeding of macros. A few folks sent back replies
addressing a somewhat different problem.  (But thanks! It was helpful.)
Since the difficulty I was having is likely to be a fairly common problem,
I thought I'd write it up briefly and share it w/ the rest of our group and
the Scheme community at large.

The query, simply put, is this:

  How do I define a macro in one file then use it in some other file and
  not get scrod?

I have crafted my own solution that I'll develop for you gradually but
first I'd like to be more clear about the real problem I've tried to solve

The Problem

This is not just a simple question of making sure your macros are
accessible from the system global syntax table (or some global package or
module or namespace or whatever you want to call it). Specifically, I don't
want to have to remember to explicitly load in some file of macro
definitions into my Scheme environment before I invoke the Scheme compiler
on my files that use those macros. I want the compiler to take care of that
itself with just some hint from me in the files that use the macro.

Just to be painfully explicit, take the following somewhat trivial example.
Say I want to define some macro like WHEN for a one-armed IF just so my
code is quite clear when I'm using a one-armed IF intentionally in the WHEN
idiom and not just losing with some typographical error that will distract
my attention when I'm trying to mull over my source code to find some other
_real_ bug.

(Yes, I know, your system already has a WHEN macro or there is some deep
m{*filter*}reason why I should use a COND to express this idiom and countless
other inane objections that net weenies and chronic pedants could foist
upon the Net of a Million Lies, but that's not what I'm driving at
here. Forget whether or not having a WHEN macro is more important than the
national debt and just relax and focus on the nature of the problem I'm
trying to get to. Sheesh!)

So here's a tired old obsolete macro syntax to define it, but at least it's

 (DEFINE-MACRO (when test .  commands)          ; Special form
  `(AND   ,test   (BEGIN . ,commands)))

If I save this text in a file then load that file into my running Scheme
environment so that the compiler will know about it, then thereafter
whenever I compiler some file that has an expression like:

  (when (odd? x)
    (display "Something odd's going on here."))

my compiler will re-write it, at compile time, to read:

  (and (odd? x) (display "Something odd's going on here."))

then proceed to generate the corresponding object code.

The Losing Scenario

Now here's the problem that has burned me a few times:

Say I go home thinking everything is just ducky (that's New English for
``OK'') but I print out some files to take with me to bed in case I have a
sudden urge to hack code in my sleep.  I log out and go home.

Sure enough, as soon as I start to doze off it suddenly occurs to me that
the termination condition of one my critical procedures is deficient: I
neglected to test for an obscure boundary case that only arises in Idaho
during a lunar eclipse.  So I mark up my printout with a quick fix then
doze off into restive bliss and dream of actually graduating some day.

Next morning, I throw on some clothes, down a cup of espresso and dive back
into the lab.

I quickly tickle the fix into my keyboard, my fingers flying like sparks
from a dragging muffler, and recompile the file.  That minor disaster
averted, I can then focus on the day's work.

The only problem is, when I logged in and fired up my Scheme, I neglected
to load in the magic macros file before I re-compiled that buggy file.  No
bother.  When I get around to loading all this stuff in and testing it,
I'll be sure at that point to have loaded all the stuff in that I need.

In MIT Scheme, however, the compiler is itself a Scheme program that runs
in your Scheme environment so that macros are expanded at compile time. If
I had loaded my magic macros into the system global syntax table before
calling the compiler, it would recognize WHEN as a macro and expand uses of
it. But since I forgot to load in my macros, the compiler assumes WHEN is
just a procedure call like any other. So, there in the future at link/load
and test time, I'll go down in flames 'cause I didn't have the macro
defined at compile time when it was needed.

What to Do?

Of course, I could just always load in every blast macro I have ever
defined at Scheme startup just to be sure I won't get hosed like this, but
that's not a very tasteful solution.

I could also run the source through some hairy cross-reference kludge to
flame about any free variables witnessed at compiler time, but that's also
not very elegant.

What I really want is some magic cookie in the source file that uses WHEN
that declares or otherwise documents to the compiler that WHEN is a macro
defined over there in the file name "magic-macros.scm" in the same file

That is, somewhere probably at the head of my WHEN-using source file, I
want to include some magic documentation like:

   (require-macros "magic-macros.scm")

Oh, and one last thing, let's pretend I'm not allowed to hack the compiler
itself to ferret out these non-portable declarations.  Let's pretend, just
for kicks, that I want a solution using MIT Scheme source stuff only.

How to Implement REQUIRE-MACROS in MIT Scheme

What I did was define one global state variable and a single macro named
REQUIRE-MACROS that are defined as part of my standard Scheme startup
(i.e., it is loaded by my ~/.scheme.init file in MIT Scheme).  This is all
I need to do at startup to ensure that everything else will work itself out
at compile time.

So here's the hack:

(DEFINE **macro-files-loaded** '())     ; Global state variable

  ;; Note: This macro's body is not quasiquoted, so it gets evaluated by the
  ;;       compiler at compile time (i.e., at macro expansion time).
  (IF (NOT (MEMQ <macro-filename> **macro-files-loaded**)) ; not yet loaded
        (LOAD <macro-filename>
              USER-INITIAL-ENVIRONMENT ; Uhm... load macro def'n into user env
              SYSTEM-GLOBAL-SYNTAX-TABLE) ;     and into system syntax table
        (SET! **macro-files-loaded**
              (CONS <macro-filename> **macro-files-loaded**))

Final Remarks

Notice that LOAD in MIT Scheme can take additional optional arguments: 1)
the environment into which the file definitions should be loaded and 2) the
syntax table into which file macro definitions are to be loaded.

In order to make the macro a little more human readable, I used
DEFINE-SYSTEM-GLOBAL-MACRO, which a few folks sent to me in answer to my
initial query. This, and a few other useful companion frobs, were posted on
comp.lang.scheme.c (by Michael Ernst and attributed by him to Robert Crew).
Its definition is:

    (macro (template . body)
           ',(CAR template)
         (MACRO ,(CDR template)

I could have instead presented the less readable version of REQUIRE-MACROS:

    (macro (<macro-filename>)


So maybe some of you may find this useful. Or maybe it will just spur a
Holy Roman macro debate or just an MIT Scheme bashing festival.  Frankly, I
don't really care either way: anything would be better than having to sit
through 800 more messages on Ada syntax. I mean really!![*?],/_{}!!

 Peace and Joy,


 (617) 253-0765 [O]        -.           545 Technology Square --- Room 439
 (617) 661-3394 [H]         /\.         Cambridge, MA   02139-3539

Mon, 29 Dec 1997 03:00:00 GMT  
 Separate Compilation using Compile-Time Macro Dependencies (a hack)

I'm not going to quote the whole (or even part of) ziggy's article.
He points out a valid problem:  compiling a file (separately) with or
without certain syntactic definitions in effect gives different
results, while compiling a file with or without any global value
definitions in effect gives you the same results.

Incidentially, the Scheme report doesn't have anything but global
value definitions (forgetting for a moment that there is a certain
macro proposal in the appendix).  From a semantics point of view there
is never a change of the denotation of a global variable -- they all
*always* denote a value cell.  Subsequent (re-)definitions (through
DEFINE) do not change this situation, because the standard requires
them to behave like a global SET!.

Now, with global syntactic definitions thrown into the picture we
arrive at difficulties like ziggy describes.

I see the current (inconsistent) definitions of the top level's
behavior as the source of all evil.  Therefore I vote for an ML-style

        - all identifiers must be declared before they can be used
        - definitions only affect the future, not the past

This does not solve ziggy's problem directly, but

        - the compiler could have issued an error message, telling him
          WHEN was not defined
        - separate compilation would be able to record assumptions
          about the environment a file is to be loaded into

As it turns out, separate copilation and its interaction with
(hygienic) macros is not entirely trivial.  (I am working on this
problem right now.)


Fri, 02 Jan 1998 03:00:00 GMT  
 [ 2 post ] 

 Relevant Pages 

1. Minor bug in ``Separate Compilation using Compile-Time Macro Dependencies''

2. Macros and separate compilation

3. library circularities (was: Macros and separate compilation)

4. circular modules (was: Re: Macros and separate compilation)

5. Separate keyword and separate compilation with GNAT?

6. separate keyword and separate compilation with GNAT?

7. New Moscow ML with separate compilation

8. Moscow ML with separate compilation

9. Modules and separate compilation

10. Preparsing and separate compilation in Modula-2.

11. Pragma Inline and its Effects of Compilation Dependencies.

12. Separate Compilation/Package interdependencies...


Powered by phpBB® Forum Software