Giter Club home page Giter Club logo

edamame's Introduction

Edamame

Configurable EDN/Clojure parser with location metadata.

CircleCI Clojars Project

Reasons to use edamame

  • You want to include locations in feedback about Clojure and EDN files
  • You want to parse Clojure-like expressions without any evaluation
  • Anonymous functions are read deterministically
  • Highly configurable
  • Auto-resolve aliased keywords based on the ns form

This library works with:

  • Clojure on the JVM
  • GraalVM compiled binaries
  • ClojureScript (including self-hosted and advanced compiled)

Installation

Use as a dependency:

Clojars Project

Projects

Project using edamame:

API

See API.

Usage

(require '[edamame.core :as e :refer [parse-string]])

Location metadata

Locations are attached as metadata:

(def s "
[{:a 1}
 {:b 2}]")
(map meta (parse-string s))
;;=>
({:row 2, :col 2, :end-row 2, :end-col 8}
 {:row 3, :col 2, :end-row 3, :end-col 8})

(->> "{:a {:b {:c [a b c]}}}"
     parse-string
     (tree-seq coll? #(if (map? %) (vals %) %))
     (map meta))
;;=>
({:row 1, :col 1, :end-row 1, :end-col 23}
 {:row 1, :col 5, :end-row 1, :end-col 22}
 {:row 1, :col 9, :end-row 1, :end-col 21}
 {:row 1, :col 13, :end-row 1, :end-col 20}
 {:row 1, :col 14, :end-row 1, :end-col 15}
 {:row 1, :col 16, :end-row 1, :end-col 17}
 {:row 1, :col 18, :end-row 1, :end-col 19})

You can control on which elements locations get added using the :location? option.

Parser options

Edamame's API consists of two functions: parse-string which parses a the first form from a string and parse-string-all which parses all forms from a string. Both functions take the same options. See the docstring of parse-string for all the options.

Examples:

(parse-string "@foo" {:deref true})
;;=> (deref foo)

(parse-string "'bar" {:quote true})
;;=> (quote bar)

(parse-string "#(* % %1 %2)" {:fn true})
;;=> (fn [%1 %2] (* %1 %1 %2))

(parse-string "#=(+ 1 2 3)" {:read-eval true})
;;=> (read-eval (+ 1 2 3))

(parse-string "#\"foo\"" {:regex true})
;;=> #"foo"

(parse-string "#'foo" {:var true})
;;=> (var foo)

(parse-string "#(alter-var-root #'foo %)" {:all true})
;;=> (fn [%1] (alter-var-root (var foo) %1))

Note that standard behavior is overridable with functions:

(parse-string "#\"foo\"" {:regex #(list 're-pattern %)})
(re-pattern "foo")

Clojure defaults

The closest defaults to how Clojure reads code:

{:all true
 :row-key :line
 :col-key :column
 :end-location false
 :location? seq?}

Reader conditionals

Process reader conditionals:

(parse-string "[1 2 #?@(:cljs [3 4])]" {:features #{:cljs} :read-cond :allow})
;;=> [1 2 3 4]

(parse-string "[1 2 #?@(:cljs [3 4])]" {:features #{:cljs} :read-cond :preserve})
;;=> [1 2 #?@(:cljs [3 4])]

(let [res (parse-string "#?@(:bb 1 :clj 2)" {:read-cond identity})]
  (prn res) (prn (meta res)))
;;=> (:bb 1 :clj 2)
;;=> {:row 1, :col 1, :end-row 1, :end-col 18, :edamame/read-cond-splicing true}

Auto-resolve

Auto-resolve keywords:

(parse-string "[::foo ::str/foo]" {:auto-resolve '{:current user str clojure.string}})
;;=> [:user/foo :clojure.string/foo]

If you don't care much about the exact value of the keyword, but just want to parse something:

(parse-string "[::foo ::str/foo]" {:auto-resolve name})
;;=> [:current/foo :str/foo]

To create options from a namespace in the process where edamame is called from:

(defn auto-resolves [ns]
  (as-> (ns-aliases ns) $
    (assoc $ :current (ns-name *ns*))
    (zipmap (keys $)
            (map ns-name (vals $)))))

(require '[clojure.string :as str]) ;; create example alias

(auto-resolves *ns*) ;;=> {str clojure.string, :current user}

(parse-string "[::foo ::str/foo]" {:auto-resolve (auto-resolves *ns*)})
;;=> [:user/foo :clojure.string/foo]

To auto-resolve keywords from the running Clojure environment:

(require '[clojure.test :as t])
(e/parse-string "::t/foo" {:auto-resolve (fn [x] (if (= :current x) *ns* (get (ns-aliases *ns*) x)))})
:clojure.test/foo

:auto-resolve-ns

To auto-magically resolve keywords based on the ns form, use :auto-resolve-ns true:

(= '[(ns foo (:require [clojure.set :as set])) :clojure.set/foo]
    (parse-string-all "(ns foo (:require [clojure.set :as set])) ::set/foo"
                      {:auto-resolve-ns true}))

(def rdr (p/reader "(ns foo (:require [clojure.set :as set])) ::set/foo"))
(def opts (p/normalize-opts {:auto-resolve-ns true}))
(= (ns foo (:require [clojure.set :as set])) (p/parse-next rdr opts))
(= :clojure.set/foo (p/parse-next rdr opts))

Syntax-quote

Syntax quoting can be enabled using the :syntax-quote option. Symbols are resolved to fully qualified symbols using :resolve-symbol which is set to identity by default:

(parse-string "`(+ 1 2 3 ~x ~@y)" {:syntax-quote true})
;;=> (clojure.core/sequence (clojure.core/seq (clojure.core/concat (clojure.core/list (quote +)) (clojure.core/list 1) (clojure.core/list 2) (clojure.core/list 3) (clojure.core/list x) y)))

(parse-string "`(+ 1 2 3 ~x ~@y)" {:syntax-quote {:resolve-symbol #(symbol "user" (name %))}})
;;=> (clojure.core/sequence (clojure.core/seq (clojure.core/concat (clojure.core/list (quote user/+)) (clojure.core/list 1) (clojure.core/list 2) (clojure.core/list 3) (clojure.core/list x) y)))

To resolve symbols in syntax quote from the running Clojure environment:

(require '[clojure.tools.reader :refer [resolve-symbol]])

(require '[clojure.test :as t])
(e/parse-string "`t/run-tests" {:syntax-quote {:resolve-symbol resolve-symbol}})
;;=> (quote clojure.test/run-tests)

Data readers

Passing data readers:

(parse-string "#js [1 2 3]" {:readers {'js (fn [v] (list 'js v))}})
(js [1 2 3])

Postprocess

Postprocess read values:

(defrecord Wrapper [obj loc])

(defn iobj? [x]
  #?(:clj (instance? clojure.lang.IObj x)
     :cljs (satisfies? IWithMeta x)))

(parse-string "[1]" {:postprocess
                       (fn [{:keys [:obj :loc]}]
                         (if (iobj? obj)
                           (vary-meta obj merge loc)
                           (->Wrapper obj loc)))})

[#user.Wrapper{:obj 1, :loc {:row 1, :col 2, :end-row 1, :end-col 3}}]

This allows you to preserve metadata for objects that do not support carrying metadata. When you use a :postprocess function, it is your responsibility to attach location metadata.

Fix incomplete expressions

Edamame exposes information via ex-data in an exception in case of unmatched delimiters. This can be used to fix incomplete expressions:

(def incomplete "{:a (let [x 5")

(defn fix-expression [expr]
  (try (when (parse-string expr)
         expr)
       (catch clojure.lang.ExceptionInfo e
         (if-let [expected-delimiter (:edamame/expected-delimiter (ex-data e))]
           (fix-expression (str expr expected-delimiter))
           (throw e)))))

(fix-expression incomplete) ;; => "{:a (let [x 5])}"

Test

For the node tests, ensure clojure is installed as a command line tool as shown here. For the JVM tests you will require leiningen to be installed. Then run the following:

script/test/jvm
script/test/node
script/test/all

Credits

The code is largely inspired by rewrite-clj and derived projects.

License

Copyright © 2019-2022 Michiel Borkent

Distributed under the Eclipse Public License 1.0. This project contains code from Clojure and ClojureScript which are also licensed under the EPL 1.0. See LICENSE.

edamame's People

Contributors

anthonygalea avatar borkdude avatar ikitommi avatar kkinnear avatar kwrooijen avatar leifandersen avatar noahtheduke avatar pfeodrippe avatar rap1ds avatar retrogradeorbit avatar sogaiu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

edamame's Issues

Reader conditional problem with newline

version

bb --help
babashka v0.0.30

platform

OSX

problem

Error when adding a newline into reader conditional statement: Unmatched delimiter: )

repro

bb -e "#?(:bb (println \"hi\")\n)"
Unmatched delimiter: ) [at line 1, column 24]

expected behavior

 bb -e "#?(:bb (println \"hi\"))"
hi

Support for read+string

It turns out we don't need this for babashka, since LineNumberingInputReader supports this well enough. Closing for now.

Parse error with reader conditional

#?(:bb
   ;; test pod with JVM:
   (load-pod ["lein" "trampoline" "run" "-m" "pod.babashka.lanterna"]
             {:socket true
              :inherit-io true})
   #_(load-pod ["./pod-babashka-lanterna"] {:socket true
                                            :inherit-io true}))
 8:    #_(load-pod ["./pod-babashka-lanterna"] {:socket true
 9:                                             :inherit-io true}))
                                                                  ^--- Unmatched delimiter: )

Parsing :nil throws

$ bb --verbose ':nil'
java.lang.NullPointerException: null
 at clojure.core$namespace.invokeStatic (core.clj:1603)

No location metadata on :read-cond :preserve items

version

[borkdude/edamame "0.0.11-alpha.9"]

platform

OS/X 10.14.5

problem

When I specify :read-cond :preserve, the reader-conditional ends up in the output of parse-string-all, but does not have any location metadata. If I specify :read-cond :allow, and also specify the proper :features, I get the full location metadata on the resulting output from parse-string-all. The problem is, I don't necessarily know the correct :features to specify.

repro

(def ex "#?(:bb (defn zpmap [] (list :this :is :a :test)))")
(def expsa (edamame.core/parse-string-all ex {:read-cond :preserve}))
(meta (first expsa))
;=> nil
(def expsa (edamame.core/parse-string-all ex {:read-cond :allow :features #{:bb}}))
(meta (first expsa))
;=> {:row 1, :col 1, :end-row 1, :end-col 50}

expected behavior

Some background. I'm using edamame as a lightweight parser to scan a whole file to find the top-level expressions in the file. I am using only the location metadata from the results of parse-string-all. This is fast and works great for me -- except for one thing. If I specify :read-cond :preserve, then I don't get metadata on the top-level reader-conditionals. If instead, I specify :read-cond :allow then I get the correct location metadata on the output -- but only if I specify at least one feature that appears in the reader conditional. The problem is -- what if I didn't guess the correct features?

One approach would be to parse the file twice, once with :read-cond :allow and features, and once with :read-cond :preserve, and see if I get different numbers of things. If I did, I could find the differences and dig through the reader conditional and find the correct features and parse it again. But that is pretty ugly and slow as well.

What I'm hoping is that it wouldn't be a terrible thing to just return the location metadata on reader conditionals that were passed through with :read-cond :preserve:

(def ex "#?(:bb (defn zpmap [] (list :this :is :a :test)))")
(def expsa (edamame.core/parse-string-all ex {:read-cond :preserve}))
(meta (first expsa))
;=> {:row 1, :col 1, :end-row 1, :end-col 50}

PushbackReader overflow with LineNumberingPushbackReader

user=> (core/parse-next (core/reader (slurp "/tmp/try.cljs")))
(defn hash "Returns the hash code of its argument. Note this is the hash code\n   consistent with =." [o] (cond (implements? IHash o) (bit-xor (-hash o) 0) (number? o) (if (js/isFinite o) (js-mod (Math/floor o) 2147483647) (case o ##Inf 2146435072 ##-Inf -1048576 2146959360)) (true? o) 1231 (false? o) 1237 (string? o) (m3-hash-int (hash-string o)) (instance? js/Date o) (bit-xor (.valueOf o) 0) (nil? o) 0 :else (bit-xor (-hash o) 0)))
user=> (core/parse-next (clojure.lang.LineNumberingPushbackReader. (java.io.PushbackReader. ((requiring-resolve 'clojure.java.io/reader) "/tmp/try.cljs"))))
Execution error (IOException) at java.io.PushbackReader/unread (PushbackReader.java:156).
Pushback buffer overflow

Performance

Just calling rt/indexing-reader? is cheaper than doing the map lookup.

user=> (time (let [m {:reader? true} rdr (e/reader "foo")] (dotimes [i 100000000] (:reader? m) #_(rt/indexing-reader? rdr))))
"Elapsed time: 370.858313 msecs"
nil
user=> (time (let [rdr (e/reader "foo")] (dotimes [i 100000000] (rt/indexing-reader? rdr))))
"Elapsed time: 110.602497 msecs"

We'll have to change this.

Mismatch of allowed spaces in namespaced map syntax

version

1.3.20 + 1.3.21

platform

Unix

problem

In 1.3.20, #:: {:a 1} was disallowed as auto-resolved namespaced maps were expected to have no whitespace.

In 1.3.21, the rules were loosened beyond what Clojure allows, accepting forms that throw errors in Clojure.

repro

1.3.20:

user=> (e/parse-string "#:: {:a 1}" '{:auto-resolve {:current user f foo}})
Execution error (ExceptionInfo) at edamame.impl.parser/throw-reader (parser.cljc:44).
Invalid symbol:
user=> #:: {:a 1}
{:user/a 1}

I won't show the rest as they're all the same error.

1.3.21:

user=> (e/parse-string "#:: {:a 1}" '{:auto-resolve {:current user f foo}})
{:user/a 1}
user=> #:: {:a 1}
{:user/a 1}

user=> (e/parse-string "#: :{:a 1}" '{:auto-resolve {:current user f foo}})
{:user/a 1}
user=> #: :{:a 1}
Syntax error reading source at (REPL:1:3).
Namespaced map must specify a namespace
Syntax error reading source at (REPL:1:5).
Invalid token: :
{:a 1}

user=> (e/parse-string "#:: f {:a 1}" '{:auto-resolve {:current user f foo}})
{:foo/a 1}
user=> #:: f {:a 1}
Syntax error reading source at (REPL:1:5).
Namespaced map must specify a namespace
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: f in this context
{:a 1}

user=> (e/parse-string "#: :f {:a 1}" '{:auto-resolve {:current user f foo}})
{:foo/a 1}
user=> #: :f {:a 1}
Syntax error reading source at (REPL:1:3).
Namespaced map must specify a namespace
:f
{:a 1}

user=> (e/parse-string "#: : {:a 1}" '{:auto-resolve {:current user f foo}})
{:user/a 1}
user=> #: : {:a 1}
Syntax error reading source at (REPL:1:3).
Namespaced map must specify a namespace
Syntax error reading source at (REPL:1:5).
Invalid token: :
{:a 1}

user=> (e/parse-string "#: : f {:a 1}" '{:auto-resolve {:current user f foo}})
{:foo/a 1}
user=> #: : f {:a 1}
Syntax error reading source at (REPL:1:3).
Namespaced map must specify a namespace
Syntax error reading source at (REPL:1:5).
Invalid token: :
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: f in this context
{:a 1}

expected behavior

I expect that the parsing behavior matches Clojure: #:: {:a 1} and #::f {:a 1} but not any other kinds of whitespace (between the first and second : or between the second : and the alias symbol).

Support parsing clojure code in tagged literals

Repro:

$ clj -e "(set! *data-readers* {'identity identity})" -e "(def a (atom 0))" -e "#identity @a"
{identity #object[clojure.core$identity 0x1187c9e8 "clojure.core$identity@1187c9e8"]}
#'user/a
0
$ bb -e "(set! *data-readers* {'identity identity})" -e "(def a (atom 0))" -e "#identity @a"
clojure.lang.ExceptionInfo: [line 1, col 72] Invalid character: @ found while reading symbol.

We currently hand the readers to tools.reader.edn but we'll have to read the code ourselves.

This should just return the sexpr as is:

user=> (edamame/parse-string "#identity @foo" {:readers {'identity identity}})
Execution error (ExceptionInfo) at clojure.tools.reader.impl.errors/throw-ex (errors.clj:34).
[line 1, col 12] Invalid character: @ found while reading symbol.

Splicing Reader conditional breaks map literals when :read-cond not :allow

version

1.3.22

platform

Ubuntu

problem

Parsing a map literal that has a splicing reader conditional in it throws an exception.

repro

$ deps-try borkdude/edamame 1.3.22
[Rebel readline] Type :repl/help for online help info
user=> (require '[edamame.core :as e])
nil
user=> (e/parse-string-all "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond :allow})
[(def example {:a 1})]
user=> (e/parse-string-all "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond :preserve})
Execution error (ExceptionInfo) at edamame.impl.parser/throw-reader (parser.cljc:44).
The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms.
user=> (e/parse-string-all "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond (fn [obj] (list 'read-cond obj))})
Execution error (ExceptionInfo) at edamame.impl.parser/throw-reader (parser.cljc:44).
The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms.

expected behavior

I'm honestly not sure how to handle it. Unlike clj-kondo (cf clj-kondo #2049), edamame doesn't produce multiple versions of a given string/file so it's not really feasible to expand in the same way.

Maybe an edamame keyword to vector that contains each splicing reader conditional? This would work with :allow, :preserve, and the function option.

{:a 1 #?@(:clj [:b 2 :c 3]) #?@(:clj [:d 4 :e 5])} read in as {:a 1 :edamame.impl.parser/read-cond [(:clj [:b 2 :c 3]) (:clj [:d 4 :e 5])]}. That doesn't warn when one of them will produce an incorrect map (odd number of entries in the spliced vector), but that feels outside of edamame's scope.

full stack trace

user=> *e
#error {
 :cause "The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms."
 :data {:type :edamame/error, :row 1, :col 14}
 :via
 [{:type clojure.lang.ExceptionInfo
   :message "The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms."
   :data {:type :edamame/error, :row 1, :col 14}
   :at [edamame.impl.parser$throw_reader invokeStatic "parser.cljc" 44]}]
 :trace
 [[edamame.impl.parser$throw_reader invokeStatic "parser.cljc" 44]
  [edamame.impl.parser$throw_reader invoke "parser.cljc" 31]
  [edamame.impl.parser$throw_odd_map invokeStatic "parser.cljc" 507]
  [edamame.impl.parser$throw_odd_map invoke "parser.cljc" 505]
  [edamame.impl.parser$parse_map invokeStatic "parser.cljc" 525]
  [edamame.impl.parser$parse_map invoke "parser.cljc" 517]
  [edamame.impl.parser$dispatch invokeStatic "parser.cljc" 643]
  [edamame.impl.parser$dispatch invoke "parser.cljc" 570]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 710]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 697]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_to_delimiter invokeStatic "parser.cljc" 202]
  [edamame.impl.parser$parse_to_delimiter invoke "parser.cljc" 189]
  [edamame.impl.parser$parse_to_delimiter invokeStatic "parser.cljc" 191]
  [edamame.impl.parser$parse_to_delimiter invoke "parser.cljc" 189]
  [edamame.impl.parser$parse_list invokeStatic "parser.cljc" 223]
  [edamame.impl.parser$parse_list invoke "parser.cljc" 222]
  [edamame.impl.parser$dispatch invokeStatic "parser.cljc" 641]
  [edamame.impl.parser$dispatch invoke "parser.cljc" 570]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 710]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 697]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_string_all invokeStatic "parser.cljc" 842]
  [edamame.impl.parser$parse_string_all invoke "parser.cljc" 836]
  [edamame.core$parse_string_all invokeStatic "core.cljc" 75]
  [edamame.core$parse_string_all invoke "core.cljc" 69]

Map literal with uneven number of forms

version 0.0.3

platform OSX, Node.js, CLJS

problem

parse-string happily parses maps with uneven number of forms. I'd expect error to be thrown instead.

Is this a desired behaviour or a bug in the implementation?

If it's not a design choice but a bug, I'd happy to provide a PR to change this behavior. I guess the dispatch function should check that parse-to-delimiter returns even number of items before applying hash-map.

\{ (apply hash-map (parse-to-delimiter ctx reader \}))

repro

(edamame/parse-string "{:a :b :c}")
# => {:c nil, :a :b}

expected behavior

Same as cljs.reader:

(cljs.reader/read-string "{:a :b :c}")

# => Throws: Error: The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms.

support reading symbols with two slashes in them

version

1.0.0

platform

macOS

problem

I would like to be able to read symbols with two slashes in them using edamame.

repro

(require '[edamame.core :as edamama]
         '[clojure.edn :as edn])

(edn/read-string "foo/bar/baz")
;; => foo/bar/baz

(read-string "foo/bar/baz")
;; => foo/bar/baz

(edamame.core/parse-string "foo/bar/baz" {})
;; throws clojure.lang.ExceptionInfo Invalid symbol: foo/bar/baz.

expected behavior

Match the behaviour of clojure.core/read-string & clojure.edn/read-string.

Support preserving location metadata of numbers, strings, etc.

This is implemented as follows (excerpt from the README):

Postprocess read values:

(defrecord Wrapper [obj loc])

(defn iobj? [x]
  #?(:clj (instance? clojure.lang.IObj x)
     :cljs (satisfies? IWithMeta x)))

(parse-string "[1]" {:postprocess
                       (fn [{:keys [:obj :loc]}]
                         (if (iobj? obj)
                           (vary-meta obj merge loc)
                           (->Wrapper obj loc)))})

[#user.Wrapper{:obj 1, :loc {:row 1, :col 2, :end-row 1, :end-col 3}}]

This allows you to preserve metadata for objects that do not support carrying
metadata. When you use a :postprocess function, it is your responsibility to
attach location metadata.

Infinite loop when parsing data.xml node.cljc

The sexpr that is causing the loop:

(deftype Element [tag attrs content meta]
  #?@
  (:cljs
   [ (-pr-writer [this writer opts]
                (-write writer "#xml/element{:tag ")
                (pr-writer tag writer opts)
                (when-not (empty? attrs)
                  (-write writer ", :attrs ")
                  (pr-writer attrs writer opts))
                (when-not (empty? content)
                  (-write writer ", :content ")
                  (pr-sequential-writer writer pr-writer "[" " " "]" opts content))
                (-write writer "}"))]))

Expose expected delimiter as public data in exception

Simple repro:

{
 :x (
     { ;; offending error
     {:a 1}
     )
 }

(:ex-data e) with the nil values removed:

{ :end-location true
  :end-row-key :end-row
  :row-key   :row
  :col-key   :col
  :source-key :source
  :edamame.impl.parser/expected-delimiter "}"
  :type      :edamame/error
  :edamame.impl.parser/opened-delimiter { :char      "{"
                                          :row       3
                                          :col       6 }
  :col       6
  :end-col-key :end-col
  :row       6 }

Originally posted by @robert-stuttaford in #80 (comment)

Data readers for comments and #_ discard next form

Is your feature request related to a problem? Please describe.
I am using edamame to analyze clojure code. I would like to be able to analyze ; comments and the #_ discard next form reader macros.

Describe the solution you'd like
A flag in parse that converts comments and #_ to some sort of edamame tagged literal, maybe converting ; hello, I'm a comment to #edamame/comment "hello, I'm a comment" and #_ (println sym) to #edamame/discard (println sym). I'm not sure exactly what's possible, but this would help me a lot.

Describe alternatives you've considered
I could switch to a different parsing engine, like rewrite-clj. That's slower but the library is more featureful.

Fix postprocess + reading keyword metadata

This is basically the test demonstrating the problem:

(deftest postprocess-test
  (is (= [(->Wrapper 1 {:row 1, :col 2, :end-row 1, :end-col 3})]
       (p/parse-string "[1]" {:postprocess
                              (fn [{:keys [:obj :loc]}]
                                (if (iobj? obj)
                                  (vary-meta obj merge loc)
                                  (->Wrapper obj loc)))})))
  (let [p-fn (fn [{:keys [obj]}]
               (if (keyword? obj)
                 {:value obj}
                 obj))]
    (is (= {{:value :foo} true}
           (meta (p/parse-string "^:foo []" {:postprocess p-fn}))
           (meta (p/parse-string "^{:foo true} []" {:postprocess p-fn}))))))

packaging edamame for npm

Would it be possible to push a version of edamame as a js library to npm?

That way non-cljs codebases can have access to it. It's a really awesome library and I'm really surprised that read-string in clojure doesn't actually do that. It saved me a lot of time debugging config files.

ClojureDart `#/` parametrized type support

Is your feature request related to a problem? Please describe.
ClojureDart has a small reader dispatch extension, /, which adds support for parametrized types. It's implemented with a data reader (source).

The data reader provides support in two different ways:

  • the base is #/[List int] is transformed to ^{:type-params [int]} List. It's like the meta reader, and it works recursively.
  • There's also support for functions with ->: #/(int -> Null) is transformed to ^{:params-types [Null int] :type-params nil} dart:core/Function.
(dart-type-params-reader '[List])
; List
(dart-type-params-reader '[List [List int]])
; ^{:type-params (^{:type-params (int)} List)} List
(dart-type-params-reader '( int -> Never))
; ^{:params-types (Never), :type-params nil} dart:core/Function
(dart-type-params-reader '(dynamic -> [List int]))
; ^{:params-types (^{:type-params (int)} List dynamic), :type-params nil} dart:core/Function

Describe the solution you'd like
Edamame can read ClojureDart files without throwing an exception.

Describe alternatives you've considered
Fork edamame to add support, do nothing.

Additional context
I think that supporting a subset of this is a pretty simple extension and have a working branch that does so. I'm willing to open a PR with the change if so desired.

My subset would be the :type-params version, and leave off the function-specific parsing. #/(-> List) would be rendered as -> with metadata {:type-params [List]}.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.