syntax-objects / Summer2021

Syntax Parse Bee 2021
11 stars 3 forks source link

Hierarchical parsing of command line arguments with the built-in command-line #16

Open Metaxal opened 3 years ago

Metaxal commented 3 years ago

Macro

The purpose of the first macro is to make it easy to parse command line arguments in a hierarchical way using the built-in command-line form. The second macro is an additional helper that displays the help message automatically when no command-line argument is specified at this level, which avoids the case where the user tries one argument is then has no information about what to do next.

;; Remove the first argument of the command line arguments
(define-syntax-parse-rule (shift-command-line-arguments body ...)
  (λ args
    (parameterize ([current-command-line-arguments (vector-copy (current-command-line-arguments) 1)])
      body ...)))

;; If the command line arguments are empty, re-parameterize it to
;; default to #("--help")
(define-syntax-parse-rule (parameterize-help-if-empty-ccla body ...)
  (let ([ccla (current-command-line-arguments)])
    (parameterize ([current-command-line-arguments
                    (if (vector-empty? ccla)
                      #("--help")
                      ccla)])
      body ...)))

Example

#lang racket
(require racket/cmdline
         syntax/parse/define)

;==================;
;=== The macros ===;
;==================;

;; Remove the first argument of the command line arguments
(define-syntax-parse-rule (shift-command-line-arguments body ...)
  (λ args
    (parameterize ([current-command-line-arguments (vector-copy (current-command-line-arguments) 1)])
      body ...)))

;; If the command line arguments are empty, re-parameterize it to
;; default to #("--help")
(define-syntax-parse-rule (parameterize-help-if-empty-ccla body ...)
  (let ([ccla (current-command-line-arguments)])
    (parameterize ([current-command-line-arguments
                    (if (vector-empty? ccla)
                      #("--help")
                      ccla)])
      body ...)))

;===============;
;=== Example ===;
;===============;

(define prog "my-prog")

(define (parse-relative)
  (parameterize-help-if-empty-ccla
   (command-line
    #:program (string-append prog " --relative")
    #:once-each
    [("--left") => (shift-command-line-arguments
                    (displayln "You're going left!")
                    (parse-main))
                '("Go to the left")]
    [("--right") => (shift-command-line-arguments
                    (displayln "You're going right!")
                    (parse-main))
                '("Go to the right")])))

(define (parse-absolute)
  (parameterize-help-if-empty-ccla
   (command-line
    #:program (string-append prog " --absolute")
    #:once-each
    [("--north") => (shift-command-line-arguments
                     (displayln "You're going north!")
                     (parse-main))
                 '("Go to the north")]
    [("--south") => (shift-command-line-arguments
                     (displayln "You're going south!")
                     (parse-main))
                 '("Go to the south")])))

(define (parse-move)
  (parameterize-help-if-empty-ccla
   (command-line
    #:program (string-append prog " --move")
    #:once-each
    [("--relative") => (shift-command-line-arguments (parse-relative))
                    '("Specify a relative direction")]
    [("--absolute") => (shift-command-line-arguments (parse-absolute))
                    '("Specify an absolute direction")])))

(define (parse-main)
  (command-line
   #:program prog
   #:once-each
   [("--move") => (shift-command-line-arguments (parse-move))
               '("Specify directions")]
   [("--jump") => (shift-command-line-arguments
                   (displayln "You're jumping!")
                   (parse-main))
               '("jump")]))

(module+ main
  (parse-main))

#| Interaction example:

$ racket syntax-bee.rkt --move --relative --left --jump --jump --move --absolute --south --jump
You're going left!
You're jumping!
You're jumping!
You're going south!
You're jumping!

|#

Before and After

I've heard several times that command-line can't parse arguments hierarchically. Well, turns out it's easy with syntax-parse! (although admittedly, syntax-rules would have worked too. But at least with syntax-parse it's easy to extend the macro to parse additional keywords, such as if one wants to shift the arguments by more than 1.)

These macros were written for a PR to resyntax. See the diff there.

Licence

Please confirm that you are submitting this code under the same MIT License that the Racket language uses. https://github.com/racket/racket/blob/master/racket/src/LICENSE-MIT.txt

Yes.

Please confirm that the associated text is licensed under the Creative Commons Attribution 4.0 International License http://creativecommons.org/licenses/by/4.0/

Yes.

Metaxal commented 3 years ago

Just realized this doesn't fit the purpose of Syntax Parse Bee, sorry! I may try to enhance it to make it a worthwhile example... if I get the time (and an idea).

Feel free to discard it.

bennn commented 3 years ago

Hm? This looks like a perfect fit to me --- two useful syntax-parse macros. It's fine that these would work as normal syntax rules.