Commit Graph

671 Commits (8735c2fca3b834e9350ba145cb63157fb9d865a6)

Author SHA1 Message Date
Mike Gerwitz 07dff3ba4e tamer: xir::parse::ele: Remove attr sum state
This removes quite a bit of work, and work that was difficult to reason
about.  While I'm disappointed that that hard work is lost (aside from
digging it up in the commit history), I am happy that it was able to be
removed, because the extra complexity and cognitive burden was significant.

This removes more `memcpy`s than the sum state could have hoped to, since
aggregation is no longer necessary.  Given that, there is a slight
performacne improvement.  The re-introduction of required and duplicate
checks later on should be more efficient than this was, and so this should
be a net win overall in the end.

DEV-13346
2022-12-01 11:09:26 -05:00
Mike Gerwitz f872181f64 tamer: xir::parse: Remove old `attr_parse!` and unused error variants
This cleans up the old implementation now that it's no longer used (as of
the previous commit) by `ele_parse!`.  It also removes the two error
variants that no longer apply: required attributes and duplicate
attributes.

DEV-13346
2022-12-01 11:09:26 -05:00
Mike Gerwitz ab0e4151a1 tamer: xir::parse::ele::ele_parse!: Integrate `attr_parse_stream!`
This handles the bulk of the integration of the new `attr_parse_stream!` as
a replacement for `attr_parse!`, which moves from aggregate attribute
objects to a stream of attribute-derived tokens.  Rationale for this change
is in the preceding commit messages.

The first striking change here is how it affects the test cases: nearly all
`Incomplete`s are removed.  Note that the parser has an existing
optimization whereby `Incomplete` with lookahead causes immediate recursion
within `Parser`, since those situations are used only for control flow and
to keep recursion out of `ParseState`s.

Next: this removes types from `nir::parse`'s grammar for attributes.  The
types will instead be derived from NIR tokens later in the lowering
pipeline.  This simplifies NIR considerably, since adding types into the mix
at this point was taking an already really complex lowering phase and making
it ever more difficult to reason about and get everything working together
the way that I needed.

Because of `attr_parse_stream!`, there are no more required attribute
checks.  Those will be handled later in the lowering pipeline, if they're
actually needed in context, with possibly one exception: namespace
declarations.  Those are really part of the document and they ought to be
handled _earlier_ in the pipeline; I'll do that at some point.  It's not
required for compilation; it's just required to maintain compliance with the
XML spec.

We also lose checks for duplicate attributes.  This is also something that
ought to be handled at the document level, and so earlier in the pipeline,
since XML cares, not us---if we get a duplicate attribute that results in an
extra NIR token, then the next parser will error out, since it has to check
for those things anyway.

A bunch of cleanup and simplification is still needed; I want to get the
initial integration committed first.  It's a shame I'm getting rid of so
much work, but this is the right approach, and results in a much simpler
system.

DEV-13346
2022-12-01 11:09:26 -05:00
Mike Gerwitz 1983e73c81 tamer: xir::parse::attrstream: Value from SPair
This really does need documentation.

With that said, this changes things up a bit: the value is now derived from
an `SPair` rather than an `Attr`, given that the name is redundant.  We do
not need the attribute name span, since the philosophy is that we're
stripping the document and it should no longer be important beyond the
current context.

It does call into question errors, but my intent in the future is to be able
to have the lowering pipline augment errors with its current state---since
we're streaming, then an error that is encountered during lowering of an
element will still have the element parser in the state representing the
parsing of that element; so that information does not need to be propagated
down the pipeline, but can be augmented as it bubbles back up.

More on that at some point in the future; not right now.

DEV-13346
2022-12-01 11:09:25 -05:00
Mike Gerwitz 9ad7742ad2 tamer: xir::parse::attrstream: Streaming attribute parser
As I talked about in the previous commit, this is going to be the
replacement for the aggreagte `attr_parse!`; the next commit will integrate
it into `ele_parse!` so that I can begin to remove the old one.

It is disappointing, since I did put a bit of work into this and I think the
end result was pretty neat, even if was never fully utilized.  But, this
simplifies things significantly; no use in maintaining features that serve
no purpose but to confound people.

DEV-13346
2022-12-01 11:09:25 -05:00
Mike Gerwitz 6d39474127 tamer: NIR re-simplification
Alright, this has been a rather tortured experience.  The previous commit
began to state what is going on.

This is reversing a lot of prior work, with the benefit of
hindsight.  Little bit of history, for the people who will probably never
read this, but who knows:

As noted at the top of NIR, I've long wanted a very simple set of general
primitives where all desugaring is done by the template system---TAME is a
metalanguage after all.  Therefore, I never intended on having any explicit
desugaring operations.

But I didn't have time to augment the template system to support parsing on
attribute strings (nor am I sure if I want to do such a thing), so it became
clear that interpolation would be a pass in the compiler.  Which led me to
the idea of a desugaring pass.

That in turn spiraled into representing the status of whether NIR was
desugared, and separating primitives, etc, which lead to a lot of additional
complexity.  The idea was to have a Sugared and Plan NIR, and further within
them have symbols that have latent types---if they require interpolation,
then those types would be deferred until after template expansion.

The obvious problem there is that now:

  1. NIR has the complexity of various types; and
  2. Types were tightly coupled with NIR and how it was defined in terms of
     XML destructuring.

The first attempt at this didn't go well: it was clear that the symbol types
would make mapping from Sugared to Plain NIR very complicated.  Further,
since NIR had any number of symbols per Sugared NIR token, interpolation was
a pain in the ass.

So that lead to the idea of interpolating at the _attribute_ level.  That
seemed to be going well at first, until I realized that the token stream of
the attribute parser does not match that of the element parser, and so that
general solution fell apart.  It wouldn't have been great anyway, since then
interpolation was _also_ coupled to the destructuring of the document.

Another goal of mine has been to decouple TAME from XML.  Not because I want
to move away from XML (if I did, I'd want S-expressions, not YAML, but I
don't think the team would go for that).  This decoupling would allow the
use of a subset of the syntax of TAME in other places, like CSVMs and YAML
test cases, for example, if appropriate.

This approach makes sense: the grammar of TAME isn't XML, it's _embedded
within_ XML.  The XML layer has to be stripped to expose it.

And so that's what NIR is now evolving into---the stripped, bare
repsentation of TAME's language.  That also has other benefits too down the
line, like a REPL where you can use any number of syntaxes.  I intend for
NIR to be stack-based, which I'd find to be intuitive for manipulating and
querying packages, but it could have any number of grammars, including
Prolog-like for expressing Horn clauses and querying with a
Prolog/Datalog-like syntax.  But that's for the future...

The next issue is that of attribute types.  If we have a better language for
NIR, then the types can be associated with the NIR tokens, rather than
having to associate each symbol with raw type data, which doesn't make a
whole lot of sense.  That also allows for AIR to better infer types and
determine what they ought to be, and further makes checking types after
template application natural, since it's not part of NIR at all.  It also
means the template system can naturally apply to any sources.

Now, if we take that final step further, and make attributes streaming
instead of aggregating, we're back to a streaming pipeline where all
aggregation takes place on the ASG (which also resolves the memcpy concerns
worked around previously, also further simplifying `ele_parse` again, though
it sucks that I wasted that time).  And, without the symbol types getting
in the way, since now NIR has types more fundamentally associated with
tokens, we're able to interpolate on a token stream using simple SPairs,
like I always hoped (and reverted back to in the previous commit).

Oh, and what about that desugaring pass?  There's the issue of how to
represent such a thing in the type system---ideally we'd know statically
that desugaring always lowers into a more primitive NIR that reduces the
mapping that needs to be done to AIR.  But that adds complexity, as
mentioned above.  The alternative is to just use the templat system, as I
originally wanted to, and resolve shortcomings by augmenting the template
system to be able to handle it.  That not only keeps NIR and the compiler
much simpler, but exposes more powerful tools to developers via TAME's
metalanguage, if such a thing is appropriate.

Anyway, this creates a system that's far more intuitive, and far
simpler.  It does kick the can to AIR, but that's okay, since it's also
better positioned to deal with it.

Everything I wrote above is a thought dump and has not been proof-read, so
good luck!  And lets hope this finally works out...it's actually feeling
good this time.  The journey was necessary to discover and justify what came
out of it---everything I'm stripping away was like a cocoon, and within it
is a more beautiful and more elegant TAME.

DEV-13346
2022-12-01 11:09:25 -05:00
Mike Gerwitz 76beb117f9 Revert "tamer: nir::desugar::interp: Include attribute name in derived param name"
Also: Revert "tamer: nir::desugar::interp: Token {SPair=>Attr}"

This reverts commit 7fd60d6cdafaedc19642a3f10dfddfa7c7ae8f53.
This reverts commit 12a008c66414c3d628097e503a98c80687e3c088.

This has been quite a tortured experience, trying to figure out how to best
fit desugaring into the existing system.  The truth is that it ultimately
failed because I was not sticking with my intuition---I was trying to get
things out quickly by compromising on the design, and in the end, it saved
me nothing.

But I wouldn't say that it was a waste of time---the path was a dead end,
but it was full of experiences.

More to come, but interpolation is back to operating on NIR directly, and I
chose to treat it as a source-to-source mapping and not represent it using
the type system---interpolation can be an optional feature when writing TAME
frontends (the principal one being the XML-based one), and it's up to later
checks to assert that identifiers match a given domain.

I am disappointed by the additional context we lose here, but that can
always be introduced in the future differently, e.g. by maintaining a
dictionary of additional context for spans that can be later referenced for
diagnostic purposes.  But let's worry about that in the future; it doesn't
make sense to further complicate IRs for such a thing.

DEV-13346
2022-12-01 11:09:25 -05:00
Mike Gerwitz 9da6cb439f tamer: nir::desugar::interp: Include attribute name in derived param name
This is simply to aid with debugging.  See commit for information on why I
didn't include the attribute name in the param name itself.

DEV-13156
2022-12-01 11:09:25 -05:00
Mike Gerwitz 6a8befb98c tamer: convert::Expect{From,Into}: Diagnostic panics
Converts to use TAME's diagnostic panics, same as previous commits.  Also
introduces impl for `Result`, which I apparently hadn't needed yet.

In the future, I hope trait impl specializations will be available to
automatically derive and expose span information in these diagnostic
messages for certain types.

DEV-13156
2022-12-01 11:09:25 -05:00
Mike Gerwitz d0a728c27f tamer: nir::desugar::interp: Token {SPair=>Attr}
This changes the input token from a more generic `SPair` to `Attr`, which
reflects the new target integration point in the `attr_parse!`
parser-generator.

This is a compromise---I'd like for it to remain generic and have stitching
deal with all integration concerns, but I have spent far too much time on
this and need to keep moving.

With that said, we do benefit from knowing where this must fit in---it's
easier to reason about in a more concrete way, and we can take advantage of
the extra information rather than being burdened by its presence and
ignoring it.  We need to be able to convert back into `XirfToken` (see a
recent commit that discusses that) for `StitchExpansion`, which is why
`Attr` is here.  And since it is, we can use it to explain to the user not
just the interpolation specification used to derive params, but also the
attribute it is associated with.  This is what TAME (in XSLT) does today,
IIRC (I wrote it, I just forget exactly).  It also means that I can name the
parameters after the attribute.

So, that'll be in a following commit; I was disappointed when my prior
approach with `SPair` didn't give me enough information to be able to do
that, since I think it's important that the system be as descriptive as
possible in how it derives information.  Of course, traces would reveal how
the parser came about the derivation, but that requires recompilation in a
special tracing mode.

DEV-13156
2022-12-01 11:09:25 -05:00
Mike Gerwitz 99dcba690f tamer: parse: SP::Token: From<Self::Token>
Of course I would run into integration issues.  My foresight is lacking.

The purpose of this is to allow for type narrowing before passing data to a
more specialized ParseState, so that the other ParseState doesn't need to
concern itself with the entire domain of inputs that it doesn't need, and
repeat unnecessary narrowing.

For example, consider XIRF: it has an `Attr` variant, which holds an `Attr`
object.  We'll want to desugar that object.  It does not make sense to
require that the desugaring process accept `XirfToken` when we've already
narrowed it to an `Attr`---we should accept an Attr.

However, we run into a problem immediately: what happens with tokens that
bubble back up due to lookahead or errors?  Those tokens need to be
converted _back_ (widened).  Fortunately, widening is a much easier process
than narrowing---we can simply use `From`, as we do today so many other
places.

So, this still keeps the onus of narrowing on the caller, but for now that
seems most appropriate.  I suspect Rust would optimize away duplicate
checks, but that still leaves the maintenance concern---the two narrowings
could get out of sync, and that's not acceptable.

Unfortunately, this is just one of the problems with integration...

DEV-13156
2022-12-01 11:09:14 -05:00
Mike Gerwitz 1aca0945df tamer: parse::util::expand::StitchExpansion: Began transition from ParseState to method
My initial plan with expansion was to wrap a `PasteState` in another that
unwraps `Expansion` and converts into a `Dead` state, so that existing
`TransitionResult` stitching methods (`delegate`, specifically) could be
used.

But the desire to use that existing method was primarily because stitching
was a complex operation that was abstracted away _as part of the `delegate`
method_, which made writing new ones verbose and difficult.  Thus began the
previous commits to begin to move that responsibility elsewhere so that it
could be more composable.

This continues with that, introducing a new trait that will culminate in the
removal of a wrapping `ParseState` in favor of a stitching method.  The old
`StitchableExpansionState` is still used for tests, which demonstrates that
the boilerplate problem still exists despite improvements made here  These
will become more generalized in the future as I have time (and the
functional aspects of the code more formalized too, now that they're taking
shape).

The benefit of this is that we avoid having to warp our abstractions in ways
that don't make sense (use of a dead state transition) just to satisfy
existing APIs.  It also means that we do not need the boilerplate of a
`ParseState` any time we want to introduce this type of
stitching/delegation.  It also means that those methods can eventually be
extracted into more general traits in the future as well.

Ultimately, though, the two would have accomplished the same thing.  But the
difference is most emphasized in the _parent_---the actual stitching still
has to take place for desugaring in the attribute parser, and I'd like for
that abstraction to still be in terms of expansion.  But if I utilized
`StitchableExpansionState`, which converted into a dead state, I'd have to
either forego the expansion abstraction---which would make the parser even
more confusing---or I'd have to create _another_ abstraction around the dead
state, which would mean that I stripped one abstraction just to introduce
another one that's essentially the same thing.  It didn't feel right, but it
would have worked.

The use of `PhantomData` in `StitchableExpansionState` was also a sign that
something wasn't quite right, in terms of how the abstractions were
integrating with one-another.

And so here we are, as I struggle to wade my way through all of the yak
shavings and make any meaningful progress on this project, while others
continue to suffer due to slow build times.

I'm sorry.  Even if the system is improving.

DEV-13156
2022-11-17 15:12:25 -05:00
Mike Gerwitz 1ce36225f6 tamer: diagnose::panic::DiagnosticOptionPanic: New panic
This is just intended to simplify the job of panicing when something is
expected to be `None`.  In my case, `Lookahead`; see upcoming commits.

This is intended to be generalized to more than just `Option`, but I have no
use for it elsewhere yet; I primarily just needed to implement a method on
`Option` so that I could have the ergonomics of the dot notation.

DEV-13156
2022-11-17 14:36:00 -05:00
Mike Gerwitz 42618c5add tamer: parse: Abstract lookahead token replacement panic
There's no use in duplicating this in util::expand.

Lookahead tokens are one of the few invariants that I haven't taken the time
of enforcing using the type system, because it'd be quite a bit of work that
I do not have time for, and may not be worth it with changes that may make
the system less ergonomic.  Nonetheless, I do hope to address it at some
point in the (possibly-far) future.

If ever you encounter this diagnostic message, ask yourself how stable TAMER
otherwise is and how many other issues like this have been entirely
prevented through compile-time proofs using the type system.

DEV-13156
2022-11-16 15:25:52 -05:00
Mike Gerwitz a377261de3 tamer: parse::state::transition::TransitionResult::with_lookahead: {=>diagnostic_}panic!
As in previous commits, this continues to replace panics with
`diagnostic_panic!`, which provides much more useful information both for
debugging and to help the user possibly work around the problem.  And lets
the user know that it's not their fault, and it's a TAMER bug that should be
reported.

...am I going to rationalize it in each commit message?

DEV-13156
2022-11-16 14:20:58 -05:00
Mike Gerwitz 8cb4eb5b81 tamer: parse::util::expand::StitchableExpansionState: Utilize bimap
This is just a light refactoring to utilize the new
`TransitionResult::bimap` method.

DEV-13156
2022-11-16 14:09:14 -05:00
Mike Gerwitz 60ce1305cc tamer: parse::state: Further generalize ParseState::delegate
This moves enough of the handling of complex type conversions into the
various components of `TransitionResult` (and itself), which simplifies
delegation and opens up the possibility of having specialized
delegation/stitching methods implemented atop of `TransitionResult`.

DEV-13156
2022-11-16 14:09:11 -05:00
Mike Gerwitz a17e53258b tamer: parse::state: Begin to tame delegation methods
These delegation methods have been a pain in my ass for quite some time, and
their lack of generalization makes the introduction of new delegation
methods (in the general sense, not necessarily trait methods) very tedious
and prone to inconsistencies.

I'm going to progressively refactor them in separate commits so it's clear
what I'm doing, primarily for future me to reference if need be.

DEV-13156
2022-11-16 10:38:58 -05:00
Mike Gerwitz fc425ff1d5 tamer: parse::state: EchoState and TransitionResult constituent primitives
This beings to introduce more primitive operations to `TransitionResult` and
its components so that I can actually work with them without having to write
a bunch of concrete, boilerplate implementations.  This is demonstrated in
part by `EchoState` (which is nearly all boilerplate, but whose correctness
should be verifiable at a glance), which will be used going forward as a
basis for default implementations for parsers (e.g. expansion delegation).

DEV-13156
2022-11-16 10:37:10 -05:00
Mike Gerwitz 55c55cabd3 tamer: parse::util::expand: Move expansion into own module
This has evolved into a more robust and independent concept, but it is still
a utility in the sense that it's utilizing existing parsing framework
features and making them more convenient.

DEV-13156
2022-11-15 13:28:54 -05:00
Mike Gerwitz ddb4f24ea5 tamer: parse::util (ExpandableParseState, ExpandableInto): Clarifying traits
These traits serve to abstract away some of the type-level details and
clearly state what the end result is (something stitchable with a parent).

I'm admittedly battling myself on this concept a bit.  The proper layer of
abstraction is the concept of expansion, which is an abstraction that is
likely to be maintained all the way through, but we strip the abstraction
for the sake of delegation.  Maybe the better option is to provide a
different method of delegation and avoid the stripping at all, and avoid the
awkward interaction with the dead state.

The awkwardness comes from the fact that delegating right now is so rigid
and defined in terms of a method on state rather than a mapping between
`TransitionResult`s.  But I really need to move on... ;_;

The original design was trying to generalize this such that composition at
the attribute parser level (for NIR) would be able to just accept any
sitchable parser with the convention that the dead state is the replacement
token.  But that is the wrong layer of abstraction, which not only makes it
confusing, but is asking for trouble when someone inevitably violates that
contract.

With all of that said, `StitchableExpansionState` _is_ a delegation.  It
could just as easily be a function (`is_accepting` always delegates too), so
perhaps that should just be generalized as reifying delegation as a
`ParseState`.

DEV-13156
2022-11-15 12:56:25 -05:00
Mike Gerwitz 03cf652c41 tamer: parse::util: Introduce StitchableExpansionState
This parser really just allows me to continue developing the NIR
interpolation system using `Expansion` terminology, and avoid having to use
dead states in tests.  This allows for the appropriate level of abstraction
to be used in isolation, and then only be stripped when stitching is
necessary.

Future commits will show how this is actually integrated and may introduce
additional abstraction to help.

DEV-13156
2022-11-15 12:19:25 -05:00
Mike Gerwitz 4117efc50c tamer: nir::desugar::interp: Generalize without NIR symbol types
This is a shift in approach.

My original idea was to try to keep NIR parsing the way it was, since it's
already hard enough to reason about with the `ele_parse!` parser-generator
macro mess.  The idea was to produce an IR that would explicitly be denoted
as "maybe sugared", and have a desugaring operation as part of the lowering
pipeline that would perform interpolation and lower the symbol into a plain
version.

The problem with that is:

  1. The use of the type was going to introduce a lot of mapping for all the
     NIR token variants there are going to be; and
  2. _The types weren't even utilized for interpolation._

Instead, if we interpolated _as attributes are encountered_ while parsing
NIR, then we'd be able to expand directly into that NIR token stream and
handle _all_ symbols in a generic way, without any mapping beyond the
definition of NIR's grammar using `ele_parse!`.

This is a step in that direction---it removes `NirSymbolTy` and introduces a
generic abstraction for the concept of expansion, which will be utilized
soon by the attribute parser to allow replacing `TryFrom` with something
akin to `ParseFrom`, or something like that, which is able to produce a
token stream before finally yielding the value of the attribute (which will
be either the original symbol or the replacement metavariable, in the case
of interpolation).

(Note that interpolation isn't yet finished---errors still need to be
implemented.  But I want a working vertical slice first.)

DEV-13156
2022-11-10 12:33:30 -05:00
Mike Gerwitz 8a430a52bc tamer: xir::prase: Extract intermediate attribute aggregate state into Context
This was a substantial change.  Design and rationale are documented on
`AttrFieldSum` and related as part of this change, so please review the diff
for more information there.

If you're a Ryan employee, DEV-13209 gives plenty of profiling information,
including raw data and visualizations from kcachegrind.  For everyone else:
you're able to easy produce your own from this commit and the previous and
comparing the `__memcpy_avk_unaligned_erms` calls.  The reduction is
significant in this commit (~90%), and the number of Parsers invoking it has
been reduced.  Rust has been able to optimize more aggressively, and
compound some of those optimizations, with the smaller `NirParseState`
width.

It also worth noting that `malloc` calls do not change at all between
these two changes, so when we refer to memory, we're referring to
pre-allocated memory on the stack, as TAMER was designed to utilize.

DEV-13209
2022-11-09 16:01:09 -05:00
Mike Gerwitz 6ae6ca716c tamer: diagnose::panic::diagnostic_unreachable!: New macro
This is a diagnostic replacement for `unreachable!`.

Eventually TAMER'll have build-time checks to enforce the use of these over
alternatives; I need to survey the old instances on a case-by-case basis to
see what diagnostic information can be reasonably presented in that context.

DEV-13209
2022-11-09 10:47:17 -05:00
Mike Gerwitz 5c5041f90e tamer: nir::desugar::interp: Proper span offsets
The spans were previously not being calculated relative to the offset of the
original symbol span.  Tests were passing because all of those spans began
at offset 0.

DEV-13156
2022-11-08 00:55:45 -05:00
Mike Gerwitz 6b9979da9a tamer: nir::desugar::interp: Valid parses
This completes the valid parses, though some more refactoring will be
done.  Next up is error handling and recovery.

DEV-13156
2022-11-07 23:59:47 -05:00
Mike Gerwitz 4a7fe887d5 tamer: nir::desugar: Initial interpolation desugaring
This demonstrates how desugaring of interpolated strings will work, testing
one of the happy paths.  The remaining work to be done is largely
refactoring; handling some other cases; and errors.  Each of those items are
marked with `todo!`s.

I'm pleased with how this is turning out, and I'm excited to see diagnostic
reporting within the specification string using the derived spans once I get
a bit further along; this robust system is going to be much more helpful to
developers than the existing system in XSLT.

This also eliminates the ~50% performance degredation mentioned in a recent
commit by eliminating the SugaredNirSymbol enum and replacing it with a
newtype; this is a much better approach, though it doesn't change that I do
need to eventually address the excessive `memcpy`s on hot code paths.

DEV-13156
2022-11-07 14:15:16 -05:00
Mike Gerwitz 66f09fa4c9 tamer: parse::prelude: New module
Not sure why I didn't add a prelude sooner, considering all the import
boilerplate.  This will evolve as needed and I'll go back and replace other
imports when I'm not in the middle of something.

DEV-13156
2022-11-02 14:56:26 -04:00
Mike Gerwitz 9922910d09 tamer: nir::NirSymbolTy (Display): Add impl
Add initial descriptions and consolodate some of the types.  There'll be
more to come; this is just to get `Display` derives working for types
that'll be using it.  I'd like to see where this description manifests
itself before I decide how user-friendly I'd like it to be.

DEV-13156
2022-11-01 16:23:51 -04:00
Mike Gerwitz 5e2d8f13a7 tamer: nir (SugaredNir): Mirror PlainNir
This mirror is only a `Todo` variant at the moment, but my hope had been to
try to creatively nest or use generics to simplify the conversaion between
the two flavors without a lot of boilerplate.  But it doesn't seem like I'm
going to be successful, and may have to resort to macros to remove
boilerplate.

But I need to stop fighting with myself and move on.  Though I would still
like to keep the types purely compile-time via const generics if possible,
since they're not needed in memory (or disk) until we get to templates;
they're otherwise static relative to a NIR token variant.

DEV-13209
2022-11-01 15:22:42 -04:00
Mike Gerwitz 7f71f3f09f tamer: nir: Detect interpolated values
This simply detects whether a value will need to be further parsed for
interpolation; it does not yet perform the parsing itself, which will happen
during desugaring.

This introduces a performance regression, for an interesting reason.  I
found that introducing a single new variant to `SugaredNir` (with a
`(SymbolId, Span)` pair), was causing the width of the `NirParseState` type
to increase just enough to cause Rust to be unable to optimize away a
significant number of memcpys related to `Parser` moves, and consequently
reducing performance by nearly 50% for `tamec`.  Yikes.

I suspected this would be a problem, and indeed have tried in all other
cases to avoid aggregation until the ASG---the problem is that I had wanted
to aggregate attributes for NIR so that the IR could actually make some
progress toward simplifying the stream (and therefore working with the
data), and be able to validate against a grammar defined in a single
place.  The problem is that the `NirParseState` type contains a sum type for
every attribute parser, and is therefore as wide as the largest one.  That
is what Rust is having trouble optimizing memcpy away for.

Indeed, reducing the number of attributes improves the situation
drastically.  However, it doesn't make it go away entirely.

If you look at a callgrind profile for `tameld` (or a dissassembly), you'll
notice that I put quite a bit of effort into ensuring that the hot code path
for the lowering pipeline contains _no_ memcpys for the parsers.  But that
is not the case with `tamec`---I had to move on.  But I do still have the
same escape hatch that I introduced for `tameld`, which is the mutable
`Context`.

It seems that may be the solution there too, but I want to get a bit further
along first to see how these data end up propagating before I go through
that somewhat significant effort.

DEV-13156
2022-11-01 15:15:40 -04:00
Mike Gerwitz 37d44e42ad tamer: sym::symbol: Use {=>diagnostic_}panic! for resolution failure
Various parts of the system have to be converted to use `diagnostic_panic!`,
which makes it very clear that this is a bug in TAMER that should be
reported.  I just happened to see this one near code I was about to touch.

DEV-13156
2022-11-01 12:42:36 -04:00
Mike Gerwitz 2a70525275 tamer: sym::prefill::quick_contains_byte: New function
This will be utilized by NIR to avoid having to perform memory lookups for
preinterned static symbols.

DEV-13156
2022-11-01 12:42:32 -04:00
Mike Gerwitz d195eedacb tamer: nir: Sugared and plain flavors
This introduces the concept of sugared NIR and provides the boilerplate for
a desugaring pass.  The earlier commits dealing with cleaning up the
lowering pipeline were to support this work, in particular to ensure that
reporting and recovery properly applied to this lowering operation without
adding a ton more boilerplate.

DEV-13158
2022-10-26 14:19:19 -04:00
Mike Gerwitz dbe834b48a tamer: tamec: Remove lowering pipeline refactoring comment
I'm struggling to go much further yet without sorting out some other things
first with regards to mutable `Context` and, in particular, the ASG.

I'm going to pause on refactoring the lowering pipeline---it's been improved
significantly with the recent work---and I will continue in the next few
weeks.

DEV-13158
2022-10-26 12:44:20 -04:00
Mike Gerwitz 7c4c0ebdda tamer: parse::lower: Separate error types for lowering and return
Lowering errors in tamec end up utilizing recovery and reporting, so there
is a distinction between recoverable and unrecoverable errors.

tameld aborts on the first error, since recovery is not currently
supported (we'll want to add it, since tameld should output e.g. lists of
unresolved externs).

Note that tamec does not yet handle `FinalizeError` like tameld because it
uses `Lower::lower`, which does not yet finalize (though it does in practice
when it reaches the end of the stream and auto-finalizes, but that is
widened into a `ParseError`).

DEV-13158
2022-10-26 12:44:20 -04:00
Mike Gerwitz 26aaf6efc1 tamer: parse::error::ParseError: Extract some variants into FinalizeError
This helps to clarify the situations under which these errors can occur, and
the generality also helps to show why the inner types are as they
are (e.g. use of `String`).

But more importantly, this allows for an error type in `finalize` that is
detached from the `ParseState`, which will be able to be utilized in the
lowering pipeline as a more general error distinguishable from other
lowering errors.  At the moment I'm maintaining BC, but a following commit
will demonstrate the use case to introduce recoverable vs. non-recoverable
errors.

DEV-13158
2022-10-26 12:44:19 -04:00
Mike Gerwitz 2087672c47 tamer: parse::parser::finalize: Introduce FinalizedParser
This newtype allows a caller to prove (using types) that a parser of a given
type (`ParseState`) has been finalized.

This will be used by the lowering pipeline to ensure that all parsers in the
pipeline end up getting finalized (as you can see from a TODO added in the
code, one of them is missing).  The lack of such a type was an oversight
during the (rather stressed) development of the parsing system, and I
shouldn't need to resort to unit tests to verify that parsers have been
finalized.

DEV-13158
2022-10-26 12:44:19 -04:00
Mike Gerwitz 7e62276907 tamer: Revert "tamer: diagnose::report::Report: {Mutable=>immutable} self reference"
This reverts commit 85ec626fcd804eb2fac3fd6f0339182554f72cfd.

This revert had to be modified to work alongside other changes.  Interior
mutability is fortunately no longer needed after the previous commit which
allows reporting to occur in a single place in the lowering pipeline (at the
terminal parser).

DEV-13158
2022-10-26 12:44:18 -04:00
Mike Gerwitz 1c181fe546 tamer: parse::lower: Propagate widened errors to terminal parser
The term "terminal parser" isn't formalized yet in the system, but is meant
to refer to the innermost parser that is responsible for pulling tokens
through the lowering pipeline.

This approach is more of what one would expect when dealing with
`Result`-like monads---we are effectively chaining the inner operation while
propagating errors to short-circuit lowering and let the caller decide
whether recovery ought to be permitted with diagnostic messages.  This will
become more clear as it is further refactored.

This also means that the previous changes for introducing interior
mutability for a shared mutable `Reporter` can be reverted, which is great,
since that approach was antithetical to how the streaming pipeline
operates (and introduces awkward mutable state into an
otherwise-mostly-immutable system).

DEV-13158
2022-10-26 12:32:51 -04:00
Mike Gerwitz 2ccdaf80fe tamer: diagnose::report: Error tracking
This extracts error tracking into the Reporter itself, which is already
shared between lowering operations.  This can then be used to display the
number of errors.

A new formatter (in tamer::fmt) will be added to handle the singular/plural
conversion in place of "error(s)" in the future; I have more important
things to work on right now.

DEV-13158
2022-10-26 12:32:51 -04:00
Mike Gerwitz f049da4496 tamer: tamec: Apply reporting (and continuing) to XirToXirf failure
Previously these errors would immediately abort.

This results in some duplicate code, but it's beginning to derive a common
implementation.  Check out the commits that follow; this is really an
intermediate refactoring state.

DEV-13158
2022-10-26 12:32:51 -04:00
Mike Gerwitz 733f44a616 tamer: diagnose::report::Report: {Mutable=>immutable} self reference
VisualReporter now uses interior mutability so that we can hold multiple
references to it for upcoming lowering pipeline changes.

DEV-13158
2022-10-26 12:32:51 -04:00
Mike Gerwitz a6e72b87f7 tamer: tamec: Extract compilation from main
Another baby step.  The small commits are intended to allow comprehension of
what changes when looking at the diffs.

This also removes a comment stating that errors do not fail compilation,
since they most certainly do.

DEV-13158
2022-10-26 12:32:51 -04:00
Mike Gerwitz 20ea83af1a tamer: tamec: Extract source reading and writing
This begins refactoring the lowering pipeline to begin to obviate
abstraction boundaries.  The lowering pipeline is the backbone of the
system, and so it needs to become clear and self-documenting, which will
take a little bit of work.

DEV-13158
2022-10-26 12:32:51 -04:00
Mike Gerwitz 8c32967cbf tamer: Cargo.toml: Sort dependencies
This always annoys me when I add a dependency and I don't know where I ought
to put it.

Anyway, I was originally going to add the `regex` crate, but with further
planning, I may not end up having use for it.  Nonetheless, at least this is
consistent.
2022-10-18 14:48:14 -04:00
Brandon Ellis 00f46b0032 [DEV-12990] Add gt, gte, lt, lte operators to if/unless
This includes updating Tamer's parser to account for the new
operator possibilities.
2022-09-22 11:38:06 -04:00
Mike Gerwitz 80d7de7376 tamer: nir: Remove token `todo!`s
Just preparing to actually define NIR itself.  The _grammar_ has been
represented (derived from our internal systems, using them as a test case),
but the IR itself has not yet received a definition.

DEV-7145
2022-09-19 16:21:42 -04:00
Mike Gerwitz 3456bd593a tamer: tamec: Fail with non-zero status if any NIR parsing errors
This is a quick-and-dirty change.  The lowering pipeline needs a proper
abstraction, but I'm about to be on vacation at the end of the week and
would like to get NIR->AIR lowering started before I consider that
abstraction further, so this will do for now.

NIR parsing has been tested in production without failing for over a week.

DEV-7145
2022-09-19 10:11:47 -04:00
Mike Gerwitz 5403dd06c6 tamer: Provide links to `tame{c,ld}`
DEV-7145
2022-09-19 10:04:40 -04:00
Mike Gerwitz 9966b82b9d tamer: nir::parse: Grammar summary docs
This is intended to provide just enough information to help elucidate how
the system works and why.

DEV-7145
2022-09-19 09:26:38 -04:00
Mike Gerwitz dcb42b6e4b tamer: xir::parse: Improvements to generated docs for NIR attributes
This hides the internal state machine and provides better language for what
remains.

DEV-7145
2022-09-16 13:37:46 -04:00
Mike Gerwitz 1dc691160b tamer: nir: Re-define "NIR"
This was originally the "noramlized" IR, but that's not possible to do
without template expansion, which is going to happen at a later point.  So,
this is just "NIR", pronounced "near", which is an IR that is "near" to the
source code.  You can define it was "Near IR" if you want, but it's just a
homonym with a not-quite-defined acronym to me.

DEV-7145
2022-09-16 09:59:38 -04:00
Mike Gerwitz f9bdcc2775 tamer: xir::parse::ele: Remove `*Error_` types
A type alias was added for BC before errors were hoisted out in a previous
commit, but they are unnecessary because of the associated type on
`ParseState`.

This also corrects the long-existing issue of using generated identifiers in
tests.

DEV-7145
2022-09-15 16:10:47 -04:00
Mike Gerwitz 071c94790f tamer: xir::ele::parse: Formatting: remove a level of indentation
This moves `paste::paste!` up a line and reduces a level of indentation,
since it's so squished.  Aside from docblock reformatting, there are no
other changes.

DEV-7145
2022-09-15 16:09:49 -04:00
Mike Gerwitz b3f4378517 tamer: xir::parse::ele: Hoist NT Display from `ele_parse!` macro
This slims out the macro even further.  It does result in an
awkwardly-placed `PhantomData` because I don't want to add another variant
that isn't actually used (since they represent states).

DEV-7145
2022-09-14 16:34:59 -04:00
Mike Gerwitz 80f29e9420 tamer: xir::parse::ele: Hoist NtState out of `ele_parse!` macro
This does the same as before with SumNtState, and takes advantage of the
preparations made by the preceding commit.  The macro is shrinking.

DEV-7145
2022-09-14 15:35:58 -04:00
Mike Gerwitz 1817659811 tamer: xir::parse::ele: Abstract child NT states in parent parser
This is in preparation for hoisting out the common states, as was done with
the Sum NT in a previous commit.

I also think that organizing states in this way is more clear.  The previous
embedding of the variants named after the NTs themselves was because the
parser was storing the child state within it, before the introduction of the
superstate trampoline.

DEV-7145
2022-09-14 14:47:54 -04:00
Mike Gerwitz d73a18d1a2 tamer: xir::parse::ele: Initial extraction of Sum NT state from macro
After introducing the superstate and trampoline some time ago, the Sum NT
states became fully generalized and can be hoisted out.

DEV-7145
2022-09-14 12:23:52 -04:00
Mike Gerwitz db3fd3f177 tamer: xir::parse::ele: Remove `unreachable!` in state transitions
This will instead fail at compile time.

DEV-7145
2022-09-14 10:00:10 -04:00
Mike Gerwitz a5c7067c68 tamer: xir::parse::ele: Remove NT `todo!` for state transition
Everything except for one state was already accounted for.  We can now have
confidence that the parser will never panic due to state transitions (beyond
legitimate error conditions).

There are some `unreachable!`s to contend with still.

DEV-7145
2022-09-14 09:41:53 -04:00
Mike Gerwitz 212ca06efe tamer: xir::parse: Extract and generalize NT errors
This is the same as the previous commits, but for non-sum NTs.

This also extracts errors into a separate module, which I had hoped to do in
a separate commit, but it's not worth separating them.  My _original_ reason
for doing so was debugging (I'll get into that below), but I had wanted to
trim down `ele.rs` anyway, since that mess is large and a lot to grok.

My debugging was trying to figure out why Rust was failing to derive
`PartialEq` on `NtError` because of `AttrParseError`.  As it turns out,
`AttrParseError::InvalidValue` was failing, thus the introduction of the
`PartialEq` trait bound on `AttrParseState::ValueError`.  Figuring this out
required implementing `PartialEq` myself without `derive` (well, using LSP,
which did all the work for me).

I'm not sure why this was not failing previously, which is a bit of a
concern, though perhaps in the context of the macro-expanded code, Rust was
able to properly resolve the types.

DEV-7145
2022-09-14 09:28:31 -04:00
Mike Gerwitz 5078bd8bda tamer: xir::parse::ele: Extract sum NT error from `ele_parse!`
The `ele_parse!` macro is a monstrosity, and expands into many different
identifiers.  The hope is that chipping away at things like this will not
only make the template easier to understand by framing portions of the
problem in terms of more traditional Rust code, but will also hopefully
reduce compile times by reducing the amount of code that is expanded by the
macro.

DEV-7145
2022-09-13 09:20:29 -04:00
Mike Gerwitz 419b24f251 tamer: Introduce NIR (accepting only)
This introduces NIR, but only as an accepting grammar; it doesn't yet emit
the NIR IR, beyond TODOs.

This modifies `tamec` to, while copying XIR, also attempt to lower NIR to
produce parser errors, if any.  It does not yet fail compilation, as I just
want to be cautious and observe that everything's working properly for a
little while as people use it, before I potentially break builds.

This is the culmination of months of supporting effort.  The NIR grammar is
derived from our existing TAME sources internally, which I use for now as a
test case until I introduce test cases directly into TAMER later on (I'd do
it now, if I hadn't spent so much time on this; I'll start introducing tests
as I begin emitting NIR tokens).  This is capable of fully parsing our
largest system with >900 packages, as well as `core`.

`tamec`'s lowering is a mess; that'll be cleaned up in future commits.  The
same can be said about `tameld`.

NIR's grammar has some initial documentation, but this will improve over
time as well.

The generated docs still need some improvement, too, especially with
generated identifiers; I just want to get this out here for testing.

DEV-7145
2022-08-29 15:52:04 -04:00
Mike Gerwitz c420ab2730 tamer: xir::parse: Correct doc xrefs
These weren't causing problems until they were output as part of NIR (in a
separate module).

NIR is about to be committed.

DEV-7145
2022-08-29 15:52:04 -04:00
Mike Gerwitz 638a9c483b tamer: xir::parse::ele: Hide internal NT enum variants
The user never sees or interacts with these; they're macro-generated, and
distract from the useful information in the generated docs.

DEV-7145
2022-08-29 15:52:04 -04:00
Mike Gerwitz 2b33a45985 tamer: xir::parse::ele: Support NT docs
This just modifies the macro to proxy attributes to generated NTs so that
they can be documented.

DEV-7145
2022-08-29 15:52:04 -04:00
Mike Gerwitz 51728545f7 tamer: xir::parse::ele: Properly handle previous state transitions
This includes when on the last state / expecting a close.

Previously, there were a couple major issues:

  1. After parsing an NT, we can't allow preemption because we must emit a
     dead state so that we can remove the NT from the stack, otherwise
     they'll never close (until the parent does) and that results in
     unbounded stack growth for a lot of siblings.  Therefore, we cannot
     preempt on `Text`, which causes the NT to receive it, emit a dead
     state, transition away from the NT, and not accept another NT of the
     same type after `Text`.

  2. When encountering an unknown element, the error message stated that a
     closing tag was expected rather than one of the elements accepted by the
     final NT.

For #1, this was solved by allowing the parent to transition back to the NT
if it would have been matched by the previous NT.  A future change may
therefore allow us to remove repetition handling entirely and allow the
parent to deal with it (maybe).

For #2, the trouble is with the parser generator macro---we don't have a
good way of knowing the last NT, and the last NT may not even exist if none
was provided.  This solution is a compromise, after having tried and failed
at many others; I desperately need to move on, and this results in the
correct behavior and doesn't sacrifice performance.  But it can be done
better in the future.

It's also worth noting for #2 that the behavior isn't _entirely_ desirable,
but in practice it is mostly correct.  Specifically, if we encounter an
unknown token, we're going to blow through all NTs until the last one, which
will be forced to handle it.  After that, we cannot return to a previous NT,
and so we've forefitted the ability to parse anything that came before it.

NIR's grammar is such that sequences are rare and, if present, there's
really only ever two NTs, and so this awkward behavior will rarely cause
practical issues.  With that said, it ought to be improved in the future,
but let's wait to see if other parts of the lowering pipeline provide more
appropriate places to handle some of these things (even though it really
ought to be handled at the grammar level).

But I'm well out of time to spend on this.  I have to move on.

DEV-7145
2022-08-29 15:52:04 -04:00
Mike Gerwitz 7466ecbe8b tamer: xir::parse::ele: Accept missing child
`ele_parse!` was recently converted to accept zero-or-more for every NT to
simplify the parser-generator, since NIR isn't going to be able to
accurately determine whether child requirements are met anyway (because of
the template system).

This ensures that `Close` can be accepted when we're expecting an
element.  It also adds a test for a scenario that's causing me some trouble
in stashed code so that I can ensure that it doesn't break.

DEV-7145
2022-08-22 09:43:59 -04:00
Mike Gerwitz 9366c0c154 tamer: xir::parse::ele: Increase parser nesting depth
This sets the maximum depth to 64, which is still arbitrary, but
unfortunately the sum types introduce multiple levels of nesting, in
particular for template applications, so nested applications can result in a
fairly large stack.

I have various ideas to improve upon that---limited a bit in that repetition
as it is current implemented inhibits tail calls---but they're not worth
doing just yet relative to other priorities.  The impact of this change is
not significant.

DEV-7145
2022-08-18 16:16:45 -04:00
Mike Gerwitz abb2c80e22 tamer: xir::parse::ele: Always repeat
This removes support for configurable repetition.

What?  Why?

As it turns out, the complexity that repetition adds is quite significant
and is not worth the effort.  The truth is that NIR is going to have to
allow zero-or-more matches on virtually everything _anyway_ because template
application is allowed virtually anywhere---it is not possible to fully
statically analyze TAME's sources because templates can expand into just
about anything.  Given that, AIR (or something down the line) is going to
have to supply the necessary invariants instead.

It does suck, though, that this removes a lot of code that I fairly recently
wrote, and spent a decent amount of time on.  But it's important to know
when to cut your losses.

Perhaps I could have planned better, but deriving this whole system as been
quite the experiment.

DEV-7145
2022-08-18 15:19:40 -04:00
Mike Gerwitz 13d3c76a31 tamer: xir::parse::ele: Test to verify close after child recovery
Just want to be sure that we emit a closing object to match the emitted
opening one after recovery, otherwise the IR becomes unbalanced.

DEV-7145
2022-08-18 12:41:27 -04:00
Mike Gerwitz 955131217b tamer: xir::parse::ele: Attribute dead state recovery
If attributes fail to parse (e.g. missing required attribute) and parsing
reaches a dead state, this will recover by ignoring the entire element.  It
previously panicked with a TODO.

DEV-7145
2022-08-18 12:41:26 -04:00
Mike Gerwitz 77fd92bbb2 tamer: xir::parse::ele: Remove `_` suffix from error variants
These were initially used to prevent conflicts with generated variants, but
we are no longer generating such variants since they're being jumped to via
the trampoline.

DEV-7145
2022-08-17 14:58:54 -04:00
Mike Gerwitz b31ebc00a7 tamer: xir::parse::ele: Handle Close when expecting Open
I'm starting to clean up some TODOs, and this was a glaring one causing
panics when encountered.  The recovery for this is simple, because we have
no choice: just stop parsing; leave it to the next lowering operation(s) to
complain that we didn't provide what was necessary.  They'll have to,
anyway, since templates mean that NIR cannot ever have enough information to
guarantee that a document is well-formed, relative to what would expand from
the template.

DEV-7145
2022-08-17 14:49:34 -04:00
Mike Gerwitz 4c86c5b63c tamer: xir::parse::ele: Support nested Sum NTs
This allows for a construction like this:

```
ele_parse! {
  [...]

  StmtX := QN_X {
    [...]
  };

  StmtY := QN_Y {
    [...]
  };

  ExprA := QN_A {
    [...]
  };

  ExprB := QN_B {
    [...]
  };

  Expr := (A | B);
  Stmt := (StmtX | StmtY);

  // This previously was not allowed:
  StmtOrExpr := (Stmt | Expr);
}
```

There were initially two barriers to doing so:

  1. Efficiently matching; and
  2. Outputting diagnostic information about the union of all expected
     elements.

The first was previously resolved with the introduction of `NT::matches`,
which is macro-expanded in a way that Rust will be able to optimize a
bit.  Worst case, it's effectively a linear search, but our Sum NTs are not
that deep in practice, so I don't expect that to be a concern.

The concern that I was trying to avoid was heap-allocated `NodeMatcher`s to
avoid recursive data structures, since that would have put heap access in a
very hot code path, which is not an option.

That left problem #2, which ended up being the harder problem.  The solution
was detailed in the previous commit, so you should look there, but it
amounts to being able to format individual entries as if they were a part
of a list by making them a function of not just the matcher itself, but also
the number of items in (recursively) the sum type and the position of the
matcher relative to that list.  The list length is easily
computed (recursively) at compile-time using `const`
functions (`NT::matches_n`).

And with that, NIR can be abstracted in sane ways using Sum NTs without a
bunch of duplication that would have been a maintenance burden and an
inevitable source of bugs (from having to duplicate NT references).

DEV-7145
2022-08-17 10:44:53 -04:00
Mike Gerwitz fd3184c795 tamer: fmt (ListDisplayWrapper::fmt_nth): List display without a slice
This exposes the internal rendering of `ListDisplayWrapper::fmt` such that
we can output a list without actually creating a list.  This is used in an
upcoming change for =ele_parse!= so that Sum NTs can render the union of all
the QNames that their constituent NTs match on, recursively, as a single
list, without having to create an ephemeral collection only for display.

If Rust supports const functions for arrays/Vecs in the future, we could
generate this at compile-time, if we were okay with the (small) cost, but
this solution seems just fine.  But output may be even _more_ performant
since they'd all be adjacent in memory.

This is used in these secenarios:

  1. Diagnostic messages;
  2. Error messages (overlaps with #1); and
  3. `Display::fmt` of the `ParseState`s themselves.

The reason that we want this to be reasonably performant is because #3
results in a _lot_ of output---easily GiB of output depending on what is
being traced.  Adding heap allocations to this would make it even slower,
since a description is generated for each individual trace.

Anyway, this is a fairly simple solution, albeit a little bit less clear,
and only came after I had tried a number of other different approaches
related to recursively constructing QName lists at compile time; they
weren't worth the effort when this was so easy to do.

DEV-7145
2022-08-17 10:44:28 -04:00
Mike Gerwitz 6b29479fd6 tamer: xir::fmt (DisplayFn): New fn wrapper
See the docblock for a description.  This is used in an upcoming commit for
=ele_parse!=.

DEV-7145
2022-08-17 10:01:47 -04:00
Mike Gerwitz 4177b8ed71 tamer: xir::parse::ele: Streaming attribute parsing
This allows using a `[attr]` special form to stream attributes as they are
encountered rather than aggregating a static attribute list.  This is
necessary in particular for short-hand template application and short-hand
function application, since the attribute names are derived from template
and function parameter lists, which are runtime values.

The syntax for this is a bit odd since there's a semi-useless and confusing
`@ {} => obj` still, but this is only going to be used by a couple of NTs
and it's not worth the time to clean this up, given the rather significant
macro complexity already.

DEV-7145
2022-08-16 23:06:38 -04:00
Mike Gerwitz 43c64babb0 tamer: xir::parse::ele: Superstate element preemption
This uses the same mechanism that was introduced for handling `Text` nodes
in mixed content, allowing for arbitrary element `Open` matches for
preemption by the superstate.

This will be used to allow for template expansion virtually
anywhere.  Unlike the existing TAME, it'll even allow for it at the root,
though whether that's ultimately permitted is really depending on how I
approach template expansion; it may fail during a later lowering operation.

This is interesting because this approach is only possible because of the
CPS-style trampoline implementation.  Previously, with the composition-based
approach, each and every parser would have to perform this check, like we
had to previously with `Text` nodes.

As usual, this is still adding to the mess a bit, and it'll need some future
cleanup.

DEV-7145
2022-08-16 15:47:41 -04:00
Mike Gerwitz 6f53c0971b tamer: xir::parse::ele: Superstate text node preemption
This introduces the concept of superstate node preemption generally, which I
hope to use for template application as well, since templates can appear in
essentially any (syntatically valid, for XML) position.

This implements mixed content handling by defining the mapping on the
superstate itself, which really simplifies the problem but foregoes
fine-grained text handling.  I had hoped to avoid that, but oh well.

This pushes the responsibility of whether text is semantically valid at that
position to NIR->AIR lowering (which we're not transition to yet), which is
really the better place for it anyway, since this is just the grammar.  The
lowering to AIR will need to validate anyway given that template expansion
happens after NIR.

Moving on!

DEV-7145
2022-08-16 12:26:24 -04:00
Mike Gerwitz 65b42022f0 tamer: xir::st: Prefix all preproc-namespaced constants with `QN_P_`
I had previously avoided this to keep names more concise, but now it's
ambiguous with parsing actual TAME sources.

DEV-7145
2022-08-15 13:00:10 -04:00
Mike Gerwitz 13641e1812 tamer: diagnose::report: `int_log` feature: {=>i}log10
https://github.com/rust-lang/rust/pull/100332

The above MR replaces `log10` and friends with `ilog10`; this is the first
time an unstable feature bit us in a substantially backwards-incompatible
way that's a pain to deal with.

Fortunately, I'm just not going to deal with it: this is used with the
diagnostic system, which isn't yet used by our projects (outside of me
testing), and so those builds shouldn't fail before people upgrade.

This is now pending stabalization with the new name, so hopefully we're good
now:

  https://github.com/rust-lang/rust/issues/70887#issuecomment-1210602692
2022-08-12 16:42:30 -04:00
Mike Gerwitz 2a36bc4210 tamer: (explicit_generic_args_with_impl_trait): Remove unstable feature flag
This was stabalized in Rust 1.63.  I was waiting to be sure our build
servers were updated properly before removing this (and they were, long
ago).
2022-08-12 16:42:30 -04:00
Mike Gerwitz ed8a2ce28a tamer: xir::parse::ele: Superstate not to accept early EOF
This was accepting an early EOF when the active child `ParseState` was in an
accepting state, because it was not ensuring that anything on the stack was
also accepting.

Ideally, there should be nothing on the stack, and hopefully in the future
that's what happens.  But with how things are today, it's important that, if
anything is on the stack, it is accepting.

Since `is_accepting` on the superstate is only called during finalization,
and because the check terminates early, and because the stack practically
speaking will only have a couple things on it max (unless we're in tail
position in a deeply nested tree, without TCO [yet]), this shouldn't be an
expensive check.

Implementing this did require that we expose `Context` to `is_accepting`,
which I had hoped to avoid having to do, but here we are.

DEV-7145
2022-08-12 00:47:15 -04:00
Mike Gerwitz a4419413fb tamer: parse::trace: Include context
This is something that I had apparently forgotten to do, but is now useful
in debugging `ele_parse!` issues with the trampoline.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz 54d8348e95 tamer: Add `--quiet` flag to `make check` (`cargo test`)
I wonder when this option was introduced, unless I never saw it because it
is called "quiet".  But this is what I always wanted (and how I write the
output for my own tools, like progtest in this repo); the output has long
gotten far too large.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz 22a9596cf4 tamer: xir::parse::ele: Hoist whitespace/comment handling to superstate
All child parsers do the same thing, so this simplifies things.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz f8a9e952e5 tamer: xir::parse::ele: Correct handling of sum dead state post-recovery
Along with this change we also had to change how we handle dead states in
the superstate.  So there were two problems here:

  1. Sum states were not yielding a dead state after recovery, which meant
     that parsing was unable to continue (we still have a `todo!`); and
  2. The superstate considered it an error when there was nothing left on
     the stack, because I assumed that ought not happen.

Regarding #2---it _shouldn't_ happen, _unless_ we have extra input after we
have completed parsing.  Which happens to be the case for this test case,
but more importantly, we shouldn't be panicing with errors about TAMER bugs
if somebody puts extra input after a closing root tag in a source file.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz b95ec5a9d8 tamer: xir::parse::ele: Adjust diagnostic display of expected element list
This does two things:

  1. Places the expected list on a separate help line as a footnote where
     it'll be a bit more tolerable when it inevitably overflows the terminal
     width in certain contexts (we may wrap in the future); and
  2. Removes angled brackets from the element names so that they (a) better
     correspond with the span which highlights only the element name and (b)
     do not imply that the elements take no attributes.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz 67ee914505 tamer: xir::parse::ele: Store matching QName on NS match
When we match a QName against a namespace, we ought to store the matching
QName to use (a) in error messages and (b) to make available as a
binding.  The former is necessary for sensible errors (rather than saying
that it's e.g. expecting a closing `t:*`) and the latter is necessary for
e.g. getting the template name out of `t:foo`.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz 8cb03d8d16 tamer: xir::parse::ele: Initial namespace prefix matching support
This allows matching on a namespace prefix by providing a `Prefix` instead
of a `QName`.  This works, but is missing a couple notable things (and
possibly more):

  1. Tracking the QName that is _actually_ matched so that it can be used in
     messages stating what the expected closing tag is; and
  2. Making that QName available via a binding.

This will be used to match on `t:*` in NIR.  If you're wondering how
attribute parsing is supposed to work with that (of course you're wondering
that, random person reading this)---that'll have to work differently for
those matches, since template shorthand application contains argument names
as attributes.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz f9fe4aa13b tamer: xir::st: Static namespace prefixes (c and t)
In particular, `t:*` will be recognized by NIR for short-hand template
application.  These will be utilized in an upcoming commit.

DEV-7145
2022-08-12 00:47:14 -04:00
Mike Gerwitz 88fa0688fa tamer: xir::parse::ele: Abstract node matching
This introduces `NodeMatcher`, with the intent of introducing wildcard QName
matches for e.g. `t:*` nodes.  It's not yet clear if I'll expand this to
support text nodes yet, or if I'll convert text nodes into elements to
re-use the existing system (which I had initially planned on doing, but
didn't because of the work and expense (token expansion) involved in the
conversion).

DEV-7145
2022-08-12 00:47:13 -04:00
Mike Gerwitz 7b9bc9e108 tamer: xir::parse::ele: Ignore Text nodes for now
I need to move on, and there are (a) a couple different ways to proceed that
I want to mull over and (b) upcoming changes that may influence my decision
one way or another.

DEV-7145
2022-08-12 00:47:12 -04:00
Mike Gerwitz 4aaf91a9e7 tamer: xir::parse::ele: Un-nest child parser errors
This will utilize the superstate's error object in place of nested errors,
which was the result of the previous composition-based delegation.

As you can see, all we had to do was remove the special handling of these
errors; the existing delegation setup continues to handle the types properly
with no change.  The composition continues to work for `*Attr_`.

The alternative was to box inner errors, since they're far from the hot code
path, but that's clearly unnecessary.

To be clear: this is necessary to allow for recursive grammars in
`ele_parse` without creating recursive data structures in Rust.

DEV-7145
2022-08-10 11:46:54 -04:00
Mike Gerwitz adf7baf115 tamer: xir::parse::ele: Handle comments like whitespace
Comments ought not have any more semantic meaning than whitespace.  Other
languages may have conventions that allow for various types of things in
comments, like annotations, but those are symptoms of language
limitations---we control the source language here.

DEV-7145
2022-08-10 11:46:54 -04:00
Mike Gerwitz 15e04d63e2 tamer: xir::parse::ele: Transition trampoline
This properly integrates the trampoline into `ele_parse!`.  The
implementation leaves some TODOs, most notably broken mixed text handling
since we can no longer intercept those tokens before passing to the
child.  That is temporarily marked as incomplete; see a future commit.

The introduced test `ParseState`s were to help me reason about the system
intuitively as I struggled to track down some type errors in the monstrosity
that is `ele_parse!`.  It will fail to compile if those invariants are
violated.  (In the end, the problems were pretty simple to resolve, and the
struggle was the type system doing its job in telling me that I needed to
step back and try to reason about the problem again until it was intuitive.)

This keeps around the NT states for now, which are quickly used to
transition to the next NT state, like a couple of bounces on a trampoline:

  NT -> Dead -> Parent -> Next NT

This could be optimized in the future, if it's worth doing.

This also makes no attempt to implement tail calls; that would have to come
after fixing mixed content and really isn't worth the added complexity
now.  I (desperately) need to move on, and still have a bunch of cleanup to
do.

I had hoped for a smaller commit, but that was too difficult to do with all
the types involved.

DEV-7145
2022-08-10 11:46:45 -04:00
Mike Gerwitz 233fa7de6a tamer: diagnose::panic: New module
This change introduces diagnostic messages for panics.  The intent is to be
able to use panics in situations where it is either not possible to or not
worth the time to recover from errors and ensure a consistent/sensible
system state.  In those situations, we still ought to be able to provide the
user with useful information to attempt to get unstuck, since the error is
surely in response to some particular input, and maybe that input can be
tweaked to work around the problem.

Ideally, invalid states are avoided using the type system and statically
verified at compile-time.  But this is not always possible, or in some cases
may be way more effort or cause way more code complexity than is worth,
given the unliklihood of the error occurring.

With that said, it's been interesting, over the past >10y that TAME has
existed, seeing how unlikely errors do sometimes pop up many years after
they were written.  It's also interesting to have my intuition of what is
"unlikely" challenged, but hopefully it holds generally.

DEV-7145
2022-08-09 15:20:37 -04:00