Lrpar section parser ctbuilder take2#549
Conversation
…arserError. This adds a type `Location`, for non-span specific error locations such as `CommandLine`. In preparation for converting command-line arguments into `Header`, and passing a header built from the command line into the `YaccParser`. This tries to minimize the impact to the `YaccParser` code by keeping most of the internals of `YaccParser` still returning `YaccGrammarError`, and converting these errors at the fringes.
|
I also wanted to ask you to please feel free to have adverse or allergic reactions to this code, I will admit that it is starting to grow on me... primarily because reasonable people will only ever need to pass in |
| } | ||
| } | ||
|
|
||
| impl From<YaccKind> for Value { |
There was a problem hiding this comment.
I guess it occurred to me that we could also add try_from, I suppose it might simplify update_yacckind a little, but I think that is the only place we convert to YaccKind, so I'm not sure if it really needs to be a trait?
impl TryFrom<Value> for YaccKind {
...
}
Edit: I think that'll be a relevant approach though for RecoveryKind, where we'll want to avoid duplicating the try_from in nimbleparse and CTParserBuilder.
There was a problem hiding this comment.
The challenge with From is that it's infallible. At the moment it looks OK but it is a strong future commitment. In that sense TryFrom gives more wriggle room in the future. So it partly depends how confident we are about the API as-is.
There was a problem hiding this comment.
Hmm, I had kind off assumed that because e.g. nimbleparse converts strings to YaccKind we'd always be able to convert or there would be some applicable string version of a YaccKind.
The most complex situation I can think of arises where we have a non-string parameter #541 comes to mind,
where I proposed to add a RecoveryKind::CPCTPlusChannel(mut channel), and one cannot come up with a textual version for passing a channel, however map this value to the normal RecoveryKind::CPCTPlus via text.
It didn't occur to me we could have one with no mapping to text whatsoever, so I'll have to think about that.
My thoughts for the situation with "CPCTPlus" is that it's string intrepretation couold be mapped via the program to be interpreted either as RecoveryKind::CPCTPlus or as a CPCTPlusChannel(...) with a channel constructed by the program.
That is to say I can at least imagine having a surjective mapping, but I hadn't thought of cases where we don't have a mapping.
There was a problem hiding this comment.
I changed this to TryFrom<YaccKind> for Value in 553ce4d
There was a problem hiding this comment.
Curiously trying to change the other direction (TryFrom<Value> for YaccKind like my initial comment), runs into the thing that we try to avoid using the query API (We can't use the query API because we already have the value), so we must duplicate all the error handling for each variant of Value.
Edit: OTOH it does mean we can just get rid of the query API and users can call the following themselves...
Which is not nearly as bad as reimplementing it both in CTParserBuilder and in nimbleparse, since both places share the TryFrom impl.
let x = header.get("yacckind");
x.map(|_| header.mark_used("yacckind"));
YaccKind::try_from(x);
I think I'll try that since the query mechanism has been a point of confusion
There was a problem hiding this comment.
switched from the query mechanism to TryFrom<Value> for YaccKind in 57285ca
Seems like an improvement to me even if each TryFrom implementation is a bit longer than with query,
I think the simplicty of getting rid of the Header wrapper and making it an alias alone is worth it.
So I believe both of our comments regarding this should be fixed.
| match self { | ||
| Self::Used => 1 << 0, | ||
| Self::Required => 1 << 1, | ||
| Self::MergeBehavior(mb) => (1 << 2) | ((mb as u16) << 8), |
There was a problem hiding this comment.
It is probably worth noting that MergeBehavior(_) and it's inner element are both using more bits than necessary, I felt it kept the logic uniform to just give each behavior a 1 bit, and since these are opaque, and not pressed for space, it seemed like we could change it at any time...
I don't know if it is worth a comment, if we were pressed for space we could:
- Drop the
1 << 2entirely forMergeBehavior(_). - Just make
MutuallyExclusivealways a zero bit.
| } | ||
| } | ||
|
|
||
| pub fn merge_from( |
There was a problem hiding this comment.
We'll need to document these functions since they're part of the public interface.
| query_mask: u16, | ||
| ) -> Option<Result<(&Location, &Value), HeaderError>> { | ||
| self.contents_mut().mark_used(&key.to_owned()); | ||
| if let Some((key_loc, value)) = self.contents.get(key) { |
There was a problem hiding this comment.
I wonder if clippy will complain about this not being self.contents.get(key).map(...)?
There was a problem hiding this comment.
It didn't for me, I don't know why it wouldn't though like maybe option is a special case?
I do kind of dislike Option::map and Result::map, because I can't tell you how many times i've gotten mixed up by it when I really intended to map over Some(iterator).
| /// Repr vaues bit representation should not overlap `SettingQuery`, | ||
| /// The first 8 bits is reserved for `ValueQuery`. | ||
| #[repr(u16)] | ||
| pub enum ValueQuery { |
There was a problem hiding this comment.
I wonder if we need to expose the maskiness of this to users? The enum seems fine (users never need to know what the integer discriminant values are) but when we get matches_query_mask(u16) in the public API I do worry a bit about the cognitive load.
A dumb question is: do users ever need to query more than one thing at a time?
There was a problem hiding this comment.
More than one thing like more than one key, or more than one mask?
It isn't set up to query more than one key at a time (I never needed it, I don't think we ever do?).
It is used to query more than one value mask value for yacckind which can be a unitary YaccKind::Grmtools or a constructor YaccKind::Original(_), this is used in the last patch here if that link works.
if let Some(result) = self.header.query(
"yacckind",
SettingQuery::Unitary as u16 | SettingQuery::Constructor as u16,
)
There was a problem hiding this comment.
Ah, then I really have misunderstood this!
If we do go down this route (and I can be convinced -- I just haven't understood it yet!) I think we would want to find a means that avoids as. I have become allergic to as because it has surprising effects on some, but not other, types.
There was a problem hiding this comment.
I don't know of any way to avoid as (I will look, maybe i've missed a way being added, but last time I looked it was the only way to convert from an enum to it's #[repr(...)])
At the very least we can wrap it in a function like fn repr(self) -> u16 though.
Oh: I actually already added one query_bits(), so these as conversions can just be replaced by calls to that.
query_bits() isn't really a great name though
|
I will admit that I'm struggling to pick up the high-level gist of where this is going. I don't think I've fully understood |
|
There is kind of two purposes to the
The query mechanism thus checks if the value mask contains the In that call to query for |
|
Dumb question: will users tend to think of these things as query bits? Is there a higher level query API that might make more sense to them? |
|
Maybe I can just take the Edit: This may require some superficial changes to the types, so that Edit: I tried to simplify this in af89bd9 |
I think there are 2 major things this is trying to do and both of those have a kind of common theme. Problems:
The experiment herein kind of takes the approach of using It does this by passing the The forcing/defaulting mechanism then gets built by a notion of That in itself introduces some problems, primarily dealing with non-parsed Edit: So |
Hopefully I've covered the query bits part already by getting rid of them, but there is still the nagging question of "Will users tend to think of these things?" So far, I've primarily been running under the assumption that users rarely, if ever need to see this part of the API, I guess I can think of use cases similar to metadata that people might want to put stuff in the header to consume. This could be useful, but likely needs some extensions, like a then people could write something to the effect of But currently lacking that kind of an API the header value is mostly constructed inside the So, there is potential we could expose it more to users, but it really isn't going to be of much use outside of programs like nimbleparse, nimbleparse_lsp, and the Edit: it isn't clear what timing to associate with But it is only after we decide we want these kinds of extensions, that the query mechanism becomes something users will have much reason to invoke. |
| Entry::Occupied(mut o) => { | ||
| o.mark_used(); | ||
| let (_, val) = o.get(); | ||
| self.yacc_kind = Some(YaccKind::try_from(val)?); |
There was a problem hiding this comment.
two things to say about this:
- this uses
use crate::markmap::Entryhere, because this source is already usinghash_map::Entry. - This took some finagling to get the borrow checker to work, because
mark_usedrequires a mutable borrow.
and we want to run it aftergetwhich requires a immutable borrow. Thus we use theEntryAPI which allows
us to delay the immutable borrow until we actually need the value, while knowing it exists.
|
Right, I get the high-level motivation now: sorry I was a bit dense! So to some extent we can mark this as an unstable, internal API that just so happen to be
This does sound intriguing, in the sense that it would reduce the quantity of "internal but pub" stuff I think? |
I really should have spent time making a table of contents to give the overall big picture too, sorry about that. I wonder if we can
I suppose it would, in the sense of perhaps giving people a reason to use it, and building library integrations for things like testing? I personally am intrigued by it for In that sense it is intriguing to me because it could allow me to get rid of the entire nimbleparse.toml file. |
👍
If it's the more general and more-difficult-to-get-wrong API, it does sound like it might be worth giving it a shot! |
|
There wasn't a super simple way to document just new, since there is no attribute to negate a hidden structure, |
|
This is definitely better than nothing! Is the plan to look at the "callback" API in this or another PR? |
|
I think in another PR, I'd probably want to try and do it along side an example that uses it, There is also migration of |
|
I think we can get this one in and iterate further from there. I think this is then ready to squash? |
|
I think so too, will work on squashing. |
MarkMap is a key, value map with an API similar to the typical `std::collections` map API. The typical map API is extended in two ways, a mechanism for marking keys with conditions. Such as whether they a key is used and required. Additionally it has an API for merging mark maps, and controlling merge behavior on a per key basis.
27d79f9 to
c5d7b00
Compare
|
I had a bit of trouble trying to get a nice commit history, |
|
Thanks! |
Here is a sequence of patches, which comprise my attempts to clean up #547
This is still a bit of a doozy, but I'm not sure how to split it up in any nicer way.
I hope the commit messages, and inline docs help a little in providing the motivation for each patch.
I have left out the
RecoveryKindadditions for a subsequent patch.I'm going to keep this as a draft for now, I at least want to go through and do some manual testing,
and trying to figure out how terribly the errors this patch produces have regressed.
I also think I'm probably leaving in some
YaccGrammarErrorKindvariants that clippy is not discovering arenot usedthat should probably be removed.