Commit Graph

71 Commits (1cf54887565cd46dccc97b98ee0b9696235e1a52)

Author SHA1 Message Date
Mike Gerwitz f29e3cfce1 tamer: asg::air: Use StateStack
This was extracted from xir::parse::ele in previous commits.  The
conventions help to ensure that pushing and returns are being performed
correctly.  The abstraction will continue to evolve.

This ends up using `Ready` as the dead state.  I need to determine if this
is ideal, and if so, maybe just use `Default`, otherwise yield an error.

DEV-13708
2023-03-30 15:44:14 -04:00
Mike Gerwitz e6c6028b37 tamer: xir::parse::ele: Move StateStack into parse::state
This will be utilized by `AirAggregate`.

DEV-13708
2023-03-30 15:44:12 -04:00
Mike Gerwitz c59b92370c tamer: parse::state: Superstate support for Token type lifting
What hell have I gotten myself into.

In the end, this wasn't too bad, but the initial batch of errors was really
demotivating; the diff does this no justice.  `Lookahead::into_super` was
created to help tame those errors.

...now I can move forward.  Imagine my disappointment when I ran into this
when expecting from previous work that superstates would now work properly
for the AirAggregate parsers.

(The reason this was needed is because AirAggregate splits tokens into
subtypes for child parsers.)

DEV-13708
2023-03-29 15:50:06 -04:00
Mike Gerwitz 68e2d5d10e tamer: parse::util::expand: Delete module
Oh, boy, I had forgotten about this, until I started working on some
SuperState stuff and discovered this again due to a compiler error.  Don't
want to fix something that isn't used.

But this does not bring back great memories.  It's unfortunate that it
didn't work out; I'm pretty sure this was part of ~1mo of wasted effort
going down a path that I ultimately had to abort.  Not good times.  I'm
still behind from it.

DEV-13708
2023-03-29 15:30:51 -04:00
Mike Gerwitz b1ce7aaf29 tamer: asg::air: AirAggregateCtx: New AirAggregate::Context
Future changes to `AirAggregate` are going to require additional context (a
stack, specifically), but the `Context` is currently utilized
by `Asg`.  This introduces a layer of abstraction that will allow us to add
the stack.

Alongside these changes, `ParseState` has been augmented with a `PubContext`
type that is utilized on public APIs, both maintaining BC with existing code
and keeping these implementation details encapsulated.

This does make a bit of a mess of the internal implementation, though, with
`asg_mut()` sprinkled about, so maybe the next commit can clean that up a
bit.  EDIT: After adding `AsMut` to a bunch of asg::graph::object::*
methods, I decided against it, because it messes with the inferred
ownership, requiring explicit borrows via `as_mut()` where they were not
required before.  I think the existing code is easier to reason about than
what would otherwise result from having `mut asg: impl AsMut<Asg>`
everwhere.

DEV-13708
2023-03-29 12:58:35 -04:00
Mike Gerwitz 266c9eb05a tamer: parse::state::ParseState: Remove `Eq` derivation
This is unneeded and is just a pain.  If ever we need `Eq`, it could be
implemented only for `ParseState`s that actually need it.

DEV-13708
2023-03-10 14:27:59 -05:00
Mike Gerwitz 25c0aa180e parse::state::transition::TransitionResult::branch_dead: Add branch context
This works around limitations of Rust's borrow checker as of the time of
writing.  See the provided documentation for more information.

The branch context is not yet exposed to the `delegate` family of methods;
it will be added only as needed in the future.

DEV-13708
2023-03-10 14:27:59 -05:00
Mike Gerwitz b6d0569b99 tamer: asg::air: Expression parser
This delegates expression parsing to `AirExprAggregate`, in an effort to
both begin to simplify the understanding and maintenance of `AirAggregate`;
and allow for parser composition for template parsing.

This utilizes the prior changes for token sum types to precisely define the
subset of AIR tokens supported by the expression parser.  This differs from
prior approaches which delegated until a dead state, relying on runtime
information to determine if a parser has finished.  This allows us to
determine that statically.

I do want to be able to eliminate the dead state from the parser so we can
get rid of the `unreachable!`, but I need to move on; that's something I had
tried to do in the past too, which ended up adding a bit of complexity, and
I'll have to consider my options in the future, including whether the dead
state transition can be entirely eliminated in favor of the combination of
these sum types and recovery; the parsing framework decisions were made
while recovery was still an open question, at least in practice.

DEV-13708
2023-03-10 14:27:59 -05:00
Mike Gerwitz aec3b97e3f tamer: parse::parser::Parser: Prevent infinite iteration on finalize
This was a rather frustrating thing to encounter.  I was working on
refactoring `AirAggregate`, and found that my tests were hanging despite no
apparent cause in the parser itself.

As it turns out, rather than failing with a `FinalizeError` as I
expected (since I was mid-refactor), `collect()` was allocating space for an
endless stream of errors.  This was easily verified by adding a `take(x)`
and observing the assertion failure (in this case, in `close_pkg_mid_expr`).

This happens to be the first time in a long time that I actually had to
debug---the combination of robust types as proofs and tests to fill in the
gaps means that runtime issues are caught at build time in all but
exceptional cases (like this one).

It's also worth noting that, because of my policy of iterating only at the
higher levels of the program, it was clear that this must somehow be
Parser-related, since that's the only part of the system that has the
potential for unbounded recursion due to its cyclic state machines.

DEV-13708
2023-03-10 14:27:58 -05:00
Mike Gerwitz e3e50c38c7 tamer: asg::graph::xmli: Extract xmli generation from parse_token
This begins to develop a pattern for doing these transformations.  I had
tried a number of things using iterators, but I wasn't satisfied with either
how they were turning out; had to fight too much with the type system; or
had to resort to heap allocations.  Sticking with an explicit
`push`/`push_all` for now works just fine.

Almost done cleaning up `AsgTreeToXirf::parse_token`, and then I can move on
to introducing more objects.

DEV-13708
2023-03-10 14:27:58 -05:00
Mike Gerwitz 82915f11af tamer: asg::graph::object::xir: Initial rate element reconstruction
This extends the POC a bit by beginning to reconstruct rate blocks (note
that NIR isn't producing sub-expressions yet).

Importantly, this also adds the first system tests, now that we have an
end-to-end system.  This not only gives me confidence that the system is
producing the expected output, but serves as a compromise: writing unit or
integration tests for this program derivation would be a great deal of work,
and wouldn't even catch the bugs I'm worried most about; the lowering
operation can be written in such a way as to give me high confidence in its
correctness without those more granular tests, or in conjunction with unit
or integration tests for a smaller portion.

DEV-13708
2023-03-10 14:27:58 -05:00
Mike Gerwitz fe925db47d tamer: parse::lower:::Lower::lower: Implement in terms of lower_with_context
This is just a special case of lowering with a context, and maintaining two
separate implementations has resulted in divergence.  I don't recall why I
didn't do this previously, though it's possible that the lowering pipeline
was in a state that made it more difficult to do (e.g. with error
handling).

DEV-13708
2023-03-10 14:27:57 -05:00
Mike Gerwitz f8c1ef5ef2 tamer: tamec: MILESONE: POC end-to-end lowering
This has been a long time coming.  The wiring of it all together is a little
rough around the edges right now, but this commit represents a working POC
to begin to fill in the gaps for the entire lowering pipeline.

I had hoped to be at this point a year ago.  Yeah.

This marks a significant milestone in the project because this allows me to
begin to observe the implementation end-to-end, testing it on real-life
inputs as part of a production build pipeline.

...and now, with that, we can begin.  So much work has gone into this
project so far, but aside from the linker (which has been in production for
years), most of this work has been foundational.  It's been a significant
investment that I intend to have pay off in many different ways.

(All this outputs right now is `<package/>`.)

DEV-13708
2023-03-10 14:27:57 -05:00
Mike Gerwitz 33d2b4f0b8 tamer: tamec: POC lowering pipeline with XirfAutoClose and XirfToXir
This replaces the stub `derive_xmli` with the same result (well, minus a
space before the '/' in the output) using what will become the lowering
pipeline.  Once again, this is quite verbose, and the lowering pipeline in
general needs to be further abstracted away.

Unlike the rest of the pipeline, an error during the derivation process will
immediately terminate with an unrecoverable error, because we do not want to
write partial files.  This does not remove the garbage file, because the
build system ought to do that itself (e.g. `make`)...but that is certainly
open for debate.

DEV-13708
2023-03-10 14:27:57 -05:00
Mike Gerwitz 29178f2360 tamer: xir::reader: Divorce from `parse`
The reader previously yielded a `ParsedResult`, presumably to simplify
lowering operations.  But the reader is not a `ParseState`, and does not
otherwise use the parsing API, so this was an inappropriate and confusing
coupling.

This resolves that, introducing a new `lowerable` which will translate an
iterator into something that can be placed in a lowering pipeline.

See the previous commit for more information.

DEV-13708
2023-03-10 14:27:57 -05:00
Mike Gerwitz 963688f889 tamer: parse::lower::ParsedObject: Include Token type parameter
The token type was previously hard-coded to `UnknownToken`, since the use
case was the beginning of the lowering pipeline at the start of the program,
where there was no token type because the first parser (`XirReader`,
currently) is responsible for producing the first token type.

But when we're lowering from the graph (so, the other side of the lowering
pipeline), we _do_ have token types to deal with.

This also emphasizes the inappropriate coupling of `<XirReader as
Iterator>::Item` with `ParsedResult`; I'd like to follow the same approach
that I'm about to introduce with `tamec`, so see a future commit.

DEV-13708
2023-03-10 14:27:57 -05:00
Mike Gerwitz a68930589e tamer: parse::lower: Handle EOF token
This was missed (because it was not used) when EOF tokens were originally
introduced via `ParseState::eof_tok`---`LowerIter` also needs to consider
the token.

This separation betwen the two iterators is a maintenance burden that needs
to be taken care of; I knew that at the time, and then I forgot about it,
and here we are.

This was caught while beginning to wire together a POC graph lowering
pipeline to emit derived sources.

DEV-13708
2023-03-10 14:27:57 -05:00
Mike Gerwitz bc8586e4b3 tamer: xir::autoclose: New lowering operation
This lowering operation is intended to allow me to write a more concise and
clear mapping from the graph to XIRF, without having to worry about
balancing tags, which really complicated the implementation.

This has details docs; see that for more information.

I can't help but be reminded of Wisp (the whitespace-based Lisp-like
syntax).  Which is unfortunate, because I'm not fond of Wisp; I like my
parenthesis.

DEV-13708
2023-03-10 14:27:57 -05:00
Mike Gerwitz 954b5a2795 Copyright year and name update
Ryan Specialty Group (RSG) rebranded to Ryan Specialty after its IPO.
2023-01-20 23:37:30 -05:00
Mike Gerwitz e6640c0019 tamer: Integrate clippy
This invokes clippy as part of `make check` now, which I had previously
avoided doing (I'll elaborate on that below).

This commit represents the changes needed to resolve all the warnings
presented by clippy.  Many changes have been made where I find the lints to
be useful and agreeable, but there are a number of lints, rationalized in
`src/lib.rs`, where I found the lints to be disagreeable.  I have provided
rationale, primarily for those wondering why I desire to deviate from the
default lints, though it does feel backward to rationalize why certain lints
ought to be applied (the reverse should be true).

With that said, this did catch some legitimage issues, and it was also
helpful in getting some older code up-to-date with new language additions
that perhaps I used in new code but hadn't gone back and updated old code
for.  My goal was to get clippy working without errors so that, in the
future, when others get into TAMER and are still getting used to Rust,
clippy is able to help guide them in the right direction.

One of the reasons I went without clippy for so long (though I admittedly
forgot I wasn't using it for a period of time) was because there were a
number of suggestions that I found disagreeable, and I didn't take the time
to go through them and determine what I wanted to follow.  Furthermore, it
was hard to make that judgment when I was new to the language and lacked
the necessary experience to do so.

One thing I would like to comment further on is the use of `format!` with
`expect`, which is also what the diagnostic system convenience methods
do (which clippy does not cover).  Because of all the work I've done trying
to understand Rust and looking at disassemblies and seeing what it
optimizes, I falsely assumed that Rust would convert such things into
conditionals in my otherwise-pure code...but apparently that's not the case,
when `format!` is involved.

I noticed that, after making the suggested fix with `get_ident`, Rust
proceeded to then inline it into each call site and then apply further
optimizations.  It was also previously invoking the thread lock (for the
interner) unconditionally and invoking the `Display` implementation.  That
is not at all what I intended for, despite knowing the eager semantics of
function calls in Rust.

Anyway, possibly more to come on that, I'm just tired of typing and need to
move on.  I'll be returning to investigate further diagnostic messages soon.
2023-01-20 23:37:29 -05:00
Mike Gerwitz edbfc87a54 tamer: f::Functor: New trait
This commit is purposefully coupled with changes that utilize it to
demonstrate that the need for this abstraction has been _derived_, not
forced; TAMER doesn't aim to be functional for the sake of it, since
idiomatic Rust achieves many of its benefits without the formalisms.

But, the formalisms do occasionally help, and this is one such
example.  There is other existing code that can be refactored to take
advantage of this style as well.

I do _not_ wish to pull an existing functional dependency into TAMER; I want
to keep these abstractions light, and eliminate them as necessary, as Rust
continues to integrate new features into its core.  I also want to be able
to modify the abstractions to suit our particular needs.  (This is _not_ a
general recommendation; it's particular to TAMER and to my experience.)

This implementation of `Functor` is one such example.  While it is modeled
after Haskell in that it provides `fmap`, the primitive here is instead
`map`, with `fmap` derived from it, since `map` allows for better use of
Rust idioms.  Furthermore, it's polymorphic over _trait_ type parameters,
not method, allowing for separate trait impls for different container types,
which can in turn be inferred by Rust and allow for some very concise
mapping; this is particularly important for TAMER because of the disciplined
use of newtypes.

For example, `foo.overwrite(span)` and `foo.overwrite(name)` are both
self-documenting, and better alternatives than, say, `foo.map_span(|_|
span)` and `foo.map_symbol(|_| name)`; the latter are perfectly clear in
what they do, but lack a layer of abstraction, and are verbose.  But the
clarity of the _new_ form does rely on either good naming conventions of
arguments, or explicit type annotations using turbofish notation if
necessary.

This will be implemented on core Rust types as appropriate and as
possible.  At the time of writing, we do not yet have trait specialization,
and there's too many soundness issues for me to be comfortable enabling it,
so that limits that we can do with something like, say, a generic `Result`,
while also allowing for specialized implementations based on newtypes.

DEV-13160
2023-01-20 23:37:27 -05:00
Mike Gerwitz af91857746 tame: obj::xmlo: Use SPair where applicable
This simply pairs the individual `SymbolId` and `Span`.  Rationale for this
pairing was added as documentation to `SPair`.

DEV-13430
2022-12-16 14:46:10 -05:00
Mike Gerwitz 0b2e563cdb tamer: asg: Associate spans with identifiers and introduce diagnostics
This ASG implementation is a refactored form of original code from the
proof-of-concept linker, which was well before the span and diagnostic
implementations, and well before I knew for certain how I was going to solve
that problem.

This was quite the pain in the ass, but introduces spans to the AIR tokens
and graph so that we always have useful diagnostic information.  With that
said, there are some important things to note:

  1. Linker spans will originate from the `xmlo` files until we persist
     spans to those object files during `tamec`'s compilation.  But it's
     better than nothing.
  2. Some additional refactoring is still needed for consistency, e.g. use
     of `SPair`.
  3. This is just a preliminary introduction.  More refactoring will come as
     tamec is continued.

DEV-13041
2022-12-16 14:44:38 -05:00
Mike Gerwitz 8d2d273932 tamer: nir::interp: Integrate NIR interpolation into lowering pipeline
This is the culmination of all the recent work---the third attempt at trying
to integrate this.  It ended up much cleaner than what was originally going
to be done, but only after gutting portions of the system and changing my
approach to how NIR is parsed (WRT attributes).  See prior commits for more
information.

The final step is to fill the error branches with actual errors rather than
`todo!`s.

What a relief.

DEV-13156
2022-12-05 16:32:00 -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 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 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 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 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 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 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 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 6bc872eb38 tamer: xir::parse::ele: Generate superstate
And here's the thing that I've been dreading, partly because of the
`macro_rules` issues involved.  But, it's not too terrible.

This module was already large and complex, and this just adds to it---it's
in need of refactoring, but I want to be sure it's fully working and capable
of handling NIR before I go spending time refactoring only to undo it.

_This does not yet use trampolining in place of the call stack._  That'll
come next; I just wanted to get the macro updated, the superstate generated,
and tests passing.  This does convert into the
superstate (`ParseState::Super`), but then converts back to the original
`ParseState` for BC with the existing composition-based delegation.  That
will go away and will then use the equivalent of CPS, using the
superstate+`Parser` as a trampoline.  This will require an explicit stack
via `Context`, like XIRF.  And it will allow for tail calls, with respect to
parser delegation, if I decide it's worth doing.

The root problem is that source XML requires recursive parsing (for
expressions and statements like `<section>`), which results in recursive
data structures (`ParseState` enum variants).  Resolving this with boxing is
not appropriate, because that puts heap indirection in an extremely hot code
path, and may also inhibit the aggressive optimizations that I need Rust to
perform to optimize away the majority of the lowering pipeline.

Once this is sorted out, this should be the last big thing for the
parser.  This unfortunately has been a nagging and looming issue for months,
that I was hoping to avoid, and in retrospect that was naive.

DEV-7145
2022-08-08 15:23:55 -04:00
Mike Gerwitz 53a689741b tamer: parse::state::ParseState::Super: Superstate concept
I'm disappointed that I keep having to implement features that I had hoped
to avoid implementing.

This introduces a "superstate" feature, which is intended really just to be
a sum type that is able to delegate to stitched `ParseState`s.  This then
allows a `ParseState` to transition directly to another `ParseState` and
have the parent `ParseState` handle the delegation---a trampoline.

This issue naturally arises out of the recursive nature of parsing a TAME
XML document, where certain statements can be nested (like `<section>`), and
where expressions can be nested.  I had gotten away with composition-based
delegation for now because `xmlo` headers do not have such nesting.

The composition-based approach falls flat for recursive structures.  The
typical naive solution is boxing, which I cannot do, because not only is
this on an extremely hot code path, but I require that Rust be able to
deeply introspect and optimize away the lowering pipeline as much as
possible.

Many months ago, I figured that such a solution would require a trampoline,
as it typically does in stack-based languages, but I was hoping to avoid
it.  Well, no longer; let's just get on with it.

This intends to implement trampolining in a `ParseState` that serves as that
sum type, rather than introducing it as yet another feature to `Parser`; the
latter would provide a more convenient API, but it would continue to bloat
`Parser` itself.  Right now, only the element parser generator will require
use of this, so if it's needed beyond that, then I'll debate whether it's
worth providing a better abstraction.  For now, the intent will be to use
the `Context` to store a stack that it can pop off of to restore the
previous `ParseState` before delegation.

DEV-7145
2022-08-08 15:23:54 -04:00
Mike Gerwitz 8f3301431c tamer: span::dummy: New module to hold DUMMY_SPAN and derivatives
Various DUMMY_SPAN-derived spans are used by many test cases, so this
finally extracts them---something I've been meaning to do for some time.

This also places DUMMY_SPAN behind a `cfg(test)` directive to ensure that it
is _only_ used in tests; UNKNOWN_SPAN should be used when a span is actually
unknown, which may also be the case during development.

DEV-7145
2022-08-01 15:01:37 -04:00
Mike Gerwitz 0edb21429d tamer: parse::error: Describe unexpected token of input
When Parser has a unhandled dead state and fails due to an unexpected token
of input, we should display what we interpreted that token as.

DEV-7145
2022-08-01 15:01:37 -04:00
Mike Gerwitz b38c16fd08 tamer: parse::trace: Generalize reason for trace output
The trace outputs a note in the footer indicating _why_ it's being output,
so that the reader understands both where the potentially-unexpected
behavior originates from and so they know (in the case of the feature flag)
how to inhibit it.

That information originally lived in `Parser`, where the `cfg` directive to
enable it lives, but it was moved into the abstraction.  This corrects that.

DEV-7145
2022-08-01 15:01:12 -04:00