Fork me on GitHub
#clojure
<
2023-09-13
>
Adam Kalisz09:09:31

How do I construct java field descriptor symbol or in general the type such as [Lorg.xbill.DNS.Record; so I can use it in (instance? ... result) ?

tatut09:09:25

[L…; is an array

tatut09:09:42

you could (class (into-array org.xbill.DNS.Record []))

👍 1
borkdude11:09:31

Or this:

(instance? (Class/forName "[Ljava.lang.Long;") 1)

vollcheck11:09:41

Hi, I'm building a macro that takes a string and transforms it operating on the &env:

(defmacro s* [some-string] ...)
and I would like to introduce a data-reader for it:
{s my-ns/s*}
It works fine if I'm using the macro explicitly:
(s* "abc")
but for data-reader #s "abc" it raises Syntax error ... telling me that it received wrong number of args (1). If I introduce the auxiliary function that tagged literal would point to, then I lose &env. How to deal with that? Apparently defmacro does not allow to use multiarity in a tandem of single argument [s] and any number of arguments [& args]

oyakushev11:09:20

I might be wrong but I don't think data readers receive &env.

vollcheck11:09:38

yeah, but macro does

oyakushev11:09:00

Also, a data reader implementation has to be a function that transforms code, not a macro.

oyakushev11:09:54

> yeah, but macro does Macro receives them because the code that invokes macros as macros passes &form and &env to it. You can't plug a macro instead of a function to data-readers map and expect it to receive &env.

borkdude11:09:56

a macro is basically a function that looks like:

(fn [form env your-arg1 your-arg2] ...)

vollcheck11:09:39

okay, I'll try to change the strategy then, thanks anyway!

oyakushev12:09:50

@U03N5HN4K1N The problem is that reader tags expand at read time (cue the name), not at macroexpension time (which is know as "analyze time" in the compiler). At read time, locals are unknown.

oyakushev12:09:50

There is a way to obtain locals not just in a macro as &env argument, but by manually calling (clojure.lang.Compiler/LOCAL_ENV). But that won't help you here because the env will be empty at read time. You can verify that yourself.

teodorlu13:09:03

Could your reader literal (#s) perhaps emit a macro or function call? (not completely sure if that works. Edit: doesn’t work. Edit2: it does work, apparently!)

Joshua Suskalo14:09:17

The value emitted by the tagged literal is not further evaluated. It's a data-returning function, not a macro.

👍 1
oyakushev15:09:40

> The value emitted by the tagged literal is not further evaluated. It's a data-returning function, not a macro. Well, actually, it is further macroexpanded, so @U3X7174KS suggestion might be valid.

=> (defmacro example-macro [v] `(println "here" ~v))

=> (defn example-reader [v] `(example-macro ~v))

=> (binding [*data-readers* {'s #'user/example-reader}] (eval (read-string "#s[]")))
here []
nil

👍 1
Joshua Suskalo15:09:53

interesting, I think I'd missed something. Sorry about that

👍 2
teodorlu15:09:36

No problem! As I noted, I wasn’t quite sure myself, but I had a hunch. Thanks Olexandr for figuring it out!

ghaskins12:09:23

Hello. I am wondering if there is some way to conditionalize (e.g. reader-macro magic) how library A behaves based on a dependency library B’s version. In this case, library B is a commonly used library that may have ABI breaking changes between versions, but different users of library A may be on different versions of B.

ghaskins12:09:44

For context: https://clojurians.slack.com/archives/C056TDVQ5L1/p1694558869377739 Temporal SDK depends on a defprotocol in promesa, but the ABI of this protocol has evolved from release to release. Different users may be on different versions of promesa, and I would like to support them all, if possible.

p-himik12:09:01

If there's a way to get the version of B at run time, you can rely on that. If not, then you can try doing something that only a particular version of B support when the namespace in A is loaded and determine the version based on whether that something succeeds or fails.

p-himik12:09:00

Seems like you can simply check for presence of particular functions in the case with Promesa.

ghaskins14:09:39

Thank you @U2FRKM4TW. I am a little fuzzy on what that probing might look like. Any pointers appreciated

madstap14:09:06

You could use resolve to check for the presence of a function. Something like this:

(def promesa-11?
  (some? (resolve 'promesa/-fmap)))

vemv14:09:37

I guess you could inspect the classpath at macroexpansion time Not very pretty... also won't be AOT-friendly

ghaskins14:09:27

All good suggestions, thank you. I will play around

pez13:09:33

I’m trying to understand PEG (someone on Twitter was passionate about it in #janet, and I got curious). Trying to use @ericnormand’s https://github.com/ericnormand/squarepeg for this and am running into troubles. Partly because the docs seem to lie a bit, and partly because PEG blows my mind, and partly because I don’t get the Java interop well enough. Wondering if there is anyone here that’s given squarepeg a spin, or that has ideas about how I can use Clojure to figure PEG out. I’ll post an example trouble I have in a reply. ->🧵

pez13:09:55

Here’s a thing strange to me:

(def digit (peg/mkpr peg/digit))

(def minutes (peg/mkseq digit digit))

(tests
 ((peg/mkfn minutes) ["1" "1" "1"] {}) := []
 ((peg/mkfn minutes) ["1" "1"] {}) := ["1" "1"]
 :rcf)
This doesn’t crash (even if it doesn’t do what I want it to either, but that’s a later matter). These two does not work:
(def minutes (peg/mkseq peg/digit peg/digit)) ; => No matching method isDigit found taking 1 args
This looks equivalent to me… peg/digit is implemented like so:
(defrule digit    (mkpr #(Character/isDigit         %)))

pez13:09:12

I have a BNF that I am trying to implement. I get the BNF to work with instaparse. Which also speaks about PEG… So maybe I can use instaparse to figure PEG out?

Noah Bogart14:09:47

i know a fair amount about PEG parsers, but I don't know the library. seems like the issue is the library and not your understanding of PEG parsers?

hiredman15:09:18

My vague guess is you are passing single character strings where a single character is expected

1
👀 1
hiredman15:09:40

Because you aren't use defrule for minutes, because defrule automatically applies mksub which seems horrendous

hiredman15:09:44

A PEG is a way of describing a grammar, it just happens that the way PEG describes grammars happens to map fairly directly to naive top down parser combinators

hiredman15:09:54

There is a good chance if some is into PEG, they are really into parser combinators

hiredman15:09:27

A PEG is the same kind of thing as a context free grammar, and they have a non-trivial amount of overlap in languages you can describe using them

hiredman15:09:07

So you can use PEG grammars as the basis of a parser generator, something that takes a text descript of the PEG and spits out code that parses the language that PEG describes, and there are libraries that do that (dunno if any exist in clojure)

hiredman15:09:41

But PEG and parser combinators are often treated as synonymous, when they definitely are not

pez17:09:10

@UEENNMX0T, it’s both. 😃 My goal is to understand what PEG, how it compares to regexes (that Janet does not have regexes and people instead use PEGs there peaks my interest), to BNF, to instaparse, and generally what they could bring to the table for me. I then hit a wall trying to use squarepeg (where not even some of the examples work, and on top of that, I just don’t understand how to use it, and that recurses back to me not knowing what PEGs are…).

👍 1
pez17:09:09

I now understand PEGs a bit better, thanks to @U0NCTKEV8. 🙏 I think instaparse might do both those, but I’m not sure.

hiredman17:09:58

instaparse both supports a deterministic choice operator (the main difference between pegs and cfgs) and also has a parser combinator api

hiredman17:09:23

regexs (that match regular langauges) are actually easy to implement in a way that peforms poorly, but it is very educational to do so

pez17:09:22

Where I’m coming from: I’m reading The Pragmatic Programmer and it had these exercises: 1. Write a BNF for a time language that understands things like 1, 2pm, 4:30, 11:30am 2. Implement a parser for the BNF using a PEG parser generator in the language of my choice, where the output should be number of minutes past midnight. 3. Implement the same parser using regexes. As I didn’t quite understand what a PEG, or a PEG parser is, I fed my BNF to instaparse and wrote a transformer and could check of 2. that way. But then I also have this interest in PEGs so wanted to do it the PEG way.

hiredman17:09:00

https://crypto.stanford.edu/~blynn/haskell/re.html might be a good place to start for that, I haven't looked at that page specifically, but the technique "derivatives of regular expressions" is what makes implementing them easy, and that page even seems to suggest you can go from that to a dfa, which is how you get something high performance, which I was not aware of

hiredman17:09:08

(for regexes)

pez17:09:20

> regexs (that match regular langauges) are actually easy to implement in a way that peforms poorly Yeah, the Clojure lexer in Calva is written with regexes, so I know all about that. 😃 Very educational indeed.

pez17:09:23

I’d love to implement it using DFA. I’m thinking that would speed things up quite considerably. But… I also need a lot of time to do that.

pez17:09:45

Thanks for that Haskell link, @U0NCTKEV8! I’ve found some other regex->dfa things before, but not quite understood what I’m reading about. Maybe this one can make it click…

hiredman17:09:01

the derivative stuff is often not how regexes are introduced, which is shame because for functional languages it is very elegant

pez17:09:42

If I could automatically translate my lexer regexes to DFA equivalents, then I could keep writing regexes, maybe?

hiredman17:09:50

and there has been some recentish working extending it from regular languages to context free languages "parsing with derivatives"

hiredman17:09:09

one way to think about context free languages is as recursive regular languages

hiredman17:09:51

so in the context of parsing time strings, you really don't need the power of the recursion, so a regex works fine

hiredman17:09:44

with a language like say clojure, each part of the expression you are trying to parse is often potentially another clojure expression, so there is the recursion

hiredman17:09:43

lexing is actually what I first got into the regex derivative stuff for, because I was writing a lexer and wanted to be able to feed characters one at a time to a regex, but the regex library built into the language I was using (lua) didn't support that

hiredman17:09:14

so I ended up writing my own regex library, and the derivative stuff was the easiest way to do that

hiredman17:09:41

derivatives of regular languages, dfas, nfas, are all things for implementing regular expressions, writing a regex library, not techniques for using an existing regex library (which I think the code above is doing? or at least using the regex library built into the lexer library)

pez18:09:15

I’ve been hoping I could find a dfa library, and then also find a translator eat my regexes and spit out something I can feed to the dfa engine. But I haven’t investigated if that is possible. These regexes are so important that I need the safety regexes give me, because I can read regexes. I don’t know how fast I could learn something else up to the same reading ability.

hiredman18:09:51

if you are using a regex library (https://github.com/BetterThanTomorrow/calva/blob/published/src/cursor-doc/lexer.ts#L95 looks like you are, not sure where Regex is coming from there? built in js?) then under the covers it is almost certainly producing a dfa to make matching efficient

hiredman18:09:20

a dfa is an optimization technique inside a regex engine

hiredman18:09:55

but it is considered such a crucial optimization technique that I don't think any regex engine would be taken seriously without it

pez18:09:50

It’s JS native regexes.

hiredman18:09:06

so dfa stuff is below your level of abstraction there

pez18:09:43

Interesting. Maybe what I am after is to actually rewrite the lexer so that it does not require backtracking. It was a while since I analyzed this, but iirc there are some backtracking scenarios.

hiredman18:09:22

this was part of why I wanted to be able to feed a character at a time to a regex, I wanted to run a bunch of regexes "in parallel", so read from the input, hand the character to each regex, do the next etc

hiredman18:09:17

where what is likely happening here is building a buffer of input and then running each regex against the buffer

pez19:09:37

Yes, makes sense.

pez19:09:27

> Because you aren’t use defrule for minutes, because defrule automatically applies mksub which seems horrendous What did you mean with this, @U0NCTKEV8? It’s not like I know what I am doing. 😃 I was inspired by this example from the README:

;; match a seq of ones
(def ones (mkzom (mklit 1)))
;; match a seq of ones followed by 2s
(def onesthentwos (mkseq (mksub ones) (mkzom (mklit 2))))
(onesthentwos [[1 1 1] 2 2] {}) => SUCCESS
(onesthentwos [1 1 1 2 2] {}) => FAILURE
Which doesn’t actually work, but I could make it work by wrapping the whatever-it-is-called in mkfn:
((peg/mkfn onesthentwos) [[1 1 1] 2 2] {})

Samuel Ludwig19:09:30

I also wish that there was a straight PEG library for clojure (also was mainly exposed to it when I played around with Janet), I like the 'flow' of PEGs a lot more than the BNF-style

Samuel Ludwig19:09:33

I've never played with squarepeg, I'll give it a look though, I'm honestly surprised there weren't too many exploratory PEG efforts in the clojure ecosystem

Samuel Ludwig19:09:25

I guess Instaparse passes the "good enough" threshold

hiredman19:09:15

crustimoney looks much better, even in terms of distinguishing PEG as a way to define a grammar vs. how a program is written to parse things that are written in that grammar, but just glancing at it, the screenshot at the top of the readme is impossible (parsing use the :root value of the map, but not passed the whole map, but the root value contains references to other keys in the map)

hiredman19:09:23

Maybe missing a call to grammar around the map?

pez19:09:09

Checking out crustimoney now. Thanks, @U0482NW9KL1!

❤️ 1
Samuel Ludwig19:09:47

@U0NCTKEV8 it looks like that sample is valid, assuming that the call to ref in the definition is proper magic that'll make things work

hiredman19:09:13

You can look at the tests

hiredman19:09:57

The tests wrap the map in a call to grammar

pez19:09:57

I’m still a bit confused around how BNF and PEG parsers relate. Does this even make sense?

hiredman19:09:59

I don't think they do, my guess is they are using PEG to mean parse combinators

hiredman19:09:26

Bnf is specifically a syntax for describing cfgs, which are not the same as pegs

pez20:09:29

When I used instaparse to parse and transform the time strings using my BNF grammar. Did I “Implement a parser for the BNF grammar using a PEG parser generator”? Sorry for the dumb questions, but this is the level of confusion I am experiencing right now. 😃

pez20:09:23

My grammar looks like so:

time ::= suffixed-time | twenty-four-hour-time
twenty-four-hour-time ::= hours [<":"> minutes]
suffixed-time ::= hours [<":"> minutes] am-or-pm
am-or-pm ::= "am" | "pm"
hours ::= [digit] digit
minutes ::= digit digit
<digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
And my parser + transformer like so:
(def parser
  (insta/parser "src/pez/pragmatic/time.bnf"))

(def transformer {:hours (comp edn/read-string str)
                  :minutes (comp edn/read-string str)
                  :suffixed-time (fn suffixed-time
                                   ([h am-or-pm]
                                    (suffixed-time h 0 am-or-pm))
                                   ([h m am-or-pm]
                                    (+ (* 60 h) (* 60 am-or-pm) m)))
                  :twenty-four-hour-time (fn twenty-four-hour-time
                                           ([h]
                                            (twenty-four-hour-time h 0))
                                           ([h m]
                                            (+ (* 60 h) m)))
                  :am-or-pm (fn [am-or-pm]
                              (case am-or-pm
                                "am" 0
                                "pm" 12))
                  :time identity})

(tests
 (->> (parser "01:12")
      (insta/transform transformer)) := 72
 (->> (parser "01:12am")
      (insta/transform transformer)) := 72
 (->> (parser "4pm")
      (insta/transform transformer)) := 960
 (->> (parser "16:00")
      (insta/transform transformer)) := 960
 (->> (parser "01:00pm")
      (insta/transform transformer)) := 780 
 :rcf)

hiredman20:09:41

The other ambiguity is they could be using BNF to refer to the textual description of the grammar, and the textual description of the grammar might not actually be BNF

hiredman20:09:50

Yeah that is not peg

hiredman20:09:08

PEG doesn't use | it uses /

hiredman20:09:28

But for this simple grammar it basically makes no difference

pez20:09:02

Thanks. It was a long time ago I dealt with BNFs so I literally checked Wikipedia about it and that’s how I came up with that BNF. It confused the heck out of me that I was then asked to create a PEG parser generator that parsed the grammar. 😃

pez20:09:50

So, I can rewrite the grammar in PEG and it will look something quite similar?

hiredman20:09:43

yeah, for that grammar I bet it would be the same, just | replaced with /

🤯 1
pez20:09:34

My next question is if PEG allows for writing the grammar for these time strings more succinctly? Or does that get into play for more complex languages?

hiredman20:09:00

I don't think so

hiredman20:09:07

(regarding more simple)

hiredman20:09:38

I think the peg description language has some features that are outside the traditionally accepted features of a bnf description, but most libraries that use a notation like bnf actually accept some kind of extended version of it

pez20:09:40

OK. So I just switched | for /, and that just worked. Thanks!

hiredman20:09:19

I think peg is actually both a textual format for describing a grammar, and an algorithm for parsing the grammars the text format describes

pez20:09:22

I’ve written my first PEG grammar! 🚀 😄

❤️ 1
hiredman20:09:18

so like instaparse is not a peg parser, I think it is gll based maybe? but it parsers the peg textual description and uses the gll bits to parse the grammar described

🙏 1
Ingy döt Net00:09:16

I have a fairly well used parsing framework called Pegex that I was planning to port to clojure. It already works in multiple other languages

pez05:09:25

Sounds great, @U05H8N9V0HZ ! I’m not having a ton of luck with crustimoney.

ericnormand22:10:33

Hey @U0ETXRFEW. I'm happy to jump on a call. BTW, that library was very terse and concise, and I didn't document it well. I don't know if I'd recommend it anymore!

ericnormand22:10:18

mksub is weird is because the parser can work equally well for a single item as it does a sequence.

ericnormand22:10:33

Nowadays, when I want to parse something, I use instaparse. My thing is better if you want to parse clojure data.

Scott Nakano20:09:54

Hello, I've been trying to authenticate to Snowflake using an unencrypted private key with JDBC and am not sure where I'm going wrong. I've tried to use buddy-core, bouncycastle, and some other java interop things but everything I've tried ends up in the same place. When I attempt to connect to the snowflake database I get

Error in warehouse, re-throwing as ExceptionInfo                                                                                                                            
net.snowflake.client.jdbc.SnowflakeSQLLoggedException, "Private key provided is invalid or not supported: Use java.security.interfaces.RSAPrivateCrtKey.class for the private key" 
(SQLState 42601, error code 200045)                                                                                                                                                                         
net.snowflake.client.jdbc.SnowflakeSQLLoggedException: Private key provided is invalid or not supported: Use java.security.interfaces.RSAPrivateCrtKey.class for the private key 
For each method I've tried to generate the private key it comes out as a sun.security.rsa.RSAPrivateCrtKeyImpl which to my understanding is a subclass of the desired java.security.interfaces.RSAPrivateCrtKey.class and should work just the same but it does not (attempting to use Java interop in Clojure is the breadth of my Java experience). The private key is used in a map of connection info as :private-key thekey I am currently working off of [org.bouncycastle/bcprov-jdk15on "1.70"] and
openjdk 11.0.20.1 2023-08-24
OpenJDK Runtime Environment (build 11.0.20.1+1-post-Ubuntu-0ubuntu120.04)
OpenJDK 64-Bit Server VM (build 11.0.20.1+1-post-Ubuntu-0ubuntu120.04, mixed mode, sharing)
I have confirmed that the private key is valid and can successfully authenticate with Python.

hiredman20:09:44

jdbc is the name of a popular java interface to sql style databases, presumably you are not using the java interface directly, but some clojure wrapper, what clojure wrapper are you using?

hiredman20:09:42

(I don't think :private-key is part of the api of any of them)

Scott Nakano20:09:47

clojure.java.jdbc

hiredman20:09:22

definitely not

hiredman20:09:15

what you'll need to do is look at the docs the presumable snowflake has for using the private key with jdbc, and then look at how to replicate that behavior with clojure.java.jdbc

hiredman20:09:54

you may want to look at moving to next.jdbc (https://github.com/seancorfield/next-jdbc), since I know some people are using that with snowflake (not sure about clojure.java.jdbc) there is a #sql room and some people have asked questions related to snowflake there in the past

Scott Nakano20:09:22

Awesome thank you! I'll look at next-jdbc

hiredman20:09:33

it might just work if you use :privateKey as the key