Parsing DSLs with predicates
Tags: clojure, learning, tutorial
Published: 2018-01-07 16:10:38

---
title: Parsing DSLs with predicates
published: true
description: This article illustrates how data specification can be used to validate and parse DSLs. We’ll use Clojure to create data-driven DSL for a subset of CSS, and clojure.spec library to define and parse the syntax.
canonical_url: https://medium.com/@roman01la/parsing-dsls-with-predicates-15e0a9a52be?source=linkShare-31daca79e798-1515339294
tags: clojure, learning, tutorial
---

_This article illustrates how data specification can be used to validate and parse DSLs. We’ll use Clojure to create data-driven DSL for a subset of CSS, and clojure.spec library to define and parse the syntax._

_clojure.spec_ is a specification library which provides tools (predicates and composition rules) to describe how data should look like, check if it conforms to that specification and provide an explanation of the cause in case of failure.

```clojure
(s/def ::name string?)
(s/def ::age pos-int?)

(s/def ::profile (s/keys :req [::name ::age]))

(s/valid? ::profile {::name "Roman" ::age 25.9})
;; false

(s/explain ::profile {::name "Roman" ::age 25.9})

In: [:specs.profile/age]
val: 25.9 fails spec: :specs.profile/age
at: [:specs.profile/age] predicate: pos-int?
```

When conforming data the returning value is destructured according to the spec. This is very useful for parsing DSLs, because the output can clearly describe the structure of the syntax. Think of it as a way to produce an AST, which is much more useful for analysis and compilation. We’ll see this later.

As an example we’ll create a syntax for _CSS Transforms_ value.

```clojure
[[:translate-x 120 :px]
 [:translate-y 150 :px]]

;; -> translateX(120px), translateY(150px)

[[:translate 120 :px]]

;; -> translate(120px)

[[:translate 120 :px 150 :px]]

;; -> translate(120px, 150px)
```

Let’s start by looking at [W3C CSS Transforms specification](https://www.w3.org/TR/css-transforms-1/) first. The spec clearly defines that the value of `transform` property is either `none` or `<transform-list>`.

![](https://thepracticaldev.s3.amazonaws.com/i/qdlz5xpv2wezbw3xsikv.png)

This translates into the code in a straightforward way:

```clojure
(s/def ::transform              ;; transform
  (s/alt                        ;; is either
    :something ::transform-list ;; <transform-list>
    :nothing   #{:none}))       ;; or `none`
```

The spec defines `<transform-list>` as one or more `<transform-function>` tokens.

![](https://thepracticaldev.s3.amazonaws.com/i/h4k1d8rz0xknqulh05qq.png)

In code this can be represented using _clojure.spec_’s regex operators which operate on sequences.

```clojure
(s/def ::transform-list   ;; <transform-list>
  (s/cat                  ;; is a sequence
    :fns (s/+ ::transform-function)))
         ;; of one or more <transform-function>
```

`<transform-function>` is one of those CSS transform functions which you may be familiar with, e.g. `translateX(x)`, `translate(x, y)`.

```clojure
(s/def ::transform-function     ;; <transform-function>
  (s/or                         ;; is either
    :translate-x ::translate-x  ;; translateX 
    :translate-y ::translate-y  ;; translateY
    :translate   ::translate))  ;; or translate
```

From the signature of these functions we can see that they accept a value of type `<length-percentage>`, which is either `<length>` (`px`, `em`, etc.) or `<percentage>` (`%`) type units.

![](https://thepracticaldev.s3.amazonaws.com/i/i2odyh8hxua7bud56mpx.png)

```clojure
(s/def ::length-percentage
  (s/alt
    :length     ::length
    :percentage ::percentage))
```

According to the syntax which we’ve specified in the beginning a value with a unit is a sequence of two elements: `number` and a `keyword` which represents the type of the unit.

```clojure
(s/def ::length
  (s/cat
    :value number?
    :unit  #{:px}))
```

`<percentage>` has the same syntax, just with different type `:%`

```clojure
(s/def ::percentage
  (s/cat
    :value number?
    :unit  #{:%}))
```

Let’s see how destructuring with spec works for `::length-percentage` specification.

```clojure
(s/conform ::length-percentage [100 :px])

;; [:length {:value 100, :unit :px}]
```

The returned value here is based on the spec that we’ve provided and the missing spots are filled in with values that are parsed out of the provided data. This representation is basically an AST.

Now that we have a spec for function parameters, let’s describe how the syntax of those functions look like.

```clojure
(s/def ::translate-x          ;; translate-x function
  (s/cat                      ;; is a sequence
    :function #{:translate-x} ;; of :translate-x keyword
    :value    ::length-percentage))
           ;; and a nested sequence of ::length-percentage
;; translate-y is similar
(s/def ::translate-y
  (s/cat
    :function #{:translate-y}
    :value    ::length-percentage))
```

`transform` function is more interesting, because it has two parameters and the second one is optional.

```clojure
(s/def ::translate          ;; translate fn
  (s/cat                    ;; is a sequence
    :function #{:translate} ;; of :translate keyword
    :value
    (s/cat                      ;; sequence
      :x ::length-percentage    ;; of ::length-percentage
      :y (s/? ::length-percentage)))))
        ;; and optional second value ::length-percentage
```

Finally let’s test the entire thing.

```clojure
(s/conform ::transform [[:translate -190 :px 80 :px]])
[:something
 {:fns
  [[:translate
    {:function :translate,
     :value
     {:x [:length {:value -190, :unit :px}],
      :y [:length {:value 80, :unit :px}]}}]]}]
```

Now the compilation process goes into traversing the tree and translating tokens into a proper CSS format.

Also when conforming fails, meaning that there’s an error somewhere in the syntax, we can ask for a reason, why it is failed, in a form of data.

```clojure
(s/conform ::transform [[:translate -190 :px 80 :p]])
;; :clojure.spec.alpha/invalid
(s/explain-data ::transform [[:translate -190 :px 80 :p]])
{:problems
 '({:path [:something :fns :translate :value :y :length :unit],
    :pred #{:px},
    :val  :p,
    :via
          [:specs.css/transform
           :specs.css/transform
           :specs.css/transform-list
           :specs.css/transform-function
           :specs.css/transform-function
           :specs.css/translate],
    :in   [0 4]}),
 :spec  :specs.css/transform,
 :value [[:translate -190 :px 80 :p]]}
```

We can see that the value `:p` in `[[:translate -190 :px 80 :p]]` failed predicate `#{:px}` of the specification `:specs.css/translate`. As you might guessed, this data can be used to report an error in a human-readable format.

For more real-world examples see [the code that I’ve written for parsing and compiling syntax for CSS Media Queries](https://github.com/roman01la/cljss/blob/master/src/cljss/media.clj).

To learn more about _clojure.spec_ check out [the rationale and overview](https://clojure.org/about/spec).