Chez Scheme: macro import at top level
Asked Answered
G

1

7

I'm running Chez Scheme 9.5 and am trying to define a syntax transformer in a library. Here's an example:

(library (forlib)
  (export for)
  (import (rnrs (6)))

  (define-syntax for
    (syntax-rules (in)
      [(for x in lst body1 body2 ...)
       (for-each (lambda (x) body1 body2 ...) lst)])))

I save this in a file forlib.ss and run chez from the same directory. Then in the REPL, I get this:

> (import (forlib))
> (for x in '(1 2 3) (display x))
Exception: invalid syntax (for x in (quote (1 2 3)) (display x))
Type (debug) to enter the debugger.

If I change the syntax definition to

(define-syntax for
  (syntax-rules ()
    [(for x lst body1 body2 ...)
     (for-each (lambda (x) body1 body2 ...) lst)])))

(without the in keyword), everything works:

> (import (forlib))
> (for x '(1 2 3) (display x))
123
> _

Back to the old definition with the in keyword. If I put the test code into a test file:

;;; test-for.ss
(import (rnrs (6))
        (forlib))

(for x in '(1 2 3) (display x))

and try to execute this file, the result depends on how I execute the file. If I run this program using chez --program, it works as expected:

$ chez --program test-for.ss
123
$ _

If I run it using chez --script, I get the same error as above:

$ chez --script test-for.ss
Exception: invalid syntax (for x in (quote (1 2 3)) (display x)) at line 6, char 1 of test-for.ss
$ _

This raises two questions:

  • Why is the REPL and --script fine with importing syntax forms without special keywords but refuses to accept syntax forms that do have special keywords in them?
  • What exactly is the difference between --script and --program? The user manual says that --program means that the file content is interpreted as an rnrs top-level program but is silent as to what the semantics of --script are.

Finally, to make my consfusion complete, if I enter the above syntax definition directly in the REPL, then everything works as expected:

> (define-syntax for
    (syntax-rules (in)
      [(for x in lst body1 body2 ...)
       (for-each (lambda (x) body1 body2 ...) lst)])))
> (for x in '(1 2 3) (display x))
123
> _

So what is different in the REPL between syntax transformers imported from a library and syntax transformers defined directly in the REPL?

Greedy answered 13/1, 2019 at 22:57 Comment(3)
forlib needs to export 'in' as well as 'for'Provincetown
@ChrisVine: That's only part of the solution. If I were to try to export in as you suggest, I get an error because there is no name defined in forlib to be exported. As pointed out by @gmw below, for this to work, it is necessary for forlib to create a dummy name in that can be exported.Greedy
On examining this, you are right. See this: cisco.github.io/ChezScheme/csug9.5/use.html#./use:s14 . I got lucky with auxiliary keywords in code of mine because they were already defined by chezscheme and I was rebinding them within the macro. All I needed to do in that case was export them.Provincetown
C
6

In Chez Scheme, some semantics differ between modes of operation. I'm going to name these modes r6rs and repl.

R6RS:

  • programs (--program on the command line)
  • libraries

REPL:

  • scripts (--script on the command line)
  • files (Using the load procedure or as CLI arguments without --program or --script)
  • and the repl, of course

For your specific question, you need to define and export an identifier named in from forlib if you want the repl and scripts to match the behavior of libraries and programs. This can be as simple as an empty define, or you can make a syntactic definition that throws an error when used outside of the macro body. Simple:

(library (forlib)
  (export for in)
  (import (rnrs))

  (define in)

  (define-syntax for ...)
)

Syntactic Definition:

(library (forlib)
  (export for in)
  (import (rnrs))

  (define-syntax in
    (identifier-syntax
      (error #f "Misplaced aux syntax in")))

  (define-syntax for ...)
)

Both work fine; use whichever you prefer.

The other differences between repl and r6rs modes - that I know of - stem from how they evaluate expressions. In repl mode, each expression is handled on its own. That is to say, Chez reads an expression, evaluates said expression, and possibly prints the results. In r6rs mode, the entire contents of the file are evaluated as a single unit. Stated explicitly, the top-level continuation of repl mode is "read, eval, maybe print, and loop" while the top-level continuation of r6rs mode is the next expression. For example, this code will behave differently between running as a program or a script:

(import (chezscheme))

(define println)

(printf "~x\n"
  (call/cc (lambda (k)
             (set! println k)
             1)))

(println 5)
(println 6)

Another difference is that in r6rs mode you cannot define a function and then use it in a syntax-case macro expansion outside of the syntax-quote.

(define ($two) 2)

(define-syntax two
  (lambda (x)
    (syntax-case x ()
      [_ ($two)])))
Corona answered 15/1, 2019 at 0:15 Comment(1)
Thanks for the great answer. It's all I need to know to work with this type of code in Chez. I also appreciate some of the differences between REPL mode and R6RS mode due to the one-by-one nature of interacting with the REPL. Why macro imports behave differently between the two modes is still a mystery to me, though.Greedy

© 2022 - 2024 — McMap. All rights reserved.