Macro of [S:N] for in-range in Racket

回眸只為那壹抹淺笑 提交于 2020-01-05 03:48:15

问题


How can I create a macro so that S:N or [S:N] returns a range of numbers starting with S and ending with N (step 1). Basically, it should be able to use it in place of 'in-range'. I tried to create something similar to Curly brackets {} to replace 'begin' in Racket but could not.

Edit: I tried following as suggested by @soegaard :

my-top.rkt:

#lang racket
(define-syntax-rule (my-top S:N)
    (range S N) )

(provide (rename-out [my-top #%top]))

test.rkt:

#lang racket
 (require "my-top.rkt")

 (1:42)

But it does not run. The error is:

 #%top: use does not match pattern: (#%top S:N) in: (#%top . 1:42)

[1:42] and 1:42 also do not work.


回答1:


Here are the steps to make S:N expand to (range S N) where S and N are numbers.

Note that S:N is an identifier. Therefore an unbound S:N is an unbound identifier. An reference to an unbound identifiers n expand to (#%top . n). Therefore 1:42 expands into (#%top 1:42).

  1. Make a macro my-top such that (my-top S:N) expands to (range S N).
  2. Save your macro in file my-top.rkt and export it using (provide (rename-out [my-top #%top])).
  3. Use your new construct like this:

.

 #lang racket
 (require "my-top.rkt")
 1:42

Step 1:

#lang racket
(require syntax/parse (for-syntax racket/match syntax/parse))

(begin-for-syntax  
  ; contains-colon? : string -> boolean
  ;   does the string str contain a colon?
  (define (contains-colon? str)
    (regexp-match ".*:.*" str))

  ; split-colon-string-into-numbers : string -> (list number number)
  ;    for a string of the form N:S return a list consisting of the
  ;    numbers corresponsing to the substrings N and S
  (define (split-colon-string-into-numbers str)    
    (match (regexp-match "(.*):(.*)" str)
      [(list _ S-str N-str)
       (list (string->number S-str) (string->number N-str))]
      [_else
       (error 'split-colon-string-into-numbers
              "expected string of the number <number>:<number>")])))

; SYNTAX (my-top . id)
;   (my-top . id)  behaves the same as (#%top . id)
;   except when id has the form N:S in which case
;   (my-top . id) behaves as (range N S)
(define-syntax (my-top stx)
  (syntax-parse stx
    [(_my-top . identifier:id)     
     (define str (symbol->string (syntax-e #'identifier)))
     (cond
       [(contains-colon? str)
        (with-syntax ([(S N) (split-colon-string-into-numbers str)])
          (syntax/loc stx
            (range S N)))]
       [else
        #'(#%top . identifier)])]))

;;; Tests

(my-top . 1:5)    ; evaluates to (1 2 3 4)
(define foo 42)
(my-top . foo)    ; evaluates to 42



回答2:


@soegaard's answer provided a #%top-based solution which expands S:N when S and N are literal integers and S:N isn't defined as an identifier. However, it's also possible to do this with a reader macro.

I've made two versions: a simple version that only works with literal integers, and another version that works with arbitrary expressions, including variables.

The literal-integer version

This simple version overrides [ to begin range expressions like [S:N], where S and N are literal integers. After the [, it reads numeric characters until it finds a :, then it reads more numeric characters until it finds a ]. It converts the strings of numeric characters into integers, and puts those integers into a list representing a call to the range function.

It would be used like this:

#lang colon-range
;; simple range by itself
[1:42]
;; using a range within a more complicated expression
(for/list ((i [2:42])
           #:when
           (for/and ((j [2:41]) #:when (< j i))
             (not (= 0 (remainder i j)))))
  i)

Note that I'm using ((i ....)) instead of the more common ([i ....]) because I can't use [ and ] normally any more.

To implement the #lang colon-range language, you should put the reader implementation in colon-range/lang/reader.rkt, where colon-range is installed as a single-collection package.

;; s-exp syntax/module-reader is a language for defining new languages.
#lang s-exp syntax/module-reader
racket
#:wrapper1 (lambda (th)
             (parameterize ([current-readtable
                             (make-colon-range-readtable (current-readtable))])
               (th)))

;; This extends the orig-readtable with an entry for `[` to convert
;; `[1:42]` to `(range 1 42)`. In this simplistic implementation, they
;; have to be literal numbers, so it can't refer to a variable.
(define (make-colon-range-readtable orig-readtable)
  (make-readtable orig-readtable
    #\[ 'terminating-macro colon-range-proc))

;; This is the function that the new readtable will use when in encounters a `[`
(define (colon-range-proc char in src ln col pos)
  (define S (read-int-until #\: in src))
  (define N (read-int-until #\] in src))
  (list 'range S N))

;; This reads until it finds the given char (consuming it),
;; and returns an exact integer
(define (read-int-until char in src)
  (define str (list->string (read-numeric-chars-until char in src)))
  (define i (string->number str))
  (unless (exact-integer? i)
    (error 'read "expected an exact integer, given `~a`" str))
  i)

;; This reads until it finds the given char (consuming it), and returns a list
;; of characters. Each char it reads before that needs to be a numeric char,
;; otherwise it throws an error.
(define (read-numeric-chars-until char in src)
  (define c (read-char in))
  (cond [(eof-object? c)
         (error 'read "end-of-file: expected either a number or a `~a`, given `~a`"
                char c)]
        [(char=? char c)
         (list)]
        [(char-numeric? c)
         (cons c (read-numeric-chars-until char in src))]
        [else
         (error 'read "expected either a number or a `~a`, given `~a`"
                char c)]))

The arbitrary-expression version

This version overrides both [ and :. It defines : as a separator so that a:b reads the same as a : b, and it defines [ as a reader macro that reads a normal list and processes it afterwards. So it will first take [a : b] as a list of three elements, and then translate it to (range a b).

It can be used like this:

#lang colon-range
;; simple range by itself
[1:42]
;; using a range within a more complicated expression
(for/list ([i [2:42]]
           #:when
           (for/and ([j [2:i]]) ; can refer to a variable
             (not (= 0 (remainder i j)))))
  i)
(define two 2)
(for/list ([i [two:42]] ; can refer to a variable for the start
           #:when
           (for/and ([j [two:(+ 1 (exact-floor (sqrt i)))]]) ; can use arbitrary expressions
             (not (= 0 (remainder i j)))))
  i)

The implementation looks like this (again in colon-range/lang/reader.rkt). The comments explain some of what it's doing.

;; s-exp syntax/module-reader is a language for defining new languages.
#lang s-exp syntax/module-reader
racket
#:wrapper1 (lambda (th)
             (parameterize ([current-readtable
                             (make-colon-range-readtable (current-readtable))])
               (th)))

;; This extends the orig-readtable with entries for `[` and `:` to convert
;; `[S:N]` to `(range S N)`.
(define (make-colon-range-readtable orig-readtable)
  (make-readtable orig-readtable
    #\[ 'terminating-macro colon-range-proc
    #\: 'terminating-macro separator-proc))

;; This is the function that the new readtable will use when in encounters a `[`
(define (colon-range-proc char in src ln col pos)
  ;; This reads the list of things ending with the character that closes `char`
  ;; The #f means it uses the racket reader for the first step, so that `[`
  ;; uses the normal behavior, grouping expressions into a reader-level list
  (define lst (read-syntax/recursive src in char #f))
  ;; This matches on that list to determine whether it has the shape `[S : N]`
  (syntax-case lst (:)
    [[S : N]
     ;; if it is, translate it to `(range S N)`
     (list 'range #'S #'N)]
    [_
     ;; otherwise leave it alone
     lst]))

;; This doesn't read any further and simply returns an identifier containing char,
;; so that it can act like a separator
(define (separator-proc char in src ln col pos)
  (char->identifier char (list src ln col pos 1)))

(define (char->identifier char srcloc)
  (datum->syntax #f (string->symbol (string char)) srcloc))


来源:https://stackoverflow.com/questions/38387744/macro-of-sn-for-in-range-in-racket

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!