2
votes

As an exercise in Racket, I am trying to create a C-like function, accepting the return keyword.

When I define the function macro:

(require (for-syntax syntax/parse))
(define-syntax (function stx)
  (syntax-parse stx
    #:datum-literals (return)
    [(_ (func-id arg-id ...)  body ...)
     #'(define (func-id arg-id ...)
         (call/cc (lambda (return) body ...)))]))

and create a function f with a return statement:

(function (f x) (return 100) (+ 1 x))
(f 2)

I get:

return: undefined;
cannot reference an identifier before its definition

However, expanding the function macro:

(require macro-debugger/expand)
(syntax->datum (expand-only
                #'(function (f x) (return 100) (+ 1 x))
                (list #'function)))

returns:

'(define (f x) (call/cc (lambda (return) (return 100) (+ 1 x))))

and in fact:

(define (f x) (call/cc (lambda (return) (return 100) (+ 1 x))))
(f 2)
> 100

works as expected.

For my dummy case, it is possible to replace the pattern matching in function definition like so:

[(_ (func-id arg-id ...) (return x-expr) body ...)
 #'(define (func-id arg-id ...)
     (call/cc (lambda (return) (return x-expr) body ...)))]

But in general the return keyword can occur anywhere in the function body.

How can I do this?

2

2 Answers

5
votes

I think you misunderstand what datum-literals means. datum-literals is for pattern matching input syntax, not for output template/syntax.

What you attempt to do couldn't be done purely by rewriting rules because Racket's hygienic macro system will rename your return into something else to avoid name collision.

The usual way to do this is using syntax parameter. Fear of Macros explains this really well already, so I won't repeat it here.

0
votes

This seems to work:

(require (for-syntax syntax/parse))
(require racket/stxparam)
(define-syntax-parameter return
  (lambda (stx)
    (raise-syntax-error (syntax-e stx) "can only be used inside aif")))

(define-syntax (function stx)
  (syntax-parse stx
    [(_ (func-id arg-id ...) body ...)
     #'(define (func-id arg-id ...)
         (call/cc (lambda (return_k)                      
                    (syntax-parameterize ([return (syntax-rules () [(_ val) (return_k val)])])
                      body ...))))]))
(function (f x) (return 100) (+ 1 x))
(f 2)

I suppose syntax-parameterize can be improved with make-rename-transformer, but I did not succeed in that.