Hi, all.

I am working on an embedded DSL for CSS in Racket. I want to get feedback from
the community regarding the project’s goals and my choice of tools for
implementing it.


LANGUAGE

I want to use Racket to generate CSS for me. I am not interested in a “#lang”
language, but in a DSL that lives embedded in the code. In particular, I want
“quote” and “unquote”. The best I can think of is something like:

  (define main-background-color #'red)
  (define the-css
    #`([body #:background-color #,main-background-color]))
  (css-expr->string the-css) ;; => "body{background-color:red;}"

Note the explicit use of syntax objects. They allow for arbitrary “quote” and
“unquote” and preserve source location information, which improves the quality
of error messages. This is the best solution I could find, but I recognize it is
unusual—I cannot name a single other example of embedded DSL in Racket that
operates with syntax objects directly. Can you think of something better?


COMPILER FRONT END

I want good error messages, so I am using “syntax-parse”. I defined the concrete
syntax tree in terms of syntax classes—each production rule on the context-free
grammar for the concrete syntax tree is a “syntax-parse” syntax class. For
example, the following defines a “stylesheet” as a list of “rules”:

  (define-syntax-class stylesheet
    (pattern (rule:rule ...)))

The abstract syntax tree are typed structs, because I want to benefit from the
correctness guarantees of Typed Racket. For example, the following is a struct
for “stylesheets”:

  (struct stylesheet ([rules : (Listof rule)]) #:transparent)

Then, the parser uses “syntax-parse” with the syntax classes to generate the
typed structs. For example, the following is a parser rule for “stylesheets”:

  (define (parse/stylesheet expression)
    (syntax-parse expression
      [stylesheet:stylesheet
       (extended:stylesheet (map parse/rule
                                 (syntax->list #'(stylesheet.rule ...))))]))

With this approach, I get a combination of good parsing error messages and typed
structures to work on the compiler back end. But there is no “syntax-parse” in
Typed Racket, so the parser module must be untyped. The interaction between
untyped (compiler front end) and typed (compiler back end) modules leads to
performance penalties. Overall, the whole architecture feels awkward, I feel
like I am fighting the tools. Can you think of a better solution?


COMPILER BACK END

I use the typed structures in a module that concatenates strings to form
CSS. This is the part I am happiest about, but I accept suggestions of more
principled approaches than carefully constructed calls to “string-append” and
“string-join”.


OPINION

My inspiration were the DSLs from http://www.cliki.net/CSS. I could not find a
Racket equivalent. The following is an example of what the language looks like,
recreating the CSS from http://bettermotherfuckingwebsite.com/:

  ([body
    {#:margin (40px auto)
     #:max-width 650px
     #:line-height 1.6
     #:font-size 18px
     #:color |#444|
     #:padding (0 10px)}]
   [h1 h2 h3
       {#:line-height 1.2}])

   ;; => body{margin:40px auto;…

Furthermore, I implemented some ideas that I borrowed from other CSS
preprocessors such as SASS/Less. For example, the language supports nested
declarations and attributes, appropriately unnesting them when generating the
CSS output. What I like about embedding this DSL in Racket is that some of the
preprocessors’ features are free. For example, CSS variables and mixins are just
“unquote” in Racket.

I am using this DSL to build my website in Pollen, and so far I am happy with
the results. I am envisioning more principled approach to CSS than concatenating
strings, which other (excellent) projects do. For example, see Typography for
Lawyers at
https://github.com/mbutterick/pollen-tfl/blob/1267be5bd002d4d0fe09aaef89b4089147128841/styles.css.pp.

Is this something that interests you too? How do you think I can make this
project better and more useful to the community?

* * *

Thank you very much for your attention and for the feedback.

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to