Have the EDN parser store keywords and symbols as rich types. Fixes #154.#163
Have the EDN parser store keywords and symbols as rich types. Fixes #154.#163rnewman wants to merge 1 commit into
Conversation
ncalexan
left a comment
There was a problem hiding this comment.
Bombs away for commits 1 and 3; I think a little time will improve commit 2 significantly.
| namespace_separator = "/" | ||
| keyword_char_initial = ":" | ||
|
|
||
| // TODO: More chars here? |
There was a problem hiding this comment.
Is this comment still relevant? _, *, and - seem like good name{space} chars...
There was a problem hiding this comment.
I decided not to attack that yet. Start narrow and broaden later.
| } | ||
| keyword -> Value | ||
| = keyword_char_initial ns:( kns:$(keyword_namespace) namespace_separator { kns })? n:$(keyword_name) { | ||
| Value::Keyword(keyword::Keyword::perhaps_namespaced(ns, n)) |
There was a problem hiding this comment.
Is this a place where From/Into can help?
| Text(String), | ||
| Symbol(String), | ||
| Keyword(String), | ||
| Keyword(keyword::Keyword), |
There was a problem hiding this comment.
Yeah, I really think we want impl From<Into<String>> for keyword::Keyword ... if that's legal, or From<String> if not. Then we can still construct Value::Keyword the easy way.
There was a problem hiding this comment.
Two reasons why not:
- Not all strings are valid keywords.
- Most constructions are namespaced, which makes that approach confusing.
There was a problem hiding this comment.
The latter "namespaced" refers to Rust's namespacing, not EDN's, right? I think you could make impl TryFrom<Into<String>> ... and Keyword(":foo/bar") work.
There was a problem hiding this comment.
No, namespaced is the specific constructor function for :foo/bar as opposed to :bar.
Keywords are essentially in two disjoint sets: those with namespaces and those without.
I modeled this as an Option<String> for the namespace, because I didn't want yet another layer of enums. I modeled this as two constructor functions with one helper (perhaps_namespaced) to avoid passing None in places.
This is a spot where you can use some trait tricks to allow coercion from &str to Some<&str> to Some<String> within the same signature, but that would make the constructors relatively more complicated.
| use edn::parse::*; | ||
|
|
||
| // Helper for making wrapped keywords with a namespace. | ||
| fn k_ns(ns: &str, name: &str) -> Value { |
There was a problem hiding this comment.
Let's spend some time to learn Into and From; these are solved problems in Rust-land.
There was a problem hiding this comment.
Maybe not worth doing the namespace bit in the parser, but instead doing it in the constructor. Since you have to check for empty strings anyway, might as well have one place that encodes the rules.
There was a problem hiding this comment.
We can't use From: those conversions cannot fail, and not all strings can be turned into a keyword.
Indeed, even TryFrom returns a Result, and thus complicates matters.
This is the root of parsing, and why I picked this division: the struct and its constructors aren't failable (they include panicking validation, but that's another matter), whereas the parser itself will refuse to parse a malformed keyword.
f4e0860 to
1025fe7
Compare
1025fe7 to
7ff0acc
Compare
|
|
||
| use std::collections::{BTreeSet, BTreeMap, LinkedList}; | ||
| use std::iter::FromIterator; | ||
|
|
There was a problem hiding this comment.
Something I miss from Java was a rough standard of individual imports, sorted alphabetically, inserted by some refactoring and automatically folded by the editor. Minimal code churn which the developer didn't need to think about.
I suspect that rust tooling will catch up shortly. So nothing to do now, mostly thinking aloud.
There was a problem hiding this comment.
Yeah, I decided to do much what we've been doing in Swift and Java: split std out, sort all alphabetically within that division.
This demonstrates some unpleasantness with name reuse between enums and structs; the verbosity encourages the use of a test helper function.
Rust also doesn't infer types as much as I'd like, so you can't, say:
because Rust doesn't know which enum value
vwill be.I could 'inline'
keyword::KeywordintoValue… but then you can't define functions that work only on keywords; they'd all have to accept aValueand handle the other cases.