From 4171771bd39017d0606c6f7fc40d2230b10fd199 Mon Sep 17 00:00:00 2001
From: Peter Siewert <cninja@gmail.com>
Date: Wed, 14 Nov 2012 15:27:21 -0600
Subject: [PATCH] Track symbol locations. Update Compiler.line with symbol
 location data. Expose Compiler.LINE as *line-number*
 variable

---
 src/jvm/clojure/lang/Compiler.java      |  102 ++++++++++++++++++++-----------
 src/jvm/clojure/lang/LispReader.java    |   22 +++++--
 test/clojure/test_clojure/metadata.clj  |   13 ++++
 test/clojure/test_clojure/protocols.clj |    4 +-
 test/clojure/test_clojure/reader.clj    |    3 +-
 5 files changed, 100 insertions(+), 44 deletions(-)

diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index a88b914..ac96cff 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -264,7 +264,12 @@ static Object elideMeta(Object m){
     }
 
 //Integer
-static final public Var LINE = Var.create(0).setDynamic();
+static final public Var LINE = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
+                                                 Symbol.intern("*line-number*"), 0).setDynamic();
+static{
+       LINE.setMeta(PersistentHashMap.create(RT.CONST_KEY, true));
+}
+
 static final public Var COLUMN = Var.create(0).setDynamic();
 
 //Integer
@@ -6733,51 +6738,76 @@ static void addParameterAnnotation(Object visitor, IPersistentMap meta, int i){
 }
 
 private static Expr analyzeSymbol(Symbol sym) {
-	Symbol tag = tagOf(sym);
-	if(sym.ns == null) //ns-qualified syms are always Vars
+	Integer line = (Integer) LINE.deref();
+	Integer column = (Integer) COLUMN.deref();
+	if(RT.meta(sym) != null) 
 		{
-		LocalBinding b = referenceLocal(sym);
-		if(b != null)
-            {
-            return new LocalBindingExpr(b, tag);
-            }
+		if(RT.meta(sym).containsKey(RT.LINE_KEY))
+			line = (Integer) RT.meta(sym).valAt(RT.LINE_KEY);
+		if(RT.meta(sym).containsKey(RT.COLUMN_KEY))
+			column = (Integer) RT.meta(sym).valAt(RT.COLUMN_KEY);
+		sym = (Symbol) sym.withMeta((IPersistentMap) elideMeta(RT.meta(sym)));
 		}
-	else
+	Var.pushThreadBindings(RT.map(LINE, line, COLUMN, column));
+	try
 		{
-		if(namespaceFor(sym) == null)
+		Symbol tag = tagOf(sym);
+		if(sym.ns == null) //ns-qualified syms are always Vars
 			{
-			Symbol nsSym = Symbol.intern(sym.ns);
-			Class c = HostExpr.maybeClass(nsSym, false);
-			if(c != null)
+			LocalBinding b = referenceLocal(sym);
+			if(b != null)
+		    {
+		    return new LocalBindingExpr(b, tag);
+		    }
+			}
+		else
+			{
+			if(namespaceFor(sym) == null)
 				{
-				if(Reflector.getField(c, sym.name, true) != null)
-					return new StaticFieldExpr((Integer) LINE.deref(), (Integer) COLUMN.deref(), c, sym.name, tag);
-				throw Util.runtimeException("Unable to find static field: " + sym.name + " in " + c);
+				Symbol nsSym = Symbol.intern(sym.ns);
+				Class c = HostExpr.maybeClass(nsSym, false);
+				if(c != null)
+					{
+					if(Reflector.getField(c, sym.name, true) != null)
+						return new StaticFieldExpr((Integer) LINE.deref(), (Integer) COLUMN.deref(), c, sym.name, tag);
+					throw Util.runtimeException("Unable to find static field: " + sym.name + " in " + c);
+					}
 				}
 			}
+		//Var v = lookupVar(sym, false);
+	//	Var v = lookupVar(sym, false);
+	//	if(v != null)
+	//		return new VarExpr(v, tag);
+		Object o = resolve(sym);
+		if(o instanceof Var)
+			{
+			Var v = (Var) o;
+			if(isMacro(v) != null)
+				throw Util.runtimeException("Can't take value of a macro: " + v);
+			if(RT.booleanCast(RT.get(v.meta(),RT.CONST_KEY)))
+				return analyze(C.EXPRESSION, RT.list(QUOTE, v.get()));
+			registerVar(v);
+			return new VarExpr(v, tag);
+			}
+		else if(o instanceof Class)
+			return new ConstantExpr(o);
+		else if(o instanceof Symbol)
+				return new UnresolvedVarExpr((Symbol) o);
+	
+		throw Util.runtimeException("Unable to resolve symbol: " + sym + " in this context");
+
 		}
-	//Var v = lookupVar(sym, false);
-//	Var v = lookupVar(sym, false);
-//	if(v != null)
-//		return new VarExpr(v, tag);
-	Object o = resolve(sym);
-	if(o instanceof Var)
+	catch(Throwable e)
 		{
-		Var v = (Var) o;
-		if(isMacro(v) != null)
-			throw Util.runtimeException("Can't take value of a macro: " + v);
-		if(RT.booleanCast(RT.get(v.meta(),RT.CONST_KEY)))
-			return analyze(C.EXPRESSION, RT.list(QUOTE, v.get()));
-		registerVar(v);
-		return new VarExpr(v, tag);
+		if(!(e instanceof CompilerException))
+			throw new CompilerException((String) SOURCE_PATH.deref(), (Integer) LINE.deref(), (Integer) COLUMN.deref(), e);
+		else
+			throw (CompilerException) e;
+		}
+	finally
+		{
+		Var.popThreadBindings();
 		}
-	else if(o instanceof Class)
-		return new ConstantExpr(o);
-	else if(o instanceof Symbol)
-			return new UnresolvedVarExpr((Symbol) o);
-
-	throw Util.runtimeException("Unable to resolve symbol: " + sym + " in this context");
-
 }
 
 static String destubClassName(String className){
diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java
index ed75004..6c06a0d 100644
--- a/src/jvm/clojure/lang/LispReader.java
+++ b/src/jvm/clojure/lang/LispReader.java
@@ -202,10 +202,17 @@ static public Object read(PushbackReader r, boolean eofIsError, Object eofValue,
 				unread(r, ch2);
 				}
 
+			int line = -1;
+			int column = -1;
+			if(r instanceof LineNumberingPushbackReader)
+				{
+				line = ((LineNumberingPushbackReader) r).getLineNumber();
+				column = ((LineNumberingPushbackReader) r).getColumnNumber()-1;
+				}
 			String token = readToken(r, (char) ch);
 			if(RT.suppressRead())
 				return null;
-			return interpretToken(token);
+			return interpretToken(token, line, column);
 			}
 		}
 	catch(Exception e)
@@ -294,6 +301,9 @@ static private int readUnicodeChar(PushbackReader r, int initch, int base, int l
 }
 
 static private Object interpretToken(String s) {
+	return interpretToken( s, -1, -1);
+}
+static private Object interpretToken(String s, int line, int column) {
 	if(s.equals("nil"))
 		{
 		return null;
@@ -316,7 +326,7 @@ static private Object interpretToken(String s) {
 		}
 	Object ret = null;
 
-	ret = matchSymbol(s);
+	ret = matchSymbol(s, line, column);
 	if(ret != null)
 		return ret;
 
@@ -324,7 +334,7 @@ static private Object interpretToken(String s) {
 }
 
 
-private static Object matchSymbol(String s){
+private static Object matchSymbol(String s, int line, int column){
 	Matcher m = symbolPat.matcher(s);
 	if(m.matches())
 		{
@@ -353,7 +363,11 @@ private static Object matchSymbol(String s){
 		Symbol sym = Symbol.intern(s.substring(isKeyword ? 1 : 0));
 		if(isKeyword)
 			return Keyword.intern(sym);
-		return sym;
+
+		if(line != -1)
+			return sym.withMeta(RT.map(RT.LINE_KEY, line, RT.COLUMN_KEY, column));
+		else
+			return sym;
 		}
 	return null;
 }
diff --git a/test/clojure/test_clojure/metadata.clj b/test/clojure/test_clojure/metadata.clj
index 42f7dfe..7a0b1a7 100644
--- a/test/clojure/test_clojure/metadata.clj
+++ b/test/clojure/test_clojure/metadata.clj
@@ -209,3 +209,16 @@
      ;; join, index, map-invert: Currently always returns a value with
      ;; no metadata.  This seems reasonable.
      ))
+
+(deftest line-number-pseudo-variable
+	(testing "Verify the pseudo variable *line-number*"
+		(let [v (eval-in-temp-ns
+				(def foo [*line-number*
+					*line-number*])
+				(def bar *line-number*) 
+				(def baz 
+					*line-number*) 
+				(defn relativeToThisLine [n] (- *line-number* n))
+				;Use relative line numbers so this test is not fragile
+				(map relativeToThisLine [(first foo) (second foo) bar baz]))]
+			(is (= v [5 4 3 1])))))
diff --git a/test/clojure/test_clojure/protocols.clj b/test/clojure/test_clojure/protocols.clj
index f3bd9bf..b7e3b5f 100644
--- a/test/clojure/test_clojure/protocols.clj
+++ b/test/clojure/test_clojure/protocols.clj
@@ -48,8 +48,7 @@
   (testing "protocol fns have useful metadata"
     (let [common-meta {:ns (find-ns 'clojure.test-clojure.protocols.examples)
                        :protocol #'ExampleProtocol}]
-      (are [m f] (= (merge (quote m) common-meta)
-                    (meta (var f)))
+      (are [m f] (every? #(= (second %) ((meta (var f)) (first %))) (merge (quote m) common-meta))
            {:name foo :arglists ([a]) :doc "method with one arg"} foo
            {:name bar :arglists ([a b]) :doc "method with two args"} bar
            {:name baz :arglists ([a] [a b]) :doc "method with multiple arities" :tag String} baz
@@ -606,4 +605,3 @@
   (is (= "foo" (sqtp "foo")))
   (is (= :foo (sqtp :foo))))
 
-
diff --git a/test/clojure/test_clojure/reader.clj b/test/clojure/test_clojure/reader.clj
index 806b8ab..644ad15 100644
--- a/test/clojure/test_clojure/reader.clj
+++ b/test/clojure/test_clojure/reader.clj
@@ -432,7 +432,8 @@
     (is (= (count expected-metadata) @verified-forms))))
 
 (deftest t-Metadata
-  (is (= (meta '^:static ^:awesome ^{:static false :bar :baz} sym) {:awesome true, :bar :baz, :static true})))
+  (is (every? #(= (second %) ((meta '^:static ^:awesome ^{:static false :bar :baz} sym) (first %)))
+    {:awesome true, :bar :baz, :static true})))
 
 ;; Var-quote (#')
 
-- 
1.7.9.5

