Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add full string interpolation as syntax extension #321

Open
2 tasks
jcubic opened this issue Feb 26, 2024 · 2 comments
Open
2 tasks

Add full string interpolation as syntax extension #321

jcubic opened this issue Feb 26, 2024 · 2 comments
Labels
documentation enhancement New feature or request
Milestone

Comments

@jcubic
Copy link
Collaborator

jcubic commented Feb 26, 2024

This is an implementation that works right now

(set-special! "$" 'interpolate)

(define (interpolate str)
  (typecheck "interpolate" str "string")
  (let* ((re #/(\$\{[^\}]+\})/)
         (parts (--> str (split re) (filter Boolean))))
    `(string-append ,@(map (lambda (part)
                             (if (not (null? (part.match re)))
                                 (let* ((expr (part.replace #/(^\$\{)|(\}$)/g ""))
                                        (port (open-input-string expr))
                                        (value (with-input-from-port port read)))
                                   `(repr ,value))
                                 part))
                           (vector->list parts)))))

(pprint (macroexpand-1 (let ((x 10)) $"x = ${(+ x 2)}")))
;; ==> (let ((x 10))
;; ==>   (string-append "x = " (repr (+ x 2))))

(let ((x 10))
  $"x = ${(+ x 2)}")
;; ==> "x = 12"

But it has limitations you can't put strings as an interpolated value, because it will break the parser, to be fully working as expected you should be able to use something like this:

(print $"x = ${(+ 1 2)}; y = ${(+ 2 3)}; s = ${(string-append "{" "hello" "}")}")

But this will not work because the parser doesn't know that it should process ${...} differently. So interpolation would need to be built into the parser but I don't want that.

The alternative is to wait to have (read) inside syntax extensions. So this will need to wait for #150

For better regex that will work with strings inside quoted expressions, see How to match everything except single character but not inside double quoted string on StackOverflow.

  • document the feature
  • add a note about performance
@jcubic jcubic added this to the 1.1 milestone Feb 26, 2024
@jcubic jcubic added the enhancement New feature or request label Feb 26, 2024
@jcubic
Copy link
Collaborator Author

jcubic commented Mar 26, 2024

After implementing #150 you can now read from the parser stream in syntax extensions:

(set-special! "$" 'raw-string lips.specials.SYMBOL)

(define (raw-string)
  (if (char=? (peek-char) #\")
      (begin
        (read-char)
        (let loop ((result (vector)) (char (peek-char)))
          (read-char)
          (if (char=? char #\")
              (apply string (vector->list result))
              (loop (vector-append result (vector char)) (peek-char)))))))

(print $"foo \ bar")

This is working example that read raw string like in Python. So it will be possible to add full string interpolation.

@jcubic
Copy link
Collaborator Author

jcubic commented Mar 30, 2024

Here is a working full string interpolation:

(set-special! "#\"" 'string-interpolation lips.specials.SYMBOL)

(define (string-interpolation)
  (let loop ((current "") (result (vector)) (char (read-char)))
    (cond ((and (char=? char #\$)
                (char=? (peek-char) #\{))
           (read-char)
           (let ((expr (read)))
             (let ((next (peek-char)))
               (if (char=? next #\})
                   (begin
                     (read-char)
                     (loop "" (vector-append result (vector current expr)) (read-char)))
                   (error (string-append "Parse Error: expecting } got " (repr next)))))))
          ((char=? char #\\)
           (loop (string-append part (repr (read-char))) result (read-char)))
          ((char=? char #\")
           `(string-append ,@(map (lambda (expr)
                                    (if (string? expr)
                                        expr
                                        `(repr ,expr)))
                                    (vector->list (vector-append result (vector current))))))
          ((eof-object? char)
           (error "Parse Error: expecting character #eof found"))
          (else
           (loop (string-append part (repr char)) result (read-char))))))

(let ((x 10) (y 20))
  (print #"this is string \" ${(+ x y)} hello ${(repr "hello" #t)}

world"))

I will probably split this into two functions.

NOTE the code doesn't handle indentation like LIPS string.

@jcubic jcubic modified the milestones: 1.1, 1.0 Mar 30, 2024
jcubic added a commit that referenced this issue Mar 31, 2024
jcubic added a commit that referenced this issue Apr 5, 2024
jcubic added a commit that referenced this issue Apr 5, 2024
jcubic added a commit that referenced this issue May 13, 2024
jcubic added a commit that referenced this issue May 13, 2024
jcubic added a commit that referenced this issue May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant