I'm trying to staple together existing tools so that I can take a datum such as
'(let ((a = 5)) (cond (b c) (d e)))
and generate this string:
{let {[a = 5]}
(cond [b c]
[d e])}
I think I can almost make this work by applying syntax wrappers with 'paren-shape syntax-properties, then using @sorawee 's fmt package. ... but I don't see a "front door" for fmt that accepts a syntax object, and I don't know how to write a syntax object to a string in a way that uses the paren-shape syntax properties (which would allow me to use the string-based front door). Either of these would solve the problem. Can anyone point me to either one of these?
syntax->string is an alternative that does accept a syntax object, but it also doesn't do the formatting that fmt does: it uses the syntax source locations to determine indentation and stuff, not good-style formatting rules
If I recall correctly, this won't work out if the box, but it should be possible with a minor change to fmt. I can take a look this weekend if you have not already solved it by then.
@sorawee Hey! I'm back. I took another look at this, and basically, I want some encouragement that what I'm trying to do can be done, before looking at the part of the implementation that pretty clearly seems to be labeled "don't look in here!".
Here's a self-contained file that uses fmt; I've included what I'm hoping to get fmt to output, along with what it currently wants to output. I see that all of the lets went back to round brackets, for one thing. Also, I'm not sure how to get it to prefer the indentation where b and equals and the beginning of the binding for b all appear on the same line.
Any pointers to docs or examples appreciated!
#lang racket
;(require "handin-render.rkt")
(require fmt)
;; given an s-expression, output a string representation where parens by default
;; are rendered as curly-braces
(define (encurlify sexp)
(match sexp
[(list elts ...)
(string-append "{"
(apply
string-append
(add-between (map encurlify elts) " "))
"}")]
[other (~s sexp)]))
(define translated
'(let ((a = 9) (b = ((fun (y) => (let ((p = 2)) in (+ 3 19))) (fun (x) => 14))))
in
(+ b a))
#;(rkt-holang-render
'(let ([a 9]
[b ((λ (y) (let ([p 2]) (+ 3 19)))
(λ (x) 14))])
(+ b a))))
;; ideal:
#|
{let {{a = 9}
{b = {{fun {y} =>
{let {{p = 2}}
in
{+ 3 19}}}
{fun {x} => 14}}}}
in
{+ b a}}
|#
(define curly-string
(encurlify translated))
(display
(program-format
#:width 70
curly-string))
;; current output:
#|
(let ({a = 9}
{b
=
{{fun
{y}
=>
(let ({p = 2})
in
{+ 3 19})}
{fun {x} => 14}}})
in
{+ b a})
|#
Fixing fun formatting is just a matter of writing an appropriate formatter for it. It’s a bit more difficult to fix {b = ...} because fmt doesn’t have a mechanism to support infix operator formatting yet at all, so the core of fmt will require an adjustment, but it should be possible to do so.
Here’s my quick attempt to fix the formatting of fun.
#lang racket
;(require "handin-render.rkt")
(require fmt
pretty-expressive)
;; given an s-expression, output a string representation where parens by default
;; are rendered as curly-braces
(define (encurlify sexp)
(match sexp
[(list elts ...)
(string-append "{"
(apply
string-append
(add-between (map encurlify elts) " "))
"}")]
[other (~s sexp)]))
(define translated
'(let ((a = 9) (b = ((fun (y) => (let ((p = 2)) in (+ 3 19))) (fun (x) => 14))))
in
(+ b a)))
;; ideal:
#|
{let {{a = 9}
{b = {{fun {y} =>
{let {{p = 2}}
in
{+ 3 19}}}
{fun {x} => 14}}}}
in
{+ b a}}
>#
(define curly-string
(encurlify translated))
(define-pretty (format-fun)
#:type node?
(match (extract (node-content doc) (list #t #t #f))
[#f (format-#%app doc)]
[(list (list -macro-name -e ...) unfits tail)
(define first-line
(match -e
['() (pretty -macro-name)]
[_
(define args (map pretty -e))
(<+s> (pretty -macro-name) (alt (v-concat args) (flatten (as-concat args))))]))
(pretty-node
#:adjust '("{" "}")
#:unfits unfits
(try-indent
#:because-of (append (cons -macro-name -e) tail)
(alt
;; multi line format
(<$> first-line
(<+> space ((format-vertical/helper
#:body-formatter pretty
#:kw-map default-kw-map)
tail)))
;; single line format
(<+> first-line
space
((format-horizontal/helper
#:body-formatter pretty
#:kw-map default-kw-map)
tail)))))]))
(display
(program-format
#:width 70
curly-string
#:formatter-map
(λ (sym)
(case sym
[("fun") (format-fun)]
[else #f]))))
;; current output:
#|
(let ({a = 9}
{b
=
{{fun {y} =>
(let ({p = 2})
in
{+ 3 19})}
{fun {x} => 14}}})
in
{+ b a})
>#
One annoying thing is that the default of #:adjust in pretty-node is to convert things to parentheses. This is the reason why let gets converted from curly braces to parens. We could improve this by creating a global parameter and modify pretty-node to consult this parameter, and see if the conversion should happen or not.
Fantastic! Many thanks. WRT the formatting of the binding clauses of let, the obvious way of avoiding the "infix operator" question is to punt upward, and say that this is part of the job of let. Is that not a reasonable approach? I can take a look at what you've given me for fun, and poke at it a bit, many thanks for the demo code.
It also seems to me that it might be nice to put something like this in the docs? Even if it's all unstable?
That is indeed a reasonable approach if = is only used in let! I somehow was under the impression that there are more places that use infix operators, but that doesn’t seem to be the case.
In fact, the current let formatter already handles the formatting of binding pairs as you suggested: it converts parens to square brackets, to fix the Scheme-style code. We could do something similar here.