- 22 Aug, 2021 1 commit
-
-
tavit ohanian authored
-
- 16 Aug, 2021 1 commit
-
-
tavit ohanian authored
-
- 29 Jul, 2021 1 commit
-
-
tavit ohanian authored
-
- 25 Feb, 2021 1 commit
-
-
Eric Myhre authored
This significantly reworks how linking is handled. All of the significant operations involved in storing and loading data are extracted into their own separate features, and the LinkSystem just composes them. The big advantage of this is we can now add as many helper methods to the LinkSystem construct as we want -- whereas previously, adding methods to the Link interface was a difficult thing to do, because that interface shows up in a lot of places. Link is now *just* treated as a data holder -- it doesn't need logic attached to it directly. This is much cleaner. The way we interact with the CID libraries is also different. We're doing multihash registries ourselves, and breaking our direct use of the go-multihash library. The big upside is we're now using the familiar and standard hash.Hash interface from the golang stdlib. (And as a bonus, that actually works streamingly; go-mulithash didn't.) However, this also implies a really big change for downstream users: we're no longer baking as many hashes into the new multihash registry by default.
-
- 10 Jan, 2021 1 commit
-
-
Daniel Martí authored
%q is superior to the manually quoted "%s", since it will properly escape the inner string when quoting. For example: "here goes a double quote: \" " Even if this does not matter in practice, using %q is easier too.
-
- 03 Jan, 2021 1 commit
-
-
Eric Myhre authored
-
- 25 Dec, 2020 1 commit
-
-
Daniel Martí authored
As discussed on the issue thread, ipld.Kind and schema.TypeKind are more intuitive, closer to the spec wording, and just generally better in the long run. The changes are almost entirely automated via the commands below. Very minor changes were needed in some of the generators, and then gofmt. sed -ri 's/\<Kind\(\)/TypeKind()/g' **/*.go git checkout fluent # since it uses reflect.Value.Kind sed -ri 's/\<Kind_/TypeKind_/g' **/*.go sed -i 's/\<Kind\>/TypeKind/g' **/*.go sed -i 's/ReprKind/Kind/g' **/*.go Plus manually undoing a few renames, as per Eric's review. Fixes #94.
-
- 14 Nov, 2020 1 commit
-
-
Will Scott authored
-
- 25 Aug, 2020 1 commit
-
-
Daniel Martí authored
There were two vet errors in two packages containing tests, resulting in 'go test' erroring out before any tests were run. Both were due to the same reason - an Error method that ends up calling itself forever, thus a panic. While at it, 'gofmt -w -s' everything, which removes a redundant type.
-
- 29 Jun, 2020 1 commit
-
-
Eric Myhre authored
Hopefully this increases clarity and eases comprehension. Notes and discussion can be found at https://github.com/ipld/go-ipld-prime/issues/54 (and also I suppose in some of our weekly video chats, but I'd have to go on quite a dig to find the relevant links and time). Many many refernces to 'ns' are also updated to 'np', making the line count in this diff pretty wild.
-
- 26 Jun, 2020 1 commit
-
-
Eric Myhre authored
See the changelog for discussion; this had already been on the docket for a while now.
-
- 24 Apr, 2020 1 commit
-
-
Eric Myhre authored
These are getting increasingly straightforward as most of the hard problems have been ironed out already when getting the other recursives (maps and structs) sorted. Now, we just need to stamp out the rest of the scalars, and I think this codegen stuff is at least early-alpha level usable! Also in this commit: added some new error types and fixed up the basicnode.List implementation to use it, removing some todos there.
-
- 13 Apr, 2020 1 commit
-
-
Eric Myhre authored
This diff is pretty fun. It's our first alternative representation, and it all works out nicely (no internal syntactical complications encountered or any other unexpected unpleasantness). It's also our first representation that involves potentially recursive destructuring work over a scalar! So, you can see the new 'construct' method conventions appearing to handle that. Works out neatly. This kind of recursive destructuring happens all within the span of processing one "token" so to speak, so naturally it can work without any stateful machinery. Some utility functions are added to the mixins package. (It's sort of betraying in the name of the package, but, well, it's an extremely expedient choice. See comments about import management. tl;dr: consistency makes things easier.) Tests for a recursive case of this will be coming soon, but requires addressing some other design flaws with the AdjunctConfig maps. (StructField is... not a good key. It's not always hashable... such as when the StructField.Type field is inhabited by TypeStruct.) Easy enough to fix, just not going to cram it into this commit. The way null and the 'fcb' are handled here differs from previous generators: here, we *always* have an 'fcb', and just make a special one for the root builder. This is... well, it works. It was an attempt to simplify things and generate fewer method overrides, and it did. However, I'm not going to dwell on this too long, because... The 'fcb' system is not working out well overall for other reasons: it's causing costly allocations. (Taking a function pointer from a method requires at least two words: one for the function pointer and one for the value to apply it on. That means an allocation.) So a serious rewrite of anything involing the 'fcb' strategy is needed. And that is going to be a little tricky. The 'fcb' idea was itself a trying-slightly-too-hard-to-be-clever attempt to avoid an alternative solution that involves generating additional types per distinct child value slot (and I'm still unwilling to pursue that one -- it suggests far too many types). The next best idea I have now seems to involve a lot of pointers into other assembler's state machines; this will surely be a bit touchy. But no worse than any of the rest of all this, I suppose: it's *all* "a bit touchy". Buckle up for fun diffs ahead.) So, considering these 'fcb' notes, this feels a bit mixed... "Two steps forward, one step back", so to speak. Still: progress! And it's very exciting that we got through several new categories of behavior without hitting any other new roadbumps.
-
- 01 Apr, 2020 1 commit
-
-
Eric Myhre authored
-
- 11 Mar, 2020 2 commits
-
-
Eric Myhre authored
Called out in https://github.com/ipld/go-ipld-prime/pull/49#discussion_r389604191 .
-
- 02 Mar, 2020 1 commit
-
-
Eric Myhre authored
This is a *lot* of changes. It's the most significant change to date, both in semantics and in character count, since the start of this repo. It changes the most central interfaces, and significantly so. But all tests pass. And all benchmarks are *improved*. The Node interface (the reading side) is mostly unchanged -- a lot of consuming code will still compile and work just fine without changes -- but any other Node implementations out there might need some updating. The NodeBuilder interface (the writing side) is *extremely* changed -- any implementations out there will *definitely* need change -- and most consumers will too. It's unavoidable with a semantic fix this big. The performance improvements should make it worth your while, though. If you want more background on how and why we got here, you've got quite a few commits on the "research-admissions" branches to catch up on reading. But here's a rundown of the changes: (Get a glass of water or something calming before reading...) === NodeAssembler introduced! === NodeAssembler is a new interface that describes most of the work of creating and filling data into a new Node. The NodeBuilder interface is still around, but changed in role. A NodeBuilder is now always also a NodeAssembler; additionally, it can return the final Node to you. A NodeAssembler, unlike NodeBuilder, can **not** return a Node to you. In this way, a NodeBuilder represents the ability to allocate memory. A NodeAssembler often *does not*: it's just *filling in* memory. This design overall is much more friendly to efficient operations: in this model, we do allocations in bulk when a NodeBuilder is used, and then NodeAssemblers are used thereafter to fill it in -- this mental model is very friendly to amortizing memory allocations. Previously, the NodeBuilder interface made such a pattern of use somewhere between difficult and outright impossible, because it was modeled around building small values, then creating a bigger value and inserting the smaller ones into it. This is the key change that cascaded into producing the entire other set of changes which land in this commit. The NodeBuilder methods for getting "child builders" are also gone as a result of these changes. The result feels a lot smoother. (You can still ask for the NodeStyle for children of a recursive kind! But you'll find that even though it's possible, it's rarely necessary.) We see some direct improvements from this interface change already. We'll see even more in the future: creating values when using codegen'd implementations of Node was hugely encumbered by the old NodeBuilder model; NodeAssembler *radically* raises the possible ceiling for performance of codegen Node implementations. === NodeStyle introduced === NodeStyle is a new interface type that is used to carry information about concrete node implementations. You can always use a NodeStyle to get a NodeBuilder. NodeStyle may also have additional features on it which can be detected by interface checks. (This isn't heavily used yet, but we imagine it might become handy in the future.) NodeStyle replaces NodeBuilder in many function arguments, because often what we wanted was to communicate a selection of Node implementation strategy, but not actually the start of construction; the NodeStyle interface now allows us to *say that*. NodeStyle typically cost nothing to pass around, whereas a NodeBuilder generally requires an allocation to create and initialize. This means we can use NodeStyle more freely in many contexts. === node package paths changed === Node implementations are now in packages under the "node/*" directory. Previously, they were under an "impl/*" directory. The "impl/free" package is replaced by the the "node/basic" package! The package name was "ipldfree"; it's now "basicnode". === basicnode is an improved runtime/anycontent Node implementation === The `basicnode` package works much the same as the `ipldfree` package used to -- you can store any kind of data in it, and it just does as best it can to represent and handle that, and it works without any kind of type info nor needs of compile-time special support, etc -- while being just quietly *better at it*. The resident memory size of most things has gone down. (We're not using "fat unions" in the implementation anymore.) The cost of iterating maps has gone down *dramatically*. Iteration previously suffered from O(n) allocations due to expensive `runtime.conv*` calls when yielding keys. Iteration is now O(1) (!!) because we redesigned `basicnode` internals to use "internal pointers" more heavily, and this avoids the costs from `runtime.conv*`. (We could've done this separately from the NodeAssembler change, admittedly. But both are the product of research into how impactful clever use of "internal pointers" can be, and lots of code in the neighborhood had to be rewritten for the NodeAssembler interface, so, these diffs arrive as one.) Error messages are more informative. Many small operations should get a few nanoseconds faster. (The implementation uses more concrete types and fewer switch statements. The difference probably isn't the most noticeable part of all these changes, but it's there.) --- basicnode constructor helpers do all return pointers --- All the "New*" helper functions in the basicnode package return interfaces which are filled by a pointer now. This is change from how they worked previously when they were first implemented in the "rsrch" package. The experience of integrating basicnode with the tests in the traversal package made it clear that having a mixture of pointer and non-pointer values flying around will be irritating in practice. And since it is the case that when returning values from inside a larger structure, we *must* end up returning a pointer, pointers are thus what we standardize on. (There was even some writeup in the HACKME file about how we *might* encounter issues on this, and need to change to pointers-everywhere -- the "pointer-vs-value inhabitant consistency" heading. Yep: we did. And since this detail is now resolved, that doc section is dropped.) This doesn't really make any difference to performance. The old way would cause an alloc in those method via 'conv*' methods; the new way just makes it more explicit and go through a different runtime method at the bottom, but it's still the same number of allocations for essentially the same reasons. (I do wonder if at some future point, the golang compiler might get cleverer about eliding 'conv*' calls, and then this change we make here might be unfortunate; but that's certainly not true today, nor in the future at any proximity that I can foresee.) === iterator getters return nil for wrong-kind === The Node.MapIterator and Node.ListIterator methods now return nil if you call them on non-maps or non-lists. Previously, they would return an iterator, but using it would just constantly error. I don't think anyone was honestly really checking those error thunks, and they made a lot of boilerplate white noise in the implementations, and the error is still entirely avoidable by checking the node kind up-front (and this is strictly preferable anyway, since it's faster than getting an error thunk, poking it to get the error, etc)... so, in total, there seem like very few reasons these were useful: the idea is thus dropped. Docs in the Node interface reflect this. === node/mixins makes new Node implementations easier === The mixins package isn't usable directly, but if you're going to make a new Node implementation, it should save you a lot of typing... and also, boost consistency of basic error handling. Codegen will look forward to using this. (Codegen already had much of these semantics internally, and so this package is sort of lifting that back out to be more generally usable. By making it live out here as exported symbols in the core library, we should also reduce the sheer character count of codegen output.) === 'typed.Node' is now 'schema.TypedNode' === A bunch of interfaces that were under the "impl/typed" path moved to be in the "schema" package instead. This probably makes sense to you if you look at them and needs no further explanation. (The reason it comes in this diff, though, is that it was forced: adding better tests to the traversal package highlighted a bunch of cyclic dependency issues that came from 'typed.Node' being in a package that had concrete use of 'basicnode'.) === codecs === The 'encoding' package is now named 'codec'. This name is shorter; it's more in line with vocabulary we use elsewhere in the IPLD project (whereas 'encoding' was more of a nod to the naming found in the golang standard library); and in my personal opinion it does better at describing the both directions of the process (whereas 'encoding' sounds like only the to-linear-bytes direction). I just like it better. === unmarshal functions no longer return node === Unmarshal functions accept an NodeAssembler parameter (rather than a NodeBuilder, as before, nor a NodeStyle, which might also make sense in the new family of interfaces). This means they no longer need to return a Node, either -- the caller can decide where the unmarshalled data lands. If the caller is using a NodeBuilder, it means they can call Build on that to get the value. (If it's a codegen NodeBuilder with More Information, the caller can use any specialized functions to get the more informative pointers without need for casting!) Broadly speaking, this means users of unmarshal functions have more control over how memory allocation comes into play. We may want to add more helper functions to the various codec packages which take a NodeStyle argument and do return a Node. That's not in this diff, though. (Need to decide what pattern of naming these various APIs would deserve, among other things.) === the fluent package === The fluent package changed significantly. The readonly/Node side of it is dropped. It didn't seem to get a ton of exercise in practice; the 'traversal' package (and in the future, perhaps also a 'cursor' package) addresses a lot of the same needs, and what remains is also covered well these days by the 'must' package; and the performance cost of fluent node wrappers as well as the composability obstruction of them... is just too much to be worth it. The few things that used fluent.Node for reading data now mostly use the 'must' package instead (and look better for it, imo). It's possible that some sort of fluent.Node will be rebuilt someday, but it's not entirely clear to me what it should look like, and indeed whether or not it's a good idea to have in the repo at all if the performance of it is counterindicated in a majority of situations... so, it's not part of today's update. The writing/NodeBuilder/NodeAssembler fluent wrappers are continued. It's similar to before (panics promptly on errors, and has a lot of closures)... but also reflects all of the changes made in the migration towards NodeAssembler: it doesn't return intermediate nodes, and there's much less kerfuffle with getting child builders. Overall, the fluent builders are now even more streamlined than before; the closures need even fewer parameters; great success! The fluent.NodeAssembler interface retains the "Create" terminology around maps and lists, even though in the core interfaces, the ipld.NodeAssembler interface now says "Begin" for maps and lists. This is because the fluent.NodeAssembler approach, with its use of closures, really does do the whole operation in one swoop. (It's amusing to note that this change includes finally nuking some fairly old "REVIEW" comment blocks from the old fluent package which regarded the "knb" value and other such sadness around typed recursion. Indeed, we've finally reviewed that: and the answer was indeed to do something drastically different to make those recursions dance well.) === selectors === Selectors essentially didn't change as part of this diff. Neat. (They should get a lot faster when applied, because our node implementations hit a lot less interface boxing in common operations! But the selector code itself didn't need to change to get the gains.) The 'selector/builder' helper package *did* change a bit. The changes are mostly invisible to the user. I do have some questions about the performance of the result; I've got a sneaking suspicion there's now a bunch of improvements that might be easier to get to now than they would've been previously. But, this is not my quest today. Perhaps it will deserve some review in the future. The 'selector/builder' package should be noted as having some interesting error handling strategies. Namely, it doesn't. Any panics raised by the fluent package will just keep rising; there's no place where they're converted to regular error value returns. I'm not sure this is a good interface, but it's the way it was before I started passing through, so that's the way it stays after this patch. ExploreFieldsSpecBuilder.Delete disappears. I hope no one misses it. I don't think anyone will. I suspect it was there only because the ipld.MapBuilder interface had such a method and it seemed like a reasonable conservative choice at the time to proxy it; now that the method proxied is gone, though, so too shall go this. === traversal === Traversal is mostly the same, but a few pieces of config have new names. `traversal.Config.LinkNodeBuilderChooser` is now `traversal.Config.LinkTargetNodeStyleChooser`. Still a mouthful; slightly more accurate; and reflects that it now works in terms of NodeStyle, which gives us a little more finesse in reasoning about where NodeBuilders are actually created, and thus better control and insight into where allocations happen. `traversal.NodeBuilderChooser` is now `traversal.LinkTargetNodeStyleChooser` for the same reasons. The actual type of the `LinkTargetNodeStyleChooser` now requires returning a `NodeStyle`, in case all the naming hasn't made it obvious. === disappearing node packages === A couple of packages under 'impl/*' are just dropped. This is no real loss. The packages dropped were Node implementations that simply weren't done. Deleting them is an increase in honesty. This doesn't mean something with the same intentions as those packages won't come back; it's just not today. --- runtime typed node wrapper disappeared --- This one will come back. It was just too much of a pain to carry along in this diff. Since it was also a fairly unfinished proof-of-concept with no downstream users, it's easier to drop and later reincarnate it than it is to carry it along now. === linking === Link.Load now takes a `NodeAssembler` parameter instead of a `NodeBuilder`, and no longer returns a `Node`! This should result in callers having a little more control over where allocations may occur, letting them potentially reuse builders, etc. This change should also make sense considering how codec.Unmarshal now similarly takes a NodeAssembler argument and does not return a Node value since its understood that the caller has some way to access or gather the effects, and it's none of our business. Something about the Link interface still feels a bit contorted. Having to give the Load method a Loader that takes half the same arguments all over again is definitely odd. And it's tempting to take a peek at fixing this, since the method is getting a signature change. It's unclear what exactly to do about this, though, and probably a consequential design decision space... so it shall not be reopened today during this other large refactor. Maybe soon. Maybe. === the dag-json codec === The dag-json codec got harder to implement. Rrgh. Since we can't tell if something is going to become a Link until *several tokens in*, dag-json is always a bit annoying to deal with. Previously, however, dag-json could still start optimistically building a map node, and then just... quietly drop it if we turn out to be dealing with a link instead. *That's no longer possible*: the process of using NodeAssembler doesn't have a general purpose mechanism for backtracking. So. Now the dag-json codec has to do even more custom work to buffer tokens until it knows what to do with them. Yey. The upside is: of course, the result is actually faster, and does fewer memory allocations, since it gathers enough information to decide what it's doing before it begins to do it. (This is a lovely example of the disciplined design of NodeAssembler's interface forcing other code to be better behaved and disciplined!) === traversal is faster === The `BenchmarkSpec_Walk_MapNStrMap3StrInt/n=32` test has about doubled in speed on the new `basicnode` implementation in comparison to the old `ipldfree.Node` implementation. This is derived primarily from the drop in costs of iteration on `basicnode` compared to the old `ipldfree.Node` implementation. Some back-of-the-envelope math on the allocation still left around suggest it could double in speed again. The next thing to target would be allocations of paths, followed by iterator allocations. Both are a tad trickier, though (see a recently merge-ignore'd commit for notes on iterators; and paths... paths will be a doozy because the path forward almost certainly involves path values becoming invalid if retained beyond a scope, which is... unsafe), so certainly need their own efforts and separate commits. === marshalling is faster === Marshalling is much faster on the new `basicnode` implementation in comparison to the old `ipldfree.Node` implementation. Same reasons as traversal. Some fixes to marshalling which previously caused unnecessary allocations of token objects during recursions have also been made. These improve speed a bit (though it's not nearly as noticeable as the boost provided by the Node implementation improvements to iteration). === size hints showed up all over the place === The appearance of size hint arguments to assembly of maps and lists is of course inevitable from the new NodeAssembler interface. It's particularly interesting to see how many of them showed up in the selector and selectorbuilder packages as constants. And super especially interesting how many of them are very small constants. 44 zeros. 86 ones. 25 twos. 9 threes. 2 fours. (Counted via variations of `grep -r 'Map(.*4, func' | wc -l`.) It's quite a distribution, neh? We should probably consider some more optimizations specifically targeted to small maps. (This is an unscientific sample, and shifted by what we chose to focus on in testing, etc etc, but the general point stands.) `-1` is used to indicate "no idea" for size. There's a small fix to the basicnode implementations to allow this. A zero would work just as well in practice, but using a negative number as a hint to the human seems potentially useful. It's a shame we can't make the argument optional; oh well. === codegen === The codegen packages still all compile... but do nonsensical things, for the moment: they've not been updated to emit NodeAssembler. Since the output of codegen still isn't well rigged to test harnesses, this breakage is silent. The codegen packages will probably undergo a fairly tabula-rasa sweep in the near future. There's been a lot of lessons learned since the start of the code currently there. Updating to emit the NodeAssembler interface will be such a large endeavor it probably represents a good point to just do a fresh pass on the whole thing all at once. -------- ... and that's all! Fun reading, eh? Please do forgive the refactors necessary for all this. Truly, the performance improvements should make it all worth your while.
-
- 27 Feb, 2020 1 commit
-
-
Eric Myhre authored
Previously it was in the 'impl/typed' package, next to the runtime-wrapper implementation of the interface. This was strange. Not only should those two things be separated just on principle, this was also causing more import cycle problems down the road: for example, the traversal package needs to consider the *interface* for a schema-typed node in order to gracefully handle some features... and if this also brings in a *concrete* dependency on the runtime-wrapper implementation of typed nodes, not only is that incorrect bloat, it becomes a show stopper because (currently, at least) that implementation also in turn transitively imports the ipldfree package for some of its scalars. Ouchouch. So. Now the interface lives over in the 'schema' package, with all the other interfaces for that feature set. Where it probably always should have been. ('typed.Maybe' also became known as 'schema.Maybe', which... does not roll off the tongue as nicely. But this is a minor concern and we might reconsider the naming and appearance of that thing later anyway.)
-
- 30 Aug, 2019 1 commit
-
-
Eric Myhre authored
ipldfree.Node is now a much better implementation of Node. In particular, this means it will work correctly when combined with the typed.Node wrapper implementations which expect to be able to use ErrNotExists for logic purposes. Signed-off-by: Eric Myhre <hash@exultant.us>
-
- 13 Aug, 2019 1 commit
-
-
Eric Myhre authored
In general, picking a consistent strategy with this error and sticking with it. The strings were starting to accumulate too much ~stuff~, otherwise. It might not be a bad idea to upgrade the MethodName strings to a bunch of consts, but I don't think it will get any harder to do that in the future if we should so choose either, so, meh. Signed-off-by: Eric Myhre <hash@exultant.us>
-
- 12 Aug, 2019 3 commits
-
-
Eric Myhre authored
Signed-off-by: Eric Myhre <hash@exultant.us>
-
Eric Myhre authored
Signed-off-by: Eric Myhre <hash@exultant.us>
-
Eric Myhre authored
Most important things first! To follow this refactor: ``` sed s/TraverseField/LookupString/g sed s/TraverseIndex/LookupIndex/g ``` It is *literally* a sed-refactor in complexity. --- Now, details. This has been pending for a while, and there is some discussion in https://github.com/ipld/go-ipld-prime/issues/22 . In short, "Traversal" seemed like a mouthful; "Field" was always a misnomer here; and we've discovered several other methods that we *should* have in the area as well, which necessitated a thought about placement. In this commit, only the renames are applied, but you can also see the outlines of two new methods in the Node interface, as comments. These will be added in future commits. Signed-off-by: Eric Myhre <hash@exultant.us>
-
- 11 Aug, 2019 1 commit
-
-
Eric Myhre authored
Leaving as a comment for now because it's not at the top of the priority-heap of refactors I want to chase right now. Signed-off-by: Eric Myhre <hash@exultant.us>
-
- 20 Jul, 2019 2 commits
-
-
Eric Myhre authored
A bit fun; wish there was a way to compress that block of ifdefs about optional vs nullable stuff, but so far nothing clearer has emerged. Added ErrIteratorOverread while at it. Should refactoring other node types to use it too, I suppose. But perhaps we'll just accumlate those todos for a while, since there's already one other error refactor with a chain of blocks in front of it.
-
Eric Myhre authored
The latter isn't used yet; that's not the main goal of this branch (and I think I'd rather do the refactor to use ipld.ErrNotExists after moving the PathSegment types from the selector package into the root... which in turn I'm not going to do while some of the other current work on Selectors is in progress in other branches. So! Latur.); I just wanted it around for documentation and clarity of intent. This also adds a couple of other dummy references to the 'typed' package. I know keeping a set of packages-used-in-this-file is a common step in the development of codegen systems targetting golang, but I'm going to try to avoid it until the need is really forced.
-
- 10 Jul, 2019 2 commits
-
-
Eric Myhre authored
typed.Node.Representation(), which returns another Node, should address most of the infelicies we've found so far in trying to plan nice code that works over the schema layer. Also added in this comment: ipld.ReprKindSet, primarily for use in the ErrWrongKind error. It comes up often enough we might as well formalize the thing.
-
Eric Myhre authored
It's high time we got started on well-typed errors; and this type name is already hinted at by the codegen outputs, so let's make it real. This file has been hanging around on my disk uncommitted for a fair while, and some of the comments about review-needed are stale, but I'm keepin' em for historical interest. I think I have fixes planned that will straight out all of them.
-