Last active
October 6, 2016 06:07
-
-
Save pervognsen/350244 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
It is a reader extension for list hoisting and splicing. You can write | |
(a `b` c) and it will be read as (b a c). It is right recursive, so | |
that (a `b` c `d` e) is read as (b a (d c e)). When the unhoisted | |
segments are lists of length greater than one, they are wrapped in a | |
singleton list rather than spliced. Thus (a b `c` d e) is read as (c | |
(a b) (d e)). The splicing and hoisting tries to have a natural DWIM | |
feel. | |
While this lets you perform surgery on any lists in the reader, its | |
strongest point is obviously infix function applications. Thus you can | |
write x `+` y `+` z. The mechanics of hoisting and splicing is such | |
that all infix operators are effectively right associative with | |
uniform precedence. Thus x `*` y `+` z is read as (* x (+ y z)). | |
Here is a quick demo transcript from the REPL: | |
Clojure 1.2.0-master-SNAPSHOT | |
user=> (defn dwim [x] (if (coll? x) (vec x) [x])) | |
#'user/dwim | |
user=> (defmacro => [bindings body] `(fn ~(dwim bindings) ~body)) | |
#'user/=> | |
user=> (defmacro where [body bindings] `(let ~(dwim bindings) ~body)) | |
#'user/where | |
user=> (defmacro || [body bindings] `(for ~(dwim bindings) ~body)) | |
#'user/|| | |
user=> (def add (x y `=>` x `+` y)) | |
#'user/add | |
user=> (add 3 5) | |
8 | |
user=> (def curried-add (x `=>` y `=>` x `+` y)) | |
#'user/curried-add | |
user=> ((curried-add 3) 5) | |
8 | |
user=> ((x `+` y `+` z) `where` x 3, y (x `+` 2), z (x `*` y)) | |
23 | |
user=> ((x `+` y) `||` x (range 10), y (range 10), :when (even? x)) | |
(0 1 2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9 10 11 4 5 6 7 8 9 10 11 12 13 6 7 | |
8 9 10 11 12 13 14 15 8 9 10 11 12 13 14 15 16 17) | |
user=> (0 `cons` 2 `cons` [1 2] `(map +)` [3 4]) | |
(0 2 4 6) | |
*/ | |
diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java | |
index dc8ce97..62e3b75 100644 | |
--- a/src/jvm/clojure/lang/LispReader.java | |
+++ b/src/jvm/clojure/lang/LispReader.java | |
@@ -37,6 +37,7 @@ static Symbol VECTOR = Symbol.create("clojure.core", "vector"); | |
static Symbol WITH_META = Symbol.create("clojure.core", "with-meta"); | |
static Symbol META = Symbol.create("clojure.core", "meta"); | |
static Symbol DEREF = Symbol.create("clojure.core", "deref"); | |
+static Symbol INFIX = Symbol.intern("infix"); | |
//static Symbol DEREF_BANG = Symbol.create("clojure.core", "deref!"); | |
static IFn[] macros = new IFn[256]; | |
@@ -686,7 +687,18 @@ public static class SyntaxQuoteReader extends AFn{ | |
RT.map(GENSYM_ENV, PersistentHashMap.EMPTY)); | |
Object form = read(r, true, null, true); | |
- return syntaxQuote(form); | |
+ | |
+ int ch = r.read(); | |
+ if ((char) ch == '`') { | |
+ if (!(form instanceof IObj)) | |
+ throw new Exception("Backtick bracketing only works with metadata carriers (IObjs)."); | |
+ IObj o = (IObj) form; | |
+ IPersistentMap meta = o.meta() != null ? o.meta() : PersistentArrayMap.EMPTY; | |
+ return o.withMeta(meta.assoc(INFIX, true)); | |
+ } | |
+ unread(r, ch); | |
+ | |
+ return syntaxQuote(form); | |
} | |
finally | |
{ | |
@@ -897,7 +909,7 @@ public static class ListReader extends AFn{ | |
List list = readDelimitedList(')', r, true); | |
if(list.isEmpty()) | |
return PersistentList.EMPTY; | |
- IObj s = (IObj) PersistentList.create(list); | |
+ IObj s = (IObj) infixup(PersistentList.create(list)); | |
// IObj s = (IObj) RT.seq(list); | |
if(line != -1) | |
return s.withMeta(RT.map(RT.LINE_KEY, line)); | |
@@ -905,6 +917,38 @@ public static class ListReader extends AFn{ | |
return s; | |
} | |
+ static private Object revcat(Object initial, Object tail) throws Exception { | |
+ for (ISeq s = RT.seq(initial); s != null; s = s.next()) | |
+ tail = RT.cons(s.first(), tail); | |
+ return tail; | |
+ } | |
+ | |
+ static private Object wrap(Object list) throws Exception { | |
+ return RT.count(list) > 1 ? new PersistentList(list) : list; | |
+ } | |
+ | |
+ static private Object splice(Object initial, Object tail) throws Exception { | |
+ return revcat(wrap(revcat(initial, null)), wrap(tail)); | |
+ } | |
+ | |
+ static private Object concat(Object initial, Object tail) throws Exception { | |
+ if (initial instanceof IPersistentList) | |
+ return revcat(revcat(initial, null), tail); | |
+ else | |
+ return RT.cons(initial, tail); | |
+ } | |
+ | |
+ static private Object infixup(Object list) throws Exception { | |
+ Object left = null; | |
+ for(ISeq s = RT.seq(list); s != null; s = s.next()) { | |
+ Object o = s.first(); | |
+ if (RT.meta(o) != null && RT.meta(o).containsKey(INFIX)) | |
+ return concat(o, splice(left, infixup(s.next()))); | |
+ left = RT.cons(o, left); | |
+ } | |
+ return list; | |
+ } | |
+ | |
} | |
static class CtorReader extends AFn{ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment