# Arithmetic with Instaparse

• 4 min read

Last week I started playing around with the Instaparse library, which makes writing parsers really easy in Clojure. What Instaparse does is take the formal definition of a language and give you a parser for that language. You can then use that parser on a string, possibly containing something in that language. You need to write the grammar in one of two forms: Extended Backus-Naur Form(EBNF) or Augmented Backus-Naur form(ABNF). You can read about these grammars in Matt Might’s excellent blog post: The language of languages. I’ve picked EBNF as my grammar of choice for now, admittedly without having studied the nuances of each. Instaparse does let you mix up EBNF and ABNF in your grammar but you might end up unnecessarily complecting things for yourself.

If the string contains a syntactically correct string, the parser gives you a tree. Instaparse also provides you with the tools to transform that tree into something else, say a Clojure form, so that it can get evaluated. To get familiar with the Backus-Naur form(and its descendants, EBNF and ABNF), I recommend going through Matt’s blog post linked above.

In this post, we’ll write a grammar for parsing arithmetic expressions, so that we can turn something like `"11*1-1/5"` into:

```(clojure.core/- (clojure.core/* 11 1) (clojure.core// 1 5))
```

Notice that the parser has spit out the Clojure form with the correct precedence, that is multiplicative operators get evaluated before additive operators.

## The grammar

Here is the grammar that I came up with. It handles the four basic arithmetic operators: `+`, `-`, `*` and `/`. `+` and `-` have the same precedence, as do `*` and `/`. The multiplicative operators(* and /) have higher precedence than the additive operators(+ and -).

```(def arithmetic
(insta/parser
"<expr> = additive-expr | multiplicative-expr | number
additive-expr = expr (('+' | '-' ) expr)
multiplicative-expr = number ( ('*' | '/') (multiplicative-expr|number) )
number = #'-?[0-9]+'"))
```

Let us now take a closer look at the grammar. Starting from the bottom-up, a `number` is any integer. A `multiplicative-expr` is any number multiplied or divided by another `multiplicative-expr` or a number. Notice that a `multiplicative-expr` cannot contain an `additive-expr`.

Next, an `additive-expr` is any `expr` added or subtracted with any other `expr`. `expr` is defined in the first line to be one of `additive-expr`, `multiplicative-expr` or `number`. The angled brackets around `expr` means that the `:expr` tag will be hidden in the parse tree Instaparse produces.

The definitions of additive and multiplicative expressions implicitly specify that multiplicative operators have higher precedence.

## Trying out the parser

Let us now try out the parser to see if it actually works:

First, let’s try the simplest possible operation:

```user> (arithmetic "42")
([:number "42"])
```

A bit more compound expression:

```user> (arithmetic "2+2")
([:additive-expr [:number "2"] "+" [:number "2"]])
```

And now a slightly ridiculous expression:

```user> (clojure.pprint/pprint (arithmetic "2+2-2/2*2-1/1+1"))
[:number "2"]
"+"
[:number "2"]
"-"
[:multiplicative-expr
[:number "2"]
"/"
[:multiplicative-expr [:number "2"] "*" [:number "2"]]]
"-"
[:multiplicative-expr [:number "1"] "/" [:number "1"]]
"+"
[:number "1"]]]]])
```

## Transforming the parse tree

Like I mentioned above, we really want to have the arithmetic expression parsed into a Clojure form. To do this we will specify what transformations we want:

```(def parse-tree->sexp
{:additive-expr (fn [operand operator & rest]
(if (= operator "+")
`(+ ~operand ~@rest)
`(- ~operand ~@rest)))
:multiplicative-expr (fn [operand operator & rest]
(if (= operator "*")
`(* ~operand ~@rest)
`(/ ~operand ~@rest)))
```

And finally to get the Clojure form we can run the `insta/transform` functions like this:

```user> (insta/transform parse-tree->sexp (arithmetic "2+2*2-2"))
((clojure.core/+ 2 (clojure.core/- (clojure.core/* 2 2) 2)))
```
Now look what you've done 🌋
Stop clicking and run for your life! 😱
Uh oh, I don't think the system can't handle it! 🔥
Stop it, you're too kind 😄
Thanks for the love! ❤️
Thanks, glad you enjoyed it! Care to share?

Hey 👋, thanks for reading!

I am currently looking for my next job.

If you know of someplace that might be a good fit for me, I'd really appreciate an intro: `mail@samrat.me`

## Recommended Posts ✍🏻

See All »
• 6 min read
TIL: Sum Types With `instructor_ex`