Commit Graph

53 Commits (dbe834b48a99e5f4fa355c97bb8f48444491d192)

Author SHA1 Message Date
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 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 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 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 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 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 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 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 454b7a163f tamer: xir::parse::ele: Move repeat configuration out of Context
I had previously used `Context` to hold the parser configuration for
repetition, since that was the easier option.  But I now want to utilize the
`Context` for a stack for the superstate trampoline, and I don't want to
have to deal with the awkwardness of the repetition in doing so, since it
requires that the configuration be created during delegation, rather than
just being passed through to all child parsers.

This adds to a mess that needs cleaning up, but I'll do that after
everything is working.

DEV-7145
2022-08-08 15:23:55 -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 2d117a4864 tamer: xir::parse::ele: Mixed content parsing
"Mixed content" is the XML term representing element nodes mixed with text
nodes.  For example, `foo <strong>bar</strong> baz` is mixed.

TAME supports text nodes as documentation, intended to be in a literate
style but never fully realized.  In any case, we need to permit them, and I
wanted to do more than just ignore the nodes.

This takes a different approach than typical parser delegation---it has the
parent parser _preempt_ the child by intercepting text before delegation
takes place, rather than having the child reject the token (or possibly
interpret it itself!) and have to handle an error or dead state.

And while this makes it more confusing in terms of state machine stitching,
it does make sense, in the sense that the parent parser is really what
"owns" the text node---the parser is delegating _element_ parsing only, take
asserts authority when necessary to take back control where it shouldn't be
delegated.

DEV-7145
2022-08-01 15:01:37 -04:00
Mike Gerwitz 8779abe2bb tamer: xir::flat: Expose depth for all node-related tokens
Previously a `Depth` was provided only for `Open` and `Close`.  This depth
information, for example, will be used by NIR to quickly determine whether a
given parser ought to assert ownership of a text/comment token rather than
delegating it.

This involved modifying a number of test cases, but it's worth repeating in
these commits that this is intentional---I've been bit in the past using
`..` in contexts where I really do want to know if variant fields change so
that I can consider whether and how that change may affect the code
utilizing that variant.

DEV-7145
2022-08-01 15:01:37 -04:00
Mike Gerwitz b3c0bdc786 tamer: xir::parse::ele: Ignore whitespace around elements
Recent changes regarding whitespace were all to support this change (though
it was also needed for XIRF, pre- and post-root).

Now I'll have to conted with how I want to handle text nodes in various
circumstances, in terms of `ele_parse!`.

DEV-7145
2022-08-01 15:01:37 -04:00
Mike Gerwitz 41b41e02c1 tamer: Xirf::Text refinement
This teaches XIRF to optionally refine Text into RefinedText, which
determines whether the given SymbolId represents entirely whitespace.

This is something I've been putting off for some time, but now that I'm
parsing source language for NIR, it is necessary, in that we can only permit
whitespace Text nodes in certain contexts.

The idea is to capture the most common whitespace as preinterned
symbols.  Note that this heuristic ought to be determined from scanning a
codebase, which I haven't done yet; this is just an initial list.

The fallback is to look up the string associated with the SymbolId and
perform a linear scan, aborting on the first non-whitespace character.  This
combination of checks should be sufficiently performant for now considering
that this is only being run on source files, which really are not all that
large.  (They become large when template-expanded.)  I'll optimize further
if I notice it show up during profiling.

This also frees XIR itself from being concerned by Whitespace.  Initially I
had used quick-xml's whitespace trimming, but it messed up my span
calculations, and those were a pain in the ass to implement to begin with,
since I had to resort to pointer arithmetic.  I'd rather avoid tweaking it.

tameld will not check for whitespace, since it's not important---xmlo files,
if malformed, are the fault of the compiler; we can ignore text nodes except
in the context of code fragments, where they are never whitespace (unless
that's also a compiler bug).

Onward and yonward.

DEV-7145
2022-08-01 15:01:37 -04:00
Mike Gerwitz 0504788a16 tamer: xir::parse::ele: Visibility specifier
We need to be able to export generated identifiers.  Trying to figure out a
syntax for this was a bit tricky considering how much is generated, so I
just settled on something that's reasonably clear and easy to parse with
`macro_rules!`.

I had intended to just make everything public by default and encapsulate
using private modules, but that then required making everything else that it
uses public (e.g. error and token objects), which would have been a bizarre
thing to do in e.g. test cases.

DEV-7145
2022-07-21 14:56:43 -04:00
Mike Gerwitz acced76788 tamer: xir::parse::ele: Expand types for external expansion for sum NT
Like a previous commit, this corrects the types for sum NTs so that they
properly resolve in contexts external to xir::parse.

DEV-7145
2022-07-21 13:44:30 -04:00
Mike Gerwitz 992c000b68 tamer: xir::parse::ele: AttrValueError for attr_parse!'s ValueError
This integrates the previous ValueError for `attr_parse!` into
`ele_parse!`.

DEV-7145
2022-07-21 09:23:34 -04:00
Mike Gerwitz 184ff6bdcc tamer: xir::parse: Fixes for {ele,attr}_parse! outside of module
The tests had certain things in scope, but now that I'm trying to use it
outside of those modules, some fixes are needed.

This is admittedly a sloppy commit, with a number of miscellaneous fixes.  I
didn't bother separating it more because most of them are type fixes, and
the `From<Attr>` stuff is going to have to change into, likely,
`TryFrom<Attr>` so that parse failures can occur when attributes do not
match certain patterns.

DEV-7145
2022-07-20 15:40:28 -04:00
Mike Gerwitz c856fd72d9 tamer: xir::parse::ele: Diagnostic output
The only additional information needed was opening spans so that we can
provide useful information regarding closing tags.

This uses a generic Span in place of {Open,Close}Span because the latter
wasn't necessary, but more descriptive types would be nice; it may be
beneficial later on to introduce newtypes for each of the span generated by
{Open,Close}Span.

DEV-7145
2022-07-20 12:17:15 -04:00
Mike Gerwitz 1ec9c963fd tamer: xir::parse::ele: Nonterminal repetition (Kleene star)
This allows an element to be repeated by the parent NT.  The easiest way I
saw to implement this for now was to abuse the Context to provide a runtime
configuration that would allow the state machine to reset after it has
completed parsing.

This also influences error recovery, in that if we're expecting zero or more
of something, we cannot provide an error for an unexpected name, and instead
must emit a dead state so that the caller can determine what to do.

DEV-7145
2022-07-19 16:14:12 -04:00
Mike Gerwitz 2f4c20dac8 tamer: xir::parse::ele: Remaining Display::fmt for nonterminals
The following commit (test tracing) requires non-panicing `Display` and
`Debug` values.

DEV-7145
2022-07-18 14:31:42 -04:00
Mike Gerwitz cf2cd882ca tamer: xir::parse::ele: Introduce sum nonterminals
This introduces `Nt := (A | ... | Z);`, where `Nt` is the name of the
nonterminal and `A ... Z` are the inner nonterminals---it produces a parser
that provides a choice between a set of nonterminals.

This is implemented efficiently by understanding the QName that is accepted
by each of the inner nonterminals and delegating that token immediately to
the appropriate parser.  This is a benefit of using a parser generator macro
over parser combinators---we do not need to implement backtracking by
letting inner parsers fail, because we know ahead of time exactly what
parser we need.

This _does not_ verify that each of the inner parsers accept a unique QName;
maybe at a later time I can figure out something for that.  However, because
this compiles into a `match`, there is no ambiguity---like a PEG parser,
there is precedence in the face of an ambiguous token, and the first one
wins.  Consequently, tests would surely fail, since the latter wouldn't be
able to be parsed.

This also demonstrates how we can have good error suggestions for this
parsing framework: because the inner nonterminals and their QNames are known
at compile time, error messages simply generate a list of QNames that are
expected.

The error recovery strategy is the same as previously noted, and subject to
the same concerns, though it may be more appropriate here: it is desirable
for the inner parser to fail rather than retrying, so that the sum parser is
able to fail and, once the Kleene operator is introduced, retry on another
potential element.  But again, that recovery strategy may happen to work in
some cases, but'll fail miserably in others (e.g. placing an unknown element
at the head of a block that expects a sequence of elements would potentially
fail the entire block rather than just the invalid one).  But more to come
on that later; it's not critical at this point.  I need to get parsing
completed for TAME's input language.

DEV-7145
2022-07-14 15:12:57 -04:00