cljs.reader/read-string and regular expression - clojurescript

Is this a bug or by design? It seems like CLJS reader cannot read regular expressions:
(cljs.reader/read-string (pr-str #"a"))
error {:message "EOF while reading.", :data {:type :reader-exception, :ex-kind :eof}}
Clojurescript v1.9.908
UPDATE: It works with v1.9.229 and Clojure 1.8.0. I'll try to see which version broke that...

cljs.reader/read-string only reads data in edn format & clojure regular expression #"..." syntax is not part of edn specification. (see http://edn-format.org)
https://cljs.github.io/api/cljs.reader/read-string
Reads one object from the string s. Returns nil when s is nil or
empty.
Reads data in the edn format (subset of Clojure data):
http://edn-format.org
opts is a map as per cljs.tools.reader.edn/read

Related

How to marshal a predicate from JSON in Prolog?

In Python it is common to marshal objects from JSON. I am seeking similar functionality in Prolog, either swi-prolog or scryer.
For instance, if we have JSON stating
{'predicate':
{'mortal(X)', ':-', 'human(X)'}
}
I'm hoping to find something like load_predicates(j) and have that data immediately consulted. A version of json.dumps() and loads() would also be extremely useful.
EDIT: For clarity, this will allow interoperability with client applications which will be collecting rules from users. That application is probably not in Prolog, but something like React.js.
I agree with the commenters that it would be easier to convert the JSON data to a .pl file in the proper format first and then load that.
However, you can load the predicates from JSON directly, convert them to a representation that Prolog understands, and use assertz to add them to the knowledge base.
If indeed the data contains all the syntax needed for a predicate (as is the case in the example data in the question) then converting the representation is fairly simple as you just need to concatenate the elements of the list into a string and then create a term out of the string. Note that this assumption skips step 2 in the first comment by Guy Coder.
Note that the Prolog JSON library is rather strict in which format it accepts: only double quotes are valid as string delimiters, and lists with singleton values (i.e., not key-value pairs) need to use the notation [a,b,c] instead of {a,b,c}. So first the example data needs to be rewritten:
{"predicate":
["mortal(X)", ":-", "human(X)"]
}
Then you can load it in SWI-Prolog. Minimal working example:
:- use_module(library(http/json)).
% example fact for testing
human(aristotle).
load_predicate(J) :-
% open the file
open(J, read, JSONstream, []),
% parse the JSON data
json_read(JSONstream, json(L)),
% check for an occurrence of the predicate key with value L2
member(predicate=L2, L),
% concatenate the list into a string
atomics_to_string(L2, S),
% create a term from the string
term_string(T, S),
% add to knowledge base
assertz(T).
Example run:
?- consult('mwe.pl').
true.
?- load_predicate('example_predicate.json').
true.
?- mortal(X).
X = aristotle.
Detailed explanation:
The predicate json_read stores the data in the following form:
json([predicate=['mortal(X)', :-, 'human(X)']])
This is a list inside a json term with one element for each key-value pair. The element has the syntax key=value. In the call to json_read you can already strip the json() term and store the list directly in the variable L.
Then member/2 is used to search for the compound term predicate=L2. If you have more than one predicate in the JSON file then you should turn this into a foreach or in a recursive call to process all predicates in the list.
Since the list L2 already contains a syntactically well-formed Prolog predicate it can just be concatenated, turned into a term using term_string/2 and asserted. Note that in case the predicate is not yet in the required format, you can construct a predicate out of the various pieces using built-in predicate manipulation functionality, see https://www.swi-prolog.org/pldoc/doc_for?object=copy_predicate_clauses/2 for some pointers.

How can I use read-csv-file to read from a string instead?

The 2htdp/batch-io library contains the useful read-csv-file procedure for reading a CSV file into a list. It takes a filename as its argument. Unfortunately, it does not take a string containing CSV as its argument. Suppose I have a CSV in a string variable and I want to use read-csv-file to parse it. Is there a way to avoid saving the CSV to a file just to be able to parse the CSV?
The documentation says:
reads the standard input device (until closed) or the content of file f and produces it as a list of lists of comma-separated values.
The standard input feature could probably be exploited to achieve this, but I don't know how to proceed with this idea.
The 2htdp libraries are meant for beginners are thus are less flexible than other csv libraries. Therefore, you have two options:
batch-io provides simulate-file which is something similar to what you want, but nothing as clean as wrapping your string in a function which makes it into a file like object:
> (simulate-file read-csv-file "test,test,test")
(list (list "test" "test" "test"))
Use csv-reading (csv-reading must be downloaded but just (require csv-reading) and continue through the errors it gives you):
#lang racket
(require csv-reading)
> (csv->list "test,test,test")
(list (list "test" "test" "test"))
If batch-io were more general it would be take in a string? or an input-port? but it does not.
I found a way to make read-csv-file read from a string instead:
> (require 2htdp/batch-io)
> (define csv-string "one,one,one\ntwo,two,two")
> (parameterize ([current-input-port (open-input-string csv-string)])
(read-csv-file 'stdin))
'(("one" "one" "one") ("two" "two" "two"))
This works by changing the standard input to be the CSV string.
#Ryan Schaefer's answer about simulate-file is great, but I feel a bit uncomfortable with using functionality that is still "under development" and not properly documented. As simulate-file's documentation says:
Note: this form is under development and will be documented in a precise manner after it is finalized and useful for a wide audience.

Convert yaml -> json in Racket

I was looking to convert some yaml documents to json using Racket and the yaml and json libraries. Both seem to work very well, but don't necessarily work well together. At the risk of this question being a little meta (I am interested in an idiomatic solution), can someone point me in the right direction?
Example yaml:
Title: Example
Description: An example
Content:
Type1:
- foo
- bar
- baz
Type2:
- chocolate
- vanilla
- strawberry
My quick attempt at converting a yaml:
#lang racket/base
(require json
yaml)
; reading is easy
(define example-yaml (file->yaml "./example.yaml"))
; writing doesn't like the keys-as-strings... why not?
; (write-json example-yaml)
; write-json: expected argument of type <legal JSON key value>; given: "Description"
; keys-as-symbols seems to be fine
(define example-yaml-2
#hash((Content
.
#hash((Type1 . ("foo" "bar" "baz"))
(Type2 . ("chocolate" "vanilla" "strawberry"))))
(Description . "An example")
(Title . "Example")))
(write-json example-yaml-2)
; {"Content":{"Type2":["chocolate","vanilla","strawberry"],"Type1":["foo","bar","baz"]},"Description":"An example","Title":"Example"}
I gather that the issue is that the json package doesn't see strings as a valid key in a jsexpr. The docs give the following example:
> (jsexpr? #hasheq(("turnip" . 82)))
#f
From where I sit the options seem to be:
Change the behavior of the yaml package to emit keys as symbols rather than as strings
Change the behavior of the json package to treat (jsexpr? #hasheq(("turnip" . 82))) as #t
Parse my yamls, then munge the resulting data structure such that keys are symbols.
I guess I don't entirely understand the implications (or have a solid handle on the implementation) of these options. I also am not entirely sure why keys as strings aren't valid jsexprs, given that the json it emits uses strings as keys as well. Thank you for any insight you can provide!
For method 3, just changing hash-table keys from strings to symbols might not be enough. It depends on how much you know about the format of your data.
For example the yaml package allows all sorts of things as "keys", not just strings but also binary data, numbers, hash-maps, or any other Yaml objects (keys may be arbitrary nodes).
So you must either:
Know beforehand that all keys in all of your Yaml data are simple strings,
Or be able to sanely convert any arbitrary Yaml value into a symbol,
Or convert Yaml maps into some Json structure other than a Json map.
For now I'm going to assume (1), that you know beforehand that all keys are strings.
;; yaml-key->symbol
;; In my Yaml data, I know beforehand that all keys are strings
(define (yaml-key->symbol key)
(cond
[(string? key) (string->symbol key)]
[else
(error 'yaml-key->symbol
"expected all Yaml keys to be strings, but got: ~v"
key)]))
There are other potential mismatches between Yaml and Json that you might have to consider.
How do you convert yaml byte-strings? As lists of bytes? Hex strings?
How do you convert yaml sets? As lists?
How do you convert yaml timestamps / dates? As Json maps mapping fields to numbers? Date strings? Number of seconds since the unix epoch?
For each of these questions, make a decision and document it. Or if you know ahead of time that your Yaml data definitely doesn't include any of these, document that too, and validate with an error similar to yaml-key->symbol above.
Once you know how to convert everything you might see in your data, you can traverse the Yaml recursively and convert it to Json.

How should I format data for a Kinesis event when using Amazonica in Clojure?

When I put an event into a stream using the AWS CLI, I can pass JSON in and get it back out, after decoding from base64. When I try to put an event using Amazonica, from Clojure, I am having a hard time formatting the event data parameter correctly though.
(kinesis/put-record "ad-stream" {:ad-id "some-id"} "parition-key"))
creates an event with a base64 encoded data field of "TlBZCAAAABXwBhsAAAACagVhZC1pZGkHc29tZS1pZA==", which decodes to
NP�jad-idisome-id
If I JSON encode the data first:
(kinesis/put-record "ad-stream" (json/write-str {:ad-id "some-id-2"}) "parition-key")
then I get an event with less junk characters, but it still isn't quite perfect, not good enough to read in other apps without breaking something:
NPi{"ad-id":"some-id-2"}
What is the significance of that leading junk, when converting Clojure maps to JSON? How to I pass a simple object to kinesis?
The tests show a plain map being passed as put-record's data parameter, I don't understand yet why that didn't just work for me:
(let [data {:name "any data"
:col #{"anything" "at" "all"}
:date now}
sn (:sequence-number (put-record my-stream data (str (UUID/randomUUID))))]
(put-record my-stream data (str (UUID/randomUUID)) sn))
(Thread/sleep 3000)
(def shard (-> (describe-stream my-stream)
:stream-description
:shards
last
:shard-id))
update
I'm pretty sure that this is a bug in the library (or the serializer that it uses), so I'm continuing the investigation in a bug report at https://github.com/mcohen01/amazonica/issues/211.
Passing a ByteBuffer of a JSON string as the record data works for me.
(kinesis/put-record "ad-stream"
(-> {:ad-id "ad-stream"}
json/write-str .getBytes ByteBuffer/wrap)
"parition-key")
Record data: "eyJhZC1pZCI6ImFkLXN0cmVhbSJ9", which decodes to:
{"ad-id":"ad-stream"}
This works around any encoding issue in the library, because Amazonica skips encoding when it is passed a ByteBuffer.

Clojurescript - update-in a Javascript map

How do I apply a given function to a JavaScript JSON structure ?
I would like to be able to do the following :
(update-in js-data [.-data] my-fn) ;; fails since .-data is not valid
update-in and similar functions only work on ClojureScript data structures.
In your specific example you could convert js-data to a ClojureScript data structure like this
(update-in (js->clj js-data) ["data"] my-fn)
If you cannot convert the Javascript object to a plain map you can always modify the original object in-place using set!.
(set! js-data -data my-fn)