Fork me on GitHub
#clojure
<
2023-01-26
>
MegaMatt14:01:30

is there a way to determine the line number read-string fails on in parsing a large edn file?

šŸ‘‹ 2
dpsutton15:01:37

(let [input "{:a\n :b\n\n\n\n\n:c\n1.a[1\n2\n3\n\n\n]}"
        stream (clojure.lang.LineNumberingPushbackReader.
                (java.io.StringReader. input))]
    (edn/read stream))

dpsutton15:01:47

this throws an clojure.lang.EdnReader$ReaderException

dpsutton15:01:01

which has line and column information

public static class ReaderException extends RuntimeException{
	final int line;
	final int column;

dpsutton15:01:26

persisted-info=> (reflect/reflect *e)
{:bases #{java.lang.RuntimeException},
 :flags #{:public},
 :members #{#clojure.reflect.Field{:name line,
                                   :type int,
                                   :declaring-class clojure.lang.EdnReader$ReaderException,
                                   :flags #{:final}}
            #clojure.reflect.Constructor{:name clojure.lang.EdnReader$ReaderException,
                                         :declaring-class clojure.lang.EdnReader$ReaderException,
                                         :parameter-types [int
                                                           int
                                                           java.lang.Throwable],
                                         :exception-types [],
                                         :flags #{:public}}
            #clojure.reflect.Field{:name column,
                                   :type int,
                                   :declaring-class clojure.lang.EdnReader$ReaderException,
                                   :flags #{:final}}}}
but they are not public. You could use reflection to make those fields visible and then grab them

MegaMatt15:01:51

great. thank you

dpsutton15:01:22

i think you could open up an http://ask.clojure.org ticket about maybe making those visible? Seems useful

delaguardo15:01:10

if you keep the reference to the reader then there are methods to get line and col: (.getLineNumber rdr) and (.getColumnNumber rdr) but they will not be accurate because EdnReader doesn't unread erroneous symbols on failure.

dpsutton15:01:31

ah right. can just check itā€™s state

dpsutton15:01:44

which is what the error has done as well. good call

Martynas Maciulevičius15:01:02

Is it safe to call empty? on an infinite lazy-seq? It calls seq inside and I was looking into implementation of clojure.lang.RT but I can't find the constructor. Maybe I don't know Java anymore...? šŸ˜…

delaguardo15:01:10

looks safe enough to me šŸ™‚

dpsutton15:01:23

yes it is safe. You can call seq on an infinite range. Just donā€™t print it. If you type (seq (range)) in a repl youā€™ll blow up. But it is just because you are printing an infinite sequence, not that the seq call did anything

dpsutton15:01:11

There are difficulties in this thought. For instance (filter neg? (range)) is empty as we know. But calling (empty? (filter neg? (range))) will lock your repl.

dpsutton15:01:22

Because it doesnā€™t reason about things like we can. It never finds an element to prove that itā€™s not empty, but never exhausts the (range) to prove that it is empty. So it keeps trying

Martynas Maciulevičius15:01:49

Thanks for the answer. I already tried to print it and it printed it. But it was weird that I can simply convert it into a seq (ok, I have some work experience with Clojure but it's good to revisit the old known things) Yes but the question also was about how would I make sense of what the Clojure compiler do? Does it actually call a method of RT.java?

dpsutton15:01:48

(defn empty?
  "Returns true if coll has no items - same as (not (seq coll)).
  Please use the idiom (seq x) rather than (not (empty? x))"
  {:added "1.0"
   :static true}
  [coll] (not (seq coll)))
it calls seq. so check (source seq):
(def
 ^{:arglists '(^clojure.lang.ISeq [coll])
   :doc "Returns a seq on the collection. If the collection is
    empty, returns nil.  (seq nil) returns nil. seq also works on
    Strings, native Java arrays (of reference types) and any objects
    that implement Iterable. Note that seqs cache values, thus seq
    should not be used on any Iterable whose iterator repeatedly
    returns the same mutable object."
   :tag clojure.lang.ISeq
   :added "1.0"
   :static true}
 seq (fn ^:static seq ^clojure.lang.ISeq [coll] (. clojure.lang.RT (seq coll))))
Thereā€™s your

Martynas Maciulevičius15:01:10

> Thereā€™s your I know the source. This is how I know that it calls RT.java. But I wanted to know whether it just calls this method: https://github.com/clojure/clojure/blob/e6fce5a42ba78fadcde00186c0b0c3cd00f45435/src/jvm/clojure/lang/RT.java#L531

dpsutton15:01:32

Iā€™m not sure I follow. It does call that from the source of seq. And there donā€™t seem to be any other calls besides not

Martynas Maciulevičius15:01:38

I wanted to know whether (seq ...) converts into the call of seq(Object object) from RT.java by compiler or does it do something different? Does the compiler call that method directly or by some other means? I expected to see an interpreter like switch op { "seq": seq(obj) } or something similar but there is nothing like that

dpsutton15:01:33

i think itā€™s just calling clojure.lang.RT/seq on the object

Martynas Maciulevičius15:01:37

Oh I see. I thought of (. clojure.lang.RT (seq coll)) not as a function call but as a (clojure.lang.RT. (seq coll)) . This is why I was looking for a constructor šŸ˜‚ Thanks.

dpsutton15:01:56

yeah. interop can look a bit strange without the sugar we are used to

Martynas Maciulevičius15:01:51

This actually works as code:

(clojure.lang.RT.)
But there is no constructor with a param so the (seq ...) one won't work šŸ˜„ This was my misconception šŸ˜„