All Projects → borkdude → edamame

borkdude / edamame

Licence: EPL-1.0 license
Configurable EDN/Clojure parser with location metadata

Programming Languages

clojure
4091 projects
shell
77523 projects

Labels

Projects that are alternatives of or similar to edamame

Fipp
Fast Idiomatic Pretty Printer for Clojure
Stars: ✭ 454 (+251.94%)
Mutual labels:  edn
Edn format
EDN reader and writer implementation in Python, using PLY (lex, yacc)
Stars: ✭ 92 (-28.68%)
Mutual labels:  edn
clj-rn
A utility for building ClojureScript-based React Native apps
Stars: ✭ 56 (-56.59%)
Mutual labels:  edn
Mpedn
EDN I/O library for Objective-C (MacOS and iOS)
Stars: ✭ 28 (-78.29%)
Mutual labels:  edn
Edn
Go implementation of EDN (Extensible Data Notation)
Stars: ✭ 76 (-41.09%)
Mutual labels:  edn
Eden Smart Contracts
EDEN - EDN Smart Token & Smart Contracts
Stars: ✭ 109 (-15.5%)
Mutual labels:  edn
Walkable
A Clojure(script) SQL library for building APIs: Datomic® (GraphQL-ish) pull syntax, data driven configuration, dynamic filtering with relations in mind
Stars: ✭ 384 (+197.67%)
Mutual labels:  edn
lines
A pure bash clojureish CI pipeline
Stars: ✭ 72 (-44.19%)
Mutual labels:  edn
Dyn Edn
Dynamic properties in EDN content
Stars: ✭ 88 (-31.78%)
Mutual labels:  edn
Eden
edn (extensible data notation) encoder/decoder for Elixir
Stars: ✭ 32 (-75.19%)
Mutual labels:  edn
Edn Data
EDN parser and generator that works with plain JS data, with support for TS and node streams
Stars: ✭ 44 (-65.89%)
Mutual labels:  edn
Fif
Stack-based Programming in Clojure(script)
Stars: ✭ 71 (-44.96%)
Mutual labels:  edn
Honeyeql
HoneyEQL is a Clojure library enables you to query database using the EDN Query Language.
Stars: ✭ 111 (-13.95%)
Mutual labels:  edn
Nippy
High-performance serialization library for Clojure
Stars: ✭ 838 (+549.61%)
Mutual labels:  edn
protean
Evolve your RESTful API's and Web Services
Stars: ✭ 31 (-75.97%)
Mutual labels:  edn
Coast
The fullest full stack clojure web framework
Stars: ✭ 442 (+242.64%)
Mutual labels:  edn
Wernicke
Redaction for structured data
Stars: ✭ 100 (-22.48%)
Mutual labels:  edn
cq
Clojure Command-line Data Processor for JSON, YAML, EDN, XML and more
Stars: ✭ 111 (-13.95%)
Mutual labels:  edn
rewrite-edn
Utility lib on top of rewrite-clj with common operations to update EDN while preserving whitespace and comments.
Stars: ✭ 65 (-49.61%)
Mutual labels:  edn
Sente
Realtime web comms for Clojure/Script
Stars: ✭ 1,626 (+1160.47%)
Mutual labels:  edn

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

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:

Usage

(require '[edamame.core :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))

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)))

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]

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]

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.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].