Eval in a function

The code as written only recognizes the bindings of +, -, * and / at the outermost level. Of course, if you don't want to recognize operators by bindings, it is possible to compare operators by symbols.

However, The name of the set of operators that $ recognizes could be configurable at expansion time or in user-provided code, but must be fixed before the code starts running and especially must be determined before any uses of the $ macro. At runtime, of course, you can provide a different interpretation of the names. A possible way is to have the configuration written in a separate module.

Unless you are using the top level i.e. the REPL, Racket won't let you redefine bindings. Note that the expansion time and the run time are separated, so it is important to add new operators to both expansion and run time. The macro add-new-operator implements one possible idea:

In a file ops.rkt:

#lang racket/base

(provide op-map)

(define op-map
  (make-hash
   (list (cons 'expt expt)
         (cons '* *)
         (cons '/ /)
         (cons '+ +)
         (cons '- -)
         (cons '= =))))

In the macro definition file, dollar.rkt:

#lang racket/base

(provide $ add-new-operator)

(struct op (sym) #:transparent)

(require (for-syntax racket/base "ops.rkt"))
(define-syntax (mark-$-ops stx)
  (syntax-case stx ()
    [(_ () (e-acc ...))
     #'(list e-acc ...)]
    [(_ (rator e ...) expr-acc)
     (and (identifier? #'rator) (hash-has-key? op-map (syntax-e #'rator)))
     #'(mark-$-ops (e ...) ((op 'rator) . expr-acc))]
    [(_ (expr0 e ...) expr-acc)
     #'(mark-$-ops (e ...) (expr0 . expr-acc))]))

(define-syntax $
  (syntax-rules ()
    [(_ e ...)
     (interp (reverse (mark-$-ops (e ...) ())))]))

(require "ops.rkt")
(define (interp expr)
  (printf "expr = ~s\n" expr)
  (for ([e (in-list expr)])
    (when (op? e)
      (printf "op ~s maps to: ~s\n" e (hash-ref op-map (op-sym e))))))

(define-syntax (add-new-operator stx)
  (syntax-case stx ()
    [(_ new-op)
     (let ()
       (hash-set! op-map (syntax-e #'new-op) #f)
       #'(hash-set! op-map 'new-op new-op))]))

Here is an example use that interprets = as eqv? and adds quotient to the list of operators:

#lang racket/base

(require "dollar.rkt" "ops.rkt")
(hash-set! op-map '= eqv?)

(define y 456)

(add-new-operator quotient)
(define (compute x)
  (let ([z 7])
    ($ x + 3 * y = quotient z 2)))

(compute 9)