How to retrieve element by key after using instaparse in clojure? - clojure

I am trying to make a compiler for a school project. I am a beginner in clojure. I have done a simple program (interpreting-lang-if) which can parse a string using instaparse and return a vector like this :
[:LangIF [:before_if "676767; "] [:condition_expression "0"]
[:statements_OK "1; 2;"] [:statements_NOK "3+4;"] [:after_if ""]]
How can I get the "before_if" element from the list?
I tried to understand the get function but I must have minsunderstood something in the usage of the function.
Here is some code :
(prn (interpreting-lang-if "676767; if (0) {1; 2;}else{3+4;};"))
(get (interpreting-lang-if "676767; if (0) {1; 2;}else{3+4;};") :before_if)
(get :before_if (interpreting-lang-if "676767; if (0) {1; 2;}else{3+4;};"))
The expected output is supposed to be "676767" instead of nil.
Thanks for your help.

If you dont know the exact position of the item:
(->> [:LangIF [:before_if "676767; "] [:condition_expression "0"]
[:statements_OK "1; 2;"] [:statements_NOK "3+4;"] [:after_if ""]]
(tree-seq vector? rest)
(filter vector?)
(filter (comp (partial = :before_if) first))
(first)
(second))
or if you do and would like to use specter:
(let [A [:LangIF [:before_if "676767; "] [:condition_expression "0"]
[:statements_OK "1; 2;"] [:statements_NOK "3+4;"] [:after_if ""]]]
(select [1 1] A))
or with simple get:
(let [A [:LangIF [:before_if "676767; "] [:condition_expression "0"]
[:statements_OK "1; 2;"] [:statements_NOK "3+4;"] [:after_if ""]]]
(get (get A 1) 1))

I've found zippers to be useful with Instaparse ASTs, especially when you need to find a particular node then find another node relative to it. Here's a function that searches through nodes to find the one matching a predicate:
(require '[clojure.zip :as zip])
(defn zip-to
([loc pred direction]
(loop [loc loc]
(if (and loc (not (zip/end? loc)))
(if (pred (zip/node loc))
loc
(recur (direction loc)))
loc))))
To find :before_if in your AST:
(-> [:LangIF
[:before_if "676767; "]
[:condition_expression "0"]
[:statements_OK "1; 2;"]
[:statements_NOK "3+4;"]
[:after_if ""]]
zip/vector-zip
(zip-to #{:before_if} zip/next)
zip/right
zip/node)

You can easily manipulate tree-like data-structures using the Tupelo Forest library. Here is a live example of your problem:
(dotest
(with-forest (new-forest)
(let [data-hiccup [:LangIF
[:before_if "676767; "]
[:condition_expression "0"]
[:statements_OK "1; 2;"]
[:statements_NOK "3+4;"]
[:after_if ""]]
root-hid (add-tree-hiccup data-hiccup)
before-hid (find-hid root-hid [:LangIF :before_if])
before-node (hid->node before-hid)
before-value (grab :value before-node)]
(is= before-node {:tupelo.forest/khids [], :tag :before_if, :value "676767; "})
(is= before-value "676767; "))))
before-hid is a pointer to the desired node, which we find by specifying the desired path [:LangIF :before_if] from the root node. We can then convert the pointer into the entire node, and extract the :value from the node. Many further manipulations are possible. See the docs and more examples.

Related

How to find the match from list of maps with list of keys in clojure

I am new to clojure.
I have a map s1 and a vector s2
(def s1 {:params [{:param :begin_date
:name "begin date"
:dtk :date
:param_operand :single}
{:param :begin_num
:name "begin num"
:dtk :numeric
:param_operand :single}
{:param :begin_num2
:name "begin num2"
:dtk :numeric
:biparam_operand :single}]})
(def s2 [:begin_date :begin_num])
I am trying to find the match from s1 with s2. I.E iterate through each element from s2 and find the match from s1.
If the match exists check the type for :dtk, if the type is :date then we accept that map.
This is the code which I have tried, when I had to find if the match exists using just one element from the list s2 - :begin_date
(defn test->map
[s1 s2]
(let [test-vec (->> s1
:params
(filter (fn [typet] (= (:param typet) :begin_date)))
first
:dtk)]
(when (= test-vec :date)
"done")))
How to modify the above function in a way that it should be able to iterate over the entire list of elements and find a match.
TIA.
One way to do it using keep:
(defn match [k]
(->> s1
:params
(keep
(fn [{:keys [param] :as v}]
(when (= param k)
v)))
first))
or by using for with :when:
(defn lookup [k]
(first
(for [{:keys [param] :as v} (:params s1)
:when (= param k)]
v)))
(This is bit similar to your approach with for and filter. Only :when does the filtering now, and I return the actual value instead of the string "done".)
With result:
(map lookup s2)
;; => ({:param :begin_date, :name "begin date", :dtk :date, :param_operand :single}
;; {:param :begin_num, :name "begin num", :dtk :numeric, :param_operand :single})
I would personally transform the current data structure s1 to a Clojure map (for example via reduce) once, so you can access the params by key and use select-keys. Then you don't have to iterate over the whole of s2 for every lookup.
(defn transform [s1]
(reduce
(fn [acc {:keys [param] :as v}]
(assoc acc param v))
{}
(:params s1)))
(transform s1)
;; => {:begin_date
;; {:param :begin_date, :name "begin date", :dtk :date, :param_operand :single},
;; :begin_num
;; {:param :begin_num, :name "begin num", :dtk :numeric, :param_operand :single},
;; :begin_num2
;; {:param :begin_num2,
;; :name "begin num2",
;; :dtk :numeric,
;; :biparam_operand :single}}
(select-keys (transform s1) s2)
;; => {:begin_date
;; {:param :begin_date, :name "begin date", :dtk :date, :param_operand :single},
;; :begin_num
;; {:param :begin_num, :name "begin num", :dtk :numeric, :param_operand :single}}

Building WHEN clause with a clojure expression in hugsql

I have a database with a status entity that I'd like to be able to fetch in many different ways. As a result, I'd like to build the WHEN clause of my query based on the content of a map.
For instance, like this:
(get-status *db* {:message_id 2 :user_id 1 :status "sent"})
;; or
(get-status *db* {:message_id 2})
;; or
(get-status *db* {:user_id 1})
;; etc.
I'm struggling using hubsql's clojure expressions. I am doing the following:
-- :name get-status
-- :doc fetch the status of a specific message
-- :command :query
-- :return :many
/* :require [clojure.string :as s] */
SELECT
*
FROM
message_status
/*~
(let [params (filter (comp some? val) params)]
(when (not (empty? params))
(str "WHERE "
(s/join ","
(for [[field value] params]
(str field " = " (keyword field)))))))
~*/
However, here is how the request is prepared by hugsql:
=> (get-status-sqlvec {:message_id 2 :user_id 1})
["SELECT\n *\nFROM\n message_status\nWHERE ? = ?,? = ?" 2 2 1 1]
Whereas I want something like:
=> (get-status-sqlvec {:message_id 2 :user_id 1})
["SELECT\n *\nFROM\n message_status\nWHERE message_id = 2, user_id = 1"]
I finally managed to get this working. The above code had two issues.
First, we have
(s/join ","
(for [[field value] params]
(str field " = " (keyword field)))
Since field is a keyword, this actually generates this kind of string: :foo = :foo, :bar = :bar. The keywords are then replaced by ? by hugsql. What we want instead is build this kind of string foo = :foo, bar = :bar, which we can do with this code:
(s/join ", "
(for [[field _] params]
(str (name field) " = " field))))))
The second problem is that the WHEN clause is not even valid SQL. The above code ends up generating requests such as:
SELECT * FROM message_status WHERE foo = 2, bar = 1
The commas in the WHERE clause should be AND, so the final (working) code is:
-- :name get-status
-- :doc fetch the status of a specific message
-- :command :query
-- :return :many
/* :require [clojure.string :as s] */
SELECT
*
FROM
message_status
/*~
(let [params (filter (comp some? val) params)]
(when (not (empty? params))
(str "WHERE "
(s/join " AND "
(for [[field _] params]
(str (name field) " = " field))))))
~*/

How to format string in Clojure by using map-indexed and vector

I am trying to read content from a file which is in the format :
ID|Name|Country-Name|Phone-Number eg:
1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
I am using following code to fetch data from it :
(
map-indexed
#(vector %1 %2)
(
map #(vec(.split #"\|" %1))
(
line-seq (clojure.java.io/reader "test.txt")
)
)
)
with this code I am getting this output:
([0 ["1" "Austin" "Germany" "34-554567"]] [1 ["2" "Mary Jane" "Australia" "45-78647"]] [2 ["3" "King Kong" "New-Zealand" "35-467533"]])
I want the output to be like:
ID:["name" "country-name" "phone-number"]
ID:["name" "country-name" "phone-number"]
eg:
1:["Austin" "Germany" "34-554567"]
2:["Mary Jane" "Australia" "45-78647"]
where ID is to be incremented by 1 (start from 1,2,3 and so on) and each result lists the identity or ID, succeeded by the data united with the ID and it should be sorted by an ID.
What changes do I do to my code to make this happen?
maybe
(into {} (map-indexed
#(vector (inc %1) (rest %2))
(repeat 2 ["1" "Austin" "Germany" "34-554567"])))
It looks like your data already has indexes in it:
(def data
"1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
3|King Kong|New-Zealand|35-467533 ")
(defn fmt [line]
(let [sections (-> line
str/trim
(str/split #"\|")) ]
sections) )
(defn run []
(let [lines (vec (str/split-lines data)) ]
(mapv fmt lines)))
(run)
with result:
sections => ["1" "Austin" "Germany" "34-554567"]
sections => ["2" "Mary Jane" "Australia" "45-78647"]
sections => ["3" "King Kong" "New-Zealand" "35-467533"]
If you wanted to throw away the indexes in the data, you could generate your own like so:
(defn fmt [idx line]
(let [sections (-> line
str/trim
(str/split #"\|"))
sections-keep (rest sections)
result (apply vector idx sections-keep)]
result))
(defn run []
(let [lines (vec (str/split-lines data))]
(mapv fmt (range 1 1e9) lines)))
Update
If you want to use a disk file, do this:
(def data
"1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
3|King Kong|New-Zealand|35-467533 ")
(defn fmt [idx line]
(let [sections (-> line
str/trim
(str/split #"\|"))
sections-keep (rest sections)
result (apply vector idx sections-keep)]
result))
(defn run [filename]
(let [lines (vec (str/split-lines (slurp filename)))]
(mapv fmt (range 1 1e9) lines)))
(let [filename "/tmp/demo.txt"]
(spit filename data)
(run filename))
A guess:
(def data
"1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
3|King Kong|New-Zealand|35-467533")
(->> (for [line (clojure.string/split data #"[ ]*[\r\n]+[ ]*")]
(-> line (clojure.string/split #"\|") rest vec))
(map vector (rest (range))))
; ([1 ["Austin" "Germany" "34-554567"]]
; [2 ["Mary Jane" "Australia" "45-78647"]]
; [3 ["King Kong" "New-Zealand" "35-467533"]])
Although I'm not sure why you want to have explicit auto-generated ID in the result and ignore the serial-number you've got in the original data.
Optionally add (into (sorted-map)) to the end so you get the sequential ids mapped to values, and this retains the id order unlike a hash-map.

clojure - correct way to apply transformations to map of data?

I'm trying to come up with a good way of applying specific transformation functions to a map of data.
Take the example map:
{:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}}
And transform it to:
{:name "foo bar"
:address "line1 /n line2"
:age 86}
I also want the transform to work the other way round, although I wouldn't mind writing a separate transform.
So far, I've tried writing a list of transformation functions: (pseudo)
(-> start-map
transform-name
transform-address
transform-age)
each transform taking [start-map {accumulator-map}]. I've also attempted writing a map containing the keys of the transformed map, and the transform functions (and arguments) as their values. I feel like I'm missing a trick.
Transform:
(require '[clj-time.core :as t])
(require '[clj-time.format :as f])
(def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
(def transformed
{:name (str (get-in data [:wrapper :firstName])
" "
(get-in data [:wrapper :lastName]))
:address (str (get-in data [:wrapper :addressLine1])
"\n"
(get-in data [:wrapper :addressLine2]))
:age (t/in-years (t/interval (f/parse
(get-in data [:wrapper :birthDate :iso] data))
(t/now)))})
Inverse transform. Note that the date lost precision.
(require '[clojure.string :as str])
(def untransformed
(let [[firstname lastname] (str/split
(:name transformed)
#" ")
[addressline1 addressline2] (str/split
(:address transformed)
#"\n")]
{:wrapper
{:firstName firstname
:lastName lastname
:addressLine1 addressline1
:addressLine2 addressline2
:birthDate
{:iso (f/unparse
(f/formatters :date)
(t/minus (t/now)
(t/years (:age transformed))))}}}))
You have the basic idea right. Here is how I would do it:
(ns tst.clj.core
(:use clj.core
clojure.test))
(def data
{:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}
)
(def target
{:name "foo bar"
:address "line1\nline2"
; :age 86 ; left as an excercise to the reader :)
})
(defn transform-name [m]
{:name (str (:firstName m) " "
(:lastName m))})
(defn transform-addr [m]
{:address (str (:addressLine1 m) \newline
(:addressLine2 m))})
(defn transform-person-simple [m]
(merge (transform-name m)
(transform-addr m)))
; You could also use the obscure function `juxt`, although this is
; more likely to confuse people.
; See http://clojuredocs.org/clojure.core/juxt
(defn transform-person-juxt [m]
(let [tx-juxt (juxt transform-name transform-addr)
juxt-answers (tx-juxt m)
result (into {} juxt-answers) ]
result ))
(deftest t-tx
(is (= target (transform-person-simple data)))
(is (= target (transform-person-juxt data)))
)
with results:
> lein test
(:repositories detected in user-level profiles! [:user]
See https://github.com/technomancy/leiningen/wiki/Repeatability)
lein test tst.clj.core
Ran 1 tests containing 2 assertions.
0 failures, 0 errors.
zipmap, juxt and destructuring are pretty handy with map transformations.
(defn unwrap [{person :wrapper}]
(let [date-format (java.text.SimpleDateFormat. "yyyy-MM-dd")
name-fn #(str (:firstName %) " " (:lastName %))
address-fn #(str (:addressLine1 %) \newline (:addressLine1 %))
age-fn #(- (.getYear (java.util.Date.))
(.getYear (.parse date-format (get-in % [:birthDate :iso]))))]
(zipmap [:name :address :age]
((juxt name-fn address-fn age-fn) person))))
You can also define your mapping as a data structure with the keys and transformation functions you provided. E.g.
(def a->b
'[[:name (->fullname [:wrapper :firstName] [:wrapper :lastName])]
[:address [:wrapper :addressLine1]] ;; left as an exercise for the reader :)
[:age (->age [:wrapper :birthDate :iso])]])
where
(defn ->fullname [& s] (str/join " " s))
(defn ->age [s]
(let [now (Date.)
d (Date. s)]
(- (.getYear now) (.getYear d))))
then implement a function to do the transformation with your mapping rule and your source map:
(transform a->b {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930/03/12"}}})
=>
{:name "foo bar", :address "line1", :age 86}
A quick implementation can be like this:
(defn get-val [src s]
(if-let [v (or (get src s)
(get-in src s))]
v
(let [[f & ss] s
mf (resolve f)]
(apply mf (map (partial get-val src) ss)))))
(defn transform [m src]
(reduce (fn [ans [t s]]
(let [af (if (coll? t) assoc-in assoc)]
(af ans t (get-val src s))))
(empty src)
m))
To make it universal, i would make the transformation function that would select paths from source object, process the selected values to the map of path-in-target to value:
(defn transform [source target paths transformation]
(reduce (partial apply assoc-in)
target
(apply transformation
(map #(get-in source %) paths))))
then you could use it like this:
user> (def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
#'user/data
user> (def data-2
(let [tr (partial transform data)]
(-> {}
(tr [[:wrapper :firstName] [:wrapper :lastName]]
(fn [f l] {[:name] (str f \space l)}))
(tr [[:wrapper :addressLine1] [:wrapper :addressLine2]]
(fn [a1 a2] {[:address] (str a1 \newline a2)}))
(tr [[:wrapper :birthDate :iso]]
(fn [d] {[:age] (reverse d)})))))
#'user/data-2
;;{:name "foo bar",
;; :address "line1\nline2",
;; :age (\2 \1 \- \3 \0 \- \0 \3 \9 \1)}
and vice versa:
user> (let [tr (partial transform data-2)]
(-> {}
(tr [[:name]]
(fn [n]
(let [[n1 n2] (clojure.string/split n #"\s")]
{[:wrapper :firstName] n1
[:wrapper :lastName] n2})))
(tr [[:address]]
(fn [a]
(let [[a1 a2] (clojure.string/split a #"\n")]
{[:wrapper :addressLine1] a1
[:wrapper :addressLine2] a2})))
(tr [[:age]]
(fn [a] {[:wrapper :birthDate :iso]
(apply str (reverse a))}))))
;;{:wrapper {:firstName "foo",
;; :lastName "bar",
;; :addressLine1 "line1",
;; :addressLine2 "line2",
;; :birthDate {:iso "1930-03-12"}}}

Clojure - Recursively Semi-Flatten Nested Map

In clojure, how can I turn a nested map like this:
(def parent {:id "parent-1"
:value "Hi dude!"
:children [{:id "child-11"
:value "How is life?"
:children [{:id "child-111"
:value "Some value"
:children []}]}
{:id "child-12"
:value "Does it work?"
:children []}]})
Into this:
[
[{:id "parent-1", :value "Hi dude!"}]
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"}]
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"} {:id "child-111", :value "Some value"}]
[{:id "parent-1", :value "Hi dude!"} {:id "child-12", :value "Does it work?"}]
]
I'm stumbling through very hacky recursive attempts and now my brain is burnt out.
What I've got so far is below. It does get the data right, however it puts the data in some extra undesired nested vectors.
How can this be fixed?
Is there a nice idiomatic way to do this in Clojure?
Thanks.
(defn do-flatten [node parent-tree]
(let [node-res (conj parent-tree (dissoc node :children))
child-res (mapv #(do-flatten % node-res) (:children node))
end-res (if (empty? child-res) [node-res] [node-res child-res])]
end-res))
(do-flatten parent [])
Which produces:
[
[{:id "parent-1", :value "Hi dude!"}]
[[
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"}]
[[
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"} {:id "child-111", :value "Some value"}]
]]]
[
[{:id "parent-1", :value "Hi dude!"} {:id "child-12", :value "Does it work?"}]
]]
]
I don't know if this is idiomatic, but it seems to work.
(defn do-flatten
([node]
(do-flatten node []))
([node parents]
(let [path (conj parents (dissoc node :children))]
(vec (concat [path] (mapcat #(do-flatten % path)
(:children node)))))))
You can leave off the [] when you call it.
another option is to use zippers:
(require '[clojure.zip :as z])
(defn paths [p]
(loop [curr (z/zipper map? :children nil p)
res []]
(cond (z/end? curr) res
(z/branch? curr) (recur (z/next curr)
(conj res
(mapv #(select-keys % [:id :value])
(conj (z/path curr) (z/node curr)))))
:else (recur (z/next curr) res))))
I'd be inclined to use a bit of local state to simplify the logic:
(defn do-flatten
([node]
(let [acc (atom [])]
(do-flatten node [] acc)
#acc))
([node base acc]
(let [new-base (into base (self node))]
(swap! acc conj new-base)
(doall
(map #(do-flatten % new-base acc) (:children node))))))
Maybe some functional purists would dislike it, and of course you can do the whole thing in a purely functional way. My feeling is that it's a temporary and entirely local piece of state (and hence isn't going to cause the kinds of problems that state is notorious for), so if it makes for greater readability (which I think it does), I'm happy to use it.