tamer: asg::air: Index scope of local metavariables

The scope system works with the AIR stack frames, expecting all parent
environments to be on that stack.  Since metavariables were (awkwardly) part
of the template parser, that didn't happen.

This change extracts metavariable parsing (with some remaining TODOs) into
its own parser, so that `AirTplAggregate` will be on the stack; then it's a
simple matter of using the existing `AirAggregateCtx` methods to define a
variable and index its shadow scope, which addresses TODOs in the existing
scope test cases.

This also involved separating the tokens from `AirTpl` into `AirMeta`; they
need to be renamed, which will happen in a following commit, since this is
large enough as it is.

Another change that had to be included here, which I wish I could have just
done separately if it wasn't too much work, was to permit overlapping
identifier shadows.  Local variables have to cast a shadow so that we can
figure out if they would in turn shadow an identifier (which would be an
error), but they don't conflict with one-another if they don't have a
shared (visible) scope.

`AirAggregate` can be simplified even further, e.g. to eliminate the
expression stack and just use the ctx stack (which didn't previously exist),
but I need to continue; I'll return to it.

DEV-13162
main
Mike Gerwitz 2023-05-23 14:33:41 -04:00
parent da4d7f83ea
commit c12bf439ae
7 changed files with 439 additions and 151 deletions

View File

@ -44,12 +44,16 @@ use super::{
};
use crate::{
diagnose::Annotate,
diagnostic_unreachable,
f::Functor,
parse::{prelude::*, StateStack},
span::Span,
sym::SymbolId,
};
use std::fmt::{Debug, Display};
use std::{
collections::hash_map::Entry,
fmt::{Debug, Display},
};
#[macro_use]
mod ir;
@ -57,10 +61,12 @@ use fxhash::FxHashMap;
pub use ir::Air;
mod expr;
mod meta;
mod opaque;
mod pkg;
mod tpl;
use expr::AirExprAggregate;
use meta::AirMetaAggregate;
use opaque::AirOpaqueAggregate;
use pkg::AirPkgAggregate;
use tpl::AirTplAggregate;
@ -99,6 +105,9 @@ pub enum AirAggregate {
/// See [`Air::TplStart`] for more information.
PkgTpl(AirTplAggregate),
/// Parsing metavariables.
PkgMeta(AirMetaAggregate),
/// Parsing opaque objects.
///
/// This parser is intended for loading declarations from object files
@ -122,6 +131,9 @@ impl Display for AirAggregate {
PkgTpl(tpl) => {
write!(f, "building a template: {tpl}")
}
PkgMeta(meta) => {
write!(f, "building metavariable: {meta}")
}
PkgOpaque(opaque) => {
write!(f, "loading opaque objects: {opaque}")
}
@ -147,6 +159,12 @@ impl From<AirTplAggregate> for AirAggregate {
}
}
impl From<AirMetaAggregate> for AirAggregate {
fn from(st: AirMetaAggregate) -> Self {
Self::PkgMeta(st)
}
}
impl From<AirOpaqueAggregate> for AirAggregate {
fn from(st: AirOpaqueAggregate) -> Self {
Self::PkgOpaque(st)
@ -188,7 +206,8 @@ impl ParseState for AirAggregate {
// frames off of the stack until reaching the still-active
// parent package frame.
(
st @ (Root(..) | PkgExpr(..) | PkgTpl(..) | PkgOpaque(..)),
st @ (Root(..) | PkgExpr(..) | PkgTpl(..) | PkgMeta(..)
| PkgOpaque(..)),
tok @ AirPkg(..),
) => ctx.ret_or_transfer(st, tok, AirPkgAggregate::new()),
(Pkg(pkg), AirPkg(etok)) => ctx.proxy(pkg, etok),
@ -211,6 +230,16 @@ impl ParseState for AirAggregate {
(PkgTpl(tplst), AirBind(ttok)) => ctx.proxy(tplst, ttok),
(PkgTpl(tplst), AirDoc(ttok)) => ctx.proxy(tplst, ttok),
// Metasyntactic variables (metavariables)
(st @ PkgTpl(_), tok @ AirMeta(..)) => {
ctx.ret_or_transfer(st, tok, AirMetaAggregate::new())
}
(PkgMeta(meta), AirMeta(mtok)) => ctx.proxy(meta, mtok),
(PkgMeta(meta), AirBind(mtok)) => ctx.proxy(meta, mtok),
(PkgMeta(meta), tok @ (AirExpr(..) | AirTpl(..) | AirDoc(..))) => {
ctx.try_ret_with_lookahead(meta, tok)
}
// Opaque
//
// By having opaque object loading be its _own_ child parser,
@ -243,10 +272,21 @@ impl ParseState for AirAggregate {
(
st @ Root(_),
tok @ (AirExpr(..) | AirBind(..) | AirTpl(..) | AirDoc(..)),
tok @ (AirExpr(..) | AirBind(..) | AirTpl(..) | AirMeta(..)
| AirDoc(..)),
) => Transition(st).err(AsgError::PkgExpected(tok.span())),
(st @ (Root(..) | PkgExpr(..) | PkgTpl(..)), AirIdent(tok)) => {
// TODO: We will need to be more intelligent about this,
// since desugaring will produce metavariables in nested contexts,
// e.g. within an expression within a template.
(st @ (Pkg(..) | PkgExpr(..) | PkgOpaque(..)), AirMeta(tok)) => {
Transition(st).err(AsgError::UnexpectedMeta(tok.span()))
}
(
st @ (Root(..) | PkgExpr(..) | PkgTpl(..) | PkgMeta(..)),
AirIdent(tok),
) => {
Transition(st).err(AsgError::UnexpectedOpaqueIdent(tok.name()))
}
}
@ -279,6 +319,7 @@ impl AirAggregate {
Pkg(st) => st.is_accepting(ctx),
PkgExpr(st) => st.is_accepting(ctx),
PkgTpl(st) => st.is_accepting(ctx),
PkgMeta(st) => st.is_accepting(ctx),
PkgOpaque(st) => st.is_accepting(ctx),
}
}
@ -297,6 +338,7 @@ impl AirAggregate {
Pkg(st) => st.is_accepting(ctx),
PkgExpr(st) => st.is_accepting(ctx),
PkgTpl(st) => st.is_accepting(ctx),
PkgMeta(st) => st.is_accepting(ctx),
PkgOpaque(st) => st.is_accepting(ctx),
}
}
@ -335,6 +377,10 @@ impl AirAggregate {
// bound therein.
PkgTpl(tplst) => tplst.active_tpl_oi().map(Into::into),
// Identifiers cannot be rooted within metavariables since they
// contain only lexical information.
PkgMeta(_) => None,
// Loading of opaque objects happens within the context of the
// parent frame.
// At the time of writing,
@ -386,10 +432,11 @@ impl AirAggregate {
// Hidden is a fixpoint.
(_, kind @ Hidden(_)) => kind,
// Expressions do not introduce their own environment
// (they are not containers)
// and so act as an identity function.
(PkgExpr(_), kind) => kind,
// Expressions and metavariables do not introduce their own
// environment
// (they are not containers)
// and so act as an identity function.
(PkgExpr(_) | PkgMeta(_), kind) => kind,
// A visible identifier will always cast a shadow in one step.
// A shadow will always be cast (propagate) until the root.
@ -532,6 +579,30 @@ impl AirAggregateCtx {
}
}
/// Attempt to return to the previous stack frame,
/// using the provided token as a token of lookahead.
///
/// If the provided `st` is in an accepting state,
/// then control will return to the frame atop of the stack.
/// Otherwise,
/// an unexpected token was found,
/// and a dead state transition will be yielded,
/// leaving `st` unchanged.
fn try_ret_with_lookahead<S: Into<AirAggregate>>(
&mut self,
st: S,
tok: impl Token + Into<Air>,
) -> TransitionResult<AirAggregate> {
let st_super = st.into();
if st_super.active_is_complete(self) {
// TODO: error (this should never happen, so maybe panic instead?)
self.stack().ret_or_dead(AirAggregate::Uninit, tok)
} else {
Transition(st_super).dead(tok)
}
}
/// Proxy `tok` to `st`,
/// returning to the state atop of the stack if parsing reaches a dead
/// state.
@ -548,6 +619,17 @@ impl AirAggregateCtx {
})
}
/// Index the provided symbol `name` as representing the
/// [`ObjectIndex`] in the immediate environment `imm_env`.
///
/// Intersecting shadows are permitted,
/// but the existing index will be kept in tact.
/// This behavior may change in the future to permit diagnostic messages
/// that list _all_ identifiers in the event of a shadowing error.
///
/// If indexing fails,
/// the _existing_ [`ObjectIndex`] will be returned,
/// leaving the caller to determine how to react.
fn try_index<
O: ObjectRelatable,
OS: ObjectIndexRelTo<O>,
@ -559,14 +641,38 @@ impl AirAggregateCtx {
eoi: EnvScopeKind<ObjectIndex<O>>,
) -> Result<(), ObjectIndex<O>> {
let sym = name.into();
let prev = index.insert(
(O::rel_ty(), sym, imm_env.widen()),
eoi.map(ObjectIndex::widen),
);
let ient = index.entry((O::rel_ty(), sym, imm_env.widen()));
match prev {
None => Ok(()),
Some(eoi) => Err(eoi.into_inner().must_narrow_into::<O>()),
use Entry::*;
use EnvScopeKind::*;
match (ient, eoi) {
(Vacant(_), Hidden(_)) => Ok(()),
(Vacant(ent), Shadow(_) | Visible(_)) => {
ent.insert(eoi.map(ObjectIndex::widen));
Ok(())
}
(Occupied(ent), eoi) => match (ent.get(), eoi) {
// This ought to be omitted from the index,
// as shown above.
(Hidden(oi), _) => diagnostic_unreachable!(
vec![oi.internal_error(
"this should not have been indexed as Hidden"
)],
"unexpected Hidden scope index entry",
),
(Visible(_), Hidden(_)) => Ok(()),
(Shadow(_), Hidden(_) | Shadow(_)) => Ok(()),
(Shadow(oi), Visible(_))
| (Visible(oi), Shadow(_) | Visible(_)) => {
Err(oi.must_narrow_into::<O>())
}
},
}
}
@ -717,6 +823,9 @@ impl AirAggregateCtx {
// considered to be dangling.
PkgTpl(tplst) => tplst.active_tpl_oi().map(Into::into),
// No definitions can be made within metavariables.
PkgMeta(_) => None,
// Expressions are transparent definitions,
// not opaque,
// and so not permitted in this context.
@ -738,6 +847,11 @@ impl AirAggregateCtx {
PkgExpr(exprst) => exprst.active_expr_oi().map(Into::into),
PkgTpl(tplst) => tplst.active_tpl_oi().map(Into::into),
// Metavariables do not historically support template
// applications within their body.
// Support for such a feature can be evaluated in the future.
PkgMeta(_) => None,
// Templates _could_ conceptually expand into opaque objects,
// but the source language of TAME provides no mechanism to do
// such a thing,

View File

@ -634,7 +634,7 @@ sum_ir! {
///
/// Templates serve as containers for objects that reference
/// metasyntactic variables,
/// defined by [`AirTpl::TplMetaStart`].
/// defined by [`AirMeta::TplMetaStart`].
///
/// Template Application
/// ====================
@ -693,6 +693,48 @@ sum_ir! {
display: |f| write!(f, "open template"),
},
/// Complete the active [`Tpl`] and exit template parsing.
///
/// The expression stack will be restored to its prior state.
///
/// If the template is anonymous,
/// then this will result in an error,
/// since nothing will be able to reference the template to
/// utilize it.
/// See [`Self::TplEndRef`] if you wish to apply an anonymous
/// template.
TplEnd(span: Span) => {
span: span,
display: |f| write!(f, "end template definition"),
},
/// Complete the active _closed_ [`Tpl`] just as [`Self::TplEnd`],
/// but reference its value,
/// with the effect of expanding it in place.
///
/// If the active template is not closed,
/// this will result in an error.
///
/// This additional token is not ideal;
/// ideally [`Air`] would have a means by which to manipulate
/// anonymous objects.
/// However,
/// until such a thing is derived,
/// this is the only current use case,
/// allowing us to avoid having to generate identifiers for
/// templates just for the sake of expansion.
///
/// If the active template is identified as τ,
/// then this has the same behavior as first completing its
/// definition with [`Self::TplEnd`] and then referencing τ as
/// in [`Air::RefIdent(SPair(τ, …))`](Air::RefIdent).
TplEndRef(span: Span) => {
span: span,
display: |f| write!(f, "end template definition and expand it"),
},
}
enum AirMeta {
/// Begin a metavariable definition.
///
/// A metavariable is anonymous unless identified via
@ -736,45 +778,6 @@ sum_ir! {
),
},
/// Complete the active [`Tpl`] and exit template parsing.
///
/// The expression stack will be restored to its prior state.
///
/// If the template is anonymous,
/// then this will result in an error,
/// since nothing will be able to reference the template to
/// utilize it.
/// See [`Self::TplEndRef`] if you wish to apply an anonymous
/// template.
TplEnd(span: Span) => {
span: span,
display: |f| write!(f, "end template definition"),
},
/// Complete the active _closed_ [`Tpl`] just as [`Self::TplEnd`],
/// but reference its value,
/// with the effect of expanding it in place.
///
/// If the active template is not closed,
/// this will result in an error.
///
/// This additional token is not ideal;
/// ideally [`Air`] would have a means by which to manipulate
/// anonymous objects.
/// However,
/// until such a thing is derived,
/// this is the only current use case,
/// allowing us to avoid having to generate identifiers for
/// templates just for the sake of expansion.
///
/// If the active template is identified as τ,
/// then this has the same behavior as first completing its
/// definition with [`Self::TplEnd`] and then referencing τ as
/// in [`Air::RefIdent(SPair(τ, …))`](Air::RefIdent).
TplEndRef(span: Span) => {
span: span,
display: |f| write!(f, "end template definition and expand it"),
},
}
enum AirDoc {
@ -840,6 +843,9 @@ sum_ir! {
/// Tokens that may be used to define or apply templates.
pub sum enum AirBindableTpl = AirTpl | AirBind | AirDoc;
/// Tokens that may be used to define metavariables.
pub sum enum AirBindableMeta = AirMeta | AirBind;
}
impl AirIdent {

View File

@ -0,0 +1,136 @@
// ASG IR metavariable parsing
//
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! AIR metavariable parser.
//!
//! See the [parent module](super) for more information.
use super::{
super::{AsgError, ObjectIndex},
ir::AirBindableMeta,
AirAggregate, AirAggregateCtx,
};
use crate::{
asg::graph::object::Meta, diagnose::Annotate, diagnostic_todo,
parse::prelude::*,
};
/// Metasyntactic variable (metavariable) parser.
#[derive(Debug, PartialEq)]
pub enum AirMetaAggregate {
/// Ready for the start of a metavariable.
Ready,
/// Defining a metavariable.
TplMeta(ObjectIndex<Meta>),
}
impl Display for AirMetaAggregate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use AirMetaAggregate::*;
match self {
Ready => write!(f, "ready for metavariable"),
TplMeta(_) => write!(f, "defining metavariable"),
}
}
}
impl ParseState for AirMetaAggregate {
type Token = AirBindableMeta;
type Object = ();
type Error = AsgError;
type Context = AirAggregateCtx;
type Super = AirAggregate;
fn parse_token(
self,
tok: Self::Token,
ctx: &mut Self::Context,
) -> TransitionResult<Self::Super> {
use super::ir::{AirBind::*, AirMeta::*};
use AirBindableMeta::*;
use AirMetaAggregate::*;
match (self, tok) {
(Ready, AirMeta(TplMetaStart(span))) => {
let oi_meta = ctx.asg_mut().create(Meta::new_required(span));
Transition(TplMeta(oi_meta)).incomplete()
}
(TplMeta(oi_meta), AirMeta(TplMetaEnd(cspan))) => {
oi_meta.close(ctx.asg_mut(), cspan);
Transition(Ready).incomplete()
}
(TplMeta(oi_meta), AirMeta(TplLexeme(lexeme))) => Transition(
TplMeta(oi_meta.assign_lexeme(ctx.asg_mut(), lexeme)),
)
.incomplete(),
(TplMeta(oi_meta), AirBind(BindIdent(name))) => ctx
.defines(name)
.and_then(|oi_ident| {
oi_ident.bind_definition(ctx.asg_mut(), name, oi_meta)
})
.map(|_| ())
.transition(TplMeta(oi_meta)),
(TplMeta(..), tok @ AirBind(RefIdent(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"AirBind in metavar context (param-value)"
)
}
(TplMeta(..), tok @ AirMeta(TplMetaStart(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"AirMeta variant"
)
}
(Ready, tok @ AirMeta(TplMetaEnd(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"unbalanced meta"
)
}
(Ready, tok @ AirMeta(TplLexeme(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"unexpected lexeme"
)
}
// Maybe the bind can be handled by the parent frame.
(Ready, tok @ AirBind(..)) => Transition(Ready).dead(tok),
}
}
fn is_accepting(&self, _: &Self::Context) -> bool {
matches!(self, Self::Ready)
}
}
impl AirMetaAggregate {
pub(super) fn new() -> Self {
Self::Ready
}
}

View File

@ -239,8 +239,8 @@ test_scopes! {
// Root never contains shadow records since it is not part of a
// hierarchy,
// so it is omitted from the metavariable's scope.
// TODO: (Pkg, m(S1, S20), Shadow),
// TODO: (Tpl, m(S2, S19), Visible),
(Pkg, m(S1, S20), Shadow (S5)),
(Tpl, m(S2, S19), Visible(S5)),
];
#[test]
@ -310,9 +310,9 @@ test_scopes! {
// we need to cast a shadow all the way up to the package level to
// ensure that we do not permit identifier shadowing.
// See `meta_outer` above for more information.
// TODO: (Pkg, m(S1, S20), Shadow),
// TODO: (Tpl, m(S2, S19), Shadow),
// TODO: (Tpl, m(S10, S18), Visible),
(Pkg, m(S1, S20), Shadow (S13)),
(Tpl, m(S2, S19), Shadow (S13)),
(Tpl, m(S10, S18), Visible(S13)),
];
#[test]
@ -327,6 +327,109 @@ test_scopes! {
];
}
test_scopes! {
setup {
let pkg_name = SPair("/pkg".into(), S1);
let tpl_outer = SPair("_tpl-outer_".into(), S3);
let tpl_inner = SPair("_tpl-inner_".into(), S9);
// Note how these have the _same name_.
let meta_name = "@param@".into();
let meta_same_a = SPair(meta_name, S5);
let meta_same_b = SPair(meta_name, S11);
// This one will be used for asserting.
let meta_same = SPair(meta_name, S11);
}
air {
// Note that,
// unlike the above set of tests,
// these templates are _siblings_.
[
// ENV: 0 global lexical scoping boundaries (envs)
PkgStart(S1, pkg_name), //- - - -.
// ENV: 1 pkg // :
TplStart(S2), //----. :
// ENV: 2 tpl // | :
BindIdent(tpl_outer), // |~ :
// | :
TplMetaStart(S4), // | :
BindIdent(meta_same_a), // vl|s : <--.
TplMetaEnd(S6), // | : |
TplEnd(S7), //----' : |
// : |s
TplStart(S8), //----. : |a
// ENV: 3 tpl // | : |m
BindIdent(tpl_inner), // |~ : |e
// | : |
TplMetaStart(S10), // | : |
BindIdent(meta_same_b), // vl|s : <--'
TplMetaEnd(S12), // | :
TplEnd(S13), //----' : ~ = ignored for
PkgEnd(S14), //- - - -' these tests
]
}
// Detailed information on metavariables is present in previous tests.
// We focus here only on the fact that these definitions were permitted
// to occur since identifiers of the same name have overlapping
// shadows.
// Keep in mind that this test is a filtering of an ontological tree,
// so this is ordered as such and does not contain duplicate objects.
#[test]
meta_same == [
// A shadow is cast by both `meta_same_a` and `meta_same_b`.
// When they intersect,
// we must make a choice:
//
// (a) Index both of them; or
// (b) Keep only one of them.
//
// At the time of writing,
// the choice was (b).
// But by keeping only one identifier indexed,
// we do lose information and therefore will only be able to
// present one diagnostic error in the event that we later
// discover that the metavariables shadow another identifier
// that is visible in scope.
// This would present only one error at a time to the user,
// depending on how they chose to resolve it,
// and so is not ideal;
// the solution may eventually move to (a) to retain this
// important information.
//
// Since (b) was chosen,
// which do we keep?
// This choice is not important,
// since in the event of a future error we'll still be providing
// correct
// (albeit incomplete)
// information to the user,
// but it is defined by implementation details,
// and so is subject to change.
// At the time of writing,
// because of how indexing is carried out,
// we retain the shadow of the _first_ encountered identifier.
(Pkg, m(S1, S14), Shadow (S5 )),
// The first identifier of this name is found in the first
// template.
// Its shadow is discussed above.
(Tpl, m(S2, S7), Visible(S5 )),
// And the second identifier in the second template,
// with its shadow also discussed above.
// As noted atop this test,
// we do not have duplicate objects in this test data,
// and so the `Pkg` object above is not duplicated despite two
// shadows being cast.
(Tpl, m(S8, S13), Visible(S11)),
];
}
// From the perspective of the linker (tameld):
test_scopes! {
setup {

View File

@ -27,9 +27,6 @@ use super::{
AirAggregate, AirAggregateCtx,
};
use crate::{
asg::graph::object::Meta,
diagnose::Annotate,
diagnostic_todo,
fmt::{DisplayWrapper, TtQuote},
parse::prelude::*,
span::Span,
@ -67,9 +64,6 @@ pub enum AirTplAggregate {
/// or creating an object directly owned by the template.
Toplevel(TplState),
/// Defining a template metavariable.
TplMeta(TplState, ObjectIndex<Meta>),
/// A template has been completed.
///
/// This is used to determine whether the next token of input ought to
@ -82,12 +76,7 @@ impl Display for AirTplAggregate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Ready => write!(f, "ready for template definition"),
Self::Toplevel(tpl) => write!(f, "building {tpl} at toplevel"),
Self::TplMeta(tpl, _) => {
write!(f, "building {tpl} metavariable")
}
Self::Done => write!(f, "completed building template"),
}
}
@ -216,71 +205,6 @@ impl ParseState for AirTplAggregate {
Transition(Toplevel(tpl)).incomplete()
}
(Toplevel(tpl), AirTpl(TplMetaStart(span))) => {
let oi_meta = ctx.asg_mut().create(Meta::new_required(span));
Transition(TplMeta(tpl, oi_meta)).incomplete()
}
(TplMeta(tpl, oi_meta), AirTpl(TplMetaEnd(cspan))) => {
oi_meta.close(ctx.asg_mut(), cspan);
Transition(Toplevel(tpl)).incomplete()
}
(TplMeta(tpl, oi_meta), AirTpl(TplLexeme(lexeme))) => Transition(
TplMeta(tpl, oi_meta.assign_lexeme(ctx.asg_mut(), lexeme)),
)
.incomplete(),
(TplMeta(tpl, oi_meta), AirBind(BindIdent(name))) => {
oi_meta.identify_as_tpl_param(ctx.asg_mut(), tpl.oi(), name);
Transition(TplMeta(tpl, oi_meta)).incomplete()
}
(TplMeta(..), tok @ AirBind(RefIdent(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"AirBind in metavar context (param-value)"
)
}
(
TplMeta(..),
tok @ AirTpl(
TplStart(..) | TplMetaStart(..) | TplEnd(..)
| TplEndRef(..),
),
) => {
diagnostic_todo!(vec![tok.note("this token")], "AirTpl variant")
}
(TplMeta(tpl, oi_meta), tok @ AirDoc(..)) => {
diagnostic_todo!(
vec![
tpl.oi().note("in this template"),
oi_meta.note("this metavariable"),
tok.internal_error(
"this metavariable-level documentation is not \
yet supported"
)
],
"metavariable-level documentation is not yet supported \
by TAMER",
)
}
(Toplevel(..), tok @ AirTpl(TplMetaEnd(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"unbalanced meta"
)
}
(Toplevel(..), tok @ AirTpl(TplLexeme(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"err: TplLexeme outside of metavar"
)
}
(Toplevel(tpl), AirTpl(TplEnd(span))) => {
tpl.close(ctx.asg_mut(), span).transition(Done)
}
@ -303,16 +227,6 @@ impl ParseState for AirTplAggregate {
.with_lookahead(AirTpl(TplEnd(span)))
}
(
Ready | Done,
tok @ AirTpl(TplMetaStart(..) | TplLexeme(..) | TplMetaEnd(..)),
) => {
diagnostic_todo!(
vec![tok.note("for this token")],
"metasyntactic token in non-tpl-toplevel context: {tok:?}"
)
}
// If we just finished a template then this end may represent
// the closing of a parent template.
(Done, tok @ AirTpl(TplEnd(_) | TplEndRef(_))) => {
@ -345,7 +259,7 @@ impl AirTplAggregate {
match self {
Ready | Done => None,
Toplevel(tplst) | TplMeta(tplst, _) => Some(tplst.oi()),
Toplevel(tplst) => Some(tplst.oi()),
}
}
}

View File

@ -151,6 +151,12 @@ pub enum AsgError {
/// An opaque identifier was declared in an invalid context.
UnexpectedOpaqueIdent(SPair),
/// A metavariable is being defined in an invalid context.
///
/// The provided [`Span`] indicates the location of the start of the
/// metavariable definition.
UnexpectedMeta(Span),
}
impl Display for AsgError {
@ -210,10 +216,13 @@ impl Display for AsgError {
UnexpectedOpaqueIdent(name) => {
write!(
f,
"opaque identifier {} declaration",
"unexpected opaque identifier {} declaration",
TtQuote::wrap(name)
)
}
UnexpectedMeta(_) => {
write!(f, "unexpected metavariable definition")
}
}
}
}
@ -375,6 +384,12 @@ impl Diagnostic for AsgError {
UnexpectedOpaqueIdent(name) => vec![name.error(
"an opaque identifier declaration was not expected here",
)],
UnexpectedMeta(span) => {
vec![
span.error("this metavariable cannot occur here"),
span.help("metavariables are expected to occur in a template context"),
]
}
}
}
}

View File

@ -128,10 +128,10 @@ impl Functor<Span> for Meta {
}
object_rel! {
/// Templates may expand into nearly any context,
/// and must therefore be able to contain just about anything.
/// Metavariables contain lexical data and references to other
/// metavariables.
Meta -> {
tree Meta,
tree Meta, // TODO: do we need tree?
cross Ident,
}
}