Skip to content
/ infix Public

A Clojure library for expressing LISP expressions as infix rather than prefix notation

License

Notifications You must be signed in to change notification settings

rm-hull/infix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Infix

Build Status Coverage Status Dependencies Status Downloads Clojars Project Maintenance

A small Clojure library for representing LISP expressions in infix rather than prefix notation... sometimes it's easier to rely on operator precedence, instead of LISP's insistence on parentheses – this is especially true when dealing with mathematical equations.

An infix expression is rewritten as a prefix expression using a macro. The operator precedence rules were taken from Wikipedia. Any function calls should generally have parens around the arguments and not the function name. Aliased unary operators (as outlined below) do not need parens however.

Pre-requisites

You will need Leiningen 2.6.1 or above installed.

Building

To build and install the library locally, run:

$ cd infix
$ lein test
$ lein install

Including in your project

There is a version hosted at Clojars. For leiningen include a dependency:

[rm-hull/infix "0.4.0"]

For maven-based projects, add the following to your pom.xml:

<dependency>
  <groupId>rm-hull</groupId>
  <artifactId>infix</artifactId>
  <version>0.4.0</version>
</dependency>

API Documentation

See www.destructuring-bind.org/infix for API details.

Basic Usage

(use 'infix.macros)
; => nil

(infix 3 + 5 * 8)
; => 43

(infix (3 + 5) * 8)
; => 64

You can also use $= as a short alias for infix, like this for example:

(refer 'infix.macros :only '[$=])
; => nil

($= 3 + 5 * 8)
; => 43

All of the examples below should work if you replace infix by $=.

Some Math functions have been aliased (see below for full list), so nullary and unary-argument functions can be used as follows:

(infix √(5 * 5))
; => 5.0

(infix121)
; => 11.0

(infix 2 ** 6)
; => 64.0

(def t 0.324)
; => #'user/t

(infix sin(2 * t) + 3 * cos(4 * t))
; => 1.4176457261295824

(infix rand() * 3)
; => 0.5544039436207262

Debugging

It may be the case that you encounter some esoteric errors emitted from the library trying to rephrase expressions from infix to prefix. Use macroexpand-1 to show how the expression would be rewritten, and if necessary file an issue.

(macroexpand-1 '(infix sin(2 * t) + 3 * cos(4 * t))
; => (+ (Math/sin (* 2 t)) (* 3 (Math/cos (* 4 t))))

Usage in ClojureScript projects

The infix macro may be used to expand infix expressions in ClojureScript code by adding the require-macros directive to a namespace, for example:

(ns my.clojurescript.project
  (:require-macros [infix.macros :refer [infix]]))

Evaluating infix expressions dynamically from a string

A function can created at runtime from an expression held in a string as follows. When building from a string, a number of binding arguments should be supplied, corresponding to any variable that may be used in the string expression, for example:

(def hypot
  (from-string [x y]
    "sqrt(x**2 + y**2)"))
; => #'user/hypot

(hypot 3 4)
; => 5

(meta hypot)
; => {:params [:x :y], :doc "sqrt(x**2 + y**2)"}

from-string is deliberately designed to look like an anonymous function definition, mainly because that is more-or-less what it is. In effect, this is equivalent to creating the following function:

(def hypot
  (fn [x y]
    (infix sqrt(x ** 2 + y ** 2))))

However, it does so without recourse to eval and read-string - instead it is built using our old friend, the monadic parser-combinator, with an EBNF grammar (implementing the infix notation) and a restricted base environment of math functions, as outlined in the next section.

The base-env may be extended with any number of key/value pairs (where keys are keywords) and values may either be values or functions, to provide the required extensions. When referenced in the string it is not necessary to prefix the name with a colon.

(def extended-env
  (merge
    base-env
    {:rad (fn [deg] (infix deg * π / 180))
     :hypot hypot}))
; => user/extended-env

(def rhs-triangle-height
  (from-string [base angle]
    extended-env
    "tan(rad(angle)) * base"))
; => user/rhs-triangle-height

(rhs-triangle-height 10 45)
; => 9.9999999999998

Obviously, a function that was previously created from a string can also referenced in a subsequent function definition:

(def hypot2
  (from-string [x y]
    extended-env
    "hypot(x, y) ** 2"))
; => user/hypot2

(hypot2 5 12)
; => 169.0

Aliased Operators & Functions

Alias Operator Alias Operator Alias Operator
&& and abs Math/abs sin Math/sin
|| or signum Math/signum cos Math/cos
== = ** Math/pow tan Math/tan
> > < <
>= >= <= <=
!= not= exp Math/exp asin Math/asin
% mod log Math/log acos Math/acos
<< bit-shift-left e Math/E atan Math/atan
>> bit-shift-right π Math/PI sinh Math/sinh
! not sqrt Math/sqrt cosh Math/cosh
& bit-and Math/sqrt tanh Math/tanh
| bit-or root b √ a sec Secant
φ Golden ratio csc Cosecant
gcd Greatest common divisor fact Factorial cot Cotangent
lcm Least common multiple Sum asec Arcsecant
rand Random number generator Product acsc Arccosecant
randInt Random int between 0..n acot Arccotangent

EBNF Grammar Rules

The from-string macro parses infix expressions based on the EBNF grammar rules as follows:

  • <expression> ::= term { addop term }.

  • <term> ::= factor { mulop factor }.

  • <factor> ::= base { expop base }.

  • <base> ::= "(" expression ")" | boolean | number | var | function.

  • <addop> ::= "+" | "-" | "|" | "&" | "||" | "&&".

  • <mulop> ::= "*" | "/" | "÷" | "%" | ">>" | ">>>" | "<<".

  • <expop> ::= "**" .

  • <function> ::= envref expression | envref "(" <empty> | expression { "," expression } ")".

  • <ternary> ::= "(" expression ")" "?" expression ":" expression.

  • <envref> ::= letter | "" { letter | digit | "_" | "." }._

  • <var> ::= envref.

  • <boolean> ::= "true" | "false"

  • <number> ::= integer | decimal | rational | binary | hex

  • <binary> :: = [ "-" ] "0b" { "0" | "1" }.

  • <hex> :: = [ "-" ] "0x" | "#" { "0" | ... | "9" | "A" | ... | "F" | "a" | ... | "f" }.

  • <integer> :: = [ "-" ] digits.

  • <decimal> :: = [ "-" ] digits "." digits.

  • <rational> :: = integer "/" digits.

  • <letter> ::= "A" | "B" | ... | "Z" | "a" | "b" | ... | "z".

  • <digit> ::= "0" | "1" | ... | "8" | "9".

  • <digits> ::= digit { digit }.

References

License

The MIT License (MIT)

Copyright (c) 2016 Richard Hull

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.