# Macro to Make Identifiers from String Diagram

**URL:** https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296
**Category:** Questions & Answers
**Tags:** macro
**Created:** [September 7, 2023, 4:56pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296 "2023-09-07T16:56:23Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)
#### Post date: [September 7, 2023, 4:56pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/1 "2023-09-07T16:56:23Z")

</div>

Hi, Racket Discourse.

I am trying to write a macro using some bits of beautiful-racket so that I can provide a string encoding of a diagram and convert the letter names in the diagram to identifiers which are used to define metapict pts (points) bearing their coordinates. It ignores any empty spaces or dots.

When I call the macro, however, the resulting definitions do not seem to be correct, since I receive unbound identifier warnings when trying to reference them.

I am not very well-versed in writing macros, so take the code with a bit of salt, but the intent should be clear enough.

```scheme
#lang br

(require metapict
         (for-syntax racket/list
                     racket/string))

(define-macro-cases parse-diagram
  [(parse-diagram WHOLE)
   (with-syntax* ([(ROW ...) ;; split the diagram up into rows and reverse them
                   (reverse (filter non-empty-string? (string-split (syntax->datum #'WHOLE) "\n")))]
                  [(Y ...) ;; count the number of rows
                   (range (length (syntax->datum #'(ROW ...))))])
     #'(begin
         (parse-diagram ROW Y) ...))]
  
  [(parse-diagram ROW Y)
   (with-syntax* ([(COL ...) ;; split the row into columns
                   (filter non-empty-string? (string-split (syntax->datum #'ROW) ""))]
                  [(X ...) ;; count the number of columns
                   (range (length (syntax->datum #'(COL ...))))])
     #'(begin
         (name-pt COL X Y) ...))])

(define-macro-cases name-pt
  [(name-pt " " X Y)
   #'(void)]

  [(name-pt "." X Y)
   #'(void)]
  
  [(name-pt SYM X Y) ;; name the pt at X Y by the symbol at X Y if it is not a space or a dot
   (with-pattern ([$NAME (prefix-id '$ #'SYM)])
     #'(define $NAME (pt X Y)))])

(parse-diagram
"
Z.....A....B................................ N
      . . . . . .
      . . . . . .
      . . . . . .
      C..... D...E...F G...H...I J...K L
      . . . . . .
      . . . . . .
      . . . . . .
      O .....................................M
")

$Z

```

> $Z: unbound identifier in: $Z

I can make identifiers well-enough with the `name-pt` macro, but it seems as if some of the information is being hidden, or lost along the way.

```scheme
(name-pt "Z" 0 0) ;; this works fine
$Z

```

> (pt 0 0)

Any help would be appreciated.

---

<div class="post-metadata">

### Author: ![soegaard](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/soegaard/32/19_2.png) [@soegaard](https://racket.discourse.group/u/soegaard)
#### Post date: [September 7, 2023, 7:39pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/2 "2023-09-07T19:39:55Z")

</div>

Great idea for making it easier to draw diagrams.

In your `name-pt` the template is `#'(define $NAME (pt X Y)))`.  
The identifier `$NAME` is "new" so it will get the context (scope) from that macro invocation.  
However, the intention is to refer to `$NAME` where the macro call `(name-pt ..)` is.  
Therefore, the context of `$NAME` needs to change. You can do this with `format-id`.

Note that most of the bookkeping in your macro can be done as normal functions.  
It simplifies your macro, if the bookkeeping is outsourced to helper functions.  
See below.

```scheme
#lang racket

(require metapict
         (for-syntax racket/list
                     racket/string
                     racket/syntax
                     syntax/parse))

(begin-for-syntax
  (define (split-diagram-into-rows whole)
    (reverse (filter non-empty-string? (string-split whole "\n"))))

  (define (split-row-into-columns row)
    (filter non-empty-string? (string-split row "")))

  (define ($sym s)
    (string->symbol
     (string-append "$" s)))
  
  (define (find-ids-and-positions whole)
    ; returns a list of elements of the form ($name x y)    
    (define rows (split-diagram-into-rows whole))
    (append*
     (for/list ([row rows]
                [y (in-naturals)])
       (for/list ([col (split-row-into-columns row)]
                  [x (in-naturals)]
                  #:unless (member col '(" " ".")))
         (list ($sym col) x y)))))

  (define (change-context lctx id)
    (format-id lctx "~a" id))

  (define (change-contexts lctx ids)
    (for/list ([id (syntax->list ids)])
      (change-context lctx id))))

(define-syntax (parse-diagram stx)
  (syntax-parse stx
    [(parse-diagram WHOLE)     
     (with-syntax ([((id x y) ...) (find-ids-and-positions (syntax->datum #'WHOLE))])       
       (with-syntax ([(id ...) (change-contexts stx #'(id ...))])
         #'(begin
             (define id (pt x y))
             ...)))]))

(parse-diagram
"
Z.....A....B................................ N
      . . . . . .
      . . . . . .
      . . . . . .
      C..... D...E...F G...H...I J...K L
      . . . . . .
      . . . . . .
      . . . . . .
      O .....................................M
")

```

---

<div class="post-metadata">

### Author: ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)
#### Post date: [September 7, 2023, 9:09pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/3 "2023-09-07T21:09:38Z")

</div>

Thanks, @soegaard!

That's really cool. I like how easy it is to draw diagrams using `curve`s in metapict and being able to build off of a 2D "schematic" like this will be fun.

All the best.

---

<div class="post-metadata">

### Author: ![soegaard](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/soegaard/32/19_2.png) [@soegaard](https://racket.discourse.group/u/soegaard)
#### Post date: [September 7, 2023, 9:26pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/4 "2023-09-07T21:26:55Z")

</div>

Btw, you might be interested in the path operations:

```scheme
 /- ; p /- q connect p and q with line(s) first vertical, then horizontal
 -/ ; p -/ p connect p and q with line(s) first horizontal, then vertical

```

Example:

```scheme
(require metapict metapict/path-operations)
(def A (pt 0 0))
(def B (pt 1 1))
(draw (curve A /- B))

```

will draw up, then right from A to B.

 ![image](https://global.discourse-cdn.com/free1/uploads/racket/original/2X/2/22eadd7accdac2969a32a24eaaa04e00b0359d98.jpeg)

---

<div class="post-metadata">

### Author: ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)
#### Post date: [September 7, 2023, 9:37pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/5 "2023-09-07T21:37:20Z")

</div>

Very nice; I saw that the path operations are mentioned in the documentation, but I haven't looked at them any further than `--` and `..`. They will probably be most useful--I have been doing something similar although not quite as ergonomic to find these joints using:

```scheme
(define (cross P Q)
  (pt (pt-x P) (pt-y Q))) ;; swap P and Q for the up-then-right vs. right-then-up joint

```

I will definitely have a look 💯.

---

<div class="post-metadata">

### Author: ![sorawee](https://avatars.discourse-cdn.com/v4/letter/s/ea5d25/32.png) [@sorawee](https://racket.discourse.group/u/sorawee)
#### Post date: [September 7, 2023, 11:24pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/6 "2023-09-07T23:24:10Z")

</div>

Here’s my version:

```scheme
#lang racket

(require metapict
         (for-syntax racket/string
                     racket/syntax
                     syntax/parse))

(begin-for-syntax
  (define (split-diagram-into-rows whole)
    (reverse (string-split whole "\n")))

  (define (split-row-into-columns row)
    (map string (string->list row)))

  (define ($sym s lctx)
    (format-id lctx "$~a" s))

  (define (find-ids-and-positions whole lctx)
    ; returns a list of elements of the form ($name x y)    
    (define rows (split-diagram-into-rows whole))
    (for*/list ([(row y) (in-indexed rows)]
                [(col x) (in-indexed (split-row-into-columns row))]
                #:unless (member col '(" " ".")))
      (list ($sym col lctx) x y))))

(define-syntax (parse-diagram stx)
  (syntax-parse stx
    [(parse-diagram WHOLE)
     #:with ((id x y) ...) (find-ids-and-positions (syntax-e #'WHOLE) stx)
     #'(begin (define id (pt 'x 'y)) ...)]))

(parse-diagram #<<EOF
Z.....A....B................................ N
      . . . . . .
      . . . . . .
      . . . . . .
      C..... D...E...F G...H...I J...K L
      . . . . . .
      . . . . . .
      . . . . . .
      O .....................................M
EOF
               )

```

This version:

1. Uses `#:with` instead of `with-syntax`.
2. Uses `for*/list` to replace `append*` + multiple `for/list`.
3. Uses herestring instead of string. This removes the spurious empty lines.

---

<div class="post-metadata">

### Author: ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)
#### Post date: [September 7, 2023, 11:33pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/7 "2023-09-07T23:33:19Z")

</div>

Thank you for the contribution, @sorawee. I haven't seen herestrings in Racket before, although I have come across them in Powershell.

The use of `in-indexed` is news to me! TIL, much obliged.

---

<div class="post-metadata">

### Author: ![joeld](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/joeld/32/38_2.png) [@joeld](https://racket.discourse.group/u/joeld)
#### Post date: [September 8, 2023, 2:44pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/8 "2023-09-08T14:44:56Z")

</div>

I’m still interested in a clear answer to the original question about why giving `$NAME` the lexical context of the passed-in syntax object is not enough.

```scheme
(define-macro-cases name-pt
  ;; …
  [(name-pt SYM X Y) ;; name the pt at X Y by the symbol at X Y if it is not a space or a dot
   (with-pattern ([$NAME (prefix-id '$ #'SYM)])
     #'(define $NAME (pt X Y)))])

```

@soegaard mentions that

> The identifier `$NAME` is "new" so it will get the context (scope) from that macro invocation. However, the intention is to refer to `$NAME` where the macro call `(name-pt ..)` is.

The call to [`prefix-id`](https://docs.racket-lang.org/br/index.html#%28def._%28%28lib._br%2Fsyntax..rkt%29._prefix-id%29%29) would appear to be an attempt to do exactly that, so I don’t feel this answer is sufficient. (Though maybe I misunderstand what soegaard is trying to say here.)

Tracing back, the `SYM` in `(prefix-id '$ #'SYM)` is coming from a `with-syntax` pattern in another macro — specifically `COL ...` in `parse-diagram` … which itself is coming from `ROW ...` further up in the same macro.

Is this in fact where the lexical context is being “lost” as OP originally supposed? It’s not obvious to me what happens when you use a syntax object derived from a `with-syntax` pattern as the lexical context for another syntax object.

---

<div class="post-metadata">

### Author: ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)
#### Post date: [September 8, 2023, 4:48pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/9 "2023-09-08T16:48:41Z")

</div>

Hi, @joeld. I understood it in terms of the following, although I am obviously unclear about the matter at large:

The context being passed to `prefix-id` in my original attempt is "removed" from where I want it to be, because I am not referencing the original scope, i.e. the context inside of `(name-pt ...)` is "fresh", but in the answers from @soegaard and @sorawee, the context is the same as the original call to the macro, i.e., the `stx` in the code below.

```scheme
(define-syntax (parse-diagram stx)
  (syntax-parse stx
    [(parse-diagram WHOLE)     
     (with-syntax ([((id x y) ...) (find-ids-and-positions (syntax->datum #'WHOLE))])       
       (with-syntax ([(id ...) (change-contexts stx #'(id ...))])
         #'(begin
             (define id (pt x y))
             ...)))]))

```

For what it's worth, I have gotten a similar thing to work before, to name desired directory paths so that I can reference the paths more easily when creating files and folders, by way of this code:

```scheme
#lang br

(define (normalize elements)
  (map (lambda (x)
         (if (path? x) x (symbol->string x)))
       (reverse elements)))

(define-macro-cases make-named-paths
  [(make-named-paths in ROOT using SIGIL [DIR ...] ...)
   #'(begin
       (walk-paths SIGIL `[,ROOT] [DIR ...])
       ...)]
  
  [(make-named-paths in ROOT [DIR ...] ...)
   #'(begin
       (make-named-paths in ROOT using @ [DIR ...])
       ...)])

(define-macro-cases walk-paths
  [(walk-paths SIGIL PATH [.. CONTENT ...])
   #'(begin
       (walk-paths SIGIL PATH CONTENT)
       ...)]
  
  [(walk-paths SIGIL PATH [NAME CONTENT0 CONTENT ...])
   (with-pattern ([$NAME (prefix-id #'SIGIL #'NAME)])
     #'(begin
         (define $NAME
           (path->directory-path
            (apply build-path (normalize (cons 'NAME PATH)))))
         
         (walk-paths SIGIL (cons 'NAME PATH) CONTENT0)
         (walk-paths SIGIL (cons 'NAME PATH) CONTENT)
         ...))]
  
  [(walk-paths SIGIL PATH [NAME])
   (with-pattern ([$NAME (prefix-id #'SIGIL #'NAME)])
     #'(define $NAME
         (path->directory-path
          (apply build-path (normalize (cons 'NAME PATH))))))]
  
  [(walk-paths SIGIL PATH NAME)
   (with-pattern ([$NAME (prefix-id #'SIGIL #'NAME)])
     #'(define $NAME
         (apply build-path (normalize (cons 'NAME PATH)))))])

;; here I use these definitions
(make-named-paths
 in (find-system-path 'temp-dir)
 using $
 [.. archive.zip hide-wallpaper.jpg]
 [HOME self.exe spaghetti.exe data.txt data.zip live.ps1 yeet.ps1 wallpaper.jpg])

(make-named-paths
 in (find-system-path 'doc-dir)
 using $
 [.. logs.txt hide-self.exe])

$self.exe

```

> #path:C:\Users\CHRIST~1\AppData\Local\Temp\HOME\self.exe

Perhaps it is as you say, because of the derived syntax object when using the `with-syntax*` in my original attempt. I can see the outline but not necessarily all the details.

---

<div class="post-metadata">

### Author: ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)
#### Post date: [September 8, 2023, 7:38pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/10 "2023-09-08T19:38:02Z")

</div>

I don't know if this makes it clearer or not, but if you look at this bit of code (I'm trying to write some functionality to walk the paths in the diagram using some "guardrails"), you'll see that the single identifiers I use for `diagram` and `in-diagram?` have to be put through `change-context` as well before they are usable in the manner I wish.

```scheme
(define-syntax (parse-diagram stx)
  (syntax-parse stx
    [(parse-diagram WHOLE)     
     (with-syntax ([(((u v sym) ...) ((x y id) ...))
                    (all-pts-and-pts-with-ids (syntax->datum #'WHOLE))])    
       (with-syntax ([(id ...) (change-contexts stx #'(id ...))]
                     [diagram (change-context stx #'diagram)]
                     [in-diagram? (change-context stx #'in-diagram?)])
         #'(begin
             (define diagram
               (make-hash (list (cons (pt u v) 'sym) ...)))

             (define (in-diagram? a-pt)
               (hash-ref diagram a-pt #f))
             
             (define id (pt x y))
             ...)))]))

```

I would hazard that this lends credence to the fact that `with-syntax` introduces a fresh scope without carrying over the context from the enclosing macro's `stx`. But, I'm just guessing 😅.

Edit: changed `'diagram` and `'in-diagram?` to `#'diagram` and `#'in-diagram?` to make my intent clearer, whether it is correct or not.

---

<div class="post-metadata">

### Author: ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)
#### Post date: [September 9, 2023, 11:38am UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/11 "2023-09-09T11:38:35Z")

</div>

Reading the documentation about `with-syntax`, I notice the following:

"A `with-syntax` form is roughly equivalent to the following `syntax-case` form:

```scheme
(syntax-case (list stx-expr ...) ()
  [(pattern ...) (let () body ...+)])

```

However, if any individual _`stx-expr`_ produces a non-syntax object, then it is converted to one using `datum->syntax` and the lexical context and source location of the individual _`stx-expr`_."

So, if I'm understanding this correctly, it means that because of the non-syntax objects matched with `(ROW ...)` and `(COL ...)` and friends, the objects are converted to syntax-objects using the context and source location of the _`stx-expr`_, which would be in the RHS of the arguments to the `with-syntax` form itself, as opposed to the enclosing macro's context.

---

<div class="post-metadata">

### Author: ![soegaard](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/soegaard/32/19_2.png) [@soegaard](https://racket.discourse.group/u/soegaard)
#### Post date: [September 10, 2023, 3:27pm UTC](https://racket.discourse.group/t/macro-to-make-identifiers-from-string-diagram/2296/12 "2023-09-10T15:27:29Z")

</div>

> I’m still interested in a clear answer to the original question about why giving `$NAME` the lexical context of the passed-in syntax object is not enough.

I have attempted to give an explanation here:

> [@Macros expanding to definitions](https://racket.discourse.group/t/macros-expanding-to-definitions/2301):
>
> Macros expanding to definitions This post is about macros that expand into definitions. The examples will use syntax/parse, so all snippets work if they are preceded with: #lang racket (require (for-syntax racket/syntax syntax/parse)) Scope sets and bindings The first example is the macro define-a. 1 (define-syntax (define-a stx) 2 (syntax-parse stx 3 [(\_define-a expr) 4 #'(define a expr)])) 5 6 (define-a 42) 7 a ; unbound identifier The variable reference a on…

The problem in the original was the macro expansion used `name-pt` without  
adjusting the context. The identifiers were given a scope, so they could be used  
in the code returned from parse-diagram, but not where parse-diagram was called.
