What's the function to render scribble string with environment in hash table?

I'm new to using scribble but can't figure out how to use its syntax in my own programs and not use the scribble language.

> (define ht (make-hash '(("Name" . "Simon"))))
> (define template "Hello @Name")
> (function-i-dont-know ht template)
"Hello Simon"

      

What function am I looking for? It should exist, but I can't find it in the documentation.

+3


source to share


3 answers


First of all, understand that Scribble is nothing more than an interface to Racket code. All Scribble does is take some input and output from the Racket executable code.

This means that using the Scribble reader on your string template

will simply give you this:

"Hello" Name

      

Imagine this is simple Racket code. It is nothing more than a string literal "Hello"

followed by a reference to a variable named Name

. This output is then passed to the Racket compiler and compiled into executable code.

So Scribble is not a template for modeling , it is a programming language. There is no concept of "substitution" as you describe, because Scribble is just blindly spitting out code. You will need to run this code to perform any string substitution.


In fact, you can do what is described above in Racket using a module racket/sandbox

that allows you to create standalone sandboxed evaluators.

(require racket/sandbox
         scribble/reader)

(define (scribble-eval-string input-str environment)
  (define eval (make-evaluator 'racket))
  (define input (read-inside (open-input-string input-str)))
  (for ([(k v) (in-hash environment)])
    (eval `(define ,(string->symbol k) ,v)))
  (string-append*
   (for/list ([expr (in-list input)])
     (eval `(#%expression ,expr)))))

      



This function has four functions. First, it creates a pure evaluator for the language racket

. Then it reads the input using read-inside

from scribble/reader

, which reads the input in string input mode and creates a list. On your input, the resulting value will be '("Hello " Name)

.

Then you need to insert the variables from the hash table into the sandbox environment. This is done by manually evaluating a set of forms define

for each key / value pair in the hash table. Finally, it evaluates each element of the input list as an expression, then concatenates the results into a single line.

With all of this, you can do this:

(define environment (make-hash '(("Name" . "Simon"))))
(define input "Hello @Name")

> (scribble-eval-string input environment)
"Hello Simon"

      


Is this a good idea? Probably no. Because Scribble is a programming language, you efficiently compile an entire program on the fly and then execute it. If any of the data comes from a user, you've made a huge number of holes in your program.

If you just need a dumb string replacement, just use format

or something similar. However, if you really want the full power of Scribble, you can do something like this to make it available to you.

+1


source


Add at-exp

to use @ -expressions in your language of choice.

#lang at-exp racket

(define x (random 5))
(define y (random 5))

@~a{@x + @y = @(+ x y)}

      



Conclusion: "3 + 1 = 4"

+4


source


The answer @soegaard gave is really enough, but for the sake of people who would only be looking for a more common system pattern, here's one way to do it.

The main thing to remember is that @ -forms are another way of writing Racket code, so we're really looking for a generic way to replace names based on a hash table. (Since Racket has tons of ways to do this, there are many ways to do it using @ -forms.)

This uses a lookup function L

that looks for values ​​in a hash table that is stored in a parameter. Since this parameter is "live" only when rendering text, it actually produces thunks to delay the search until the text is displayed. (I modified the symbols hash table slightly for more comfortable keys.) It uses a function output

from scribble/text

to get a result that allows many kinds of values ​​in the template (for example, nested lists). For the same reason, there is no need to try to use a string for the result, it is just a list of things. It with-output-to-string

is then used to collect text into a string.

#lang at-exp racket
(require scribble/text)

(define current-replacements (make-parameter #f))
(define (L key) (λ() (hash-ref (current-replacements) key)))
(define (render-with-hash ht template)
  (parameterize ([current-replacements ht])
    (with-output-to-string (λ() (output template)))))

(define ht (make-hash '([Name . "Simon"])))
(define template @list{Hello @L['Name]})
(render-with-hash ht template) ; => "Hello Simon"

      

A slightly more convenient option is to use a macro for L

, which makes the quote redundant:

...
(define-syntax-rule (L key) (λ() (hash-ref (current-replacements) 'key)))
...
(define template @list{Hello @L[Name]})
...

      

... or, since {}

are just @ -syntax for strings, revert to using string for hash keys:

#lang at-exp racket
(require scribble/text)

(define current-replacements (make-parameter #f))
(define (L key) (λ() (hash-ref (current-replacements) key)))
(define (render-with-hash ht template)
  (parameterize ([current-replacements ht])
    (with-output-to-string (λ() (output template)))))

(define ht (make-hash '(["Name" . "Simon"])))
(define template @list{Hello @L{Name}})
(render-with-hash ht template) ; => "Hello Simon"

      

Beware to keep in mind what {}

can be multiple lines, for example if there is a newline in the text content. If you want to deal with this, you can configure the function L

to take multiple arguments and add them together, and then normalize whitespace before the search is done:

#lang at-exp racket
(require scribble/text)

(define current-replacements (make-parameter #f))
(define (L . keys)
  (λ() (hash-ref (current-replacements)
                 (regexp-replace #px"\\s+" (string-append* keys) " "))))
(define (render-with-hash ht template)
  (parameterize ([current-replacements ht])
    (with-output-to-string (λ() (output template)))))

(define ht (make-hash '(["First Name" . "Simon"])))
(define template @list{Hello @L{First
                       Name}})
(render-with-hash ht template) ; => "Hello Simon"

      

One thing that is a little awkward in all of these cases is the use of a parameter containing a hash table. Something like this is only needed when you don't know the keys that are being used in advance. Most of the time you do this, and for that you can simply use simple variables as arguments to the template, which becomes a simple function:

#lang at-exp racket
(require scribble/text)

(define (template Name)
  @list{Hello @Name})
(with-output-to-string (λ() (output (template "Simon"))))
; => "Hello Simon"

      

One final note: I've used output

all of these things so that you can have nested structures of things in the text. If all you need is just a bunch of strings, you can use string-append

:

#lang at-exp racket
(define (template Name)
  @string-append{Hello @Name})
(template "Simon") ; => "Hello Simon"

      

Or, as in @ soegaard's answer, use a function ~a

that is kind of a cheap version output

(to string) that can add a bunch of string values ​​(and display

non-standard values):

#lang at-exp racket
(define (template Name)
  @~a{Hello @Name})
(template "Simon") ; => "Hello Simon"

      

+2


source







All Articles