Compare commits
47 Commits
6769f0c280
...
d889aca13a
Author | SHA1 | Date |
---|---|---|
Mike Gerwitz | d889aca13a | |
Mike Gerwitz | 579575a358 | |
Mike Gerwitz | 66512bf20d | |
Mike Gerwitz | 3c9e1add20 | |
Mike Gerwitz | 38c0161257 | |
Mike Gerwitz | e14854a555 | |
Mike Gerwitz | c19ecba6ef | |
Mike Gerwitz | aec721f4fa | |
Mike Gerwitz | 37c962a7ee | |
Mike Gerwitz | 4168c579fd | |
Mike Gerwitz | 2ecc143e02 | |
Mike Gerwitz | 28b83ad6a3 | |
Mike Gerwitz | e414782def | |
Mike Gerwitz | 0f93f3a498 | |
Mike Gerwitz | 85b08eb45e | |
Mike Gerwitz | 507669cb30 | |
Mike Gerwitz | 5a301c1548 | |
Mike Gerwitz | b30018c23b | |
Mike Gerwitz | 85892caeb2 | |
Mike Gerwitz | 760223f0c9 | |
Mike Gerwitz | b4b85a5e85 | |
Mike Gerwitz | d2d29d8957 | |
Mike Gerwitz | b4bbc0d8f0 | |
Mike Gerwitz | 8a10f8bbbe | |
Mike Gerwitz | 2e33e9e93e | |
Mike Gerwitz | 24ee041373 | |
Mike Gerwitz | a144730981 | |
Mike Gerwitz | 8449a2b759 | |
Mike Gerwitz | 8685527feb | |
Mike Gerwitz | 7314562671 | |
Mike Gerwitz | 15071a1824 | |
Mike Gerwitz | 828d8918a3 | |
Mike Gerwitz | 6b54eafd70 | |
Mike Gerwitz | d10bf00f5d | |
Mike Gerwitz | 9887abd037 | |
Mike Gerwitz | a9bbb87612 | |
Mike Gerwitz | 7487bdccc3 | |
Mike Gerwitz | 454f5f4d04 | |
Mike Gerwitz | 9eeb18bda2 | |
Mike Gerwitz | 341af3fdaf | |
Mike Gerwitz | 61d556c89e | |
Mike Gerwitz | 31f6a102eb | |
Mike Gerwitz | 26c4076579 | |
Mike Gerwitz | 0b9e91b936 | |
Mike Gerwitz | 1bb25b05b3 | |
Mike Gerwitz | 672cc54c14 | |
Mike Gerwitz | d6e9ec7207 |
|
@ -136,7 +136,12 @@ common: $(xmlo_common)
|
|||
|
||||
program-ui: ui/package.strip.js ui/Program.js ui/html/index.phtml
|
||||
|
||||
# Handle an intermediate step as we transition to the new compiler
|
||||
# Handle an intermediate step as we transition to the new compiler.
|
||||
# If a source file is paired with an `*.experimental` file with the same
|
||||
# stem, then it will trigger compilation using `xmlo-experimental`. The
|
||||
# file may contain additional arguments to the pass to the compiler.
|
||||
%.xmli: %.xml %.experimental
|
||||
$(path_tame)/tamer/target/release/tamec --emit xmlo-experimental $$(grep -v '^#' $*.experimental) -o $@ $<
|
||||
%.xmli: %.xml
|
||||
$(path_tame)/tamer/target/release/tamec --emit xmlo -o $@ $<
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@
|
|||
|
||||
<!-- make the name of the supplier available -->
|
||||
<text>/**@expose*/rater.supplier = '</text>
|
||||
<value-of select="substring-after( $name, '/' )" />
|
||||
<value-of select="( tokenize( $name, '/' ) )[ last() ]" />
|
||||
<text>'; </text>
|
||||
|
||||
<text>/**@expose*/rater.meta = meta;</text>
|
||||
|
|
|
@ -53,9 +53,3 @@ unicode-width = "0.1.5"
|
|||
# This is enabled automatically for the `test` profile.
|
||||
parser-trace-stderr = []
|
||||
|
||||
# Derive `xmli` file from the ASG rather than a XIR token stream. This
|
||||
# proves that enough information has been added to the graph for the entire
|
||||
# program to be reconstructed. The `xmli` file will be a new program
|
||||
# _derived from_ the original, and so will not match exactly.
|
||||
wip-asg-derived-xmli = []
|
||||
|
||||
|
|
|
@ -32,6 +32,24 @@
|
|||
# }
|
||||
# }
|
||||
#
|
||||
# But edges also support arbitrary code definitions:
|
||||
#
|
||||
# object_rel! {
|
||||
# Source -> {
|
||||
# tree TargetA {
|
||||
# fn pre_add_edge(...) {
|
||||
# // ...
|
||||
# }
|
||||
# },
|
||||
# tree TargetB,
|
||||
# cross TargetC,
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# And because of that,
|
||||
# this script hard-codes the expected level of nesting just to make life
|
||||
# easier.
|
||||
#
|
||||
# This script expects to receive a list of files containing such
|
||||
# definitions.
|
||||
# It will output,
|
||||
|
@ -81,7 +99,8 @@ BEGINFILE {
|
|||
/^object_rel! {$/, /^}$/ { in_block = 1 }
|
||||
|
||||
# `Foo -> {` line declares the source of the relation.
|
||||
in_block && /->/ {
|
||||
# We hard-code the expected depth to simplify parsing.
|
||||
in_block && /^ \w+ -> \{$/ {
|
||||
block_src = $1
|
||||
|
||||
printf " # `%s` from `%s:%d`\n", block_src, FILENAME, FNR
|
||||
|
@ -92,7 +111,7 @@ in_block && /->/ {
|
|||
# A closing curly brace always means that we've finished with the current
|
||||
# source relation,
|
||||
# since we're at the innermost level of nesting.
|
||||
block_src && /}/ {
|
||||
block_src && /^ }$/ {
|
||||
block_src = ""
|
||||
print ""
|
||||
}
|
||||
|
@ -116,7 +135,12 @@ block_src && /^ *\/\/ empty$/ {
|
|||
# we must independently define each one.
|
||||
# But that's okay;
|
||||
# the output is quite legible.
|
||||
block_src && $NF ~ /\w+,$/ {
|
||||
block_src && /^ \w+ \w+(,| \{)$/ {
|
||||
# Clean up the end of the string _before_ pulling information out of
|
||||
# fields,
|
||||
# since the number of fields can vary.
|
||||
gsub(/(,| \{)$/, "")
|
||||
|
||||
# Edge type (cross, tree)
|
||||
ty = $(NF-1)
|
||||
|
||||
|
@ -139,8 +163,6 @@ block_src && $NF ~ /\w+,$/ {
|
|||
break;
|
||||
}
|
||||
|
||||
gsub(/,$/, "")
|
||||
|
||||
# This may need updating over time as object names in Rust sources
|
||||
# exceed the fixed-width definition here.
|
||||
# This output is intended to form a table that is easy to read and
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
# you will need to modify `configure.ac` / `Makefile.am` to do your bidding.
|
||||
|
||||
[toolchain]
|
||||
channel = "nightly-2023-04-15"
|
||||
channel = "nightly-2023-04-15" #¹
|
||||
|
||||
# The components should be checked in `configure.ac`
|
||||
# - Note that `cargo-fmt` is `rustfmt`.
|
||||
|
@ -50,3 +50,26 @@ components = ["rustfmt", "clippy"]
|
|||
# another version of Rust already available elsewhere), you may use the
|
||||
# `TAMER_RUST_TOOLCHAIN` `configure` parameter.
|
||||
|
||||
|
||||
# ¹ TAMER uses the incomplete feature `adt_const_params`. "Incomplete"
|
||||
# features require a special flag to enable---`incomplete_features`---and
|
||||
# are described as "incomplete and may not be safe to use and/or cause
|
||||
# compiler crashes".
|
||||
#
|
||||
# The `ConstParamTy` trait was introduced by
|
||||
# <https://github.com/rust-lang/rust/pull/111670> and merged in early
|
||||
# June 2023. After this change, const params types must implement this
|
||||
# trait.
|
||||
#
|
||||
# Unfortunately, at the time of writing (2023-06), while the trait is
|
||||
# implemented on a number of core primitives, it is _not_ implemented on the
|
||||
# `NonZero*` types. There is an inquiry into this limitation on Zulip, and
|
||||
# it is expected to be resolved in the future:
|
||||
# <https://rust-lang.zulipchat.com/#narrow/stream/182449-t-compiler.2Fhelp/topic/ConstParamTy.20for.20NonZero*.20types>
|
||||
#
|
||||
# We will sit on this and watch how it evolves over the following
|
||||
# weeks/months to assess how to best proceed.
|
||||
#
|
||||
# Aside from this, there seems to be a rustdoc bug at some point between
|
||||
# April and June that causes documentation failures. This needs further
|
||||
# investigation, and possibly a bug report.
|
||||
|
|
|
@ -36,7 +36,7 @@ use super::{
|
|||
use crate::{
|
||||
diagnose::Annotate,
|
||||
diagnostic_unreachable,
|
||||
f::Functor,
|
||||
f::Map,
|
||||
parse::{prelude::*, StateStack},
|
||||
span::Span,
|
||||
sym::SymbolId,
|
||||
|
@ -46,6 +46,9 @@ use std::{
|
|||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use super::graph::object::ObjectRelTo;
|
||||
|
||||
#[macro_use]
|
||||
mod ir;
|
||||
use fxhash::FxHashMap;
|
||||
|
@ -223,13 +226,14 @@ 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(..)) => {
|
||||
// Metavariables
|
||||
(st @ (PkgTpl(_) | PkgExpr(_)), 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(..))) => {
|
||||
(PkgMeta(meta), AirDoc(mtok)) => ctx.proxy(meta, mtok),
|
||||
(PkgMeta(meta), tok @ (AirExpr(..) | AirTpl(..))) => {
|
||||
ctx.try_ret_with_lookahead(meta, tok)
|
||||
}
|
||||
|
||||
|
@ -272,7 +276,7 @@ impl ParseState for AirAggregate {
|
|||
// 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)) => {
|
||||
(st @ (Pkg(..) | PkgOpaque(..)), AirMeta(tok)) => {
|
||||
Transition(st).err(AsgError::UnexpectedMeta(tok.span()))
|
||||
}
|
||||
|
||||
|
@ -429,6 +433,42 @@ impl AirAggregate {
|
|||
Root(_) => Filter,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the active context represents an object that can be
|
||||
/// lexically instantiated.
|
||||
///
|
||||
/// Only containers that support _instantiation_ are able to contain
|
||||
/// abstract identifiers.
|
||||
/// Instantiation triggers expansion,
|
||||
/// which resolves metavariables and makes all abstract objects
|
||||
/// therein concrete.
|
||||
fn is_lexically_instantiatible(&self) -> bool {
|
||||
use AirAggregate::*;
|
||||
|
||||
match self {
|
||||
Uninit => false,
|
||||
|
||||
// These objects cannot be instantiated,
|
||||
// and so the abstract identifiers that they own would never
|
||||
// be able to be made concrete.
|
||||
Root(_) => false,
|
||||
Pkg(_) => false,
|
||||
PkgExpr(_) => false,
|
||||
|
||||
// Templates are the metalinguistic abstraction and are,
|
||||
// at the time of writing,
|
||||
// the only containers capable of instantiation.
|
||||
PkgTpl(_) => true,
|
||||
|
||||
// Metavariables cannot own identifiers
|
||||
// (they can only reference them).
|
||||
PkgMeta(_) => false,
|
||||
|
||||
// If an object is opaque to us then we cannot possibly look
|
||||
// into it to see what needs expansion.
|
||||
PkgOpaque(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Behavior of an environment boundary when crossing environment upward
|
||||
|
@ -823,7 +863,7 @@ impl AirAggregateCtx {
|
|||
},
|
||||
)?;
|
||||
|
||||
oi_pkg.root(&mut self.asg);
|
||||
oi_pkg.root(&mut self.asg)?;
|
||||
self.ooi_pkg.replace(oi_pkg);
|
||||
|
||||
Ok(oi_pkg)
|
||||
|
@ -847,11 +887,33 @@ impl AirAggregateCtx {
|
|||
///
|
||||
/// A value of [`None`] indicates that no bindings are permitted in the
|
||||
/// current context.
|
||||
fn rooting_oi(&self) -> Option<ObjectIndexToTree<Ident>> {
|
||||
fn rooting_oi(&self) -> Option<(&AirAggregate, ObjectIndexToTree<Ident>)> {
|
||||
self.stack
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|st| st.active_rooting_oi())
|
||||
.find_map(|st| st.active_rooting_oi().map(|oi| (st, oi)))
|
||||
}
|
||||
|
||||
/// The active container (rooting context) for _abstract_ [`Ident`]s.
|
||||
///
|
||||
/// Only containers that support _instantiation_ are able to contain
|
||||
/// abstract identifiers.
|
||||
/// Instantiation triggers expansion,
|
||||
/// which resolves metavariables and makes all abstract objects
|
||||
/// therein concrete.
|
||||
///
|
||||
/// This utilizes [`Self::rooting_oi`] to determine the active rooting
|
||||
/// context.
|
||||
/// If that context does not support instantiation,
|
||||
/// [`None`] is returned.
|
||||
/// This method will _not_ continue looking further up the stack for a
|
||||
/// context that is able to be instantiated,
|
||||
/// since that would change the parent of the binding.
|
||||
fn instantiable_rooting_oi(
|
||||
&self,
|
||||
) -> Option<(&AirAggregate, ObjectIndexToTree<Ident>)> {
|
||||
self.rooting_oi()
|
||||
.filter(|(st, _)| st.is_lexically_instantiatible())
|
||||
}
|
||||
|
||||
/// The active dangling expression context for [`Expr`]s.
|
||||
|
@ -925,17 +987,77 @@ impl AirAggregateCtx {
|
|||
})
|
||||
}
|
||||
|
||||
/// Root an identifier using the [`Self::rooting_oi`] atop of the stack.
|
||||
fn defines(&mut self, name: SPair) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||
let oi_root = self
|
||||
/// Root a concrete identifier using the [`Self::rooting_oi`] atop of
|
||||
/// the stack.
|
||||
///
|
||||
/// This definition will index the identifier into the proper
|
||||
/// environments,
|
||||
/// giving it scope.
|
||||
/// If the identifier is abstract,
|
||||
/// it is important to use [`Self::defines_abstract`] instead so that
|
||||
/// the metavariable that the identifier references will not be
|
||||
/// indexed as the binding `name`.
|
||||
fn defines_concrete(
|
||||
&mut self,
|
||||
binding_name: SPair,
|
||||
) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||
let (_, oi_root) = self
|
||||
.rooting_oi()
|
||||
.ok_or(AsgError::InvalidBindContext(name))?;
|
||||
.ok_or(AsgError::InvalidBindContext(binding_name))?;
|
||||
|
||||
Ok(self.lookup_lexical_or_missing(name).add_edge_from(
|
||||
self.asg_mut(),
|
||||
oi_root,
|
||||
None,
|
||||
))
|
||||
self.lookup_lexical_or_missing(binding_name)
|
||||
.defined_by(self.asg_mut(), oi_root)
|
||||
}
|
||||
|
||||
/// Define an abstract identifier within the context of a container that
|
||||
/// is able to hold dangling objects.
|
||||
///
|
||||
/// If the identifier is concrete,
|
||||
/// then it is important to use [`Self::defines_concrete`] instead to
|
||||
/// ensure that the identifier has its scope computed and indexed.
|
||||
///
|
||||
/// TODO: This is about to evolve;
|
||||
/// document further.
|
||||
fn defines_abstract(
|
||||
&mut self,
|
||||
meta_name: SPair,
|
||||
) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||
match self.instantiable_rooting_oi() {
|
||||
// The container cannot be instantiated and so there is no
|
||||
// chance that this expression will be expanded in the future.
|
||||
None => {
|
||||
// Since we do not have an abstract container,
|
||||
// the nearest container (if any) is presumably concrete,
|
||||
// so let's reference that in the hope of making the error
|
||||
// more informative.
|
||||
// Note that this _does_ re-search the stack,
|
||||
// but this is an error case that should seldom occur.
|
||||
// If it's a problem,
|
||||
// we can have `instantiable_rooting_oi` retain
|
||||
// information.
|
||||
let rooting_span = self
|
||||
.rooting_oi()
|
||||
.map(|(_, oi)| oi.widen().resolve(self.asg_ref()).span());
|
||||
|
||||
// Note that we _discard_ the attempted bind token
|
||||
// and so remain in a dangling state.
|
||||
Err(AsgError::InvalidAbstractBindContext(
|
||||
meta_name,
|
||||
rooting_span,
|
||||
))
|
||||
}
|
||||
|
||||
// We root a new identifier in the instantiable container,
|
||||
// but we do not index it,
|
||||
// since its name is not known until instantiation.
|
||||
Some((_, oi_root)) => {
|
||||
let oi_meta_ident = self.lookup_lexical_or_missing(meta_name);
|
||||
|
||||
oi_meta_ident
|
||||
.new_abstract_ident(self.asg_mut(), meta_name.span())
|
||||
.and_then(|oi| oi.defined_by(self.asg_mut(), oi_root))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to retrieve an identifier and its scope information from the
|
||||
|
@ -966,6 +1088,26 @@ impl AirAggregateCtx {
|
|||
.map(EnvScopeKind::into_inner)
|
||||
}
|
||||
|
||||
/// Resolve an identifier at the scope of the provided environment and
|
||||
/// retrieve its definition.
|
||||
///
|
||||
/// If the identifier is not in scope or does not have a definition,
|
||||
/// [`None`] will be returned;
|
||||
/// the caller cannot distinguish between the two using this method;
|
||||
/// see [`Self::env_scope_lookup`] if that distinction is important.
|
||||
#[cfg(test)]
|
||||
fn env_scope_lookup_ident_dfn<O: ObjectRelatable>(
|
||||
&self,
|
||||
env: impl ObjectIndexRelTo<Ident>,
|
||||
name: SPair,
|
||||
) -> Option<ObjectIndex<O>>
|
||||
where
|
||||
Ident: ObjectRelTo<O>,
|
||||
{
|
||||
self.env_scope_lookup::<Ident>(env, name)
|
||||
.and_then(|oi| oi.definition(self.asg_ref()))
|
||||
}
|
||||
|
||||
/// Attempt to retrieve an identifier from the graph by name relative to
|
||||
/// the immediate environment `imm_env`.
|
||||
///
|
||||
|
@ -1168,7 +1310,7 @@ impl<T> AsRef<T> for EnvScopeKind<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, U> Functor<T, U> for EnvScopeKind<T> {
|
||||
impl<T, U> Map<T, U> for EnvScopeKind<T> {
|
||||
type Target = EnvScopeKind<U>;
|
||||
|
||||
fn map(self, f: impl FnOnce(T) -> U) -> Self::Target {
|
||||
|
|
|
@ -30,11 +30,8 @@ use super::{
|
|||
AirAggregate, AirAggregateCtx,
|
||||
};
|
||||
use crate::{
|
||||
asg::{
|
||||
graph::object::{ObjectIndexRelTo, ObjectIndexTo},
|
||||
ObjectKind,
|
||||
},
|
||||
f::Functor,
|
||||
asg::{graph::object::ObjectIndexTo, ObjectKind},
|
||||
f::Map,
|
||||
parse::prelude::*,
|
||||
};
|
||||
|
||||
|
@ -96,8 +93,12 @@ impl ParseState for AirExprAggregate {
|
|||
}
|
||||
|
||||
(BuildingExpr(es, poi), AirExpr(ExprStart(op, span))) => {
|
||||
let oi = poi.create_subexpr(ctx.asg_mut(), Expr::new(op, span));
|
||||
Transition(BuildingExpr(es.push(poi), oi)).incomplete()
|
||||
match poi.create_subexpr(ctx.asg_mut(), Expr::new(op, span)) {
|
||||
Ok(oi) => {
|
||||
Transition(BuildingExpr(es.push(poi), oi)).incomplete()
|
||||
}
|
||||
Err(e) => Transition(BuildingExpr(es, poi)).err(e),
|
||||
}
|
||||
}
|
||||
|
||||
(BuildingExpr(es, oi), AirExpr(ExprEnd(end))) => {
|
||||
|
@ -123,7 +124,7 @@ impl ParseState for AirExprAggregate {
|
|||
}
|
||||
|
||||
(BuildingExpr(es, oi), AirBind(BindIdent(id))) => {
|
||||
let result = ctx.defines(id).and_then(|oi_ident| {
|
||||
let result = ctx.defines_concrete(id).and_then(|oi_ident| {
|
||||
oi_ident.bind_definition(ctx.asg_mut(), id, oi)
|
||||
});
|
||||
|
||||
|
@ -138,20 +139,38 @@ impl ParseState for AirExprAggregate {
|
|||
}
|
||||
}
|
||||
|
||||
(BuildingExpr(es, oi), AirBind(RefIdent(name))) => {
|
||||
let oi_ident = ctx.lookup_lexical_or_missing(name);
|
||||
Transition(BuildingExpr(
|
||||
es,
|
||||
oi.ref_expr(ctx.asg_mut(), oi_ident),
|
||||
))
|
||||
.incomplete()
|
||||
(BuildingExpr(es, oi), AirBind(BindIdentAbstract(meta_name))) => {
|
||||
let result =
|
||||
ctx.defines_abstract(meta_name).and_then(|oi_ident| {
|
||||
oi_ident.bind_definition(ctx.asg_mut(), meta_name, oi)
|
||||
});
|
||||
|
||||
// It is important that we do not mark this expression as
|
||||
// reachable unless we successfully bind the identifier.
|
||||
// Even though the identifier is abstract,
|
||||
// we want to mimic the concrete structure of the graph.
|
||||
match result {
|
||||
Ok(oi_ident) => {
|
||||
Transition(BuildingExpr(es.reachable_by(oi_ident), oi))
|
||||
.incomplete()
|
||||
}
|
||||
Err(e) => Transition(BuildingExpr(es, oi)).err(e),
|
||||
}
|
||||
}
|
||||
|
||||
(BuildingExpr(es, oi), AirDoc(DocIndepClause(clause))) => {
|
||||
oi.desc_short(ctx.asg_mut(), clause);
|
||||
Transition(BuildingExpr(es, oi)).incomplete()
|
||||
(BuildingExpr(es, oi), AirBind(RefIdent(name))) => {
|
||||
let oi_ident = ctx.lookup_lexical_or_missing(name);
|
||||
|
||||
oi.ref_expr(ctx.asg_mut(), oi_ident)
|
||||
.map(|_| ())
|
||||
.transition(BuildingExpr(es, oi))
|
||||
}
|
||||
|
||||
(BuildingExpr(es, oi), AirDoc(DocIndepClause(clause))) => oi
|
||||
.add_desc_short(ctx.asg_mut(), clause)
|
||||
.map(|_| ())
|
||||
.transition(BuildingExpr(es, oi)),
|
||||
|
||||
(BuildingExpr(es, oi), AirDoc(DocText(text))) => Transition(
|
||||
BuildingExpr(es, oi),
|
||||
)
|
||||
|
@ -188,9 +207,10 @@ impl AirExprAggregate {
|
|||
oi_root: Option<ObjectIndexTo<Expr>>,
|
||||
oi_expr: ObjectIndex<Expr>,
|
||||
) -> Result<(), AsgError> {
|
||||
oi_root
|
||||
.ok_or(AsgError::DanglingExpr(oi_expr.resolve(asg).span()))?
|
||||
.add_edge_to(asg, oi_expr, None);
|
||||
let oi_container = oi_root
|
||||
.ok_or(AsgError::DanglingExpr(oi_expr.resolve(asg).span()))?;
|
||||
|
||||
oi_expr.held_by(asg, oi_container)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -18,18 +18,23 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use crate::asg::{
|
||||
air::{
|
||||
test::{
|
||||
air_ctx_from_pkg_body_toks, air_ctx_from_toks, parse_as_pkg_body,
|
||||
pkg_expect_ident_obj, pkg_expect_ident_oi, pkg_lookup,
|
||||
},
|
||||
Air, AirAggregate,
|
||||
},
|
||||
graph::object::{expr::ExprRel, Doc, ObjectRel},
|
||||
ExprOp, Ident,
|
||||
};
|
||||
use crate::span::dummy::*;
|
||||
use crate::{
|
||||
asg::{
|
||||
air::{
|
||||
test::{
|
||||
air_ctx_from_pkg_body_toks, air_ctx_from_toks,
|
||||
parse_as_pkg_body, pkg_expect_ident_obj, pkg_expect_ident_oi,
|
||||
pkg_lookup,
|
||||
},
|
||||
Air::*,
|
||||
AirAggregate,
|
||||
},
|
||||
graph::object::{expr::ExprRel, Doc, ObjectRel},
|
||||
ExprOp, Ident,
|
||||
},
|
||||
parse::util::spair,
|
||||
};
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
type Sut = AirAggregate;
|
||||
|
@ -46,13 +51,13 @@ pub fn collect_subexprs(
|
|||
|
||||
#[test]
|
||||
fn expr_empty_ident() {
|
||||
let id = SPair("foo".into(), S2);
|
||||
let id = spair("foo", S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S3),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
BindIdent(id),
|
||||
ExprEnd(S3),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
|
@ -65,13 +70,13 @@ fn expr_empty_ident() {
|
|||
|
||||
#[test]
|
||||
fn expr_without_pkg() {
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
// No package
|
||||
// (because we're not parsing with `parse_as_pkg_body` below)
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// RECOVERY
|
||||
Air::PkgStart(S2, SPair("/pkg".into(), S2)),
|
||||
Air::PkgEnd(S3),
|
||||
PkgStart(S2, spair("/pkg", S2)),
|
||||
PkgEnd(S3),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
|
@ -88,18 +93,18 @@ fn expr_without_pkg() {
|
|||
// Note that this can't happen in e.g. NIR / TAME's source XML.
|
||||
#[test]
|
||||
fn close_pkg_mid_expr() {
|
||||
let id = SPair("foo".into(), S4);
|
||||
let id = spair("foo", S4);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
Air::PkgEnd(S3),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
ExprStart(ExprOp::Sum, S2),
|
||||
PkgEnd(S3),
|
||||
// RECOVERY: Let's finish the expression first...
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S5),
|
||||
BindIdent(id),
|
||||
ExprEnd(S5),
|
||||
// ...and then try to close again.
|
||||
Air::PkgEnd(S6),
|
||||
PkgEnd(S6),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
|
@ -123,20 +128,20 @@ fn close_pkg_mid_expr() {
|
|||
|
||||
#[test]
|
||||
fn open_pkg_mid_expr() {
|
||||
let pkg_a = SPair("/pkg".into(), S1);
|
||||
let pkg_nested = SPair("/pkg-nested".into(), S3);
|
||||
let id = SPair("foo".into(), S4);
|
||||
let pkg_a = spair("/pkg", S1);
|
||||
let pkg_nested = spair("/pkg-nested", S3);
|
||||
let id = spair("foo", S4);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::PkgStart(S1, pkg_a),
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
Air::PkgStart(S3, pkg_nested),
|
||||
let toks = [
|
||||
PkgStart(S1, pkg_a),
|
||||
ExprStart(ExprOp::Sum, S2),
|
||||
PkgStart(S3, pkg_nested),
|
||||
// RECOVERY: We should still be able to complete successfully.
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S5),
|
||||
BindIdent(id),
|
||||
ExprEnd(S5),
|
||||
// Closes the _original_ package.
|
||||
Air::PkgEnd(S6),
|
||||
PkgEnd(S6),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
|
@ -163,23 +168,23 @@ fn open_pkg_mid_expr() {
|
|||
|
||||
#[test]
|
||||
fn expr_non_empty_ident_root() {
|
||||
let id_a = SPair("foo".into(), S2);
|
||||
let id_b = SPair("bar".into(), S2);
|
||||
let id_a = spair("foo", S2);
|
||||
let id_b = spair("bar", S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// Identifier while still empty...
|
||||
Air::BindIdent(id_a),
|
||||
BindIdent(id_a),
|
||||
|
||||
Air::ExprStart(ExprOp::Sum, S3),
|
||||
ExprStart(ExprOp::Sum, S3),
|
||||
// (note that the inner expression _does not_ have an ident
|
||||
// binding)
|
||||
Air::ExprEnd(S4),
|
||||
ExprEnd(S4),
|
||||
|
||||
// ...and an identifier non-empty.
|
||||
Air::BindIdent(id_b),
|
||||
Air::ExprEnd(S6),
|
||||
BindIdent(id_b),
|
||||
ExprEnd(S6),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
|
@ -197,20 +202,20 @@ fn expr_non_empty_ident_root() {
|
|||
// which only becomes reachable at the end.
|
||||
#[test]
|
||||
fn expr_non_empty_bind_only_after() {
|
||||
let id = SPair("foo".into(), S2);
|
||||
let id = spair("foo", S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// Expression root is still dangling at this point.
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
Air::ExprEnd(S3),
|
||||
ExprStart(ExprOp::Sum, S2),
|
||||
ExprEnd(S3),
|
||||
|
||||
// We only bind an identifier _after_ we've created the expression,
|
||||
// which should cause the still-dangling root to become
|
||||
// reachable.
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S5),
|
||||
BindIdent(id),
|
||||
ExprEnd(S5),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
|
@ -225,11 +230,11 @@ fn expr_non_empty_bind_only_after() {
|
|||
// since they're either mistakes or misconceptions.
|
||||
#[test]
|
||||
fn expr_dangling_no_subexpr() {
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// No `BindIdent`,
|
||||
// so this expression is dangling.
|
||||
Air::ExprEnd(S2),
|
||||
ExprEnd(S2),
|
||||
];
|
||||
|
||||
// The error span should encompass the entire expression.
|
||||
|
@ -251,14 +256,14 @@ fn expr_dangling_no_subexpr() {
|
|||
#[test]
|
||||
fn expr_dangling_with_subexpr() {
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// Expression root is still dangling at this point.
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
Air::ExprEnd(S3),
|
||||
ExprStart(ExprOp::Sum, S2),
|
||||
ExprEnd(S3),
|
||||
// Still no ident binding,
|
||||
// so root should still be dangling.
|
||||
Air::ExprEnd(S4),
|
||||
ExprEnd(S4),
|
||||
];
|
||||
|
||||
let full_span = S1.merge(S4).unwrap();
|
||||
|
@ -280,23 +285,23 @@ fn expr_dangling_with_subexpr() {
|
|||
|
||||
#[test]
|
||||
fn expr_dangling_with_subexpr_ident() {
|
||||
let id = SPair("foo".into(), S3);
|
||||
let id = spair("foo", S3);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// Expression root is still dangling at this point.
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
ExprStart(ExprOp::Sum, S2),
|
||||
// The _inner_ expression receives an identifier,
|
||||
// but that should have no impact on the dangling status of
|
||||
// the root,
|
||||
// especially given that subexpressions are always reachable
|
||||
// anyway.
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S4),
|
||||
BindIdent(id),
|
||||
ExprEnd(S4),
|
||||
// But the root still has no ident binding,
|
||||
// and so should still be dangling.
|
||||
Air::ExprEnd(S5),
|
||||
ExprEnd(S5),
|
||||
];
|
||||
|
||||
let full_span = S1.merge(S5).unwrap();
|
||||
|
@ -323,18 +328,18 @@ fn expr_dangling_with_subexpr_ident() {
|
|||
// but this also protects against potential future breakages.
|
||||
#[test]
|
||||
fn expr_reachable_subsequent_dangling() {
|
||||
let id = SPair("foo".into(), S2);
|
||||
let id = spair("foo", S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
// Reachable
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S3),
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
BindIdent(id),
|
||||
ExprEnd(S3),
|
||||
|
||||
// Dangling
|
||||
Air::ExprStart(ExprOp::Sum, S4),
|
||||
Air::ExprEnd(S5),
|
||||
ExprStart(ExprOp::Sum, S4),
|
||||
ExprEnd(S5),
|
||||
];
|
||||
|
||||
// The error span should encompass the entire expression.
|
||||
|
@ -362,18 +367,18 @@ fn expr_reachable_subsequent_dangling() {
|
|||
// Recovery from dangling expression.
|
||||
#[test]
|
||||
fn recovery_expr_reachable_after_dangling() {
|
||||
let id = SPair("foo".into(), S4);
|
||||
let id = spair("foo", S4);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
// Dangling
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
Air::ExprEnd(S2),
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
ExprEnd(S2),
|
||||
|
||||
// Reachable, after error from dangling.
|
||||
Air::ExprStart(ExprOp::Sum, S3),
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S5),
|
||||
ExprStart(ExprOp::Sum, S3),
|
||||
BindIdent(id),
|
||||
ExprEnd(S5),
|
||||
];
|
||||
|
||||
// The error span should encompass the entire expression.
|
||||
|
@ -415,21 +420,21 @@ fn recovery_expr_reachable_after_dangling() {
|
|||
|
||||
#[test]
|
||||
fn expr_close_unbalanced() {
|
||||
let id = SPair("foo".into(), S3);
|
||||
let id = spair("foo", S3);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
// Close before _any_ open.
|
||||
Air::ExprEnd(S1),
|
||||
ExprEnd(S1),
|
||||
|
||||
// Should recover,
|
||||
// allowing for a normal expr.
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
Air::BindIdent(id),
|
||||
Air::ExprEnd(S4),
|
||||
ExprStart(ExprOp::Sum, S2),
|
||||
BindIdent(id),
|
||||
ExprEnd(S4),
|
||||
|
||||
// And now an extra close _after_ a valid expr.
|
||||
Air::ExprEnd(S5),
|
||||
ExprEnd(S5),
|
||||
];
|
||||
|
||||
let mut sut = parse_as_pkg_body(toks);
|
||||
|
@ -467,26 +472,26 @@ fn expr_close_unbalanced() {
|
|||
// for non-associative expressions.
|
||||
#[test]
|
||||
fn sibling_subexprs_have_ordered_edges_to_parent() {
|
||||
let id_root = SPair("root".into(), S1);
|
||||
let id_root = spair("root", S1);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// Identify the root so that it is not dangling.
|
||||
Air::BindIdent(id_root),
|
||||
BindIdent(id_root),
|
||||
|
||||
// Sibling A
|
||||
Air::ExprStart(ExprOp::Sum, S3),
|
||||
Air::ExprEnd(S4),
|
||||
ExprStart(ExprOp::Sum, S3),
|
||||
ExprEnd(S4),
|
||||
|
||||
// Sibling B
|
||||
Air::ExprStart(ExprOp::Sum, S5),
|
||||
Air::ExprEnd(S6),
|
||||
ExprStart(ExprOp::Sum, S5),
|
||||
ExprEnd(S6),
|
||||
|
||||
// Sibling C
|
||||
Air::ExprStart(ExprOp::Sum, S7),
|
||||
Air::ExprEnd(S8),
|
||||
Air::ExprEnd(S9),
|
||||
ExprStart(ExprOp::Sum, S7),
|
||||
ExprEnd(S8),
|
||||
ExprEnd(S9),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
|
@ -516,21 +521,21 @@ fn sibling_subexprs_have_ordered_edges_to_parent() {
|
|||
|
||||
#[test]
|
||||
fn nested_subexprs_related_to_relative_parent() {
|
||||
let id_root = SPair("root".into(), S1);
|
||||
let id_suba = SPair("suba".into(), S2);
|
||||
let id_root = spair("root", S1);
|
||||
let id_suba = spair("suba", S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1), // 0
|
||||
Air::BindIdent(id_root),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1), // 0
|
||||
BindIdent(id_root),
|
||||
|
||||
Air::ExprStart(ExprOp::Sum, S2), // 1
|
||||
Air::BindIdent(id_suba),
|
||||
ExprStart(ExprOp::Sum, S2), // 1
|
||||
BindIdent(id_suba),
|
||||
|
||||
Air::ExprStart(ExprOp::Sum, S3), // 2
|
||||
Air::ExprEnd(S4),
|
||||
Air::ExprEnd(S5),
|
||||
Air::ExprEnd(S6),
|
||||
ExprStart(ExprOp::Sum, S3), // 2
|
||||
ExprEnd(S4),
|
||||
ExprEnd(S5),
|
||||
ExprEnd(S6),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
|
@ -556,18 +561,18 @@ fn nested_subexprs_related_to_relative_parent() {
|
|||
fn expr_redefine_ident() {
|
||||
// Same identifier but with different spans
|
||||
// (which would be the case in the real world).
|
||||
let id_first = SPair("foo".into(), S2);
|
||||
let id_dup = SPair("foo".into(), S3);
|
||||
let id_first = spair("foo", S2);
|
||||
let id_dup = spair("foo", S3);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
Air::BindIdent(id_first),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
BindIdent(id_first),
|
||||
|
||||
Air::ExprStart(ExprOp::Sum, S3),
|
||||
Air::BindIdent(id_dup),
|
||||
Air::ExprEnd(S4),
|
||||
Air::ExprEnd(S5),
|
||||
ExprStart(ExprOp::Sum, S3),
|
||||
BindIdent(id_dup),
|
||||
ExprEnd(S4),
|
||||
ExprEnd(S5),
|
||||
];
|
||||
|
||||
let mut sut = parse_as_pkg_body(toks);
|
||||
|
@ -606,32 +611,32 @@ fn expr_redefine_ident() {
|
|||
fn expr_still_dangling_on_redefine() {
|
||||
// Same identifier but with different spans
|
||||
// (which would be the case in the real world).
|
||||
let id_first = SPair("foo".into(), S2);
|
||||
let id_dup = SPair("foo".into(), S5);
|
||||
let id_dup2 = SPair("foo".into(), S8);
|
||||
let id_second = SPair("bar".into(), S9);
|
||||
let id_first = spair("foo", S2);
|
||||
let id_dup = spair("foo", S5);
|
||||
let id_dup2 = spair("foo", S8);
|
||||
let id_second = spair("bar", S9);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
// First expr (OK)
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
Air::BindIdent(id_first),
|
||||
Air::ExprEnd(S3),
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
BindIdent(id_first),
|
||||
ExprEnd(S3),
|
||||
|
||||
// Second expr should still dangle due to use of duplicate
|
||||
// identifier
|
||||
Air::ExprStart(ExprOp::Sum, S4),
|
||||
Air::BindIdent(id_dup),
|
||||
Air::ExprEnd(S6),
|
||||
ExprStart(ExprOp::Sum, S4),
|
||||
BindIdent(id_dup),
|
||||
ExprEnd(S6),
|
||||
|
||||
// Third expr will error on redefine but then be successful.
|
||||
// This probably won't happen in practice with TAME's original
|
||||
// source language,
|
||||
// but could happen at e.g. a REPL.
|
||||
Air::ExprStart(ExprOp::Sum, S7),
|
||||
Air::BindIdent(id_dup2), // fail
|
||||
Air::BindIdent(id_second), // succeed
|
||||
Air::ExprEnd(S10),
|
||||
ExprStart(ExprOp::Sum, S7),
|
||||
BindIdent(id_dup2), // fail
|
||||
BindIdent(id_second), // succeed
|
||||
ExprEnd(S10),
|
||||
];
|
||||
|
||||
let mut sut = parse_as_pkg_body(toks);
|
||||
|
@ -691,27 +696,27 @@ fn expr_still_dangling_on_redefine() {
|
|||
|
||||
#[test]
|
||||
fn expr_ref_to_ident() {
|
||||
let id_foo = SPair("foo".into(), S2);
|
||||
let id_bar = SPair("bar".into(), S6);
|
||||
let id_foo = spair("foo", S2);
|
||||
let id_bar = spair("bar", S6);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
Air::BindIdent(id_foo),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
BindIdent(id_foo),
|
||||
|
||||
// Reference to an as-of-yet-undefined id (okay),
|
||||
// with a different span than `id_bar`.
|
||||
Air::RefIdent(SPair("bar".into(), S3)),
|
||||
Air::ExprEnd(S4),
|
||||
RefIdent(spair("bar", S3)),
|
||||
ExprEnd(S4),
|
||||
|
||||
//
|
||||
// Another expression to reference the first
|
||||
// (we don't handle cyclic references until a topological sort,
|
||||
// so no point in referencing ourselves;
|
||||
// it'd work just fine here.)
|
||||
Air::ExprStart(ExprOp::Sum, S5),
|
||||
Air::BindIdent(id_bar),
|
||||
Air::ExprEnd(S7),
|
||||
ExprStart(ExprOp::Sum, S5),
|
||||
BindIdent(id_bar),
|
||||
ExprEnd(S7),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
|
@ -748,23 +753,23 @@ fn expr_ref_to_ident() {
|
|||
|
||||
#[test]
|
||||
fn idents_share_defining_pkg() {
|
||||
let id_foo = SPair("foo".into(), S3);
|
||||
let id_bar = SPair("bar".into(), S5);
|
||||
let id_baz = SPair("baz".into(), S6);
|
||||
let id_foo = spair("foo", S3);
|
||||
let id_bar = spair("bar", S5);
|
||||
let id_baz = spair("baz", S6);
|
||||
|
||||
// An expression nested within another.
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
Air::BindIdent(id_foo),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
ExprStart(ExprOp::Sum, S2),
|
||||
BindIdent(id_foo),
|
||||
|
||||
Air::ExprStart(ExprOp::Sum, S4),
|
||||
Air::BindIdent(id_bar),
|
||||
Air::RefIdent(id_baz),
|
||||
Air::ExprEnd(S7),
|
||||
Air::ExprEnd(S8),
|
||||
Air::PkgEnd(S9),
|
||||
ExprStart(ExprOp::Sum, S4),
|
||||
BindIdent(id_bar),
|
||||
RefIdent(id_baz),
|
||||
ExprEnd(S7),
|
||||
ExprEnd(S8),
|
||||
PkgEnd(S9),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_toks(toks);
|
||||
|
@ -789,15 +794,15 @@ fn idents_share_defining_pkg() {
|
|||
|
||||
#[test]
|
||||
fn expr_doc_short_desc() {
|
||||
let id_expr = SPair("foo".into(), S2);
|
||||
let clause = SPair("short desc".into(), S3);
|
||||
let id_expr = spair("foo", S2);
|
||||
let clause = spair("short desc", S3);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::ExprStart(ExprOp::Sum, S1),
|
||||
Air::BindIdent(id_expr),
|
||||
Air::DocIndepClause(clause),
|
||||
Air::ExprEnd(S4),
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
BindIdent(id_expr),
|
||||
DocIndepClause(clause),
|
||||
ExprEnd(S4),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
|
@ -813,3 +818,73 @@ fn expr_doc_short_desc() {
|
|||
oi_docs.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
// Binding an abstract identifier to an expression means that the expression
|
||||
// may _eventually_ be reachable after expansion,
|
||||
// but it is not yet.
|
||||
// They must therefore only be utilized within the context of a container
|
||||
// that supports dangling expressions,
|
||||
// like a template.
|
||||
#[test]
|
||||
fn abstract_bind_without_dangling_container() {
|
||||
let id_meta = spair("@foo@", S2);
|
||||
let id_ok = spair("concrete", S5);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = [
|
||||
ExprStart(ExprOp::Sum, S1),
|
||||
// This expression is bound to an _abstract_ identifier,
|
||||
// which will be expanded at a later time.
|
||||
// Consequently,
|
||||
// this expression is still dangling.
|
||||
BindIdentAbstract(id_meta),
|
||||
|
||||
// Since the expression is still dangling,
|
||||
// attempting to close it will produce an error.
|
||||
ExprEnd(S3),
|
||||
|
||||
// RECOVERY: Since an attempt at identification has been made,
|
||||
// we shouldn't expect that another attempt will be made.
|
||||
// The sensible thing to do is to move on to try to find other
|
||||
// errors,
|
||||
// leaving the expression alone and unreachable.
|
||||
ExprStart(ExprOp::Sum, S4),
|
||||
// This is intended to demonstrate that we can continue on to the
|
||||
// next expression despite the prior error.
|
||||
BindIdent(id_ok),
|
||||
ExprEnd(S6),
|
||||
];
|
||||
|
||||
let mut sut = parse_as_pkg_body(toks);
|
||||
|
||||
assert_eq!(
|
||||
#[rustfmt::skip]
|
||||
vec![
|
||||
Ok(Parsed::Incomplete), // PkgStart
|
||||
Ok(Parsed::Incomplete), // ExprStart
|
||||
|
||||
// This provides an _abstract_ identifier,
|
||||
// which is not permitted in this context.
|
||||
Err(ParseError::StateError(AsgError::InvalidAbstractBindContext(
|
||||
id_meta,
|
||||
Some(S1), // Pkg
|
||||
))),
|
||||
|
||||
// RECOVERY: Ignore the bind and move to close.
|
||||
// The above identifier was rejected and so we are still dangling.
|
||||
Err(ParseError::StateError(AsgError::DanglingExpr(
|
||||
S1.merge(S3).unwrap()
|
||||
))),
|
||||
|
||||
// RECOVERY: This observes that we're able to continue parsing
|
||||
// the package after the above identification problem.
|
||||
Ok(Parsed::Incomplete), // ExprStart
|
||||
Ok(Parsed::Incomplete), // BindIdent (ok)
|
||||
Ok(Parsed::Incomplete), // ExprEnd
|
||||
Ok(Parsed::Incomplete), // PkgEnd
|
||||
],
|
||||
sut.by_ref().collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let _ = sut.finalize().unwrap();
|
||||
}
|
||||
|
|
|
@ -542,6 +542,10 @@ sum_ir! {
|
|||
/// Assign an identifier to the active object.
|
||||
///
|
||||
/// The "active" object depends on the current parsing state.
|
||||
///
|
||||
/// See also [`Self::BindIdentAbstract`] if the name of the
|
||||
/// identifier cannot be know until future expansion based on
|
||||
/// the value of a metavariable.
|
||||
BindIdent(id: SPair) => {
|
||||
span: id,
|
||||
display: |f| write!(
|
||||
|
@ -551,6 +555,26 @@ sum_ir! {
|
|||
),
|
||||
},
|
||||
|
||||
/// Assign an abstract identifier to the active object.
|
||||
///
|
||||
/// This differs from [`Self::BindIdent`] in that the name of
|
||||
/// the identifier will not be known until expansion time.
|
||||
/// The identifier is bound to a metavariable of the
|
||||
/// name `meta`,
|
||||
/// from which its name will eventually be derived.
|
||||
///
|
||||
/// If the name is known,
|
||||
/// use [`Self::BindIdent`] to bind a concrete identifier.
|
||||
BindIdentAbstract(meta: SPair) => {
|
||||
span: meta,
|
||||
display: |f| write!(
|
||||
f,
|
||||
"identify active object by the value of the \
|
||||
metavariable {} during future expansion",
|
||||
TtQuote::wrap(meta),
|
||||
),
|
||||
},
|
||||
|
||||
/// Reference another object identified by the given [`SPair`].
|
||||
///
|
||||
/// Objects can be referenced before they are declared or defined,
|
||||
|
@ -646,7 +670,7 @@ sum_ir! {
|
|||
/// Subset of [`Air`] tokens for defining [`Tpl`]s.
|
||||
///
|
||||
/// Templates serve as containers for objects that reference
|
||||
/// metasyntactic variables,
|
||||
/// metavariables,
|
||||
/// defined by [`AirMeta::MetaStart`].
|
||||
///
|
||||
/// Template Application
|
||||
|
@ -747,6 +771,18 @@ sum_ir! {
|
|||
},
|
||||
}
|
||||
|
||||
/// Metalinguistic objects.
|
||||
///
|
||||
/// TAME's metalanguage supports the generation of lexemes using
|
||||
/// metavariables.
|
||||
/// Those generated lexemes are utilized by the template system
|
||||
/// (via [`AirTpl`])
|
||||
/// during expansion,
|
||||
/// yielding objects as if the user had entered the lexemes
|
||||
/// statically.
|
||||
///
|
||||
/// [`AirBind`] is able to utilize metavariables for
|
||||
/// dynamically generated bindings.
|
||||
enum AirMeta {
|
||||
/// Begin a metavariable definition.
|
||||
///
|
||||
|
@ -769,7 +805,7 @@ sum_ir! {
|
|||
span: span,
|
||||
display: |f| write!(
|
||||
f,
|
||||
"open definition of metasyntactic variable",
|
||||
"open definition of metavariable",
|
||||
),
|
||||
},
|
||||
|
||||
|
@ -787,7 +823,7 @@ sum_ir! {
|
|||
span: span,
|
||||
display: |f| write!(
|
||||
f,
|
||||
"close definition of metasyntactic variable",
|
||||
"close definition of metavariable",
|
||||
),
|
||||
},
|
||||
|
||||
|
@ -854,18 +890,7 @@ sum_ir! {
|
|||
pub sum enum AirBindableTpl = AirTpl | AirBind | AirDoc;
|
||||
|
||||
/// Tokens that may be used to define metavariables.
|
||||
pub sum enum AirBindableMeta = AirMeta | AirBind;
|
||||
}
|
||||
|
||||
impl AirBind {
|
||||
/// Name of the identifier described by this token.
|
||||
pub fn name(&self) -> SPair {
|
||||
use AirBind::*;
|
||||
|
||||
match self {
|
||||
BindIdent(name) | RefIdent(name) => *name,
|
||||
}
|
||||
}
|
||||
pub sum enum AirBindableMeta = AirMeta | AirBind | AirDoc;
|
||||
}
|
||||
|
||||
impl AirIdent {
|
||||
|
|
|
@ -26,12 +26,9 @@ use super::{
|
|||
ir::AirBindableMeta,
|
||||
AirAggregate, AirAggregateCtx,
|
||||
};
|
||||
use crate::{
|
||||
asg::graph::object::Meta, diagnose::Annotate, diagnostic_todo,
|
||||
parse::prelude::*,
|
||||
};
|
||||
use crate::{asg::graph::object::Meta, diagnostic_todo, parse::prelude::*};
|
||||
|
||||
/// Metasyntactic variable (metavariable) parser.
|
||||
/// Metalinguistic variable (metavariable) parser.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AirMetaAggregate {
|
||||
/// Ready for the start of a metavariable.
|
||||
|
@ -64,7 +61,7 @@ impl ParseState for AirMetaAggregate {
|
|||
tok: Self::Token,
|
||||
ctx: &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
use super::ir::{AirBind::*, AirMeta::*};
|
||||
use super::ir::{AirBind::*, AirDoc::*, AirMeta::*};
|
||||
use AirBindableMeta::*;
|
||||
use AirMetaAggregate::*;
|
||||
|
||||
|
@ -78,26 +75,58 @@ impl ParseState for AirMetaAggregate {
|
|||
Transition(Ready).incomplete()
|
||||
}
|
||||
|
||||
(TplMeta(oi_meta), AirMeta(MetaLexeme(lexeme))) => Transition(
|
||||
TplMeta(oi_meta.assign_lexeme(ctx.asg_mut(), lexeme)),
|
||||
)
|
||||
.incomplete(),
|
||||
(TplMeta(oi_meta), AirMeta(MetaLexeme(lexeme))) => oi_meta
|
||||
.append_lexeme(ctx.asg_mut(), lexeme)
|
||||
.map(|_| ())
|
||||
.transition(TplMeta(oi_meta)),
|
||||
|
||||
(TplMeta(oi_meta), AirBind(BindIdent(name))) => ctx
|
||||
.defines(name)
|
||||
.defines_concrete(name)
|
||||
.and_then(|oi_ident| {
|
||||
oi_ident.bind_definition(ctx.asg_mut(), name, oi_meta)
|
||||
})
|
||||
.map(|_| ())
|
||||
.transition(TplMeta(oi_meta)),
|
||||
|
||||
(TplMeta(..), tok @ AirBind(RefIdent(..))) => {
|
||||
(TplMeta(oi_meta), AirBind(BindIdentAbstract(meta_name))) => {
|
||||
diagnostic_todo!(
|
||||
vec![
|
||||
oi_meta.note("for this metavariable"),
|
||||
meta_name.note(
|
||||
"attempting to bind an abstract identifier with \
|
||||
this metavariable"
|
||||
),
|
||||
],
|
||||
"attempt to bind abstract identifier to metavariable",
|
||||
)
|
||||
}
|
||||
|
||||
(TplMeta(oi_meta), AirDoc(DocIndepClause(clause))) => oi_meta
|
||||
.add_desc_short(ctx.asg_mut(), clause)
|
||||
.map(|_| ())
|
||||
.transition(TplMeta(oi_meta)),
|
||||
|
||||
// TODO: The user _probably_ meant to use `<text>` in XML NIR,
|
||||
// so maybe we should have an error to that effect.
|
||||
(TplMeta(..), tok @ AirDoc(DocText(..))) => {
|
||||
diagnostic_todo!(
|
||||
vec![tok.note("this token")],
|
||||
"AirBind in metavar context (param-value)"
|
||||
"AirDoc in metavar context \
|
||||
(is this something we want to support?)"
|
||||
)
|
||||
}
|
||||
|
||||
// Reference to another metavariable,
|
||||
// e.g. using `<param-value>` in XML NIR.
|
||||
(TplMeta(oi_meta), AirBind(RefIdent(name))) => {
|
||||
let oi_ref = ctx.lookup_lexical_or_missing(name);
|
||||
|
||||
oi_meta
|
||||
.concat_ref(ctx.asg_mut(), oi_ref)
|
||||
.map(|_| ())
|
||||
.transition(TplMeta(oi_meta))
|
||||
}
|
||||
|
||||
(TplMeta(..), tok @ AirMeta(MetaStart(..))) => {
|
||||
diagnostic_todo!(
|
||||
vec![tok.note("this token")],
|
||||
|
@ -119,8 +148,10 @@ impl ParseState for AirMetaAggregate {
|
|||
)
|
||||
}
|
||||
|
||||
// Maybe the bind can be handled by the parent frame.
|
||||
(Ready, tok @ AirBind(..)) => Transition(Ready).dead(tok),
|
||||
// Maybe the token can be handled by the parent frame.
|
||||
(Ready, tok @ (AirBind(..) | AirDoc(..))) => {
|
||||
Transition(Ready).dead(tok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,3 +165,6 @@ impl AirMetaAggregate {
|
|||
Self::Ready
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
|
|
@ -0,0 +1,369 @@
|
|||
// Tests for 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/>.
|
||||
|
||||
/// Metavariable parsing tests.
|
||||
///
|
||||
/// Note that some metavariable-related tests are in
|
||||
/// [`super::super::tpl::test`],
|
||||
/// where the focus is on how they are integrated as template
|
||||
/// parameters.
|
||||
/// The tests here focus instead on the particulars of metavariables
|
||||
/// themselves,
|
||||
/// with templates being only incidental.
|
||||
use super::*;
|
||||
use crate::{
|
||||
asg::{
|
||||
air::{
|
||||
test::{parse_as_pkg_body, pkg_lookup},
|
||||
Air::{self, *},
|
||||
},
|
||||
graph::object::{meta::MetaRel, Meta, Tpl},
|
||||
},
|
||||
parse::{util::spair, Parser},
|
||||
span::{dummy::*, Span},
|
||||
sym::SymbolId,
|
||||
};
|
||||
|
||||
type Sut = AirAggregate;
|
||||
|
||||
// Metavariables can reference the lexical value of other metavariables.
|
||||
// This does not actually check that the target reference is a metavariable,
|
||||
// at least not at the time of writing.
|
||||
// TODO: But the system ought to,
|
||||
// since if we defer that to expansion-time,
|
||||
// then we don't have confidence that the template definition is actually
|
||||
// valid as a part of this compilation unit.
|
||||
#[test]
|
||||
fn metavar_ref_only() {
|
||||
let name_meta = "@foo@";
|
||||
let name_other = "@other@";
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_concat_list(
|
||||
[
|
||||
MetaStart(S1),
|
||||
BindIdent(spair(name_meta, S2)),
|
||||
|
||||
// Reference to another metavariable,
|
||||
// effectively creating an alias.
|
||||
RefIdent(spair(name_other, S3)), // --.
|
||||
MetaEnd(S4), // |
|
||||
// |
|
||||
MetaStart(S5), // |
|
||||
BindIdent(spair(name_other, S6)), // <-'
|
||||
MetaEnd(S7),
|
||||
],
|
||||
|
||||
name_meta,
|
||||
S1.merge(S4),
|
||||
|
||||
[
|
||||
&Meta::Required(S5.merge(S7).unwrap()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Similar to above,
|
||||
// but multiple references.
|
||||
#[test]
|
||||
fn metavar_ref_multiple() {
|
||||
let name_meta = "@foo@";
|
||||
let name_other_a = "@other-a@";
|
||||
let name_other_b = "@other-b@";
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_concat_list(
|
||||
// Def/ref is commutative,
|
||||
// so this defines with both orderings to demonstrate that.
|
||||
[
|
||||
MetaStart(S1),
|
||||
BindIdent(spair(name_other_a, S2)), // <-.
|
||||
MetaEnd(S3), // |
|
||||
// |
|
||||
MetaStart(S3), // |
|
||||
BindIdent(spair(name_meta, S4)), // |
|
||||
// |
|
||||
RefIdent(spair(name_other_a, S5)), // --'
|
||||
RefIdent(spair(name_other_b, S6)), // --.
|
||||
MetaEnd(S7), // |
|
||||
// |
|
||||
MetaStart(S8), // |
|
||||
BindIdent(spair(name_other_b, S9)), // <-'
|
||||
MetaEnd(S10),
|
||||
],
|
||||
|
||||
name_meta,
|
||||
S3.merge(S7),
|
||||
|
||||
[
|
||||
&Meta::Required(S1.merge(S3).unwrap()),
|
||||
&Meta::Required(S8.merge(S10).unwrap()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// If a metavariable already has a concrete lexical value,
|
||||
// then appending a reference to it should convert it into a list.
|
||||
#[test]
|
||||
fn metavar_ref_after_lexeme() {
|
||||
let name_meta = "@foo@";
|
||||
let value = "foo value";
|
||||
let name_other = "@other@";
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_concat_list(
|
||||
[
|
||||
MetaStart(S1),
|
||||
BindIdent(spair(name_meta, S2)),
|
||||
|
||||
// At this point,
|
||||
// we have only a concrete lexeme,
|
||||
// and so we are not a list.
|
||||
MetaLexeme(spair(value, S3)),
|
||||
|
||||
// Upon encountering this token,
|
||||
// we should convert into a list.
|
||||
RefIdent(spair(name_other, S4)), // --.
|
||||
MetaEnd(S5), // |
|
||||
// |
|
||||
MetaStart(S6), // |
|
||||
BindIdent(spair(name_other, S7)), // <-'
|
||||
MetaEnd(S8),
|
||||
],
|
||||
|
||||
name_meta,
|
||||
S1.merge(S5),
|
||||
|
||||
// Having been converted into a list,
|
||||
// we should have a reference to _two_ metavariables:
|
||||
// one holding the original lexeme and the reference.
|
||||
[
|
||||
&Meta::Lexeme(S3, spair(value, S3)),
|
||||
&Meta::Required(S6.merge(S8).unwrap()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// If we have a reference,
|
||||
// then that means we already have a `ConcatList`,
|
||||
// and therefore should just have to append to it.
|
||||
// This is the same as the above test,
|
||||
// with operations reversed.
|
||||
// It is not commutative,
|
||||
// though,
|
||||
// since concatenation is ordered.
|
||||
#[test]
|
||||
fn lexeme_after_metavar_ref() {
|
||||
let name_meta = "@foo@";
|
||||
let value = "foo value";
|
||||
let name_other = "@other@";
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_concat_list(
|
||||
[
|
||||
MetaStart(S1),
|
||||
BindIdent(spair(name_meta, S2)),
|
||||
|
||||
// We produce a list here...
|
||||
RefIdent(spair(name_other, S3)), // --.
|
||||
// |
|
||||
// ...and should just append a // |
|
||||
// `Lexeme` here. // |
|
||||
MetaLexeme(spair(value, S4)), // |
|
||||
MetaEnd(S5), // |
|
||||
// |
|
||||
MetaStart(S6), // |
|
||||
BindIdent(spair(name_other, S7)), // <-'
|
||||
MetaEnd(S8),
|
||||
],
|
||||
|
||||
name_meta,
|
||||
S1.merge(S5),
|
||||
|
||||
// Same as the previous test,
|
||||
// but concatenation order is reversed.
|
||||
[
|
||||
&Meta::Required(S6.merge(S8).unwrap()),
|
||||
|
||||
// Note the first span here is not that of the parent
|
||||
// metavariable,
|
||||
// since we do not want diagnostics to suggest that this
|
||||
// object represents the entirety of the parent.
|
||||
&Meta::Lexeme(S4, spair(value, S4)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Multiple lexemes should _also_ produce a list.
|
||||
// While this could have been replaced by a single lexeme,
|
||||
// there are still uses:
|
||||
// maybe this was the result of template expansion,
|
||||
// or simply a multi-line formatting choice in the provided sources.
|
||||
#[test]
|
||||
fn lexeme_after_lexeme() {
|
||||
let name_meta = "@foo@";
|
||||
let value_a = "foo value a";
|
||||
let value_b = "foo value b";
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_concat_list(
|
||||
[
|
||||
MetaStart(S1),
|
||||
BindIdent(spair(name_meta, S2)),
|
||||
|
||||
MetaLexeme(spair(value_a, S3)),
|
||||
MetaLexeme(spair(value_b, S4)),
|
||||
MetaEnd(S5),
|
||||
],
|
||||
|
||||
name_meta,
|
||||
S1.merge(S5),
|
||||
|
||||
[
|
||||
// Since these are Meta objects derived from the original,
|
||||
// our spans
|
||||
// (the first value in the tuple)
|
||||
// are not that of the containing metavariable;
|
||||
// we don't want diagnostics implying that this represents
|
||||
// the entirety of that the parent metavariable.
|
||||
&Meta::Lexeme(S3, spair(value_a, S3)),
|
||||
&Meta::Lexeme(S4, spair(value_b, S4)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/////// ///////
|
||||
/////// Tests above; plumbing begins below ///////
|
||||
/////// ///////
|
||||
|
||||
fn assert_concat_list<'a, IT, IE: 'a>(
|
||||
toks: IT,
|
||||
meta_name: impl Into<SymbolId>,
|
||||
expect_span: Option<Span>,
|
||||
expect: IE,
|
||||
) where
|
||||
IT: IntoIterator<Item = Air>,
|
||||
IT::IntoIter: Debug,
|
||||
IE: IntoIterator<Item = &'a Meta>,
|
||||
IE::IntoIter: Debug + DoubleEndedIterator,
|
||||
{
|
||||
let (ctx, oi_tpl) = air_ctx_from_tpl_body_toks(toks);
|
||||
let asg = ctx.asg_ref();
|
||||
|
||||
let oi_meta = ctx
|
||||
.env_scope_lookup_ident_dfn::<Meta>(
|
||||
oi_tpl,
|
||||
spair(meta_name.into(), DUMMY_SPAN),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Meta references are only supported through lexical concatenation.
|
||||
assert_eq!(
|
||||
&Meta::ConcatList(expect_span.expect("expected metavar span is None")),
|
||||
oi_meta.resolve(asg),
|
||||
"expected metavariable to be a ConcatList with the provided span",
|
||||
);
|
||||
|
||||
// We `collect()` rather than using `Iterator::eq` so that the failure
|
||||
// message includes the data.
|
||||
// If we do this too often,
|
||||
// we can consider a crate like `itertools` or write our own
|
||||
// comparison,
|
||||
// but it's not worth doing for these tests where the cost of
|
||||
// collection is so low and insignificant.
|
||||
assert_eq!(
|
||||
// TODO: We need to modify edge methods to return in the proper
|
||||
// order (not reversed) without a performance hit,
|
||||
// which will involve investigating Petgraph further.
|
||||
expect.into_iter().rev().collect::<Vec<_>>(),
|
||||
// Each reference is to an Ident whose definition is the other
|
||||
// metavariable.
|
||||
oi_meta
|
||||
.edges(asg)
|
||||
.filter_map(|rel| match rel {
|
||||
MetaRel::Meta(oi) => Some(oi),
|
||||
MetaRel::Ident(oi) => oi.definition::<Meta>(asg),
|
||||
MetaRel::Doc(_) => None,
|
||||
})
|
||||
.map(ObjectIndex::cresolve(asg))
|
||||
.collect::<Vec<_>>(),
|
||||
"note: expected tokens are in reverse order in this error message",
|
||||
);
|
||||
}
|
||||
|
||||
const STUB_TPL_NAME: &str = "__incidental-tpl__";
|
||||
|
||||
/// Parse the provided tokens within the context of a template body.
|
||||
///
|
||||
/// This allows tests to focus on testing of metavariables instead of
|
||||
/// mudding tests with setup.
|
||||
///
|
||||
/// This creates a package and template that are purely incidental and serve
|
||||
/// only as scaffolding to put the parser into the necessary state and
|
||||
/// context.
|
||||
/// This is an alternative to providing methods to force the parse into a
|
||||
/// certain context,
|
||||
/// since we're acting as users of the SUT's public API and are
|
||||
/// therefore testing real-world situations.
|
||||
fn parse_as_tpl_body<I: IntoIterator<Item = Air>>(
|
||||
toks: I,
|
||||
) -> Parser<Sut, impl Iterator<Item = Air> + Debug>
|
||||
where
|
||||
<I as IntoIterator>::IntoIter: Debug,
|
||||
{
|
||||
#[rustfmt::skip]
|
||||
let head = [
|
||||
TplStart(S1),
|
||||
BindIdent(spair(STUB_TPL_NAME, S1)),
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
let tail = [
|
||||
TplEnd(S1),
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
parse_as_pkg_body(
|
||||
head.into_iter()
|
||||
.chain(toks.into_iter())
|
||||
.chain(tail.into_iter())
|
||||
)
|
||||
}
|
||||
|
||||
fn air_ctx_from_tpl_body_toks<I: IntoIterator<Item = Air>>(
|
||||
toks: I,
|
||||
) -> (<Sut as ParseState>::Context, ObjectIndex<Tpl>)
|
||||
where
|
||||
I::IntoIter: Debug,
|
||||
{
|
||||
let mut sut = parse_as_tpl_body(toks);
|
||||
assert!(sut.all(|x| x.is_ok()));
|
||||
|
||||
let ctx = sut.finalize().unwrap().into_private_context();
|
||||
|
||||
let oi_tpl = pkg_lookup(&ctx, spair(STUB_TPL_NAME, S1))
|
||||
.expect(
|
||||
"could not locate stub template (did you call \
|
||||
air_ctx_from_tpl_body_toks without parse_as_tpl_body?)",
|
||||
)
|
||||
.definition(ctx.asg_ref())
|
||||
.expect("missing stub template definition (test setup bug?)");
|
||||
|
||||
(ctx, oi_tpl)
|
||||
}
|
|
@ -26,7 +26,6 @@
|
|||
|
||||
use super::{super::AsgError, ir::AirIdent, AirAggregate, AirAggregateCtx};
|
||||
use crate::parse::prelude::*;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AirOpaqueAggregate {
|
||||
|
@ -74,9 +73,11 @@ impl ParseState for AirOpaqueAggregate {
|
|||
(Ready, IdentDep(name, dep)) => {
|
||||
let oi_from = ctx.lookup_lexical_or_missing(name);
|
||||
let oi_to = ctx.lookup_lexical_or_missing(dep);
|
||||
oi_from.add_opaque_dep(ctx.asg_mut(), oi_to);
|
||||
|
||||
Transition(Ready).incomplete()
|
||||
oi_from
|
||||
.add_opaque_dep(ctx.asg_mut(), oi_to)
|
||||
.map(|_| ())
|
||||
.transition(Ready)
|
||||
}
|
||||
|
||||
(Ready, IdentFragment(name, text)) => ctx
|
||||
|
@ -85,11 +86,11 @@ impl ParseState for AirOpaqueAggregate {
|
|||
.map(|_| ())
|
||||
.transition(Ready),
|
||||
|
||||
(Ready, IdentRoot(name)) => {
|
||||
ctx.lookup_lexical_or_missing(name).root(ctx.asg_mut());
|
||||
|
||||
Transition(Ready).incomplete()
|
||||
}
|
||||
(Ready, IdentRoot(name)) => ctx
|
||||
.lookup_lexical_or_missing(name)
|
||||
.root(ctx.asg_mut())
|
||||
.map(|_| ())
|
||||
.transition(Ready),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ use super::{
|
|||
ir::AirLiteratePkg,
|
||||
AirAggregate, AirAggregateCtx,
|
||||
};
|
||||
use crate::{diagnose::Annotate, diagnostic_todo, parse::prelude::*};
|
||||
use crate::{diagnostic_todo, parse::prelude::*};
|
||||
|
||||
/// Package parsing with support for loaded identifiers.
|
||||
///
|
||||
|
@ -112,10 +112,10 @@ impl ParseState for AirPkgAggregate {
|
|||
)
|
||||
}
|
||||
|
||||
(Toplevel(oi_pkg), AirDoc(DocText(text))) => {
|
||||
oi_pkg.append_doc_text(ctx.asg_mut(), text);
|
||||
Transition(Toplevel(oi_pkg)).incomplete()
|
||||
}
|
||||
(Toplevel(oi_pkg), AirDoc(DocText(text))) => oi_pkg
|
||||
.append_doc_text(ctx.asg_mut(), text)
|
||||
.map(|_| ())
|
||||
.transition(Toplevel(oi_pkg)),
|
||||
|
||||
// Package import
|
||||
(Toplevel(oi_pkg), AirPkg(PkgImport(namespec))) => oi_pkg
|
||||
|
|
|
@ -26,11 +26,11 @@ use crate::{
|
|||
graph::object::{ObjectRel, ObjectRelFrom, ObjectRelatable},
|
||||
IdentKind, ObjectIndexRelTo, Source, TransitionError,
|
||||
},
|
||||
parse::{ParseError, Parsed, Parser},
|
||||
parse::{util::spair, ParseError, Parsed, Parser},
|
||||
span::dummy::*,
|
||||
};
|
||||
|
||||
type Sut = AirAggregate;
|
||||
pub type Sut = AirAggregate;
|
||||
|
||||
use Air::*;
|
||||
use Parsed::Incomplete;
|
||||
|
@ -39,7 +39,7 @@ mod scope;
|
|||
|
||||
#[test]
|
||||
fn ident_decl() {
|
||||
let id = SPair("foo".into(), S2);
|
||||
let id = spair("foo", S2);
|
||||
let kind = IdentKind::Tpl;
|
||||
let src = Source {
|
||||
src: Some("test/decl".into()),
|
||||
|
@ -47,8 +47,8 @@ fn ident_decl() {
|
|||
};
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
IdentDecl(id, kind.clone(), src.clone()),
|
||||
// Attempt re-declaration.
|
||||
IdentDecl(id, kind.clone(), src.clone()),
|
||||
|
@ -88,8 +88,8 @@ fn ident_decl() {
|
|||
|
||||
#[test]
|
||||
fn ident_extern_decl() {
|
||||
let id = SPair("foo".into(), S2);
|
||||
let re_id = SPair("foo".into(), S3);
|
||||
let id = spair("foo", S2);
|
||||
let re_id = spair("foo", S3);
|
||||
let kind = IdentKind::Tpl;
|
||||
let different_kind = IdentKind::Meta;
|
||||
let src = Source {
|
||||
|
@ -98,8 +98,8 @@ fn ident_extern_decl() {
|
|||
};
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
IdentExternDecl(id, kind.clone(), src.clone()),
|
||||
// Redeclare with a different kind
|
||||
IdentExternDecl(re_id, different_kind.clone(), src.clone()),
|
||||
|
@ -141,12 +141,12 @@ fn ident_extern_decl() {
|
|||
|
||||
#[test]
|
||||
fn ident_dep() {
|
||||
let id = SPair("foo".into(), S2);
|
||||
let dep = SPair("dep".into(), S3);
|
||||
let id = spair("foo", S2);
|
||||
let dep = spair("dep", S3);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
IdentDep(id, dep),
|
||||
PkgEnd(S4),
|
||||
].into_iter();
|
||||
|
@ -174,7 +174,7 @@ fn ident_dep() {
|
|||
|
||||
#[test]
|
||||
fn ident_fragment() {
|
||||
let id = SPair("frag".into(), S2);
|
||||
let id = spair("frag", S2);
|
||||
let kind = IdentKind::Tpl;
|
||||
let src = Source {
|
||||
src: Some("test/frag".into()),
|
||||
|
@ -183,8 +183,8 @@ fn ident_fragment() {
|
|||
let frag = "fragment text".into();
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
// Identifier must be declared before it can be given a
|
||||
// fragment.
|
||||
IdentDecl(id, kind.clone(), src.clone()),
|
||||
|
@ -232,11 +232,11 @@ fn ident_fragment() {
|
|||
// `Ident::Missing`.
|
||||
#[test]
|
||||
fn ident_root_missing() {
|
||||
let id = SPair("toroot".into(), S2);
|
||||
let id = spair("toroot", S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
IdentRoot(id),
|
||||
PkgEnd(S3),
|
||||
].into_iter();
|
||||
|
@ -269,7 +269,7 @@ fn ident_root_missing() {
|
|||
|
||||
#[test]
|
||||
fn ident_root_existing() {
|
||||
let id = SPair("toroot".into(), S2);
|
||||
let id = spair("toroot", S2);
|
||||
let kind = IdentKind::Tpl;
|
||||
let src = Source {
|
||||
src: Some("test/root-existing".into()),
|
||||
|
@ -281,10 +281,10 @@ fn ident_root_existing() {
|
|||
assert!(!kind.is_auto_root());
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
IdentDecl(id, kind.clone(), src.clone()),
|
||||
IdentRoot(SPair(id.symbol(), S3)),
|
||||
IdentRoot(spair(id, S3)),
|
||||
PkgEnd(S3),
|
||||
]
|
||||
.into_iter();
|
||||
|
@ -329,8 +329,8 @@ fn declare_kind_auto_root() {
|
|||
assert!(auto_kind.is_auto_root());
|
||||
assert!(!no_auto_kind.is_auto_root());
|
||||
|
||||
let id_auto = SPair("auto_root".into(), S2);
|
||||
let id_no_auto = SPair("no_auto_root".into(), S3);
|
||||
let id_auto = spair("auto_root", S2);
|
||||
let id_no_auto = spair("no_auto_root", S3);
|
||||
|
||||
let src = Source {
|
||||
src: Some("src/pkg".into()),
|
||||
|
@ -339,7 +339,7 @@ fn declare_kind_auto_root() {
|
|||
|
||||
#[rustfmt::skip]
|
||||
let toks = [
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
// auto-rooting
|
||||
IdentDecl(id_auto, auto_kind, src.clone()),
|
||||
// non-auto-rooting
|
||||
|
@ -372,8 +372,8 @@ fn declare_kind_auto_root() {
|
|||
#[test]
|
||||
fn pkg_is_rooted() {
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
let toks = [
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
PkgEnd(S2),
|
||||
];
|
||||
|
||||
|
@ -394,10 +394,10 @@ fn pkg_is_rooted() {
|
|||
|
||||
#[test]
|
||||
fn close_pkg_without_open() {
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
PkgEnd(S1),
|
||||
// RECOVERY: Try again.
|
||||
PkgStart(S2, SPair("/pkg".into(), S2)),
|
||||
PkgStart(S2, spair("/pkg", S2)),
|
||||
PkgEnd(S3),
|
||||
];
|
||||
|
||||
|
@ -414,11 +414,11 @@ fn close_pkg_without_open() {
|
|||
|
||||
#[test]
|
||||
fn nested_open_pkg() {
|
||||
let name_a = SPair("/pkg-a".into(), S2);
|
||||
let name_b = SPair("/pkg-b".into(), S4);
|
||||
let name_a = spair("/pkg-a", S2);
|
||||
let name_b = spair("/pkg-b", S4);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
PkgStart(S1, name_a),
|
||||
// Cannot nest package
|
||||
PkgStart(S3, name_b),
|
||||
|
@ -442,10 +442,10 @@ fn nested_open_pkg() {
|
|||
|
||||
#[test]
|
||||
fn pkg_canonical_name() {
|
||||
let name = SPair("/foo/bar".into(), S2);
|
||||
let name = spair("/foo/bar", S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
PkgStart(S1, name),
|
||||
PkgEnd(S3),
|
||||
];
|
||||
|
@ -477,12 +477,12 @@ fn pkg_canonical_name() {
|
|||
// filenames.
|
||||
#[test]
|
||||
fn pkg_cannot_redeclare() {
|
||||
let name = SPair("/foo/bar".into(), S2);
|
||||
let name2 = SPair("/foo/bar".into(), S5);
|
||||
let namefix = SPair("/foo/fix".into(), S7);
|
||||
let name = spair("/foo/bar", S2);
|
||||
let name2 = spair("/foo/bar", S5);
|
||||
let namefix = spair("/foo/fix", S7);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
PkgStart(S1, name),
|
||||
PkgEnd(S3),
|
||||
|
||||
|
@ -527,11 +527,11 @@ fn pkg_cannot_redeclare() {
|
|||
|
||||
#[test]
|
||||
fn pkg_import_canonicalized_against_current_pkg() {
|
||||
let pkg_name = SPair("/foo/bar".into(), S2);
|
||||
let pkg_rel = SPair("baz/quux".into(), S3);
|
||||
let pkg_name = spair("/foo/bar", S2);
|
||||
let pkg_rel = spair("baz/quux", S3);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
PkgStart(S1, pkg_name),
|
||||
PkgImport(pkg_rel),
|
||||
PkgEnd(S3),
|
||||
|
@ -553,18 +553,18 @@ fn pkg_import_canonicalized_against_current_pkg() {
|
|||
.resolve(&asg);
|
||||
|
||||
// TODO
|
||||
assert_eq!(SPair("/foo/baz/quux".into(), S3), import.canonical_name());
|
||||
assert_eq!(spair("/foo/baz/quux", S3), import.canonical_name());
|
||||
}
|
||||
|
||||
// Documentation can be mixed in with objects in a literate style.
|
||||
#[test]
|
||||
fn pkg_doc() {
|
||||
let doc_a = SPair("first".into(), S2);
|
||||
let id_import = SPair("import".into(), S3);
|
||||
let doc_b = SPair("first".into(), S4);
|
||||
let doc_a = spair("first", S2);
|
||||
let id_import = spair("import", S3);
|
||||
let doc_b = spair("first", S4);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
DocText(doc_a),
|
||||
|
||||
// Some object to place in-between the two
|
||||
|
@ -597,19 +597,19 @@ fn pkg_doc() {
|
|||
// index.
|
||||
#[test]
|
||||
fn resume_previous_parsing_context() {
|
||||
let name_foo = SPair("foo".into(), S2);
|
||||
let name_bar = SPair("bar".into(), S5);
|
||||
let name_baz = SPair("baz".into(), S6);
|
||||
let name_foo = spair("foo", S2);
|
||||
let name_bar = spair("bar", S5);
|
||||
let name_baz = spair("baz", S6);
|
||||
let kind = IdentKind::Tpl;
|
||||
let src = Source::default();
|
||||
|
||||
// We're going to test with opaque objects as if we are the linker.
|
||||
// This is the first parse.
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
// The first package will reference an identifier from another
|
||||
// package.
|
||||
PkgStart(S1, SPair("/pkg-a".into(), S1)),
|
||||
PkgStart(S1, spair("/pkg-a", S1)),
|
||||
IdentDep(name_foo, name_bar),
|
||||
PkgEnd(S3),
|
||||
];
|
||||
|
@ -620,11 +620,11 @@ fn resume_previous_parsing_context() {
|
|||
// This is the token stream for the second parser,
|
||||
// which will re-use the above context.
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
let toks = [
|
||||
// This package will define that identifier,
|
||||
// which should also find the identifier having been placed into
|
||||
// the global environment.
|
||||
PkgStart(S4, SPair("/pkg-b".into(), S4)),
|
||||
PkgStart(S4, spair("/pkg-b", S4)),
|
||||
IdentDecl(name_bar, kind.clone(), src.clone()),
|
||||
|
||||
// This is a third identifier that is unique to this package.
|
||||
|
@ -686,16 +686,23 @@ fn resume_previous_parsing_context() {
|
|||
pub fn parse_as_pkg_body<I: IntoIterator<Item = Air>>(
|
||||
toks: I,
|
||||
) -> Parser<Sut, impl Iterator<Item = Air> + Debug>
|
||||
where
|
||||
<I as IntoIterator>::IntoIter: Debug,
|
||||
{
|
||||
Sut::parse(as_pkg_body(toks))
|
||||
}
|
||||
|
||||
pub fn as_pkg_body<I: IntoIterator<Item = Air>>(
|
||||
toks: I,
|
||||
) -> impl Iterator<Item = Air> + Debug
|
||||
where
|
||||
<I as IntoIterator>::IntoIter: Debug,
|
||||
{
|
||||
use std::iter;
|
||||
|
||||
Sut::parse(
|
||||
iter::once(PkgStart(S1, SPair("/pkg".into(), S1)))
|
||||
.chain(toks.into_iter())
|
||||
.chain(iter::once(PkgEnd(S1))),
|
||||
)
|
||||
iter::once(PkgStart(S1, spair("/incidental-pkg", S1)))
|
||||
.chain(toks.into_iter())
|
||||
.chain(iter::once(PkgEnd(S1)))
|
||||
}
|
||||
|
||||
pub(super) fn asg_from_pkg_body_toks<I: IntoIterator<Item = Air>>(
|
||||
|
|
|
@ -51,6 +51,7 @@ use crate::{
|
|||
visit::{tree_reconstruction, TreeWalkRel},
|
||||
ExprOp,
|
||||
},
|
||||
parse::util::spair,
|
||||
span::UNKNOWN_SPAN,
|
||||
};
|
||||
use std::iter::once;
|
||||
|
@ -120,9 +121,9 @@ macro_rules! test_scopes {
|
|||
|
||||
test_scopes! {
|
||||
setup {
|
||||
let pkg_name = SPair("/pkg".into(), S1);
|
||||
let outer = SPair("outer".into(), S3);
|
||||
let inner = SPair("inner".into(), S5);
|
||||
let pkg_name = spair("/pkg", S1);
|
||||
let outer = spair("outer", S3);
|
||||
let inner = spair("inner", S5);
|
||||
}
|
||||
|
||||
air {
|
||||
|
@ -166,15 +167,15 @@ test_scopes! {
|
|||
|
||||
test_scopes! {
|
||||
setup {
|
||||
let pkg_name = SPair("/pkg".into(), S1);
|
||||
let pkg_name = spair("/pkg", S1);
|
||||
|
||||
let tpl_outer = SPair("_tpl-outer_".into(), S3);
|
||||
let meta_outer = SPair("@param_outer@".into(), S5);
|
||||
let expr_outer = SPair("exprOuter".into(), S8);
|
||||
let tpl_outer = spair("_tpl-outer_", S3);
|
||||
let meta_outer = spair("@param_outer@", S5);
|
||||
let expr_outer = spair("exprOuter", S8);
|
||||
|
||||
let tpl_inner = SPair("_tpl-inner_".into(), S11);
|
||||
let meta_inner = SPair("@param_inner@".into(), S13);
|
||||
let expr_inner = SPair("exprInner".into(), S16);
|
||||
let tpl_inner = spair("_tpl-inner_", S11);
|
||||
let meta_inner = spair("@param_inner@", S13);
|
||||
let expr_inner = spair("exprInner", S16);
|
||||
}
|
||||
|
||||
air {
|
||||
|
@ -329,18 +330,18 @@ test_scopes! {
|
|||
|
||||
test_scopes! {
|
||||
setup {
|
||||
let pkg_name = SPair("/pkg".into(), S1);
|
||||
let pkg_name = spair("/pkg", S1);
|
||||
|
||||
let tpl_outer = SPair("_tpl-outer_".into(), S3);
|
||||
let tpl_inner = SPair("_tpl-inner_".into(), S9);
|
||||
let tpl_outer = spair("_tpl-outer_", S3);
|
||||
let tpl_inner = spair("_tpl-inner_", 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);
|
||||
let meta_name = "@param@";
|
||||
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);
|
||||
let meta_same = spair(meta_name, S11);
|
||||
}
|
||||
|
||||
air {
|
||||
|
@ -433,11 +434,11 @@ test_scopes! {
|
|||
// From the perspective of the linker (tameld):
|
||||
test_scopes! {
|
||||
setup {
|
||||
let pkg_a = SPair("/pkg/a".into(), S1);
|
||||
let opaque_a = SPair("opaque_a".into(), S2);
|
||||
let pkg_a = spair("/pkg/a", S1);
|
||||
let opaque_a = spair("opaque_a", S2);
|
||||
|
||||
let pkg_b = SPair("/pkg/b".into(), S4);
|
||||
let opaque_b = SPair("opaque_b".into(), S5);
|
||||
let pkg_b = spair("/pkg/b", S4);
|
||||
let opaque_b = spair("opaque_b", S5);
|
||||
}
|
||||
|
||||
air {
|
||||
|
|
|
@ -26,11 +26,7 @@ use super::{
|
|||
ir::AirBindableTpl,
|
||||
AirAggregate, AirAggregateCtx,
|
||||
};
|
||||
use crate::{
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
parse::prelude::*,
|
||||
span::Span,
|
||||
};
|
||||
use crate::{fmt::TtQuote, parse::prelude::*, span::Span};
|
||||
|
||||
/// Template parser and token aggregator.
|
||||
///
|
||||
|
@ -179,31 +175,47 @@ impl ParseState for AirTplAggregate {
|
|||
}
|
||||
|
||||
(Toplevel(tpl), AirBind(BindIdent(id))) => ctx
|
||||
.defines(id)
|
||||
.defines_concrete(id)
|
||||
.and_then(|oi_ident| {
|
||||
oi_ident.bind_definition(ctx.asg_mut(), id, tpl.oi())
|
||||
})
|
||||
.map(|_| ())
|
||||
.transition(Toplevel(tpl.identify(id))),
|
||||
|
||||
(Toplevel(tpl), AirBind(BindIdentAbstract(meta_name))) => {
|
||||
diagnostic_todo!(
|
||||
vec![
|
||||
tpl.oi().note("for this template"),
|
||||
meta_name.note(
|
||||
"attempting to bind an abstract identifier with \
|
||||
this metavariable"
|
||||
),
|
||||
],
|
||||
"attempt to bind abstract identifier to template",
|
||||
)
|
||||
}
|
||||
|
||||
(Toplevel(tpl), AirBind(RefIdent(name))) => {
|
||||
let tpl_oi = tpl.oi();
|
||||
let ref_oi = ctx.lookup_lexical_or_missing(name);
|
||||
|
||||
tpl_oi.apply_named_tpl(ctx.asg_mut(), ref_oi, name.span());
|
||||
|
||||
Transition(Toplevel(tpl)).incomplete()
|
||||
tpl_oi
|
||||
.apply_named_tpl(ctx.asg_mut(), ref_oi, name.span())
|
||||
.map(|_| ())
|
||||
.transition(Toplevel(tpl))
|
||||
}
|
||||
|
||||
(Toplevel(tpl), AirDoc(DocIndepClause(clause))) => {
|
||||
tpl.oi().desc_short(ctx.asg_mut(), clause);
|
||||
Transition(Toplevel(tpl)).incomplete()
|
||||
}
|
||||
(Toplevel(tpl), AirDoc(DocIndepClause(clause))) => tpl
|
||||
.oi()
|
||||
.add_desc_short(ctx.asg_mut(), clause)
|
||||
.map(|_| ())
|
||||
.transition(Toplevel(tpl)),
|
||||
|
||||
(Toplevel(tpl), AirDoc(DocText(text))) => {
|
||||
tpl.oi().append_doc_text(ctx.asg_mut(), text);
|
||||
Transition(Toplevel(tpl)).incomplete()
|
||||
}
|
||||
(Toplevel(tpl), AirDoc(DocText(text))) => tpl
|
||||
.oi()
|
||||
.append_doc_text(ctx.asg_mut(), text)
|
||||
.map(|_| ())
|
||||
.transition(Toplevel(tpl)),
|
||||
|
||||
(Toplevel(tpl), AirTpl(TplEnd(span))) => {
|
||||
tpl.close(ctx.asg_mut(), span).transition(Done)
|
||||
|
@ -215,12 +227,12 @@ impl ParseState for AirTplAggregate {
|
|||
// we are effectively discarding the ref and translating
|
||||
// into a `TplEnd`.
|
||||
match ctx.expansion_oi() {
|
||||
Some(oi_target) => {
|
||||
tpl.oi().expand_into(ctx.asg_mut(), oi_target);
|
||||
|
||||
Transition(Toplevel(tpl.anonymous_reachable()))
|
||||
.incomplete()
|
||||
}
|
||||
Some(oi_target) => tpl
|
||||
.oi()
|
||||
.close(ctx.asg_mut(), span)
|
||||
.expand_into(ctx.asg_mut(), oi_target)
|
||||
.map(|_| ())
|
||||
.transition(Toplevel(tpl.anonymous_reachable())),
|
||||
None => Transition(Toplevel(tpl))
|
||||
.err(AsgError::InvalidExpansionContext(span)),
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -117,13 +117,20 @@ pub enum AsgError {
|
|||
/// delimiter.
|
||||
UnbalancedTpl(Span),
|
||||
|
||||
/// Attempted to bind an identifier to an object while not in a context
|
||||
/// that can receive an identifier binding.
|
||||
/// Attempted to bind a concrete identifier to an object while not in a
|
||||
/// context that can receive an identifier binding.
|
||||
///
|
||||
/// Note that the user may encounter an error from a higher-level IR
|
||||
/// instead of this one.
|
||||
InvalidBindContext(SPair),
|
||||
|
||||
/// Attempted to bind an abstract identifier in a context where
|
||||
/// expansion will not take place.
|
||||
///
|
||||
/// This is intended to preempt future errors when we know that the
|
||||
/// context does not make sense for an abstract binding.
|
||||
InvalidAbstractBindContext(SPair, Option<Span>),
|
||||
|
||||
/// Attempted to reference an identifier while not in a context that can
|
||||
/// receive an identifier reference.
|
||||
///
|
||||
|
@ -160,8 +167,26 @@ pub enum AsgError {
|
|||
/// The provided [`Span`] indicates the location of the start of the
|
||||
/// metavariable definition.
|
||||
UnexpectedMeta(Span),
|
||||
|
||||
/// A template already has a shape of
|
||||
/// [`TplShape::Expr`](super::graph::object::tpl::TplShape::Expr),
|
||||
/// but another expression was found that would violate this shape
|
||||
/// constraint.
|
||||
///
|
||||
/// The template name is provided if it is known at the time of the
|
||||
/// error.
|
||||
TplShapeExprMulti(Option<SPair>, ErrorOccurrenceSpan, FirstOccurrenceSpan),
|
||||
}
|
||||
|
||||
/// A [`Span`] representing the subject of this error.
|
||||
type ErrorOccurrenceSpan = Span;
|
||||
|
||||
/// A [`Span`] representing the first occurrence of an object related to the
|
||||
/// subject of this error.
|
||||
///
|
||||
/// This should be paired with [`ErrorOccurrenceSpan`].
|
||||
type FirstOccurrenceSpan = Span;
|
||||
|
||||
impl Display for AsgError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use AsgError::*;
|
||||
|
@ -202,8 +227,20 @@ impl Display for AsgError {
|
|||
),
|
||||
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
|
||||
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
|
||||
InvalidBindContext(_) => {
|
||||
write!(f, "invalid identifier binding context")
|
||||
InvalidBindContext(name) => {
|
||||
write!(
|
||||
f,
|
||||
"invalid identifier binding context for {}",
|
||||
TtQuote::wrap(name),
|
||||
)
|
||||
}
|
||||
InvalidAbstractBindContext(name, _) => {
|
||||
write!(
|
||||
f,
|
||||
"invalid abstract identifier binding context for \
|
||||
metavariable {}",
|
||||
TtQuote::wrap(name),
|
||||
)
|
||||
}
|
||||
InvalidRefContext(ident) => {
|
||||
write!(
|
||||
|
@ -231,6 +268,18 @@ impl Display for AsgError {
|
|||
UnexpectedMeta(_) => {
|
||||
write!(f, "unexpected metavariable definition")
|
||||
}
|
||||
|
||||
TplShapeExprMulti(Some(name), _, _) => write!(
|
||||
f,
|
||||
"definition of template {} would produce multiple inline \
|
||||
expressions when expanded",
|
||||
TtQuote::wrap(name),
|
||||
),
|
||||
TplShapeExprMulti(None, _, _) => write!(
|
||||
f,
|
||||
"template definition would produce multiple inline \
|
||||
expressions when expanded"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -351,6 +400,25 @@ impl Diagnostic for AsgError {
|
|||
InvalidBindContext(name) => vec![name
|
||||
.error("an identifier binding is not valid in this context")],
|
||||
|
||||
InvalidAbstractBindContext(name, parent_span) => parent_span
|
||||
.map(|span| {
|
||||
span.note(
|
||||
"this definition context does not support metavariable \
|
||||
expansion"
|
||||
)
|
||||
})
|
||||
.into_iter()
|
||||
.chain(vec![
|
||||
name.error("this metavariable will never be expanded"),
|
||||
name.help(format!(
|
||||
"this identifier must have its name derived from \
|
||||
the metavariable {},
|
||||
but that metavariable will never be expanded here",
|
||||
TtQuote::wrap(name),
|
||||
)),
|
||||
])
|
||||
.collect(),
|
||||
|
||||
InvalidRefContext(ident) => vec![ident.error(
|
||||
"cannot reference the value of an expression from outside \
|
||||
of an expression context",
|
||||
|
@ -407,6 +475,42 @@ impl Diagnostic for AsgError {
|
|||
span.help("metavariables are expected to occur in a template context"),
|
||||
]
|
||||
}
|
||||
|
||||
// TODO: Perhaps we should have a documentation page that can be
|
||||
// referenced with examples and further rationale,
|
||||
// like Rust does.
|
||||
TplShapeExprMulti(oname, err, first) => oname
|
||||
.map(|name| name.note("for this template"))
|
||||
.into_iter()
|
||||
.chain(vec![
|
||||
first.note(
|
||||
"this is the first expression that would be inlined \
|
||||
at an expansion site",
|
||||
),
|
||||
err.error(
|
||||
"template cannot inline more than one expression \
|
||||
into an expansion site",
|
||||
),
|
||||
err.help(
|
||||
"this restriction is intended to ensure that \
|
||||
templates expand in ways that are consistent \
|
||||
given any expansion context; consider either:",
|
||||
),
|
||||
err.help(
|
||||
" - wrapping both expressions in a parent \
|
||||
expression; or",
|
||||
),
|
||||
err.help(
|
||||
" - giving at least one expression an id to prevent \
|
||||
inlining",
|
||||
),
|
||||
// in case they were wondering
|
||||
err.help(
|
||||
"this restriction did not exist in versions of \
|
||||
TAME prior to TAMER",
|
||||
),
|
||||
])
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ use self::object::{
|
|||
use super::{AsgError, Object, ObjectIndex, ObjectKind};
|
||||
use crate::{
|
||||
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
||||
f::Functor,
|
||||
f::Map,
|
||||
global,
|
||||
span::Span,
|
||||
};
|
||||
|
@ -40,6 +40,9 @@ use petgraph::{
|
|||
};
|
||||
use std::{fmt::Debug, result::Result};
|
||||
|
||||
#[cfg(doc)]
|
||||
use object::{ObjectIndexTo, Tpl};
|
||||
|
||||
pub mod object;
|
||||
pub mod visit;
|
||||
pub mod xmli;
|
||||
|
@ -197,17 +200,24 @@ impl Asg {
|
|||
///
|
||||
/// For more information on how the ASG's ontology is enforced statically,
|
||||
/// see [`ObjectRelTo`](object::ObjectRelTo).
|
||||
fn add_edge<OB: ObjectKind + ObjectRelatable>(
|
||||
///
|
||||
/// Callers external to this module should use [`ObjectIndex`] APIs to
|
||||
/// manipulate the graph;
|
||||
/// this allows those objects to uphold their own invariants
|
||||
/// relative to the state of the graph.
|
||||
fn add_edge<OA: ObjectIndexRelTo<OB>, OB: ObjectKind + ObjectRelatable>(
|
||||
&mut self,
|
||||
from_oi: impl ObjectIndexRelTo<OB>,
|
||||
from_oi: OA,
|
||||
to_oi: ObjectIndex<OB>,
|
||||
ctx_span: Option<Span>,
|
||||
) {
|
||||
self.graph.add_edge(
|
||||
from_oi.widen().into(),
|
||||
to_oi.into(),
|
||||
(from_oi.src_rel_ty(), OB::rel_ty(), ctx_span),
|
||||
);
|
||||
) -> Result<(), AsgError> {
|
||||
from_oi.pre_add_edge(self, to_oi, ctx_span).map(|()| {
|
||||
self.graph.add_edge(
|
||||
from_oi.widen().into(),
|
||||
to_oi.into(),
|
||||
(from_oi.src_rel_ty(), OB::rel_ty(), ctx_span),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve an object from the graph by [`ObjectIndex`].
|
||||
|
@ -386,5 +396,153 @@ fn diagnostic_node_missing_desc<O: ObjectKind>(
|
|||
]
|
||||
}
|
||||
|
||||
/// Mutation of an [`Object`]'s relationships (edges) on the [`Asg`].
|
||||
///
|
||||
/// This trait is intended to delegate certain responsibilities to
|
||||
/// [`ObjectKind`]s so that they may enforce their own invariants with
|
||||
/// respect to their relationships to other objects on the graph.
|
||||
///
|
||||
/// TODO: It'd be nice if only [`Asg`] were able to invoke methods on this
|
||||
/// trait,
|
||||
/// but the current module structure together with Rust's visibility
|
||||
/// with sibling modules doesn't seem to make that possible.
|
||||
///
|
||||
/// How Does This Work With Trait Specialization?
|
||||
/// =============================================
|
||||
/// [`Asg::add_edge`] is provided a [`ObjectIndexRelTo`],
|
||||
/// which needs narrowing to an appropriate source [`ObjectKind`] so that
|
||||
/// we can invoke [`<O as AsgRelMut>::pre_add_edge`](AsgRelMut::pre_add_edge).
|
||||
///
|
||||
/// At the time of writing,
|
||||
/// there are two implementors of [`ObjectIndexRelTo`]:
|
||||
///
|
||||
/// - [`ObjectIndex<O>`],
|
||||
/// for which we will know `O: ObjectKind`.
|
||||
/// - [`ObjectIndexTo<OB>`],
|
||||
/// for which we only know the _target_ `OB: ObjectKind`.
|
||||
///
|
||||
/// The entire purpose of [`ObjectIndexTo`] is to allow for a dynamic
|
||||
/// source [`ObjectKind`];
|
||||
/// we do not know what it is statically.
|
||||
/// So [`ObjectIndexTo::pre_add_edge`] is a method that will dynamically
|
||||
/// branch to an appropriate static path to invoke the correct
|
||||
/// [`AsgRelMut::pre_add_edge`].
|
||||
///
|
||||
/// And there's the problem.
|
||||
///
|
||||
/// We match on each [`ObjectRelTy`] based on
|
||||
/// [`ObjectIndexTo::src_rel_ty`],
|
||||
/// and invoke the appropriate [`AsgRelMut::pre_add_edge`].
|
||||
/// But the trait bound on `OB` for the `ObjectIndexRelTo` `impl` is
|
||||
/// [`ObjectRelatable`].
|
||||
/// So it resolves as `AsgRelMut<OB: ObjectRelatable>`.
|
||||
///
|
||||
/// But we don't have that implementation.
|
||||
/// We have implementations for _individual target [`ObjectRelatable`]s,
|
||||
/// e.g. `impl AsgRelMut<Expr> for Tpl`.
|
||||
/// So Rust rightfully complains that `AsgRelMut<OB: ObjectRelatable>`
|
||||
/// is not implemented for [`Tpl`].
|
||||
/// (Go ahead and remove the generic `impl` block containing `default fn`
|
||||
/// and see what happens.)
|
||||
///
|
||||
/// Of course,
|
||||
/// _we_ know that there's a trait implemented for every possible
|
||||
/// [`ObjectRelFrom<Tpl>`],
|
||||
/// because `object_rel!` does that for us based on the same
|
||||
/// definition that generates those other types.
|
||||
/// But Rust does not perform that type of analysis---
|
||||
/// it does not know that we've accounted for every type.
|
||||
/// So the `default fn`` uses the unstable `min_specialization` feature to
|
||||
/// satisfy those more generic trait bounds,
|
||||
/// making the compiler happy.
|
||||
///
|
||||
/// But if Rust is seeing `OB: ObjectRelatable`,
|
||||
/// why is it not monomorphizing to _this_ one rather than the more
|
||||
/// specialized implementation?
|
||||
///
|
||||
/// That type error described above is contemplating bounds for _any
|
||||
/// potential caller_.
|
||||
/// But when we're about to add an edge,
|
||||
/// we're invoking with a specific type of `OB`.
|
||||
/// Monomorphization takes place at that point,
|
||||
/// with the expected type,
|
||||
/// and uses the appropriate specialization.
|
||||
///
|
||||
/// Because of other trait bounds leading up to this point,
|
||||
/// including those on [`Asg::add_edge`] and [`ObjectIndexRelTo`],
|
||||
/// this cannot be invoked for any `to_oi` that is not a valid target
|
||||
/// for `Self`.
|
||||
/// But we cannot be too strict on that bound _here_,
|
||||
/// because otherwise it's not general enough for
|
||||
/// [`ObjectIndexTo::pre_add_edge`].
|
||||
/// We could do more runtime verification and further refine types,
|
||||
/// but that is a lot of work for no additional practical benefit,
|
||||
/// at least at this time.
|
||||
pub trait AsgRelMut<OB: ObjectRelatable>: ObjectRelatable {
|
||||
/// Allow an object to handle or reject the creation of an edge from it
|
||||
/// to another object.
|
||||
///
|
||||
/// Objects own both their node on the graph and the edges _from_ that
|
||||
/// node to another object.
|
||||
/// Phrased another way:
|
||||
/// they own their data and their relationships.
|
||||
///
|
||||
/// This gives an opportunity for the [`ObjectKind`] associated with the
|
||||
/// source object to evaluate the proposed relationship.
|
||||
/// This guarantee allows objects to cache information about these
|
||||
/// relationships and enforce any associated invariants without
|
||||
/// worrying about how the object may change out from underneath
|
||||
/// them.
|
||||
/// In some cases,
|
||||
/// this is the only way that an object will know whether an edge has
|
||||
/// been added,
|
||||
/// since the [`ObjectIndex`] APIs may not be utilized
|
||||
/// (e.g. in the case of [`ObjectIndexRelTo`].
|
||||
///
|
||||
/// This is invoked by [`Asg::add_edge`].
|
||||
/// The provided `commit` callback will complete the addition of the
|
||||
/// edge if provided [`Ok`],
|
||||
/// and the commit cannot fail.
|
||||
/// If [`Err`] is provided to `commit`,
|
||||
/// then [`Asg::add_edge`] will fail with that error.
|
||||
///
|
||||
/// Unlike the type of [`Asg::add_edge`],
|
||||
/// the source [`ObjectIndex`] has been narrowed to the appropriate
|
||||
/// type for you.
|
||||
fn pre_add_edge(
|
||||
asg: &mut Asg,
|
||||
rel: ProposedRel<Self, OB>,
|
||||
) -> Result<(), AsgError>;
|
||||
}
|
||||
|
||||
impl<OA: ObjectRelatable, OB: ObjectRelatable> AsgRelMut<OB> for OA {
|
||||
/// Default edge creation method for all [`ObjectKind`]s.
|
||||
///
|
||||
/// This takes the place of a default implementation on the trait itself
|
||||
/// above.
|
||||
/// It will be invoked any time there is not a more specialized
|
||||
/// implementation.
|
||||
/// Note that `object_rel!` doesn't provide method
|
||||
/// definitions unless explicitly specified by the user,
|
||||
/// so this is effective the method called for all edges _unless_
|
||||
/// overridden for a particular edge for a particular object
|
||||
/// (see [`object::tpl`] as an example).
|
||||
default fn pre_add_edge(
|
||||
_asg: &mut Asg,
|
||||
_rel: ProposedRel<Self, OB>,
|
||||
) -> Result<(), AsgError> {
|
||||
let _ = _rel.ctx_span; // TODO: remove when used (dead_code)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The relationship proposed by [`Asg::add_edge`],
|
||||
/// requiring approval from [`AsgRelMut::pre_add_edge`].
|
||||
pub struct ProposedRel<OA: ObjectKind, OB: ObjectKind> {
|
||||
from_oi: ObjectIndex<OA>,
|
||||
to_oi: ObjectIndex<OB>,
|
||||
ctx_span: Option<Span>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
|
|
@ -112,12 +112,18 @@
|
|||
//! Since [`ObjectRel`] narrows into an [`ObjectIndex`],
|
||||
//! the system will produce runtime panics if there is ever any attempt to
|
||||
//! follow an edge to an unexpected [`ObjectKind`].
|
||||
//!
|
||||
//! In addition to these static guarantees,
|
||||
//! [`AsgRelMut`](super::AsgRelMut) is utilized by [`Asg`] to consult an
|
||||
//! object before an edge is added _from_ it,
|
||||
//! allowing objects to assert ownership over their edges and cache
|
||||
//! information about them as the graph is being built.
|
||||
|
||||
use super::Asg;
|
||||
use super::{Asg, AsgError};
|
||||
use crate::{
|
||||
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
||||
diagnostic_panic,
|
||||
f::Functor,
|
||||
f::{Map, TryMap},
|
||||
parse::util::SPair,
|
||||
span::{Span, UNKNOWN_SPAN},
|
||||
};
|
||||
|
@ -156,7 +162,7 @@ pub use tpl::Tpl;
|
|||
/// Often-needed exports for [`ObjectKind`]s.
|
||||
pub mod prelude {
|
||||
pub use super::{
|
||||
super::{super::error::AsgError, Asg},
|
||||
super::{super::error::AsgError, Asg, AsgRelMut},
|
||||
Object, ObjectIndex, ObjectIndexRelTo, ObjectKind, ObjectRel,
|
||||
ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable,
|
||||
ObjectTreeRelTo,
|
||||
|
@ -243,6 +249,20 @@ macro_rules! object_gen {
|
|||
}
|
||||
}
|
||||
|
||||
/// Narrowed [`ObjectIndex`] types for each [`ObjectKind`].
|
||||
///
|
||||
/// This allows for converting a dynamic
|
||||
/// [`ObjectIndex<Object>`](ObjectIndex) into a statically known
|
||||
/// [`ObjectKind`],
|
||||
/// while also providing the ability to exhaustively match
|
||||
/// against all such possibilities.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ObjectIndexRefined {
|
||||
$(
|
||||
$kind(ObjectIndex<$kind>),
|
||||
)+
|
||||
}
|
||||
|
||||
/// The collection of potential objects of [`Object`].
|
||||
pub trait ObjectInner {
|
||||
$(type $kind;)+
|
||||
|
@ -363,7 +383,7 @@ object_gen! {
|
|||
/// A template definition.
|
||||
Tpl,
|
||||
|
||||
/// Metasyntactic variable (metavariable).
|
||||
/// Metalinguistic variable (metavariable).
|
||||
Meta,
|
||||
|
||||
/// Documentation.
|
||||
|
@ -573,23 +593,52 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add an edge from `self` to `to_oi` on the provided [`Asg`].
|
||||
///
|
||||
/// Since the only invariant asserted by [`ObjectIndexRelTo`] is that
|
||||
/// it may be related to `OB`,
|
||||
/// this method will only permit edges to `OB`;
|
||||
/// nothing else about the inner object is statically known.
|
||||
///
|
||||
/// See also [`Self::add_edge_from`].
|
||||
///
|
||||
/// _This method must remain private_,
|
||||
/// forcing callers to go through APIs for specific operations that
|
||||
/// allow objects to enforce their own invariants.
|
||||
/// This is also the reason why this method is defined here rather than
|
||||
/// on [`ObjectIndexRelTo`].
|
||||
fn add_edge_to<OB: ObjectRelatable>(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
to_oi: ObjectIndex<OB>,
|
||||
ctx_span: Option<Span>,
|
||||
) -> Result<Self, AsgError>
|
||||
where
|
||||
Self: ObjectIndexRelTo<OB>,
|
||||
{
|
||||
asg.add_edge(self, to_oi, ctx_span).map(|()| self)
|
||||
}
|
||||
|
||||
/// Add an edge from `from_oi` to `self` on the provided [`Asg`].
|
||||
///
|
||||
/// An edge can only be added if ontologically valid;
|
||||
/// see [`ObjectRelTo`] for more information.
|
||||
///
|
||||
/// See also [`Self::add_edge_to`].
|
||||
pub fn add_edge_from<OF: ObjectIndexRelTo<O>>(
|
||||
///
|
||||
/// _This method must remain private_,
|
||||
/// forcing callers to go through APIs for specific operations that
|
||||
/// allow objects to enforce their own invariants.
|
||||
fn add_edge_from<OA: ObjectIndexRelTo<O>>(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
from_oi: OF,
|
||||
from_oi: OA,
|
||||
ctx_span: Option<Span>,
|
||||
) -> Self
|
||||
) -> Result<Self, AsgError>
|
||||
where
|
||||
O: ObjectRelatable,
|
||||
{
|
||||
asg.add_edge(from_oi, self, ctx_span);
|
||||
self
|
||||
asg.add_edge(from_oi, self, ctx_span).map(|()| self)
|
||||
}
|
||||
|
||||
/// Create an iterator over the [`ObjectIndex`]es of the outgoing edges
|
||||
|
@ -686,6 +735,26 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
|||
asg.try_map_obj(self, f)
|
||||
}
|
||||
|
||||
/// Resolve the identifier and map over an inner `T` of the resulting
|
||||
/// [`Object`] narrowed to [`ObjectKind`] `O`,
|
||||
/// replacing the object on the given [`Asg`].
|
||||
///
|
||||
/// This uses [`Self::try_map_obj`] to retrieve the object from
|
||||
/// the [`Asg`].
|
||||
///
|
||||
/// If this operation is [`Infallible`],
|
||||
/// see [`Self::map_obj_inner`].
|
||||
pub fn try_map_obj_inner<T, E>(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
f: impl FnOnce(T) -> <O as TryMap<T>>::FnResult<E>,
|
||||
) -> Result<Self, E>
|
||||
where
|
||||
O: TryMap<T, Result<E> = Result<O, (O, E)>>,
|
||||
{
|
||||
self.try_map_obj(asg, O::try_fmap(f))
|
||||
}
|
||||
|
||||
/// Resolve the identifier and infallibly map over the resulting
|
||||
/// [`Object`] narrowed to [`ObjectKind`] `O`,
|
||||
/// replacing the object on the given [`Asg`].
|
||||
|
@ -702,6 +771,22 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolve the identifier and map over an inner `T` of the resulting
|
||||
/// [`Object`] narrowed to [`ObjectKind`] `O`,
|
||||
/// replacing the object on the given [`Asg`].
|
||||
///
|
||||
/// This uses [`Self::map_obj`] to retrieve the object from
|
||||
/// the [`Asg`].
|
||||
///
|
||||
/// If this operation is _not_ [`Infallible`],
|
||||
/// see [`Self::try_map_obj_inner`].
|
||||
pub fn map_obj_inner<T>(self, asg: &mut Asg, f: impl FnOnce(T) -> T) -> Self
|
||||
where
|
||||
O: Map<T, Target = O>,
|
||||
{
|
||||
self.map_obj(asg, O::fmap(f))
|
||||
}
|
||||
|
||||
/// Lift [`Self`] into [`Option`] and [`filter`](Option::filter) based
|
||||
/// on whether the [`ObjectRelatable::rel_ty`] of [`Self`]'s `O`
|
||||
/// matches that of `OB`.
|
||||
|
@ -736,12 +821,13 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
|||
/// objects.
|
||||
/// Forcing objects to be reachable can prevent them from being
|
||||
/// optimized away if they are not used.
|
||||
pub fn root(self, asg: &mut Asg) -> Self
|
||||
pub fn root(self, asg: &mut Asg) -> Result<Self, AsgError>
|
||||
where
|
||||
Root: ObjectRelTo<O>,
|
||||
{
|
||||
asg.root(self.span()).add_edge_to(asg, self, None);
|
||||
self
|
||||
asg.root(self.span())
|
||||
.add_edge_to(asg, self, None)
|
||||
.map(|_| self)
|
||||
}
|
||||
|
||||
/// Whether this object has been rooted in the ASG's [`Root`] object.
|
||||
|
@ -810,13 +896,23 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
|||
/// on the graph,
|
||||
/// like common subexpression elimination,
|
||||
/// in which case it's best not to rely on following edges in reverse.
|
||||
pub fn ident<'a>(&self, asg: &'a Asg) -> Option<&'a Ident>
|
||||
pub fn ident<'a>(&self, asg: &'a Asg) -> Option<ObjectIndex<Ident>>
|
||||
where
|
||||
O: ObjectRelFrom<Ident>,
|
||||
{
|
||||
self.incoming_edges_filtered(asg)
|
||||
.map(ObjectIndex::cresolve(asg))
|
||||
.next()
|
||||
self.incoming_edges_filtered(asg).next()
|
||||
}
|
||||
|
||||
/// Indicate that the given identifier `oi` is defined by this object.
|
||||
pub fn defines(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
oi: ObjectIndex<Ident>,
|
||||
) -> Result<Self, AsgError>
|
||||
where
|
||||
Self: ObjectIndexRelTo<Ident>,
|
||||
{
|
||||
oi.defined_by(asg, self).map(|_| self)
|
||||
}
|
||||
|
||||
/// Describe this expression using a short independent clause.
|
||||
|
@ -825,13 +921,31 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
|||
/// simple sentence or as part of a compound sentence.
|
||||
/// There should only be one such clause for any given object,
|
||||
/// but that is not enforced here.
|
||||
pub fn desc_short(&self, asg: &mut Asg, clause: SPair) -> Self
|
||||
pub fn add_desc_short(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
clause: SPair,
|
||||
) -> Result<Self, AsgError>
|
||||
where
|
||||
O: ObjectRelTo<Doc>,
|
||||
{
|
||||
let oi_doc = asg.create(Doc::new_indep_clause(clause));
|
||||
self.add_edge_to(asg, oi_doc, None)
|
||||
}
|
||||
|
||||
/// Retrieve a description of this expression using a short independent
|
||||
/// clause,
|
||||
/// if one has been set.
|
||||
///
|
||||
/// See [`Self::add_desc_short`] to add such a description.
|
||||
pub fn desc_short(&self, asg: &Asg) -> Option<SPair>
|
||||
where
|
||||
O: ObjectRelTo<Doc>,
|
||||
{
|
||||
self.edges_filtered::<Doc>(asg)
|
||||
.map(ObjectIndex::cresolve(asg))
|
||||
.find_map(Doc::indep_clause)
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectIndex<Object> {
|
||||
|
@ -846,7 +960,7 @@ impl ObjectIndex<Object> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<O: ObjectKind> Functor<Span> for ObjectIndex<O> {
|
||||
impl<O: ObjectKind> Map<Span> for ObjectIndex<O> {
|
||||
fn map(self, f: impl FnOnce(Span) -> Span) -> Self {
|
||||
match self {
|
||||
Self(index, span, ph) => Self(index, f(span), ph),
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
//! Expressions on the ASG.
|
||||
|
||||
use super::{prelude::*, Doc, Ident, Tpl};
|
||||
use crate::{f::Functor, num::Dim, span::Span};
|
||||
use super::{prelude::*, Doc, Ident, ObjectIndexTo, Tpl};
|
||||
use crate::{f::Map, num::Dim, span::Span};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg(doc)]
|
||||
|
@ -52,7 +52,7 @@ impl Expr {
|
|||
}
|
||||
}
|
||||
|
||||
impl Functor<Span> for Expr {
|
||||
impl Map<Span> for Expr {
|
||||
fn map(self, f: impl FnOnce(Span) -> Span) -> Self {
|
||||
match self {
|
||||
Self(op, dim, span) => Self(op, dim, f(span)),
|
||||
|
@ -233,14 +233,38 @@ impl ObjectIndex<Expr> {
|
|||
self,
|
||||
asg: &mut Asg,
|
||||
expr: Expr,
|
||||
) -> ObjectIndex<Expr> {
|
||||
) -> Result<ObjectIndex<Expr>, AsgError> {
|
||||
let oi_subexpr = asg.create(expr);
|
||||
oi_subexpr.add_edge_from(asg, self, None)
|
||||
}
|
||||
|
||||
/// Reference the value of the expression identified by `oi_ident` as if
|
||||
/// it were a subexpression.
|
||||
pub fn ref_expr(self, asg: &mut Asg, oi_ident: ObjectIndex<Ident>) -> Self {
|
||||
pub fn ref_expr(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
oi_ident: ObjectIndex<Ident>,
|
||||
) -> Result<Self, AsgError> {
|
||||
self.add_edge_to(asg, oi_ident, Some(oi_ident.span()))
|
||||
}
|
||||
|
||||
/// The expression is held by the container `oi_container`.
|
||||
///
|
||||
/// This is intended to convey that an expression would otherwise be
|
||||
/// dangling (unreachable) were it not for the properties
|
||||
/// of `oi_container`.
|
||||
/// If this is not true,
|
||||
/// consider using:
|
||||
///
|
||||
/// 1. [`Self::create_subexpr`] to create and assign ownership of
|
||||
/// expressions contained within other expressions; or
|
||||
/// 2. [`ObjectIndex<Ident>::bind_definition`] if this expression is to
|
||||
/// be assigned to an identifier.
|
||||
pub fn held_by(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
oi_container: ObjectIndexTo<Expr>,
|
||||
) -> Result<Self, AsgError> {
|
||||
self.add_edge_from(asg, oi_container, None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
|
||||
use super::{prelude::*, Expr, Meta, Pkg, Tpl};
|
||||
use crate::{
|
||||
diagnose::{Annotate, Diagnostic},
|
||||
diagnose::{panic::DiagnosticPanic, Annotate, Diagnostic},
|
||||
diagnostic_todo,
|
||||
f::Functor,
|
||||
f::Map,
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
num::{Dim, Dtype},
|
||||
parse::{util::SPair, Token},
|
||||
|
@ -143,6 +143,20 @@ pub enum Ident {
|
|||
/// itself;
|
||||
/// this is safe since identifiers in TAME are immutable.
|
||||
Transparent(SPair),
|
||||
|
||||
/// The name of the identifier is not yet known and will be determined
|
||||
/// by the lexical value of a metavariable.
|
||||
///
|
||||
/// This is intended for use by identifiers that will be generated as a
|
||||
/// result of template expansion---
|
||||
/// it represents the abstract _idea_ of an identifier,
|
||||
/// to be made concrete at a later time,
|
||||
/// and is not valid outside of a metalinguistic context.
|
||||
///
|
||||
/// The associated span represents the location that the identifier
|
||||
/// was defined,
|
||||
/// e.g. within a template body.
|
||||
Abstract(Span),
|
||||
}
|
||||
|
||||
impl Display for Ident {
|
||||
|
@ -165,19 +179,28 @@ impl Display for Ident {
|
|||
Transparent(id) => {
|
||||
write!(f, "transparent identifier {}", TtQuote::wrap(id))
|
||||
}
|
||||
Abstract(_) => {
|
||||
write!(f, "pending identifier (to be named during expansion)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ident {
|
||||
/// Identifier name.
|
||||
pub fn name(&self) -> SPair {
|
||||
/// Concrete identifier name.
|
||||
///
|
||||
/// Note: This [`Option`] is in preparation for identifiers that may not
|
||||
/// yet have names,
|
||||
/// awaiting expansion of a metavariable.
|
||||
pub fn name(&self) -> Option<SPair> {
|
||||
match self {
|
||||
Missing(name)
|
||||
| Opaque(name, ..)
|
||||
| Extern(name, ..)
|
||||
| IdentFragment(name, ..)
|
||||
| Transparent(name) => *name,
|
||||
| Transparent(name) => Some(*name),
|
||||
|
||||
Abstract(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,6 +211,8 @@ impl Ident {
|
|||
| Extern(name, ..)
|
||||
| IdentFragment(name, ..)
|
||||
| Transparent(name) => name.span(),
|
||||
|
||||
Abstract(span) => *span,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +223,7 @@ impl Ident {
|
|||
/// [`None`] is returned.
|
||||
pub fn kind(&self) -> Option<&IdentKind> {
|
||||
match self {
|
||||
Missing(_) | Transparent(_) => None,
|
||||
Missing(_) | Transparent(_) | Abstract(_) => None,
|
||||
|
||||
Opaque(_, kind, _)
|
||||
| Extern(_, kind, _)
|
||||
|
@ -213,7 +238,7 @@ impl Ident {
|
|||
/// [`None`] is returned.
|
||||
pub fn src(&self) -> Option<&Source> {
|
||||
match self {
|
||||
Missing(_) | Extern(_, _, _) | Transparent(_) => None,
|
||||
Missing(_) | Extern(_, _, _) | Transparent(_) | Abstract(_) => None,
|
||||
|
||||
Opaque(_, _, src) | IdentFragment(_, _, src, _) => Some(src),
|
||||
}
|
||||
|
@ -225,9 +250,11 @@ impl Ident {
|
|||
/// [`None`] is returned.
|
||||
pub fn fragment(&self) -> Option<FragmentText> {
|
||||
match self {
|
||||
Missing(_) | Opaque(_, _, _) | Extern(_, _, _) | Transparent(_) => {
|
||||
None
|
||||
}
|
||||
Missing(_)
|
||||
| Opaque(_, _, _)
|
||||
| Extern(_, _, _)
|
||||
| Transparent(_)
|
||||
| Abstract(_) => None,
|
||||
|
||||
IdentFragment(_, _, _, text) => Some(*text),
|
||||
}
|
||||
|
@ -244,6 +271,17 @@ impl Ident {
|
|||
Missing(ident)
|
||||
}
|
||||
|
||||
/// Create a new abstract identifier at the given location.
|
||||
///
|
||||
/// The provided [`Span`] is the only way to uniquely identify this
|
||||
/// identifier since it does not yet have a name.
|
||||
/// Note that this just _represents_ an abstract identifier;
|
||||
/// it is given meaning only when given the proper relationships on
|
||||
/// the ASG.
|
||||
pub fn new_abstract<S: Into<Span>>(at: S) -> Self {
|
||||
Abstract(at.into())
|
||||
}
|
||||
|
||||
/// Attempt to redeclare an identifier with additional information.
|
||||
///
|
||||
/// If an existing identifier is an [`Ident::Extern`],
|
||||
|
@ -357,12 +395,24 @@ impl Ident {
|
|||
|
||||
Missing(name) => Ok(Opaque(name.overwrite(span), kind, src)),
|
||||
|
||||
// TODO: Remove guards and catch-all for exhaustiveness check.
|
||||
_ => {
|
||||
let err = TransitionError::Redeclare(self.name(), span);
|
||||
|
||||
// TODO: Remove guards for better exhaustiveness check
|
||||
Opaque(name, _, _)
|
||||
| IdentFragment(name, _, _, _)
|
||||
| Transparent(name) => {
|
||||
let err = TransitionError::Redeclare(name, span);
|
||||
Err((self, err))
|
||||
}
|
||||
|
||||
// This really should never happen at the time of writing,
|
||||
// since to resolve an identifier it first needs to be located
|
||||
// on the graph,
|
||||
// and abstract identifiers do not have an indexed name.
|
||||
// Does the system now discover identifiers through other means,
|
||||
// e.g. by trying to pre-draw edges within template bodies?
|
||||
Abstract(abstract_span) => Err((
|
||||
self,
|
||||
TransitionError::ResolveAbstract(abstract_span, span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +431,7 @@ impl Ident {
|
|||
/// At present,
|
||||
/// both [`Ident::Missing`] and [`Ident::Extern`] are
|
||||
/// considered to be unresolved.
|
||||
pub fn resolved(&self) -> Result<&Ident, UnresolvedError> {
|
||||
pub fn resolved(&self) -> Result<(&Ident, SPair), UnresolvedError> {
|
||||
match self {
|
||||
Missing(name) => Err(UnresolvedError::Missing(*name)),
|
||||
|
||||
|
@ -389,7 +439,11 @@ impl Ident {
|
|||
Err(UnresolvedError::Extern(*name, kind.clone()))
|
||||
}
|
||||
|
||||
Opaque(..) | IdentFragment(..) | Transparent(..) => Ok(self),
|
||||
Abstract(span) => Err(UnresolvedError::Abstract(*span)),
|
||||
|
||||
Opaque(name, ..)
|
||||
| IdentFragment(name, ..)
|
||||
| Transparent(name, ..) => Ok((self, *name)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,22 +468,33 @@ impl Ident {
|
|||
kind: IdentKind,
|
||||
src: Source,
|
||||
) -> TransitionResult<Ident> {
|
||||
match self.kind() {
|
||||
None => Ok(Extern(self.name().overwrite(span), kind, src)),
|
||||
Some(cur_kind) => {
|
||||
match self {
|
||||
Missing(name) | Transparent(name) => {
|
||||
Ok(Extern(name.overwrite(span), kind, src))
|
||||
}
|
||||
|
||||
Opaque(name, ref cur_kind, _)
|
||||
| Extern(name, ref cur_kind, _)
|
||||
| IdentFragment(name, ref cur_kind, _, _) => {
|
||||
if cur_kind != &kind {
|
||||
let err = TransitionError::ExternResolution(
|
||||
self.name(),
|
||||
name,
|
||||
cur_kind.clone(),
|
||||
(kind, span),
|
||||
);
|
||||
|
||||
return Err((self, err));
|
||||
Err((self, err))
|
||||
} else {
|
||||
// Resolved successfully, so keep what we already have.
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
// Resolved successfully, so keep what we already have.
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
// See notes on `resolve()` for this arm.
|
||||
Abstract(abstract_span) => Err((
|
||||
self,
|
||||
TransitionError::ResolveAbstract(abstract_span, span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,6 +506,11 @@ impl Ident {
|
|||
/// Note, however, that an identifier's fragment may be cleared under
|
||||
/// certain circumstances (such as symbol overrides),
|
||||
/// making way for a new fragment to be set.
|
||||
///
|
||||
/// Fragments cannot be attached to abstract identifiers,
|
||||
/// nor does it make sense to,
|
||||
/// since fragment code generation only takes place on expanded
|
||||
/// objects.
|
||||
pub fn set_fragment(self, text: FragmentText) -> TransitionResult<Ident> {
|
||||
match self {
|
||||
Opaque(sym, kind, src) => Ok(IdentFragment(sym, kind, src, text)),
|
||||
|
@ -457,6 +527,9 @@ impl Ident {
|
|||
IdentFragment(_, _, ref src, ..) if src.override_ => Ok(self),
|
||||
|
||||
// These represent the prologue and epilogue of maps.
|
||||
//
|
||||
// TODO: Is this arm still needed after having eliminated their
|
||||
// fragments from xmlo files?
|
||||
IdentFragment(
|
||||
_,
|
||||
IdentKind::MapHead
|
||||
|
@ -466,10 +539,16 @@ impl Ident {
|
|||
..,
|
||||
) => Ok(self),
|
||||
|
||||
_ => {
|
||||
let name = self.name();
|
||||
Missing(name)
|
||||
| Extern(name, _, _)
|
||||
| IdentFragment(name, _, _, _)
|
||||
| Transparent(name) => {
|
||||
Err((self, TransitionError::BadFragmentDest(name)))
|
||||
}
|
||||
|
||||
Abstract(span) => {
|
||||
Err((self, TransitionError::AbstractFragmentDest(span)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -503,6 +582,16 @@ pub enum TransitionError {
|
|||
///
|
||||
/// See [`Ident::set_fragment`].
|
||||
BadFragmentDest(SPair),
|
||||
|
||||
/// Attempted to resolve an abstract identifier.
|
||||
///
|
||||
/// An abstract identifier must be made to be concrete before any
|
||||
/// resolution can occur.
|
||||
ResolveAbstract(Span, Span),
|
||||
|
||||
/// Like [`Self::BadFragmentDest`] but for abstract identifiers without
|
||||
/// a name.
|
||||
AbstractFragmentDest(Span),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TransitionError {
|
||||
|
@ -540,7 +629,15 @@ impl std::fmt::Display for TransitionError {
|
|||
|
||||
BadFragmentDest(name) => {
|
||||
write!(fmt, "bad fragment destination: {}", TtQuote::wrap(name))
|
||||
},
|
||||
|
||||
ResolveAbstract(_, _) => {
|
||||
write!(fmt, "cannot resolve abstract identifier")
|
||||
}
|
||||
|
||||
AbstractFragmentDest(_) => {
|
||||
write!(fmt, "cannot attach fragment to abstract identifier")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -610,6 +707,27 @@ impl Diagnostic for TransitionError {
|
|||
),
|
||||
name.help(" object file; this error should never occur."),
|
||||
],
|
||||
|
||||
ResolveAbstract(span, resolve_span) => vec![
|
||||
span.note("for this abstract identifier"),
|
||||
resolve_span.internal_error(
|
||||
"attempted to resolve abstract identifier here",
|
||||
),
|
||||
resolve_span.help(
|
||||
"this is a suspicious error that may represent \
|
||||
a compiler bug",
|
||||
),
|
||||
],
|
||||
|
||||
AbstractFragmentDest(span) => vec![
|
||||
span.internal_error(
|
||||
"this abstract identifier cannot be assigned a text fragment",
|
||||
),
|
||||
span.help(
|
||||
"the term 'text fragment' refers to compiled code from an \
|
||||
object file; this error should never occur."
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -629,6 +747,13 @@ pub enum UnresolvedError {
|
|||
/// Expected identifier has not yet been resolved with a concrete
|
||||
/// definition.
|
||||
Extern(SPair, IdentKind),
|
||||
|
||||
/// The identifier at the given location is pending expansion and is not
|
||||
/// yet a concrete identifier.
|
||||
///
|
||||
/// These identifiers represent a template for the creation of a future
|
||||
/// identifier during template expansion.
|
||||
Abstract(Span),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UnresolvedError {
|
||||
|
@ -646,6 +771,8 @@ impl std::fmt::Display for UnresolvedError {
|
|||
TtQuote::wrap(name),
|
||||
TtQuote::wrap(kind),
|
||||
),
|
||||
|
||||
Abstract(_) => write!(fmt, "abstract (unexpanded) identifier"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -684,6 +811,16 @@ impl Diagnostic for UnresolvedError {
|
|||
" later provide a concrete definition for it."
|
||||
)
|
||||
],
|
||||
|
||||
// This should not occur under normal circumstances;
|
||||
// the user is likely to hit a more helpful and
|
||||
// context-specific error before this.
|
||||
Abstract(span) => vec![
|
||||
span.error("this identifier has not been expanded"),
|
||||
span.help(
|
||||
"are you using a metavariable outside of a template body?",
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -980,7 +1117,10 @@ object_rel! {
|
|||
/// This is a legacy feature expected to be removed in the future;
|
||||
/// see [`ObjectRel::can_recurse`] for more information.
|
||||
Ident -> {
|
||||
tree Ident,
|
||||
// Could represent an opaque dependency or an abstract identifier's
|
||||
// metavariable reference.
|
||||
dyn Ident,
|
||||
|
||||
tree Expr,
|
||||
tree Tpl,
|
||||
tree Meta,
|
||||
|
@ -1098,6 +1238,23 @@ impl ObjectIndex<Ident> {
|
|||
)
|
||||
}
|
||||
|
||||
// An abstract identifier will become `Transparent` during
|
||||
// expansion.
|
||||
// This does not catch multiple definitions,
|
||||
// but this is hopefully not a problem in practice since there
|
||||
// is no lookup mechanism in source languages for abstract
|
||||
// identifiers since this has no name yet and cannot be
|
||||
// indexed in the usual way.
|
||||
// Even so,
|
||||
// multiple definitions can be caught at expansion-time if
|
||||
// they somehow are able to slip through
|
||||
// (which would be a compiler bug);
|
||||
// it is not worth complicating `Ident`'s API or variants
|
||||
// even further,
|
||||
// and not worth the cost of a graph lookup here when
|
||||
// we'll have to do it later anyway.
|
||||
Abstract(span) => Ok(Abstract(span)),
|
||||
|
||||
// We are okay to proceed to add an edge to the `definition`.
|
||||
// Discard the original span
|
||||
// (which is the location of the first reference _to_ this
|
||||
|
@ -1105,7 +1262,7 @@ impl ObjectIndex<Ident> {
|
|||
// and use the newly provided `id` and its span.
|
||||
Missing(_) => Ok(Transparent(id)),
|
||||
})
|
||||
.map(|ident_oi| ident_oi.add_edge_to(asg, definition, None))
|
||||
.and_then(|ident_oi| ident_oi.add_edge_to(asg, definition, None))
|
||||
}
|
||||
|
||||
/// Set the fragment associated with a concrete identifier.
|
||||
|
@ -1159,14 +1316,94 @@ impl ObjectIndex<Ident> {
|
|||
self.incoming_edges_filtered(asg).next()
|
||||
}
|
||||
|
||||
/// Root this identifier into the provided object,
|
||||
/// as if making the statement "`oi_root` defines `self`".
|
||||
///
|
||||
/// This causes `oi_root` to act as an owner of this identifier.
|
||||
/// An identifier really only ought to have one owner,
|
||||
/// but this is not enforced here.
|
||||
pub fn defined_by(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
oi_root: impl ObjectIndexRelTo<Ident>,
|
||||
) -> Result<Self, AsgError> {
|
||||
self.add_edge_from(asg, oi_root, None)
|
||||
}
|
||||
|
||||
/// Declare that `oi_dep` is an opaque dependency of `self`.
|
||||
pub fn add_opaque_dep(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
oi_dep: ObjectIndex<Ident>,
|
||||
) -> Self {
|
||||
) -> Result<Self, AsgError> {
|
||||
self.add_edge_to(asg, oi_dep, None)
|
||||
}
|
||||
|
||||
/// Retrieve either the concrete name of the identifier or the name of
|
||||
/// the metavariable that will be used to produce it.
|
||||
pub fn name_or_meta(&self, asg: &Asg) -> SPair {
|
||||
let ident = self.resolve(asg);
|
||||
|
||||
// It would be nice if this could be built more into the type system
|
||||
// in the future,
|
||||
// if it's worth the effort of doing so.
|
||||
// This is a simple lookup;
|
||||
// the robust internal diagnostic messages make it look
|
||||
// more complicated than it is.
|
||||
ident.name().unwrap_or_else(|| {
|
||||
let oi_meta_ident =
|
||||
self.edges_filtered::<Ident>(asg).next().diagnostic_expect(
|
||||
|| {
|
||||
vec![
|
||||
self.internal_error(
|
||||
"this abstract identifier has no Ident edge",
|
||||
),
|
||||
self.help(
|
||||
"the compiler created an `Ident::Abstract` \
|
||||
object but did not produce an edge to the \
|
||||
Ident of the Meta from which its name \
|
||||
will be derived",
|
||||
),
|
||||
]
|
||||
},
|
||||
"invalid ASG representation of abstract identifier",
|
||||
);
|
||||
|
||||
oi_meta_ident.resolve(asg).name().diagnostic_expect(
|
||||
|| {
|
||||
vec![
|
||||
self.note(
|
||||
"while trying to find the Meta name of this abstract \
|
||||
identifier"
|
||||
),
|
||||
oi_meta_ident.internal_error(
|
||||
"encountered another abstract identifier"
|
||||
),
|
||||
oi_meta_ident.help(
|
||||
"an abstract identifier must reference a concrete \
|
||||
`Ident`"
|
||||
),
|
||||
]
|
||||
},
|
||||
"abstract identifier references another abstract identifier",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new abstract identifier whose name will be derived from
|
||||
/// this one during expansion.
|
||||
///
|
||||
/// It is expected that `self` defines a [`Meta`],
|
||||
/// but this is not enforced here and will be checked during
|
||||
/// expansion.
|
||||
pub fn new_abstract_ident(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
at: Span,
|
||||
) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||
asg.create(Ident::new_abstract(at))
|
||||
.add_edge_to(asg, self, Some(at))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -30,20 +30,20 @@ fn ident_name() {
|
|||
let name = "name".into();
|
||||
let spair = SPair(name, S1);
|
||||
|
||||
assert_eq!(spair, Ident::Missing(spair).name());
|
||||
assert_eq!(Some(spair), Ident::Missing(spair).name());
|
||||
|
||||
assert_eq!(
|
||||
spair,
|
||||
Some(spair),
|
||||
Ident::Opaque(spair, IdentKind::Meta, Source::default()).name()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
spair,
|
||||
Some(spair),
|
||||
Ident::Extern(spair, IdentKind::Meta, Source::default()).name()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
spair,
|
||||
Some(spair),
|
||||
Ident::IdentFragment(
|
||||
spair,
|
||||
IdentKind::Meta,
|
||||
|
@ -182,7 +182,10 @@ fn resolved_on_ident() {
|
|||
.unwrap()
|
||||
.resolved()
|
||||
.unwrap(),
|
||||
&Ident::Opaque(SPair(sym, S2), kind.clone(), src.clone()),
|
||||
(
|
||||
&Ident::Opaque(SPair(sym, S2), kind.clone(), src.clone()),
|
||||
SPair(sym, S2)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -403,7 +406,10 @@ fn resolved_on_fragment() {
|
|||
|
||||
assert_eq!(
|
||||
ident.set_fragment(text.clone()).unwrap().resolved(),
|
||||
Ok(&Ident::IdentFragment(SPair(sym, S2), kind, src, text)),
|
||||
Ok((
|
||||
&Ident::IdentFragment(SPair(sym, S2), kind, src, text),
|
||||
SPair(sym, S2),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Metasyntactic variables represented on the ASG
|
||||
// Metalinguistic objects represented on the ASG
|
||||
//
|
||||
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
|
||||
//
|
||||
|
@ -17,25 +17,33 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Metasyntactic variables on the ASG.
|
||||
//! Metalinguistic objects on the ASG.
|
||||
//!
|
||||
//! Metasyntactic variables
|
||||
//! (sometimes called "metavariables" herein for short)
|
||||
//! Metalinguistic variables[^w],
|
||||
//! called "metavariables" for short,
|
||||
//! have historically been a feature of the template system.
|
||||
//! The canonical metavariable is the template parameter.
|
||||
//!
|
||||
//! [^w]: This term comes from logic; see
|
||||
//! <https://en.wikipedia.org/wiki/Metavariable_(logic)>.
|
||||
//! The term "metasyntactic" was originally used with TAMER,
|
||||
//! but that term generally has a different meaning in programming:
|
||||
//! <https://en.wikipedia.org/wiki/Metasyntactic_variable>.
|
||||
|
||||
use super::{prelude::*, Ident};
|
||||
use arrayvec::ArrayVec;
|
||||
|
||||
use super::{prelude::*, Doc, Ident};
|
||||
use crate::{
|
||||
diagnose::Annotate,
|
||||
diagnostic_todo,
|
||||
f::Functor,
|
||||
f::Map,
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
parse::util::SPair,
|
||||
parse::{util::SPair, Token},
|
||||
span::Span,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Metasyntactic variable (metavariable).
|
||||
/// Metalinguistic variable (metavariable).
|
||||
///
|
||||
/// A metavariable is a lexical construct.
|
||||
/// Its value is a lexeme that represents an [`Ident`],
|
||||
|
@ -49,9 +57,26 @@ use std::fmt::Display;
|
|||
/// the symbol representing that identifier then acts as a metavariable.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Meta {
|
||||
/// Metavariable represents a parameter without a value.
|
||||
///
|
||||
/// A value must be provided at or before expansion,
|
||||
/// generally via template application arguments.
|
||||
Required(Span),
|
||||
ConcatList(Span),
|
||||
|
||||
/// Metavariable has a concrete lexical value.
|
||||
///
|
||||
/// This metavariable represents a literal and requires no further
|
||||
/// reduction or processing.
|
||||
Lexeme(Span, SPair),
|
||||
|
||||
/// Metavariable whose value is to be the concatenation of all
|
||||
/// referenced metavariables.
|
||||
///
|
||||
/// This object has no value on its own;
|
||||
/// it must contain edges to other metavariables,
|
||||
/// and the order of those edges on the ASG represents concatenation
|
||||
/// order.
|
||||
ConcatList(Span),
|
||||
}
|
||||
|
||||
impl Meta {
|
||||
|
@ -93,6 +118,20 @@ impl Meta {
|
|||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve a concrete lexeme,
|
||||
/// if any.
|
||||
///
|
||||
/// If this metavariable represents a concatenation list,
|
||||
/// this will return [`None`].
|
||||
/// This method _does not_ expand metavariables,
|
||||
/// and does not have the context necessary to do so.
|
||||
pub fn lexeme(&self) -> Option<SPair> {
|
||||
match self {
|
||||
Self::Required(_) | Self::ConcatList(_) => None,
|
||||
Self::Lexeme(_, lex) => Some(*lex),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Meta> for Span {
|
||||
|
@ -105,10 +144,10 @@ impl Display for Meta {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Required(_) => {
|
||||
write!(f, "metasyntactic parameter with required value")
|
||||
write!(f, "metalinguistic parameter with required value")
|
||||
}
|
||||
Self::ConcatList(_) => {
|
||||
write!(f, "metasyntactic concatenation list")
|
||||
write!(f, "metalinguistic concatenation list")
|
||||
}
|
||||
Self::Lexeme(_, spair) => {
|
||||
write!(f, "lexeme {}", TtQuote::wrap(spair))
|
||||
|
@ -117,7 +156,7 @@ impl Display for Meta {
|
|||
}
|
||||
}
|
||||
|
||||
impl Functor<Span> for Meta {
|
||||
impl Map<Span> for Meta {
|
||||
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
|
||||
match self {
|
||||
Self::Required(span) => Self::Required(f(span)),
|
||||
|
@ -131,14 +170,83 @@ object_rel! {
|
|||
/// Metavariables contain lexical data and references to other
|
||||
/// metavariables.
|
||||
Meta -> {
|
||||
tree Meta, // TODO: do we need tree?
|
||||
// References to other metavariables
|
||||
// (e.g. `<param-value>` in XML-based sources).
|
||||
cross Ident,
|
||||
|
||||
// Owned lexical values.
|
||||
//
|
||||
// These differ from the above references because they represent
|
||||
// inline lexemes that have no identifier.
|
||||
tree Meta,
|
||||
|
||||
// e.g. template paramater description.
|
||||
tree Doc,
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectIndex<Meta> {
|
||||
pub fn assign_lexeme(self, asg: &mut Asg, lexeme: SPair) -> Self {
|
||||
self.map_obj(asg, |meta| meta.assign_lexeme(lexeme))
|
||||
/// Append a lexeme to this metavariable.
|
||||
///
|
||||
/// If `self` is [`Meta::Required`],
|
||||
/// this provides a value and reuses the object already allocated.
|
||||
///
|
||||
/// If `self` is a single [`Meta::Lexeme`],
|
||||
/// it is re-allocated to a separate [`Meta`] object along with the
|
||||
/// provided `lexeme`,
|
||||
/// and edges are added to both,
|
||||
/// indicating concatenation.
|
||||
///
|
||||
/// Metavariables with multiple values already represents concatenation
|
||||
/// and a new edge will be added without changing `self`.
|
||||
pub fn append_lexeme(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
lexeme: SPair,
|
||||
) -> Result<Self, AsgError> {
|
||||
use Meta::*;
|
||||
|
||||
let mut rels = ArrayVec::<SPair, 2>::new();
|
||||
|
||||
// We don't have access to `asg` within this closure because of
|
||||
// `map_obj`;
|
||||
// the above variable will be mutated by it to return extra
|
||||
// information to do those operations afterward.
|
||||
// If we do this often,
|
||||
// then let's create a `map_obj` that is able to return
|
||||
// supplemental information or create additional relationships
|
||||
// (so, a map over a subgraph rather than an object).
|
||||
self.map_obj(asg, |meta| match meta {
|
||||
// Storage is already allocated for this lexeme.
|
||||
Required(span) => Lexeme(span, lexeme),
|
||||
|
||||
// We could technically allocate a new symbol and combine the
|
||||
// lexeme now,
|
||||
// but let's wait so that we can avoid allocating
|
||||
// intermediate symbols.
|
||||
Lexeme(span, first_lexeme) => {
|
||||
// We're converting from a single lexeme stored on `self` to
|
||||
// a `Meta` with edges to both individual lexemes.
|
||||
rels.push(first_lexeme);
|
||||
rels.push(lexeme);
|
||||
|
||||
ConcatList(span)
|
||||
}
|
||||
|
||||
// We're already representing concatenation so we need only add
|
||||
// an edge to the new lexeme.
|
||||
ConcatList(span) => {
|
||||
rels.push(lexeme);
|
||||
ConcatList(span)
|
||||
}
|
||||
});
|
||||
|
||||
for rel_lexeme in rels {
|
||||
let oi = asg.create(Meta::Lexeme(rel_lexeme.span(), rel_lexeme));
|
||||
self.add_edge_to(asg, oi, None)?;
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
|
||||
|
@ -148,4 +256,53 @@ impl ObjectIndex<Meta> {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Append a reference to a metavariable identified by `oi_ref`.
|
||||
//
|
||||
// The value of the metavariable will not be known until expansion time,
|
||||
// at which point its lexical value will be concatenated with those of
|
||||
// any other references,
|
||||
// in the order that they were added.
|
||||
//
|
||||
// It is expected that the value of `oi_ref` was produced via a lookup
|
||||
// from the reference location and therefore contains the reference
|
||||
// [`Span`];
|
||||
// this is used to provide accurate diagnostic information.
|
||||
pub fn concat_ref(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
oi_ref: ObjectIndex<Ident>,
|
||||
) -> Result<Self, AsgError> {
|
||||
use Meta::*;
|
||||
|
||||
// We cannot mutate the ASG within `map_obj` below because of the
|
||||
// held reference to `asg`,
|
||||
// so this will be used to store data for later mutation.
|
||||
let mut pre = None;
|
||||
|
||||
// References are only valid for a [`Self::ConcatList`].
|
||||
self.map_obj(asg, |meta| match meta {
|
||||
Required(span) | ConcatList(span) => ConcatList(span),
|
||||
|
||||
Lexeme(span, lex) => {
|
||||
// We will move the lexeme into a _new_ object,
|
||||
// and store a reference to it.
|
||||
pre.replace(Meta::Lexeme(lex.span(), lex));
|
||||
|
||||
ConcatList(span)
|
||||
}
|
||||
});
|
||||
|
||||
// This represents a lexeme that was extracted into a new `Meta`;
|
||||
// we must add the edge before appending the ref since
|
||||
// concatenation will occur during expansion in edge order.
|
||||
if let Some(orig) = pre {
|
||||
asg.create(orig).add_edge_from(asg, self, None)?;
|
||||
}
|
||||
|
||||
// Having been guaranteed a `ConcatList` above,
|
||||
// we now only need to append an edge that references what to
|
||||
// concatenate.
|
||||
self.add_edge_to(asg, oi_ref, Some(oi_ref.span()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
use super::{prelude::*, Doc, Ident, Tpl};
|
||||
use crate::{
|
||||
f::Functor,
|
||||
f::Map,
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
parse::{util::SPair, Token},
|
||||
span::Span,
|
||||
|
@ -87,7 +87,7 @@ impl Display for Pkg {
|
|||
}
|
||||
}
|
||||
|
||||
impl Functor<Span> for Pkg {
|
||||
impl Map<Span> for Pkg {
|
||||
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
|
||||
match self {
|
||||
Self(span, path) => Self(f(span), path),
|
||||
|
@ -134,11 +134,15 @@ impl ObjectIndex<Pkg> {
|
|||
let parent = self.resolve(asg);
|
||||
let oi_import = asg.create(Pkg::new_imported(parent, namespec)?);
|
||||
|
||||
Ok(self.add_edge_to(asg, oi_import, Some(namespec.span())))
|
||||
self.add_edge_to(asg, oi_import, Some(namespec.span()))
|
||||
}
|
||||
|
||||
/// Arbitrary text serving as documentation in a literate style.
|
||||
pub fn append_doc_text(&self, asg: &mut Asg, text: SPair) -> Self {
|
||||
pub fn append_doc_text(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
text: SPair,
|
||||
) -> Result<Self, AsgError> {
|
||||
let oi_doc = asg.create(Doc::new_text(text));
|
||||
self.add_edge_to(asg, oi_doc, None)
|
||||
}
|
||||
|
|
|
@ -22,12 +22,15 @@
|
|||
//! See (parent module)[super] for more information.
|
||||
|
||||
use super::{
|
||||
Doc, Expr, Ident, Meta, Object, ObjectIndex, ObjectKind, OiPairObjectInner,
|
||||
Pkg, Root,
|
||||
Doc, Expr, Ident, Meta, Object, ObjectIndex, ObjectIndexRefined,
|
||||
ObjectKind, OiPairObjectInner, Pkg, Root,
|
||||
};
|
||||
use crate::{
|
||||
asg::{graph::object::Tpl, Asg},
|
||||
f::Functor,
|
||||
asg::{
|
||||
graph::{object::Tpl, AsgRelMut, ProposedRel},
|
||||
Asg, AsgError,
|
||||
},
|
||||
f::Map,
|
||||
span::Span,
|
||||
};
|
||||
use std::{fmt::Display, marker::PhantomData};
|
||||
|
@ -55,7 +58,7 @@ macro_rules! object_rel {
|
|||
(
|
||||
$(#[$attr:meta])+
|
||||
$from:ident -> {
|
||||
$($ety:ident $kind:ident,)*
|
||||
$($ety:ident $kind:ident $({$($impl:tt)*})?,)*
|
||||
}
|
||||
$(can_recurse($rec_obj:ident) if $rec_expr:expr)?
|
||||
) => {paste::paste! {
|
||||
|
@ -168,6 +171,17 @@ macro_rules! object_rel {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This generates a specialized implementation _per target `$kind`_
|
||||
// and allows for the caller to override methods on the trait.
|
||||
// This takes advantage of trait specialization via
|
||||
// `min_specialization`;
|
||||
// see `AsgRelMut` for more information.
|
||||
$(
|
||||
impl AsgRelMut<$kind> for $from {
|
||||
$( $($impl)* )?
|
||||
}
|
||||
)*
|
||||
}};
|
||||
|
||||
// Static edge types.
|
||||
|
@ -270,12 +284,47 @@ impl<S> DynObjectRel<S, ObjectIndex<Object>> {
|
|||
/// Attempt to narrow the target into the [`ObjectRel`] of `O`.
|
||||
///
|
||||
/// See [`ObjectRelatable::new_rel_dyn`] for more information.
|
||||
///
|
||||
/// To exhaustively match against all possible [`ObjectKind`]s,
|
||||
/// see [`Self::refine_target`].
|
||||
pub fn narrow_target<O: ObjectKind + ObjectRelatable>(
|
||||
&self,
|
||||
) -> Option<O::Rel> {
|
||||
O::new_rel_dyn(self.target_ty(), *self.target())
|
||||
}
|
||||
|
||||
/// Refine the target [`ObjectIndex<Object>`](ObjectIndex) into
|
||||
/// [`ObjectIndexRefined`] such that the returned variant has a
|
||||
/// narrowed [`ObjectIndex<O>`] type.
|
||||
///
|
||||
/// This allows converting a dynamic [`ObjectIndex`] into a statically
|
||||
/// known type where `O` is derived from [`Self::target_ty`].
|
||||
/// This avoids having to manually match on [`Self::target_ty`] and then
|
||||
/// use [`ObjectIndex::must_narrow_into`] on the matching
|
||||
/// [`ObjectKind`],
|
||||
/// since there is a risk of those getting out of sync.
|
||||
///
|
||||
/// In contrast to [`Self::narrow_target`],
|
||||
/// where the caller must specify the expected [`ObjectKind`],
|
||||
/// this allows for exhaustively matching against all possible objects.
|
||||
pub fn refine_target(&self) -> ObjectIndexRefined {
|
||||
macro_rules! narrow_each_rel_ty {
|
||||
( $($var:ident),+ ) => {
|
||||
match self.target_ty() {
|
||||
$(
|
||||
ObjectRelTy::$var => {
|
||||
ObjectIndexRefined::$var(
|
||||
self.target().must_narrow_into()
|
||||
)
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
narrow_each_rel_ty!(Root, Pkg, Ident, Expr, Tpl, Meta, Doc)
|
||||
}
|
||||
|
||||
/// Attempt to convert [`Self`] into an [`ObjectIndex`] with an
|
||||
/// [`ObjectKind`] of type `O`.
|
||||
///
|
||||
|
@ -432,7 +481,7 @@ impl DynObjectRel<ObjectIndex<Object>, ObjectIndex<Object>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, T, U, V> Functor<(S, T), (U, V)> for DynObjectRel<S, T> {
|
||||
impl<S, T, U, V> Map<(S, T), (U, V)> for DynObjectRel<S, T> {
|
||||
type Target = DynObjectRel<U, V>;
|
||||
|
||||
fn map(self, f: impl FnOnce((S, T)) -> (U, V)) -> Self::Target {
|
||||
|
@ -466,7 +515,8 @@ impl<T: Display> Display for DynObjectRel<T> {
|
|||
/// statically analyzed by the type system to ensure that they only
|
||||
/// construct graphs that adhere to this schema.
|
||||
pub trait ObjectRelTo<OB: ObjectKind + ObjectRelatable> =
|
||||
ObjectRelatable where <Self as ObjectRelatable>::Rel: From<ObjectIndex<OB>>;
|
||||
ObjectRelatable + AsgRelMut<OB>
|
||||
where <Self as ObjectRelatable>::Rel: From<ObjectIndex<OB>>;
|
||||
|
||||
/// Reverse of [`ObjectRelTo`].
|
||||
///
|
||||
|
@ -791,39 +841,36 @@ pub trait ObjectIndexRelTo<OB: ObjectRelatable>: Sized + Clone + Copy {
|
|||
/// See [`ObjectIndex::widen`] for more information.
|
||||
fn widen(&self) -> ObjectIndex<Object>;
|
||||
|
||||
/// Add an edge from `self` to `to_oi` on the provided [`Asg`].
|
||||
/// Request permission to add an edge from `self` to another object.
|
||||
///
|
||||
/// Since the only invariant asserted by [`ObjectIndexRelTo`] is that
|
||||
/// it may be related to `OB`,
|
||||
/// this method will only permit edges to `OB`;
|
||||
/// nothing else about the inner object is statically known.
|
||||
/// To create edges to other types of objects,
|
||||
/// and for more information about this operation
|
||||
/// (including `ctx_span`),
|
||||
/// see [`ObjectIndex::add_edge_to`].
|
||||
fn add_edge_to(
|
||||
self,
|
||||
/// This gives the object ownership over the edges that are created,
|
||||
/// in addition to the static guarantees provided by
|
||||
/// [`ObjectIndexRelTo`].
|
||||
/// Since [`ObjectIndexRelTo` supports dynamic source objects,
|
||||
/// this allows calling code to be written in a concise manner that is
|
||||
/// agnostic to the source type,
|
||||
/// without sacrificing edge ownership.
|
||||
///
|
||||
/// For more information,
|
||||
/// see [`AsgRelMut::pre_add_edge`].
|
||||
///
|
||||
/// _This should only be called by [`Asg`]_;
|
||||
/// `commit` is expected to be a continuation that adds the edge to
|
||||
/// the graph,
|
||||
/// and the object represented by `self` may modify itself expecting
|
||||
/// such an edge to be added.
|
||||
fn pre_add_edge(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
to_oi: ObjectIndex<OB>,
|
||||
ctx_span: Option<Span>,
|
||||
) -> Self {
|
||||
asg.add_edge(self, to_oi, ctx_span);
|
||||
self
|
||||
}
|
||||
) -> Result<(), AsgError>;
|
||||
|
||||
/// Check whether an edge exists from `self` to `to_oi`.
|
||||
fn has_edge_to(&self, asg: &Asg, to_oi: ObjectIndex<OB>) -> bool {
|
||||
asg.has_edge(*self, to_oi)
|
||||
}
|
||||
|
||||
/// Indicate that the given identifier `oi` is defined by this object.
|
||||
fn defines(self, asg: &mut Asg, oi: ObjectIndex<Ident>) -> Self
|
||||
where
|
||||
Self: ObjectIndexRelTo<Ident>,
|
||||
{
|
||||
self.add_edge_to(asg, oi, None)
|
||||
}
|
||||
|
||||
/// Iterate over the [`ObjectIndex`]es of the outgoing edges of `self`
|
||||
/// that match the [`ObjectKind`] `OB`.
|
||||
///
|
||||
|
@ -875,8 +922,10 @@ pub trait ObjectIndexRelTo<OB: ObjectRelatable>: Sized + Clone + Copy {
|
|||
Self: ObjectIndexRelTo<Ident>,
|
||||
{
|
||||
// Rust fails to infer OB with `self.edges_rel_to` as of 2023-03
|
||||
ObjectIndexRelTo::<Ident>::edges_rel_to(self, asg)
|
||||
.find(|oi| oi.resolve(asg).name().symbol() == name.symbol())
|
||||
ObjectIndexRelTo::<Ident>::edges_rel_to(self, asg).find(|oi| {
|
||||
oi.resolve(asg).name().map(|name| name.symbol())
|
||||
== Some(name.symbol())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -892,6 +941,22 @@ where
|
|||
fn widen(&self) -> ObjectIndex<Object> {
|
||||
ObjectIndex::<O>::widen(*self)
|
||||
}
|
||||
|
||||
fn pre_add_edge(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
to_oi: ObjectIndex<OB>,
|
||||
ctx_span: Option<Span>,
|
||||
) -> Result<(), AsgError> {
|
||||
O::pre_add_edge(
|
||||
asg,
|
||||
ProposedRel {
|
||||
from_oi: self.widen().must_narrow_into::<O>(),
|
||||
to_oi,
|
||||
ctx_span,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexTo<OB> {
|
||||
|
@ -904,6 +969,36 @@ impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexTo<OB> {
|
|||
fn widen(&self) -> ObjectIndex<Object> {
|
||||
*self.as_ref()
|
||||
}
|
||||
|
||||
fn pre_add_edge(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
to_oi: ObjectIndex<OB>,
|
||||
ctx_span: Option<Span>,
|
||||
) -> Result<(), AsgError> {
|
||||
macro_rules! pre_add_edge {
|
||||
($ty:ident) => {
|
||||
$ty::pre_add_edge(
|
||||
asg,
|
||||
ProposedRel {
|
||||
from_oi: self.widen().must_narrow_into::<$ty>(),
|
||||
to_oi,
|
||||
ctx_span,
|
||||
},
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
match self.src_rel_ty() {
|
||||
ObjectRelTy::Root => pre_add_edge!(Root),
|
||||
ObjectRelTy::Pkg => pre_add_edge!(Pkg),
|
||||
ObjectRelTy::Ident => pre_add_edge!(Ident),
|
||||
ObjectRelTy::Expr => pre_add_edge!(Expr),
|
||||
ObjectRelTy::Tpl => pre_add_edge!(Tpl),
|
||||
ObjectRelTy::Meta => pre_add_edge!(Meta),
|
||||
ObjectRelTy::Doc => pre_add_edge!(Doc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexToTree<OB> {
|
||||
|
@ -918,6 +1013,17 @@ impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexToTree<OB> {
|
|||
Self(oito) => oito.widen(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pre_add_edge(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
to_oi: ObjectIndex<OB>,
|
||||
ctx_span: Option<Span>,
|
||||
) -> Result<(), AsgError> {
|
||||
match self {
|
||||
Self(oito) => oito.pre_add_edge(asg, to_oi, ctx_span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<OB: ObjectRelatable> From<ObjectIndexTo<OB>> for ObjectIndex<Object> {
|
||||
|
|
|
@ -66,7 +66,7 @@ impl ObjectIndex<Root> {
|
|||
&self,
|
||||
asg: &mut Asg,
|
||||
oi: ObjectIndex<Ident>,
|
||||
) -> ObjectIndex<Ident> {
|
||||
) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||
oi.add_edge_from(asg, *self, None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,35 +22,192 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use super::{prelude::*, Doc, Expr, Ident};
|
||||
use crate::{f::Functor, parse::util::SPair, span::Span};
|
||||
use crate::{asg::graph::ProposedRel, f::Map, parse::util::SPair, span::Span};
|
||||
|
||||
/// Template with associated name.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Tpl(Span);
|
||||
pub struct Tpl(Span, TplShape);
|
||||
|
||||
impl Tpl {
|
||||
pub fn new(span: Span) -> Self {
|
||||
Self(span, TplShape::default())
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
Self(span) => *span,
|
||||
Self(span, _) => *span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(span: Span) -> Self {
|
||||
Self(span)
|
||||
pub fn shape(&self) -> TplShape {
|
||||
match self {
|
||||
Self(_, shape) => *shape,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Functor<Span> for Tpl {
|
||||
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
|
||||
match self {
|
||||
Self(span) => Self(f(span)),
|
||||
}
|
||||
}
|
||||
impl_mono_map! {
|
||||
Span => Tpl(@, shape),
|
||||
TplShape => Tpl(span, @),
|
||||
}
|
||||
|
||||
impl Display for Tpl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "template")
|
||||
let Self(_, shape) = self;
|
||||
write!(f, "template with {shape}")
|
||||
}
|
||||
}
|
||||
|
||||
/// The "shape" of a template when expanded into an expression context.
|
||||
///
|
||||
/// The shape of a template can be thought of like a puzzle piece.
|
||||
/// Each application context permits a particular type of puzzle piece,
|
||||
/// and a compatible template must be expanded into it,
|
||||
/// or otherwise be made to be compatible.
|
||||
///
|
||||
/// Template shapes must be known statically by the time the definition has
|
||||
/// completed.
|
||||
/// A definition is not complete until all missing identifier references
|
||||
/// have been defined.
|
||||
/// A corollary of this is that templates applied _within_ templates will
|
||||
/// be able to determine their shape because the shape of the applied
|
||||
/// template will be known,
|
||||
/// allowing them to compose without compromising this property.
|
||||
///
|
||||
/// Objects that would typically be hoisted out of an expression context do
|
||||
/// not contribute to the shape of a template.
|
||||
/// That is---
|
||||
/// if an object would not typically be parented to the expansion context
|
||||
/// if manually written at that source location,
|
||||
/// then it will not be parented by a template expansion,
|
||||
/// and so will not contribute to its shape.
|
||||
///
|
||||
/// Dynamic Inner Template Application
|
||||
/// ==================================
|
||||
/// Sometimes the shape of inner applications cannot be known because their
|
||||
/// application depends on values of metavariables that are provided by
|
||||
/// the caller.
|
||||
/// One such example is that the body of the template is conditional
|
||||
/// depending on what values are provided to the template.
|
||||
///
|
||||
/// In this case,
|
||||
/// it may be necessary for the body of the template to _coerce_ into a
|
||||
/// statically known shape by wrapping the dynamic application in a known
|
||||
/// object.
|
||||
/// For example,
|
||||
/// if a template's body can conditionally expand into one of a set of
|
||||
/// [`TplShape::Expr`] templates,
|
||||
/// then that condition can be wrapped in an [`Expr`] object so that,
|
||||
/// no matter what the expansion,
|
||||
/// we'll always have a shape of [`TplShape::Expr`].
|
||||
///
|
||||
/// Expansion Ordering
|
||||
/// ==================
|
||||
/// By requiring a shape to be available by the time the definition of a
|
||||
/// template is completed,
|
||||
/// a system like [`AIR`](crate::asg::air) is able to pre-allocate an
|
||||
/// [`Object`] at the application site.
|
||||
/// This ensures that we are able to generate a graph with the proper edge
|
||||
/// ordering,
|
||||
/// which is important for non-commutative objects.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
|
||||
pub enum TplShape {
|
||||
/// The template will not inline any objects.
|
||||
#[default]
|
||||
Empty,
|
||||
|
||||
/// The template is non-[`Empty`](Self::Empty),
|
||||
/// but its shape cannot yet be determined.
|
||||
///
|
||||
/// A template's shape must be known by the time its definition has been
|
||||
/// completed.
|
||||
/// Note that a definition is not complete until all missing identifiers
|
||||
/// have been defined.
|
||||
Unknown,
|
||||
|
||||
/// The template can be expanded inline into a single [`Expr`].
|
||||
///
|
||||
/// This allows a template to be expanded into an expression context and
|
||||
/// provides assurances that it will not take the place of more than a
|
||||
/// single expression.
|
||||
///
|
||||
/// The associated span provides rationale for this shape assertion.
|
||||
/// The [`ObjectIndex`] is not cached here to avoid having to keep them
|
||||
/// in sync if the graph changes,
|
||||
/// in which case this rationale may represent the _original_
|
||||
/// rationale before any graph rewriting.
|
||||
Expr(Span),
|
||||
}
|
||||
|
||||
impl TplShape {
|
||||
/// Attempt to adapt a template shape to that of another.
|
||||
///
|
||||
/// If the shape of `other` is a refinement of the shape of `self`,
|
||||
/// then `other` will be chosen.
|
||||
/// If the shape of `other` conflicts with `self`,
|
||||
/// an appropriate [`AsgError`] will describe the problem.
|
||||
fn try_adapt_to(
|
||||
self,
|
||||
other: TplShape,
|
||||
tpl_name: Option<SPair>,
|
||||
) -> Result<Self, (Self, AsgError)> {
|
||||
match (self, other) {
|
||||
(TplShape::Expr(first_span), TplShape::Expr(bad_span)) => Err((
|
||||
self,
|
||||
AsgError::TplShapeExprMulti(tpl_name, bad_span, first_span),
|
||||
)),
|
||||
|
||||
// Higher levels of specificity take precedence.
|
||||
(shape @ TplShape::Expr(_), TplShape::Empty)
|
||||
| (TplShape::Empty, shape @ TplShape::Expr(_))
|
||||
| (shape @ TplShape::Empty, TplShape::Empty) => Ok(shape),
|
||||
|
||||
// Unknown is not yet handled.
|
||||
(
|
||||
TplShape::Unknown,
|
||||
TplShape::Empty | TplShape::Unknown | TplShape::Expr(_),
|
||||
)
|
||||
| (TplShape::Empty | TplShape::Expr(_), TplShape::Unknown) => {
|
||||
todo!("TplShape::Unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the shape stores [`Span`] information as evidence of inference,
|
||||
/// overwrite it with the provided `span`.
|
||||
///
|
||||
/// This is most commonly used to encapsulate a previous shape
|
||||
/// inference.
|
||||
/// For example,
|
||||
/// a template application's span may overwrite the inferred shape of
|
||||
/// its own body.
|
||||
fn overwrite_span_if_any(self, span: Span) -> Self {
|
||||
match self {
|
||||
TplShape::Empty | TplShape::Unknown => self,
|
||||
TplShape::Expr(_) => TplShape::Expr(span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to adapt a template shape to that of another.
|
||||
///
|
||||
/// This returns a partially applied [`TplShape::try_adapt_to`],
|
||||
/// where the remaining argument is `self`.
|
||||
fn try_adapt_to(
|
||||
other: TplShape,
|
||||
tpl_name: Option<SPair>,
|
||||
) -> impl FnOnce(TplShape) -> Result<TplShape, (TplShape, AsgError)> {
|
||||
move |s| s.try_adapt_to(other, tpl_name)
|
||||
}
|
||||
|
||||
impl Display for TplShape {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
// phrase as "template with ..."
|
||||
match self {
|
||||
TplShape::Unknown => write!(f, "unknown shape"),
|
||||
TplShape::Empty => write!(f, "empty shape"),
|
||||
TplShape::Expr(_) => write!(f, "shape of a single expression"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,14 +217,43 @@ object_rel! {
|
|||
Tpl -> {
|
||||
// Expressions must be able to be anonymous to allow templates in
|
||||
// any `Expr` context.
|
||||
tree Expr,
|
||||
tree Expr {
|
||||
fn pre_add_edge(
|
||||
asg: &mut Asg,
|
||||
rel: ProposedRel<Self, Expr>,
|
||||
) -> Result<(), AsgError> {
|
||||
let tpl_name = rel.from_oi.name(asg);
|
||||
let span = rel.to_oi.resolve(asg).span();
|
||||
|
||||
rel.from_oi.try_map_obj_inner(
|
||||
asg,
|
||||
try_adapt_to(TplShape::Expr(span), tpl_name),
|
||||
).map(|_| ())
|
||||
}
|
||||
},
|
||||
|
||||
// Identifiers are used for both references and identifiers that
|
||||
// will expand into an application site.
|
||||
dyn Ident,
|
||||
|
||||
// Template application.
|
||||
tree Tpl,
|
||||
tree Tpl {
|
||||
fn pre_add_edge(
|
||||
asg: &mut Asg,
|
||||
rel: ProposedRel<Self, Tpl>,
|
||||
) -> Result<(), AsgError> {
|
||||
let tpl_name = rel.from_oi.name(asg);
|
||||
let apply = rel.to_oi.resolve(asg);
|
||||
let apply_shape = apply
|
||||
.shape()
|
||||
.overwrite_span_if_any(apply.span());
|
||||
|
||||
rel.from_oi.try_map_obj_inner(
|
||||
asg,
|
||||
try_adapt_to(apply_shape, tpl_name),
|
||||
).map(|_| ())
|
||||
}
|
||||
},
|
||||
|
||||
// Short template description and arbitrary documentation to be
|
||||
// expanded into the application site.
|
||||
|
@ -76,13 +262,23 @@ object_rel! {
|
|||
}
|
||||
|
||||
impl ObjectIndex<Tpl> {
|
||||
/// Name of template,
|
||||
/// if any.
|
||||
///
|
||||
/// A template may either be anonymous,
|
||||
/// or it may not yet have a name because it is still under
|
||||
/// construction.
|
||||
pub fn name(&self, asg: &Asg) -> Option<SPair> {
|
||||
self.ident(asg).and_then(|oi| oi.resolve(asg).name())
|
||||
}
|
||||
|
||||
/// Attempt to complete a template definition.
|
||||
///
|
||||
/// This updates the span of the template to encompass the entire
|
||||
/// definition.
|
||||
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
|
||||
self.map_obj(asg, |tpl| {
|
||||
tpl.map(|open_span| {
|
||||
tpl.map(|open_span: Span| {
|
||||
open_span.merge(close_span).unwrap_or(open_span)
|
||||
})
|
||||
})
|
||||
|
@ -98,7 +294,7 @@ impl ObjectIndex<Tpl> {
|
|||
asg: &mut Asg,
|
||||
oi_apply: ObjectIndex<Ident>,
|
||||
ref_span: Span,
|
||||
) -> Self {
|
||||
) -> Result<Self, AsgError> {
|
||||
self.add_edge_to(asg, oi_apply, Some(ref_span))
|
||||
}
|
||||
|
||||
|
@ -116,13 +312,17 @@ impl ObjectIndex<Tpl> {
|
|||
self,
|
||||
asg: &mut Asg,
|
||||
oi_target_parent: OP,
|
||||
) -> Self {
|
||||
) -> Result<Self, AsgError> {
|
||||
self.add_edge_from(asg, oi_target_parent, None)
|
||||
}
|
||||
|
||||
/// Arbitrary text serving as documentation in a literate style,
|
||||
/// to be expanded into the application site.
|
||||
pub fn append_doc_text(&self, asg: &mut Asg, text: SPair) -> Self {
|
||||
pub fn append_doc_text(
|
||||
&self,
|
||||
asg: &mut Asg,
|
||||
text: SPair,
|
||||
) -> Result<Self, AsgError> {
|
||||
let oi_doc = asg.create(Doc::new_text(text));
|
||||
self.add_edge_to(asg, oi_doc, None)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
//! This is a [depth-first search][w-depth-first-search]
|
||||
//! visiting all nodes that are _reachable_ from the graph root
|
||||
//! (see [`Asg::root`]).
|
||||
//! [`ObjectIndex`]es are emitted in pre-order during the traversal,
|
||||
//! [`TreeWalkRel`]s are emitted in pre-order during the traversal,
|
||||
//! and may be emitted more than once if
|
||||
//! (a) they are the destination of cross edges or
|
||||
//! (b) they are shared between trees
|
||||
|
@ -103,7 +103,7 @@
|
|||
//!
|
||||
//! Depth Tracking
|
||||
//! ==============
|
||||
//! Each [`ObjectIndex`] emitted by this traversal is accompanied by a
|
||||
//! Each [`TreeWalkRel`] emitted by this traversal is accompanied by a
|
||||
//! [`Depth`] representing the length of the current path relative to the
|
||||
//! [`Asg`] root.
|
||||
//! Since the ASG root is never emitted,
|
||||
|
@ -135,11 +135,34 @@
|
|||
//! because the [`Depth`] represents the current _path_,
|
||||
//! the same [`ObjectIndex`] may be emitted multiple times with different
|
||||
//! [`Depth`]s.
|
||||
//!
|
||||
//!
|
||||
//! Edge Order
|
||||
//! ==========
|
||||
//! The order of edges in the tree is important,
|
||||
//! since there are a number of non-commutative operations in TAME.
|
||||
//! Ordering is determined by a [`TreeEdgeOrder`] strategy:
|
||||
//!
|
||||
//! 1. [`NaturalTreeEdgeOrder`] will traverse in the same order that edges
|
||||
//! were added to the graph.
|
||||
//! This ordering is fine for most internal operations,
|
||||
//! but is not suitable for [`tree_reconstruction`].
|
||||
//!
|
||||
//! 2. [`SourceCompatibleTreeEdgeOrder`] traverses edges in an order that
|
||||
//! will produce a valid source file for [NIR XML](crate::nir).
|
||||
//! For example,
|
||||
//! templates require a header and body section,
|
||||
//! where [`Asg`] permits mixing them.
|
||||
//! This maintains natural order in all other cases.
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::{fmt::Display, marker::PhantomData};
|
||||
|
||||
use super::super::{object::DynObjectRel, Asg, Object, ObjectIndex};
|
||||
use super::super::{
|
||||
object::{self, DynObjectRel},
|
||||
Asg,
|
||||
};
|
||||
use crate::{
|
||||
asg::graph::object::ObjectTy,
|
||||
parse::{self, Token},
|
||||
span::{Span, UNKNOWN_SPAN},
|
||||
};
|
||||
|
@ -149,7 +172,9 @@ use crate::{
|
|||
pub use crate::xir::flat::Depth;
|
||||
|
||||
#[cfg(doc)]
|
||||
use super::super::object::ObjectRel;
|
||||
use super::super::object::{ObjectIndex, ObjectRel};
|
||||
|
||||
pub use order::*;
|
||||
|
||||
/// Produce an iterator suitable for reconstructing a source tree based on
|
||||
/// the contents of the [`Asg`].
|
||||
|
@ -160,7 +185,9 @@ use super::super::object::ObjectRel;
|
|||
///
|
||||
/// See the [module-level documentation](super) for important information
|
||||
/// about this traversal.
|
||||
pub fn tree_reconstruction(asg: &Asg) -> TreePreOrderDfs {
|
||||
pub fn tree_reconstruction(
|
||||
asg: &Asg,
|
||||
) -> TreePreOrderDfs<SourceCompatibleTreeEdgeOrder> {
|
||||
TreePreOrderDfs::new(asg)
|
||||
}
|
||||
|
||||
|
@ -170,11 +197,11 @@ pub fn tree_reconstruction(asg: &Asg) -> TreePreOrderDfs {
|
|||
/// _it does not track visited nodes_,
|
||||
/// relying instead on the ontology and recognition of cross edges to
|
||||
/// produce the intended spanning tree.
|
||||
/// An [`ObjectIndex`] that is the target of a cross edge will be output
|
||||
/// An object that is the target of a cross edge will be output
|
||||
/// more than once.
|
||||
///
|
||||
/// See [`tree_reconstruction`] for more information.
|
||||
pub struct TreePreOrderDfs<'a> {
|
||||
pub struct TreePreOrderDfs<'a, O: TreeEdgeOrder> {
|
||||
/// Reference [`Asg`].
|
||||
///
|
||||
/// Holding a reference to the [`Asg`] allows us to serve conveniently
|
||||
|
@ -189,6 +216,8 @@ pub struct TreePreOrderDfs<'a> {
|
|||
///
|
||||
/// The traversal ends once the stack becomes empty.
|
||||
stack: Vec<(DynObjectRel, Depth)>,
|
||||
|
||||
_phantom: PhantomData<O>,
|
||||
}
|
||||
|
||||
/// Initial size of the DFS stack for [`TreePreOrderDfs`].
|
||||
|
@ -196,34 +225,40 @@ pub struct TreePreOrderDfs<'a> {
|
|||
/// TODO: Derive a heuristic from our systems.
|
||||
const TREE_INITIAL_STACK_SIZE: usize = 8;
|
||||
|
||||
impl<'a> TreePreOrderDfs<'a> {
|
||||
impl<'a, O: TreeEdgeOrder> TreePreOrderDfs<'a, O> {
|
||||
fn new(asg: &'a Asg) -> Self {
|
||||
let span = UNKNOWN_SPAN;
|
||||
|
||||
let mut dfs = Self {
|
||||
asg,
|
||||
stack: Vec::with_capacity(TREE_INITIAL_STACK_SIZE),
|
||||
_phantom: PhantomData,
|
||||
};
|
||||
|
||||
let root = asg.root(span);
|
||||
dfs.push_edges_of(root.widen(), Depth::root());
|
||||
let root_rel = DynObjectRel::new(
|
||||
root.rel_ty(),
|
||||
root.rel_ty(),
|
||||
root.widen(),
|
||||
root.widen(),
|
||||
None,
|
||||
);
|
||||
|
||||
dfs.push_edges_of(&root_rel, Depth::root());
|
||||
dfs
|
||||
}
|
||||
|
||||
fn push_edges_of(&mut self, oi: ObjectIndex<Object>, depth: Depth) {
|
||||
self.asg
|
||||
.edges_dyn(oi)
|
||||
.map(|rel| (rel, depth.child_depth()))
|
||||
.collect_into(&mut self.stack);
|
||||
fn push_edges_of(&mut self, rel: &DynObjectRel, depth: Depth) {
|
||||
O::push_edges_of(self.asg, rel, depth, &mut self.stack)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TreePreOrderDfs<'a> {
|
||||
impl<'a, O: TreeEdgeOrder> Iterator for TreePreOrderDfs<'a, O> {
|
||||
type Item = TreeWalkRel;
|
||||
|
||||
/// Produce the next [`ObjectIndex`] from the traversal in pre-order.
|
||||
/// Produce the next [`TreeWalkRel`] from the traversal in pre-order.
|
||||
///
|
||||
/// An [`ObjectIndex`] may be emitted more than once;
|
||||
/// An object may be emitted more than once;
|
||||
/// see [`tree_reconstruction`] for more information.
|
||||
///
|
||||
/// Each item contains a corresponding [`Depth`],
|
||||
|
@ -233,12 +268,17 @@ impl<'a> Iterator for TreePreOrderDfs<'a> {
|
|||
/// This depth is the only way to derive the tree structure from this
|
||||
/// iterator.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Note that we pushed edges in the order that `Asg` provided,
|
||||
// and now pop them,
|
||||
// which causes us to visit the edges in reverse.
|
||||
// Because of implementation details (Petgraph),
|
||||
// this reversal ends up giving us the correct ordering.
|
||||
let (rel, depth) = self.stack.pop()?;
|
||||
|
||||
// We want to output information about references to other trees,
|
||||
// but we must not traverse into them.
|
||||
if !rel.is_cross_edge() {
|
||||
self.push_edges_of(*rel.target(), depth);
|
||||
self.push_edges_of(&rel, depth);
|
||||
}
|
||||
|
||||
Some(TreeWalkRel(rel, depth))
|
||||
|
@ -283,5 +323,196 @@ impl Token for TreeWalkRel {
|
|||
|
||||
impl parse::Object for TreeWalkRel {}
|
||||
|
||||
mod order {
|
||||
use crate::asg::graph::object::ObjectIndexRefined;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Emit edges in the same order that they were added to the graph.
|
||||
///
|
||||
/// Various parts of the system take care in what order edges are
|
||||
/// added.
|
||||
/// This ordering is important for operations that are not commutative.
|
||||
pub struct NaturalTreeEdgeOrder;
|
||||
|
||||
/// Emit edges in as close to [`NaturalTreeEdgeOrder`] as possible,
|
||||
/// sorting only where object ordering would otherwise be
|
||||
/// syntactically or grammatically invalid for streaming source
|
||||
/// generation.
|
||||
///
|
||||
/// Unless otherwise mentioned below,
|
||||
/// ordering for objects will be the same as
|
||||
/// [`NaturalTreeEdgeOrder`].
|
||||
///
|
||||
/// Template Headers
|
||||
/// ----------------
|
||||
/// For [NIR XML](crate::nir) sources for TAME,
|
||||
/// templates require that parameters be placed in a header,
|
||||
/// before all objects representing the body of the template.
|
||||
/// This is necessary to disambiguate `<param>` nodes in sources,
|
||||
/// even though no such ambiguity exists on the [`Asg`].
|
||||
///
|
||||
/// All metavariables representing template params will be hoisted into
|
||||
/// the header,
|
||||
/// immediately after any template description [`Doc`](object::Doc)
|
||||
/// node.
|
||||
/// This is a stable partial ordering:
|
||||
/// the ordering of parameters relative to one-another will not change,
|
||||
/// nor will the order of any objects in the body of the template.
|
||||
/// See [`TplOrder`].
|
||||
pub struct SourceCompatibleTreeEdgeOrder;
|
||||
|
||||
/// Order in which tree edges are emitted.
|
||||
///
|
||||
/// TAME is sensitive to edge ordering for non-commutative operations.
|
||||
/// For source lk
|
||||
pub trait TreeEdgeOrder {
|
||||
/// Push the edges of `rel` onto the `target` stack.
|
||||
///
|
||||
/// The system will pop edges off of `target` to determine what edge
|
||||
/// to traverse next.
|
||||
/// This means that edges will be visited in an order that is the
|
||||
/// reverse of the elements of `target`.
|
||||
fn push_edges_of(
|
||||
asg: &Asg,
|
||||
rel: &DynObjectRel,
|
||||
depth: Depth,
|
||||
target: &mut Vec<(DynObjectRel, Depth)>,
|
||||
);
|
||||
}
|
||||
|
||||
impl TreeEdgeOrder for NaturalTreeEdgeOrder {
|
||||
fn push_edges_of(
|
||||
asg: &Asg,
|
||||
rel: &DynObjectRel,
|
||||
depth: Depth,
|
||||
stack: &mut Vec<(DynObjectRel, Depth)>,
|
||||
) {
|
||||
let oi = *rel.target();
|
||||
|
||||
asg.edges_dyn(oi)
|
||||
.map(|rel| (rel, depth.child_depth()))
|
||||
.collect_into(stack);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ordering of template children for [`SourceCompatibleTreeEdgeOrder`].
|
||||
///
|
||||
/// Since these edges are added to a _stack_,
|
||||
/// larger values will be `pop`'d _before_ smaller ones,
|
||||
/// as demonstrated by the variant order.
|
||||
/// That is:
|
||||
/// we sort in the reverse order that we will visit them.
|
||||
///
|
||||
/// ```text
|
||||
/// ------> Sort direction
|
||||
/// [ Body, Body, Param, Param, Desc ]
|
||||
/// <------ visit direction
|
||||
/// (pop from stack)
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
enum TplOrder {
|
||||
/// Forcing the template description to come first mimics
|
||||
/// the expected [`NaturalTreeEdgeOrder`].
|
||||
///
|
||||
/// We don't want to re-order things before it,
|
||||
/// since we want to be able to stream source output,
|
||||
/// e.g. `template/@desc` in XML.
|
||||
TplDesc = 2,
|
||||
/// Template parameters must appear in the template
|
||||
/// "header",
|
||||
/// which is all the nodes before the body that is to be
|
||||
/// expanded on application.
|
||||
Param = 1,
|
||||
/// The body of the template is anything that is not part of
|
||||
/// the header.
|
||||
///
|
||||
/// The body represents the contents of the template that
|
||||
/// will be expanded in place of any template application.
|
||||
Body = 0,
|
||||
}
|
||||
|
||||
impl TreeEdgeOrder for SourceCompatibleTreeEdgeOrder {
|
||||
fn push_edges_of(
|
||||
asg: &Asg,
|
||||
rel: &DynObjectRel,
|
||||
depth: Depth,
|
||||
stack: &mut Vec<(DynObjectRel, Depth)>,
|
||||
) {
|
||||
// We start by adding edges to the stack in natural order,
|
||||
// remembering the original stack offset so that we can sort
|
||||
// just the portion that we added.
|
||||
let offset = stack.len();
|
||||
NaturalTreeEdgeOrder::push_edges_of(asg, rel, depth, stack);
|
||||
|
||||
use ObjectTy::*;
|
||||
match rel.target_ty() {
|
||||
// Templates require partial ordering into a header and a body.
|
||||
Tpl => {
|
||||
// This represents the portion of the stack that we just
|
||||
// contributed to via [`NaturalTreeEdgeOrder`] above.
|
||||
let part = &mut stack[offset..];
|
||||
|
||||
// TODO: Ideally we'd have metadata on the edge itself
|
||||
// about what type of object an `Ident` points to,
|
||||
// so that we can use the faster `sort_by_key`.
|
||||
// With that said,
|
||||
// initial profiling on large packages with many
|
||||
// template applications did not yield a
|
||||
// significant difference between the two methods on
|
||||
// system-level tests,
|
||||
// and given the small number of template
|
||||
// children,
|
||||
// this consideration may be a micro-optimization.
|
||||
// An unstable sort is avoided because we wish to
|
||||
// retain natural ordering as much as possible.
|
||||
//
|
||||
// TODO: In practice,
|
||||
// most of these are template _applications_ resulting
|
||||
// from `tplshort` desugaring.
|
||||
// At the time of writing,
|
||||
// _all_ need sorting because `Ref` is output before
|
||||
// the params.
|
||||
// We could recognize them as template applications at
|
||||
// some point and leave their order alone,
|
||||
// but at the time of writing we have no imports,
|
||||
// and so most refs are `Ident::Missing` in
|
||||
// practice.
|
||||
// Once template imports are taken care of,
|
||||
// then _nearly every single `Tpl` in practice` will
|
||||
// already be ordered and this will rarely have any
|
||||
// reordering to do
|
||||
// (just hoisting for interpolation in template
|
||||
// definitions,
|
||||
// which are not all that common relative to
|
||||
// everything else).
|
||||
use ObjectIndexRefined::*;
|
||||
part.sort_by_cached_key(|(child_rel, _)| {
|
||||
match child_rel.refine_target() {
|
||||
Ident(oi_ident) => {
|
||||
// This is the (comparatively) expensive lookup,
|
||||
// requiring a small graph traversal.
|
||||
match oi_ident.definition::<object::Meta>(asg) {
|
||||
Some(_) => TplOrder::Param,
|
||||
None => TplOrder::Body,
|
||||
}
|
||||
}
|
||||
|
||||
Doc(_) => TplOrder::TplDesc,
|
||||
|
||||
Root(_) | Pkg(_) | Expr(_) | Tpl(_) | Meta(_) => {
|
||||
TplOrder::Body
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Leave natural (graph) ordering for everything else.
|
||||
Root | Pkg | Ident | Expr | Meta | Doc => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
|
|
@ -24,8 +24,11 @@ use crate::{
|
|||
graph::object::ObjectRelTy,
|
||||
ExprOp,
|
||||
},
|
||||
f::Functor,
|
||||
parse::{util::SPair, ParseState},
|
||||
f::Map,
|
||||
parse::{
|
||||
util::{spair, SPair},
|
||||
ParseState,
|
||||
},
|
||||
span::{dummy::*, Span},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
@ -204,21 +207,21 @@ fn traverses_ontological_tree_tpl_apply() {
|
|||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||
// The template that will be applied.
|
||||
TplStart(S2),
|
||||
BindIdent(id_tpl),
|
||||
|
||||
// This test is light for now,
|
||||
// until we develop the ASG further.
|
||||
TplEnd(S4),
|
||||
|
||||
// Apply the above template.
|
||||
TplStart(S5),
|
||||
RefIdent(ref_tpl),
|
||||
|
||||
MetaStart(S7),
|
||||
BindIdent(id_param),
|
||||
MetaLexeme(value_param),
|
||||
MetaEnd(S10),
|
||||
TplEndRef(S11), // notice the `Ref` at the end
|
||||
BindIdent(id_tpl), // <-,
|
||||
// |
|
||||
// This test is light for now, // |
|
||||
// until we develop the ASG further. // |
|
||||
TplEnd(S4), // |
|
||||
// |
|
||||
// Apply the above template. // |
|
||||
TplStart(S5), // |
|
||||
RefIdent(ref_tpl), // |
|
||||
// |
|
||||
MetaStart(S7), // |
|
||||
BindIdent(id_param), // |
|
||||
MetaLexeme(value_param), // |
|
||||
MetaEnd(S10), // |
|
||||
TplEndRef(S11), // notice the `Ref` at the end --'
|
||||
PkgEnd(S12),
|
||||
];
|
||||
|
||||
|
@ -234,9 +237,13 @@ fn traverses_ontological_tree_tpl_apply() {
|
|||
(d(Pkg, Ident, m(S1, S12), S3, None ), Depth(2)),
|
||||
(d(Ident, Tpl, S3, m(S2, S4), None ), Depth(3)),
|
||||
(d(Pkg, Tpl, m(S1, S12), m(S5, S11), None ), Depth(2)),
|
||||
/*cross*/ (d(Tpl, Ident, m(S5, S11), S3, Some(S6)), Depth(3)),
|
||||
(d(Tpl, Ident, m(S5, S11), S8, None ), Depth(3)),
|
||||
(d(Ident, Meta, S8, m(S7, S10), None ), Depth(4)),
|
||||
/*cross*/ (d(Tpl, Ident, m(S5, S11), S3, Some(S6)), Depth(3)),
|
||||
// ^
|
||||
// `- Note that the cross edge was moved to the bottom
|
||||
// because all template params are moved into the
|
||||
// template header for `SourceCompatibleTreeEdgeOrder`.
|
||||
],
|
||||
tree_reconstruction_report(toks),
|
||||
);
|
||||
|
@ -328,3 +335,147 @@ fn traverses_ontological_tree_tpl_within_template() {
|
|||
tree_reconstruction_report(toks),
|
||||
);
|
||||
}
|
||||
|
||||
// Metavariables are used to represent template parameters,
|
||||
// and are used to perform various lexical manipulations.
|
||||
// The most fundamental of them is concatenation,
|
||||
// and in the special case of concatenating a single value,
|
||||
// assignment.
|
||||
//
|
||||
// This asserts that concatenation results in the expected graph and that
|
||||
// the traversal respects concatenation order.
|
||||
#[test]
|
||||
fn traverses_ontological_tree_complex_tpl_meta() {
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
TplStart(S2),
|
||||
BindIdent(spair("_tpl_", S3)),
|
||||
|
||||
// -- Above this line was setup -- //
|
||||
|
||||
MetaStart(S4),
|
||||
BindIdent(spair("@param@", S5)),
|
||||
|
||||
// It will be important to observe that ordering
|
||||
// is respected during traversal,
|
||||
// otherwise concatenation order will be wrong.
|
||||
MetaLexeme(spair("foo", S6)),
|
||||
RefIdent(spair("@other@", S7)), // --.
|
||||
MetaLexeme(spair("bar", S8)), // |
|
||||
MetaEnd(S9), // |
|
||||
// |
|
||||
MetaStart(S10), // |
|
||||
BindIdent(spair("@other@", S11)), // <-'
|
||||
MetaEnd(S12),
|
||||
TplEnd(S13),
|
||||
PkgEnd(S14),
|
||||
];
|
||||
|
||||
// We need more concise expressions for the below table of values.
|
||||
let d = DynObjectRel::new;
|
||||
let m = |a: Span, b: Span| a.merge(b).unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
// A -|-> B | A span -|-> B span | espan | depth
|
||||
vec![//-----|-------|-----------|------------|--------|-----------------
|
||||
(d(Root, Pkg, SU, m(S1, S14), None ), Depth(1)),
|
||||
(d(Pkg, Ident, m(S1, S14), S3, None ), Depth(2)),
|
||||
(d(Ident, Tpl, S3, m(S2, S13), None ), Depth(3)),
|
||||
(d(Tpl, Ident, m(S2, S13), S5, None ), Depth(4)),
|
||||
(d(Ident, Meta, S5, m(S4, S9), None ), Depth(5)),
|
||||
(d(Meta, Meta, m(S4, S9), S6, None ), Depth(6)),
|
||||
/*cross*/ (d(Meta, Ident, m(S4, S9), S11, Some(S7)), Depth(6)),
|
||||
(d(Meta, Meta, m(S4, S9), S8, None, ), Depth(6)),
|
||||
(d(Tpl, Ident, m(S2, S13), S11, None ), Depth(4)),
|
||||
(d(Ident, Meta, S11, m(S10, S12), None ), Depth(5)),
|
||||
],
|
||||
tree_reconstruction_report(toks),
|
||||
);
|
||||
}
|
||||
|
||||
// TAME's grammar expects that template parameters be defined in a header,
|
||||
// before the template body.
|
||||
// This is important for disambiguating `<param`> in the sources,
|
||||
// since they could otherwise refer to other types of parameters.
|
||||
//
|
||||
// TAMER generates metavariables during interpolation,
|
||||
// causing params to be mixed with the body of the template;
|
||||
// this is reflected in the natural ordering.
|
||||
// But this would result in a semantically invalid source reconstruction,
|
||||
// and so we must reorder edges during traversal such that the
|
||||
// metavariables representing template parameters are visited _before_
|
||||
// everything else.
|
||||
#[test]
|
||||
fn tpl_header_source_order() {
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1, spair("/pkg", S1)),
|
||||
TplStart(S2),
|
||||
BindIdent(spair("_tpl_", S3)),
|
||||
|
||||
// -- Above this line was setup -- //
|
||||
|
||||
MetaStart(S4),
|
||||
BindIdent(spair("@param_before@", S5)),
|
||||
MetaEnd(S6),
|
||||
|
||||
// Dangling (no Ident)
|
||||
ExprStart(ExprOp::Sum, S7),
|
||||
ExprEnd(S8),
|
||||
|
||||
MetaStart(S9),
|
||||
BindIdent(spair("@param_after_a@", S10)),
|
||||
MetaEnd(S11),
|
||||
|
||||
MetaStart(S12),
|
||||
BindIdent(spair("@param_after_b@", S13)),
|
||||
MetaEnd(S14),
|
||||
|
||||
// Reachable (with an Ident)
|
||||
// (We want to be sure that we're not just hoisting all Idents
|
||||
// without checking that they're actually Meta
|
||||
// definitions).
|
||||
ExprStart(ExprOp::Sum, S15),
|
||||
BindIdent(spair("sum", S16)),
|
||||
ExprEnd(S17),
|
||||
TplEnd(S18),
|
||||
PkgEnd(S19),
|
||||
];
|
||||
|
||||
// We need more concise expressions for the below table of values.
|
||||
let d = DynObjectRel::new;
|
||||
let m = |a: Span, b: Span| a.merge(b).unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
// A -|-> B | A span -|-> B span | espan | depth
|
||||
vec![//-----|-------|-----------|------------|--------|-----------------
|
||||
(d(Root, Pkg, SU, m(S1, S19), None ), Depth(1)),
|
||||
(d(Pkg, Ident, m(S1, S19), S3, None ), Depth(2)),
|
||||
(d(Ident, Tpl, S3, m(S2, S18), None ), Depth(3)),
|
||||
(d(Tpl, Ident, m(S2, S18), S5, None ), Depth(4)),
|
||||
(d(Ident, Meta, S5, m(S4, S6), None ), Depth(5)),
|
||||
// ,-----------------------------------------------------------------------,
|
||||
(d(Tpl, Ident, m(S2, S18), S10, None ), Depth(4)),
|
||||
(d(Ident, Meta, S10, m(S9, S11), None ), Depth(5)),
|
||||
(d(Tpl, Ident, m(S2, S18), S13, None ), Depth(4)),
|
||||
(d(Ident, Meta, S13, m(S12, S14), None ), Depth(5)),
|
||||
// '-----------------------------------------------------------------------'
|
||||
(d(Tpl, Expr, m(S2, S18), m(S7, S8), None ), Depth(4)),
|
||||
(d(Tpl, Ident, m(S2, S18), S16, None ), Depth(4)),
|
||||
(d(Ident, Expr, S16, m(S15, S17), None ), Depth(5)),
|
||||
],
|
||||
// ^ The enclosed Ident->Meta pairs above have been hoisted out of
|
||||
// the body and into the header of `Tpl`.
|
||||
// This is a stable, partial ordering;
|
||||
// elements do not change poisitions relative to one-another
|
||||
// with the exception of hoisting.
|
||||
// This means that all hoisted params retain their order relative
|
||||
// to other params,
|
||||
// and all objects in the body retain their positions relative
|
||||
// to other objects in the body.
|
||||
tree_reconstruction_report(toks),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ use crate::{
|
|||
},
|
||||
};
|
||||
use arrayvec::ArrayVec;
|
||||
use std::{convert::Infallible, fmt::Display, marker::PhantomData};
|
||||
use std::{fmt::Display, marker::PhantomData};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AsgTreeToXirf<'a> {
|
||||
|
@ -75,10 +75,14 @@ impl<'a> Display for AsgTreeToXirf<'a> {
|
|||
|
||||
type Xirf = XirfToken<Text>;
|
||||
|
||||
diagnostic_infallible! {
|
||||
pub enum AsgTreeToXirfError {}
|
||||
}
|
||||
|
||||
impl<'a> ParseState for AsgTreeToXirf<'a> {
|
||||
type Token = TreeWalkRel;
|
||||
type Object = Xirf;
|
||||
type Error = Infallible;
|
||||
type Error = AsgTreeToXirfError;
|
||||
type Context = TreeContext<'a>;
|
||||
|
||||
fn parse_token(
|
||||
|
@ -150,6 +154,10 @@ type TokenStack = ArrayVec<Xirf, TOK_STACK_SIZE>;
|
|||
pub struct TreeContext<'a> {
|
||||
stack: TokenStack,
|
||||
asg: &'a Asg,
|
||||
|
||||
/// Whether the most recently encountered template has been interpreted
|
||||
/// as an application.
|
||||
tpl_apply: Option<ObjectIndex<Tpl>>,
|
||||
}
|
||||
|
||||
impl<'a> TreeContext<'a> {
|
||||
|
@ -185,18 +193,30 @@ impl<'a> TreeContext<'a> {
|
|||
),
|
||||
},
|
||||
|
||||
// Identifiers will be considered in context;
|
||||
// pass over it for now.
|
||||
// But we must not skip over its depth,
|
||||
// otherwise we parent a following sibling at a matching
|
||||
// depth;
|
||||
// this close will force the auto-closing system to close
|
||||
// any siblings in preparation for the object to follow.
|
||||
Object::Ident((ident, _)) => Some(Xirf::Close(
|
||||
None,
|
||||
CloseSpan::without_name_span(ident.span()),
|
||||
depth,
|
||||
)),
|
||||
Object::Ident((ident, oi_ident)) => match paired_rel.source() {
|
||||
Object::Meta(..) => {
|
||||
self.emit_tpl_param_value(ident, oi_ident, depth)
|
||||
}
|
||||
|
||||
// All other identifiers will be considered in context;
|
||||
// pass over it for now.
|
||||
// But we must not skip over its depth,
|
||||
// otherwise we parent a following sibling at a matching
|
||||
// depth;
|
||||
// this close will force the auto-closing system to
|
||||
// close any siblings in preparation for the object to
|
||||
// follow.
|
||||
Object::Root(..)
|
||||
| Object::Pkg(..)
|
||||
| Object::Ident(..)
|
||||
| Object::Expr(..)
|
||||
| Object::Tpl(..)
|
||||
| Object::Doc(..) => Some(Xirf::Close(
|
||||
None,
|
||||
CloseSpan::without_name_span(ident.span()),
|
||||
depth,
|
||||
)),
|
||||
},
|
||||
|
||||
Object::Expr((expr, oi_expr)) => {
|
||||
self.emit_expr(expr, *oi_expr, paired_rel.source(), depth)
|
||||
|
@ -206,9 +226,10 @@ impl<'a> TreeContext<'a> {
|
|||
self.emit_template(tpl, *oi_tpl, paired_rel.source(), depth)
|
||||
}
|
||||
|
||||
Object::Meta((meta, oi_meta)) => {
|
||||
self.emit_tpl_arg(meta, *oi_meta, depth)
|
||||
}
|
||||
Object::Meta((meta, oi_meta)) => match self.tpl_apply {
|
||||
Some(_) => self.emit_tpl_arg(meta, *oi_meta, depth),
|
||||
None => self.emit_tpl_param(meta, *oi_meta, depth),
|
||||
},
|
||||
|
||||
Object::Doc((doc, oi_doc)) => {
|
||||
self.emit_doc(doc, *oi_doc, paired_rel.source(), depth)
|
||||
|
@ -273,8 +294,9 @@ impl<'a> TreeContext<'a> {
|
|||
depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
match src {
|
||||
Object::Ident((ident, _)) => {
|
||||
self.emit_expr_ident(expr, ident, depth)
|
||||
Object::Ident((_, oi_ident)) => {
|
||||
let name = oi_ident.name_or_meta(self.asg);
|
||||
self.emit_expr_ident(expr, name, depth)
|
||||
}
|
||||
Object::Expr((pexpr, _)) => match (pexpr.op(), expr.op()) {
|
||||
(ExprOp::Conj | ExprOp::Disj, ExprOp::Eq) => {
|
||||
|
@ -306,7 +328,7 @@ impl<'a> TreeContext<'a> {
|
|||
fn emit_expr_ident(
|
||||
&mut self,
|
||||
expr: &Expr,
|
||||
ident: &Ident,
|
||||
name: SPair,
|
||||
depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
let (qname, ident_qname) = match expr.op() {
|
||||
|
@ -322,8 +344,8 @@ impl<'a> TreeContext<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
let ispan = ident.span();
|
||||
self.push(Xirf::attr(ident_qname, ident.name(), (ispan, ispan)));
|
||||
let span = name.span();
|
||||
self.push(Xirf::attr(ident_qname, name, (span, span)));
|
||||
|
||||
Some(Xirf::open(
|
||||
qname,
|
||||
|
@ -344,21 +366,15 @@ impl<'a> TreeContext<'a> {
|
|||
let mut edges = oi_expr.edges_filtered::<Ident>(self.asg);
|
||||
|
||||
// note: the edges are reversed (TODO?)
|
||||
let value = edges
|
||||
.next()
|
||||
.diagnostic_expect(
|
||||
|| vec![oi_expr.note("for this match")],
|
||||
"missing @value ref",
|
||||
)
|
||||
.resolve(self.asg);
|
||||
let value = edges.next().diagnostic_expect(
|
||||
|| vec![oi_expr.note("for this match")],
|
||||
"missing @value ref",
|
||||
);
|
||||
|
||||
let on = edges
|
||||
.next()
|
||||
.diagnostic_expect(
|
||||
|| vec![oi_expr.note("for this match")],
|
||||
"missing @on ref",
|
||||
)
|
||||
.resolve(self.asg);
|
||||
let on = edges.next().diagnostic_expect(
|
||||
|| vec![oi_expr.note("for this match")],
|
||||
"missing @on ref",
|
||||
);
|
||||
|
||||
if let Some(unexpected) = edges.next() {
|
||||
diagnostic_panic!(
|
||||
|
@ -367,8 +383,8 @@ impl<'a> TreeContext<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
self.push(attr_value(value.name()));
|
||||
self.push(attr_on(on.name()));
|
||||
self.push(attr_value(value.name_or_meta(self.asg)));
|
||||
self.push(attr_on(on.name_or_meta(self.asg)));
|
||||
|
||||
Xirf::open(QN_MATCH, OpenSpan::without_name_span(expr.span()), depth)
|
||||
}
|
||||
|
@ -382,8 +398,9 @@ impl<'a> TreeContext<'a> {
|
|||
depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
match src {
|
||||
Object::Ident((ident, _)) => {
|
||||
self.push(attr_name(ident.name()));
|
||||
Object::Ident((_, oi_ident)) => {
|
||||
self.tpl_apply = None;
|
||||
self.push(attr_name(oi_ident.name_or_meta(self.asg)));
|
||||
|
||||
Some(Xirf::open(
|
||||
QN_TEMPLATE,
|
||||
|
@ -400,6 +417,12 @@ impl<'a> TreeContext<'a> {
|
|||
// do not have to deal with converting underscore-padded
|
||||
// template names back into short-hand form.
|
||||
Object::Pkg(..) | Object::Tpl(..) | Object::Expr(..) => {
|
||||
// This really ought to be a state transition;
|
||||
// this is a sheer act of laziness.
|
||||
// If we introduce states for other things,
|
||||
// let's convert this as well.
|
||||
self.tpl_apply = Some(oi_tpl);
|
||||
|
||||
// [`Ident`]s are skipped during traversal,
|
||||
// so we'll handle it ourselves here.
|
||||
// This also gives us the opportunity to make sure that
|
||||
|
@ -422,7 +445,7 @@ impl<'a> TreeContext<'a> {
|
|||
"cannot derive name of template for application",
|
||||
);
|
||||
|
||||
self.push(attr_name(apply_tpl.resolve(self.asg).name()));
|
||||
self.push(attr_name(apply_tpl.name_or_meta(self.asg)));
|
||||
|
||||
Some(Xirf::open(
|
||||
QN_APPLY_TEMPLATE,
|
||||
|
@ -440,6 +463,99 @@ impl<'a> TreeContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Emit a metavariable as a template parameter.
|
||||
///
|
||||
/// For the parent template,
|
||||
/// see [`Self::emit_template`].
|
||||
fn emit_tpl_param(
|
||||
&mut self,
|
||||
meta: &Meta,
|
||||
oi_meta: ObjectIndex<Meta>,
|
||||
depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
if let Some(pname) =
|
||||
oi_meta.ident(self.asg).map(|oi| oi.name_or_meta(self.asg))
|
||||
{
|
||||
// This may have a body that is a single lexeme,
|
||||
// representing a default value for the parameter.
|
||||
if let Some(lexeme) = meta.lexeme() {
|
||||
let open = self.emit_text_node(
|
||||
lexeme,
|
||||
lexeme.span(),
|
||||
depth.child_depth(),
|
||||
);
|
||||
self.push(open);
|
||||
}
|
||||
|
||||
// Because of the above,
|
||||
// we must check here if we have a description rather than
|
||||
// waiting to encounter it during the traversal;
|
||||
// otherwise we'd be outputting child nodes before a
|
||||
// description attribute,
|
||||
// which would result in invalid XML that is rejected by
|
||||
// the XIR writer.
|
||||
if let Some(desc_short) = oi_meta.desc_short(self.asg) {
|
||||
self.push(attr_desc(desc_short));
|
||||
}
|
||||
|
||||
self.push(attr_name(pname));
|
||||
|
||||
Some(Xirf::open(
|
||||
QN_PARAM,
|
||||
OpenSpan::without_name_span(meta.span()),
|
||||
depth,
|
||||
))
|
||||
} else if let Some(lexeme) = meta.lexeme() {
|
||||
Some(self.emit_text_node(lexeme, meta.span(), depth))
|
||||
} else {
|
||||
// TODO: Rewrite the above to be an exhaustive match, perhaps,
|
||||
// so we know what we'll error on.
|
||||
diagnostic_todo!(
|
||||
vec![oi_meta.internal_error("unsupported Meta type")],
|
||||
"xmli output does not yet support this Meta object",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a `<text>` node containing a lexeme.
|
||||
fn emit_text_node(
|
||||
&mut self,
|
||||
lexeme: SPair,
|
||||
node_span: Span,
|
||||
depth: Depth,
|
||||
) -> Xirf {
|
||||
self.push(Xirf::close(
|
||||
Some(QN_TEXT),
|
||||
CloseSpan::without_name_span(node_span),
|
||||
depth,
|
||||
));
|
||||
|
||||
self.push(Xirf::text(
|
||||
Text(lexeme.symbol(), lexeme.span()),
|
||||
depth.child_depth(),
|
||||
));
|
||||
|
||||
Xirf::open(QN_TEXT, OpenSpan::without_name_span(node_span), depth)
|
||||
}
|
||||
|
||||
/// Emit a `<param-value>` node assumed to be within a template param
|
||||
/// body.
|
||||
fn emit_tpl_param_value(
|
||||
&mut self,
|
||||
ident: &Ident,
|
||||
oi_ident: &ObjectIndex<Ident>,
|
||||
depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
let name = oi_ident.name_or_meta(self.asg);
|
||||
self.push(attr_name(name));
|
||||
|
||||
Some(Xirf::open(
|
||||
QN_PARAM_VALUE,
|
||||
OpenSpan::without_name_span(ident.span()),
|
||||
depth,
|
||||
))
|
||||
}
|
||||
|
||||
/// Emit a long-form template argument.
|
||||
///
|
||||
/// For the parent template application,
|
||||
|
@ -450,7 +566,7 @@ impl<'a> TreeContext<'a> {
|
|||
oi_meta: ObjectIndex<Meta>,
|
||||
depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
let pname = oi_meta.ident(self.asg).map(Ident::name)
|
||||
let pname = oi_meta.ident(self.asg).map(|oi| oi.name_or_meta(self.asg))
|
||||
.diagnostic_unwrap(|| vec![meta.internal_error(
|
||||
"anonymous metavariables are not supported as template arguments"
|
||||
)]);
|
||||
|
@ -497,6 +613,14 @@ impl<'a> TreeContext<'a> {
|
|||
Some(attr_desc(*desc))
|
||||
}
|
||||
|
||||
// template/param/@desc
|
||||
(Object::Meta(_), Doc::IndepClause(_desc))
|
||||
if self.tpl_apply.is_none() =>
|
||||
{
|
||||
// This is already covered in `emit_tpl_param`
|
||||
None
|
||||
}
|
||||
|
||||
(_, Doc::Text(_text)) => {
|
||||
// TODO: This isn't utilized by the XSLT parser and
|
||||
// `xmllint` for system tests does not format with mixed
|
||||
|
@ -560,6 +684,7 @@ impl<'a> From<&'a Asg> for TreeContext<'a> {
|
|||
TreeContext {
|
||||
stack: Default::default(),
|
||||
asg,
|
||||
tpl_apply: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ pub use graph::{
|
|||
ObjectKind,
|
||||
},
|
||||
visit,
|
||||
xmli::AsgTreeToXirf,
|
||||
xmli::{AsgTreeToXirf, AsgTreeToXirfError},
|
||||
Asg, AsgResult, IndexType,
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
// Use your judgment;
|
||||
// a `match` may be more clear within a given context.
|
||||
#![allow(clippy::single_match)]
|
||||
#![feature(assert_matches)]
|
||||
|
||||
//! This is the TAME compiler.
|
||||
//!
|
||||
|
@ -39,27 +40,49 @@ use std::{
|
|||
path::Path,
|
||||
};
|
||||
use tamer::{
|
||||
asg::{air::Air, AsgError, DefaultAsg},
|
||||
asg::DefaultAsg,
|
||||
diagnose::{
|
||||
AnnotatedSpan, Diagnostic, FsSpanResolver, Reporter, VisualReporter,
|
||||
},
|
||||
nir::{InterpError, Nir, NirToAirError, XirfToNirError},
|
||||
parse::{lowerable, FinalizeError, ParseError, Token, UnknownToken},
|
||||
pipeline::parse_package_xml,
|
||||
xir::{
|
||||
self,
|
||||
flat::{RefinedText, XirToXirfError, XirfToken},
|
||||
reader::XmlXirReader,
|
||||
DefaultEscaper, Token as XirToken,
|
||||
},
|
||||
nir::NirToAirParseType,
|
||||
parse::{lowerable, FinalizeError, ParseError, Token},
|
||||
pipeline::{parse_package_xml, LowerXmliError, ParsePackageXmlError},
|
||||
xir::{self, reader::XmlXirReader, writer::XmlWriter, DefaultEscaper},
|
||||
};
|
||||
|
||||
/// Types of commands
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Command {
|
||||
Compile(String, String, String),
|
||||
Compile(String, ObjectFileKind, String),
|
||||
Usage,
|
||||
}
|
||||
|
||||
/// The type of object file to output.
|
||||
///
|
||||
/// While TAMER is under development,
|
||||
/// object files serve as a transition between the new compiler and the
|
||||
/// old.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
enum ObjectFileKind {
|
||||
/// Produce something akin to an object file.
|
||||
///
|
||||
/// During TAME's development,
|
||||
/// this is an `xmli` file that is passed to the old compiler to pick
|
||||
/// up where this one left off.
|
||||
///
|
||||
/// This is the stable feature set,
|
||||
/// expected to work with any package.
|
||||
XmloStable,
|
||||
|
||||
/// Enable experimental flag(s),
|
||||
/// attempting to build the given package with an system that has not
|
||||
/// yet stabalized and is bound to fail on some packages.
|
||||
///
|
||||
/// This is intentionally vague.
|
||||
/// It should be used only for testing.
|
||||
XmloExperimental,
|
||||
}
|
||||
|
||||
/// Create a [`XmlXirReader`] for a source file.
|
||||
///
|
||||
/// The provided escaper must be shared between all readers and writers in
|
||||
|
@ -84,18 +107,15 @@ fn src_reader<'a>(
|
|||
/// transition period between the XSLT-based TAME and TAMER.
|
||||
/// Writing XIR proves that the source file is being successfully parsed and
|
||||
/// helps to evaluate system performance.
|
||||
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
||||
fn copy_xml_to<'e, W: io::Write + 'e>(
|
||||
mut fout: W,
|
||||
mut fout: Option<W>,
|
||||
escaper: &'e DefaultEscaper,
|
||||
) -> impl FnMut(&Result<XirToken, tamer::xir::Error>) + 'e {
|
||||
use tamer::xir::writer::XmlWriter;
|
||||
|
||||
) -> impl FnMut(&Result<tamer::xir::Token, tamer::xir::Error>) + 'e {
|
||||
let mut xmlwriter = Default::default();
|
||||
|
||||
move |tok_result| match tok_result {
|
||||
Ok(tok) => {
|
||||
xmlwriter = tok.write(&mut fout, xmlwriter, escaper).unwrap();
|
||||
move |tok_result| match (fout.as_mut(), tok_result) {
|
||||
(Some(mut dest), Ok(tok)) => {
|
||||
xmlwriter = tok.write(&mut dest, xmlwriter, escaper).unwrap();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
@ -110,16 +130,34 @@ fn compile<R: Reporter>(
|
|||
src_path: &String,
|
||||
dest_path: &String,
|
||||
reporter: &mut R,
|
||||
kind: ObjectFileKind,
|
||||
) -> Result<(), UnrecoverableError> {
|
||||
let dest = Path::new(&dest_path);
|
||||
#[allow(unused_mut)] // wip-asg-derived-xmli
|
||||
let mut fout = BufWriter::new(fs::File::create(dest)?);
|
||||
|
||||
let (fcopy, fout, parse_type) = match kind {
|
||||
// Parse XML and re-emit into target verbatim
|
||||
// (but missing some formatting).
|
||||
// Tokens will act as no-ops after NIR.
|
||||
ObjectFileKind::XmloStable => (
|
||||
Some(BufWriter::new(fs::File::create(dest)?)),
|
||||
None,
|
||||
NirToAirParseType::Noop,
|
||||
),
|
||||
|
||||
// Parse sources into ASG and re-generate sources from there.
|
||||
// This will fail if the source package utilize features that are
|
||||
// not yet supported.
|
||||
ObjectFileKind::XmloExperimental => (
|
||||
None,
|
||||
Some(BufWriter::new(fs::File::create(dest)?)),
|
||||
NirToAirParseType::LowerKnownErrorRest,
|
||||
),
|
||||
};
|
||||
|
||||
let escaper = DefaultEscaper::default();
|
||||
|
||||
let mut ebuf = String::new();
|
||||
|
||||
let report_err = |result: Result<(), RecoverableError>| {
|
||||
let report_err = |result: Result<(), ParsePackageXmlError<_>>| {
|
||||
result.or_else(|e| {
|
||||
// See below note about buffering.
|
||||
ebuf.clear();
|
||||
|
@ -130,44 +168,26 @@ fn compile<R: Reporter>(
|
|||
})
|
||||
};
|
||||
|
||||
// TODO: We're just echoing back out XIR,
|
||||
// which will be the same sans some formatting.
|
||||
let src = &mut lowerable(src_reader(src_path, &escaper)?.inspect({
|
||||
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
||||
{
|
||||
copy_xml_to(fout, &escaper)
|
||||
}
|
||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
||||
{
|
||||
|_| ()
|
||||
}
|
||||
}));
|
||||
let src = &mut lowerable(
|
||||
src_reader(src_path, &escaper)?.inspect(copy_xml_to(fcopy, &escaper)),
|
||||
);
|
||||
|
||||
// TODO: Determine a good default capacity once we have this populated
|
||||
// and can come up with some heuristics.
|
||||
let (air_ctx,) = parse_package_xml(
|
||||
src,
|
||||
let (_, air_ctx) = parse_package_xml(
|
||||
parse_type,
|
||||
DefaultAsg::with_capacity(1024, 2048),
|
||||
report_err,
|
||||
)?;
|
||||
)(src, report_err)?;
|
||||
|
||||
match reporter.has_errors() {
|
||||
false => {
|
||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
||||
{
|
||||
let asg = air_ctx.finish();
|
||||
derive_xmli(asg, fout, &escaper)
|
||||
}
|
||||
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
||||
{
|
||||
let _ = air_ctx; // unused_variables
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
true => Err(UnrecoverableError::ErrorsDuringLowering(
|
||||
if reporter.has_errors() {
|
||||
Err(UnrecoverableError::ErrorsDuringLowering(
|
||||
reporter.error_count(),
|
||||
)),
|
||||
))
|
||||
} else if let Some(dest) = fout {
|
||||
let asg = air_ctx.finish();
|
||||
derive_xmli(asg, dest, &escaper)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,16 +201,13 @@ fn compile<R: Reporter>(
|
|||
/// and must be an equivalent program,
|
||||
/// but will look different;
|
||||
/// TAMER reasons about the system using a different paradigm.
|
||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
||||
fn derive_xmli(
|
||||
asg: tamer::asg::Asg,
|
||||
mut fout: impl std::io::Write,
|
||||
escaper: &DefaultEscaper,
|
||||
) -> Result<(), UnrecoverableError> {
|
||||
use tamer::{
|
||||
asg::visit::tree_reconstruction,
|
||||
pipeline,
|
||||
xir::writer::{WriterState, XmlWriter},
|
||||
asg::visit::tree_reconstruction, pipeline, xir::writer::WriterState,
|
||||
};
|
||||
|
||||
let src = lowerable(tree_reconstruction(&asg).map(Ok));
|
||||
|
@ -198,15 +215,17 @@ fn derive_xmli(
|
|||
// TODO: Remove bad file?
|
||||
// Let make do it?
|
||||
let mut st = WriterState::default();
|
||||
let (_asg,) = pipeline::lower_xmli(src, &asg, |result| {
|
||||
let (_asg,) = pipeline::lower_xmli(&asg)(src, |result| {
|
||||
// Write failures should immediately bail out;
|
||||
// we can't skip writing portions of the file and
|
||||
// just keep going!
|
||||
result.and_then(|tok| {
|
||||
tok.write(&mut fout, st, escaper)
|
||||
.map(|newst| st = newst)
|
||||
.map_err(Into::<UnrecoverableError>::into)
|
||||
})
|
||||
result
|
||||
.map_err(Into::<UnrecoverableError>::into)
|
||||
.and_then(|tok| {
|
||||
tok.write(&mut fout, st, escaper)
|
||||
.map(|newst| st = newst)
|
||||
.map_err(Into::<UnrecoverableError>::into)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
@ -220,10 +239,10 @@ pub fn main() -> Result<(), UnrecoverableError> {
|
|||
let usage = opts.usage(&format!("Usage: {program} [OPTIONS] INPUT"));
|
||||
|
||||
match parse_options(opts, args) {
|
||||
Ok(Command::Compile(src_path, _, dest_path)) => {
|
||||
Ok(Command::Compile(src_path, kind, dest_path)) => {
|
||||
let mut reporter = VisualReporter::new(FsSpanResolver);
|
||||
|
||||
compile(&src_path, &dest_path, &mut reporter).map_err(
|
||||
compile(&src_path, &dest_path, &mut reporter, kind).map_err(
|
||||
|e: UnrecoverableError| {
|
||||
// Rendering to a string ensures buffering so that we
|
||||
// don't interleave output between processes.
|
||||
|
@ -285,15 +304,12 @@ fn parse_options(opts: Options, args: Vec<String>) -> Result<Command, Fail> {
|
|||
|
||||
let emit = match matches.opt_str("emit") {
|
||||
Some(m) => match &m[..] {
|
||||
"xmlo" => m,
|
||||
_ => {
|
||||
return Err(Fail::ArgumentMissing(String::from("--emit xmlo")))
|
||||
}
|
||||
"xmlo" => Ok(ObjectFileKind::XmloStable),
|
||||
"xmlo-experimental" => Ok(ObjectFileKind::XmloExperimental),
|
||||
_ => Err(Fail::ArgumentMissing(String::from("--emit xmlo"))),
|
||||
},
|
||||
None => {
|
||||
return Err(Fail::OptionMissing(String::from("--emit xmlo")));
|
||||
}
|
||||
};
|
||||
None => Err(Fail::OptionMissing(String::from("--emit xmlo"))),
|
||||
}?;
|
||||
|
||||
let output = match matches.opt_str("o") {
|
||||
Some(m) => m,
|
||||
|
@ -309,14 +325,20 @@ fn parse_options(opts: Options, args: Vec<String>) -> Result<Command, Fail> {
|
|||
///
|
||||
/// These are errors that will result in aborting execution and exiting with
|
||||
/// a non-zero status.
|
||||
/// Contrast this with [`RecoverableError`],
|
||||
/// Contrast this with recoverable errors in [`tamer::pipeline`],
|
||||
/// which is reported real-time to the user and _does not_ cause the
|
||||
/// program to abort until the end of the compilation unit.
|
||||
///
|
||||
/// Note that an recoverable error,
|
||||
/// under a normal compilation strategy,
|
||||
/// will result in an [`UnrecoverableError::ErrorsDuringLowering`] at the
|
||||
/// end of the compilation unit.
|
||||
#[derive(Debug)]
|
||||
pub enum UnrecoverableError {
|
||||
Io(io::Error),
|
||||
Fmt(fmt::Error),
|
||||
XirWriterError(xir::writer::Error),
|
||||
LowerXmliError(LowerXmliError<Infallible>),
|
||||
ErrorsDuringLowering(ErrorCount),
|
||||
FinalizeError(FinalizeError),
|
||||
}
|
||||
|
@ -327,37 +349,6 @@ pub enum UnrecoverableError {
|
|||
/// have in your code.
|
||||
type ErrorCount = usize;
|
||||
|
||||
/// An error that occurs during the lowering pipeline that may be recovered
|
||||
/// from to continue parsing and collection of additional errors.
|
||||
///
|
||||
/// This represents the aggregation of all possible errors that can occur
|
||||
/// during lowering.
|
||||
/// This cannot include panics,
|
||||
/// but efforts have been made to reduce panics to situations that
|
||||
/// represent the equivalent of assertions.
|
||||
///
|
||||
/// These errors are distinct from [`UnrecoverableError`],
|
||||
/// which represents the errors that could be returned to the toplevel
|
||||
/// `main`,
|
||||
/// because these errors are intended to be reported to the user _and then
|
||||
/// recovered from_ so that compilation may continue and more errors may
|
||||
/// be collected;
|
||||
/// nobody wants a compiler that reports one error at a time.
|
||||
///
|
||||
/// Note that an recoverable error,
|
||||
/// under a normal compilation strategy,
|
||||
/// will result in an [`UnrecoverableError::ErrorsDuringLowering`] at the
|
||||
/// end of the compilation unit.
|
||||
#[derive(Debug)]
|
||||
pub enum RecoverableError {
|
||||
XirParseError(ParseError<UnknownToken, xir::Error>),
|
||||
XirfParseError(ParseError<XirToken, XirToXirfError>),
|
||||
NirParseError(ParseError<XirfToken<RefinedText>, XirfToNirError>),
|
||||
InterpError(ParseError<Nir, InterpError>),
|
||||
NirToAirError(ParseError<Nir, NirToAirError>),
|
||||
AirAggregateError(ParseError<Air, AsgError>),
|
||||
}
|
||||
|
||||
impl From<io::Error> for UnrecoverableError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Self::Io(e)
|
||||
|
@ -376,15 +367,15 @@ impl From<xir::writer::Error> for UnrecoverableError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<FinalizeError> for UnrecoverableError {
|
||||
fn from(e: FinalizeError) -> Self {
|
||||
Self::FinalizeError(e)
|
||||
impl From<LowerXmliError<Infallible>> for UnrecoverableError {
|
||||
fn from(e: LowerXmliError<Infallible>) -> Self {
|
||||
Self::LowerXmliError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Infallible> for UnrecoverableError {
|
||||
fn from(_: Infallible) -> Self {
|
||||
unreachable!("<UnrecoverableError as From<Infallible>>::from")
|
||||
impl From<FinalizeError> for UnrecoverableError {
|
||||
fn from(e: FinalizeError) -> Self {
|
||||
Self::FinalizeError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -396,52 +387,6 @@ impl<T: Token> From<ParseError<T, Infallible>> for UnrecoverableError {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Token> From<ParseError<T, Infallible>> for RecoverableError {
|
||||
fn from(_: ParseError<T, Infallible>) -> Self {
|
||||
unreachable!(
|
||||
"<RecoverableError as From<ParseError<T, Infallible>>>::from"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<UnknownToken, xir::Error>> for RecoverableError {
|
||||
fn from(e: ParseError<UnknownToken, xir::Error>) -> Self {
|
||||
Self::XirParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<XirToken, XirToXirfError>> for RecoverableError {
|
||||
fn from(e: ParseError<XirToken, XirToXirfError>) -> Self {
|
||||
Self::XirfParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<XirfToken<RefinedText>, XirfToNirError>>
|
||||
for RecoverableError
|
||||
{
|
||||
fn from(e: ParseError<XirfToken<RefinedText>, XirfToNirError>) -> Self {
|
||||
Self::NirParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<Nir, InterpError>> for RecoverableError {
|
||||
fn from(e: ParseError<Nir, InterpError>) -> Self {
|
||||
Self::InterpError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<Nir, NirToAirError>> for RecoverableError {
|
||||
fn from(e: ParseError<Nir, NirToAirError>) -> Self {
|
||||
Self::NirToAirError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<Air, AsgError>> for RecoverableError {
|
||||
fn from(e: ParseError<Air, AsgError>) -> Self {
|
||||
Self::AirAggregateError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UnrecoverableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use UnrecoverableError::*;
|
||||
|
@ -449,6 +394,7 @@ impl Display for UnrecoverableError {
|
|||
match self {
|
||||
Io(e) => Display::fmt(e, f),
|
||||
Fmt(e) => Display::fmt(e, f),
|
||||
LowerXmliError(e) => Display::fmt(e, f),
|
||||
XirWriterError(e) => Display::fmt(e, f),
|
||||
FinalizeError(e) => Display::fmt(e, f),
|
||||
|
||||
|
@ -460,55 +406,14 @@ impl Display for UnrecoverableError {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for RecoverableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use RecoverableError::*;
|
||||
|
||||
match self {
|
||||
XirParseError(e) => Display::fmt(e, f),
|
||||
XirfParseError(e) => Display::fmt(e, f),
|
||||
NirParseError(e) => Display::fmt(e, f),
|
||||
InterpError(e) => Display::fmt(e, f),
|
||||
NirToAirError(e) => Display::fmt(e, f),
|
||||
AirAggregateError(e) => Display::fmt(e, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for UnrecoverableError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
use UnrecoverableError::*;
|
||||
|
||||
match self {
|
||||
Io(e) => Some(e),
|
||||
Fmt(e) => Some(e),
|
||||
XirWriterError(e) => Some(e),
|
||||
ErrorsDuringLowering(_) => None,
|
||||
FinalizeError(e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RecoverableError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
use RecoverableError::*;
|
||||
|
||||
match self {
|
||||
XirParseError(e) => Some(e),
|
||||
XirfParseError(e) => Some(e),
|
||||
NirParseError(e) => Some(e),
|
||||
InterpError(e) => Some(e),
|
||||
NirToAirError(e) => Some(e),
|
||||
AirAggregateError(e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for UnrecoverableError {}
|
||||
|
||||
impl Diagnostic for UnrecoverableError {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
use UnrecoverableError::*;
|
||||
|
||||
match self {
|
||||
LowerXmliError(e) => e.describe(),
|
||||
FinalizeError(e) => e.describe(),
|
||||
|
||||
// Fall back to `Display`
|
||||
|
@ -519,24 +424,10 @@ impl Diagnostic for UnrecoverableError {
|
|||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for RecoverableError {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
use RecoverableError::*;
|
||||
|
||||
match self {
|
||||
XirParseError(e) => e.describe(),
|
||||
XirfParseError(e) => e.describe(),
|
||||
NirParseError(e) => e.describe(),
|
||||
InterpError(e) => e.describe(),
|
||||
NirToAirError(e) => e.describe(),
|
||||
AirAggregateError(e) => e.describe(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn parse_options_help() {
|
||||
|
@ -670,7 +561,7 @@ mod test {
|
|||
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
||||
assert_eq!("foo.xml", infile);
|
||||
assert_eq!("foo.xmlo", outfile);
|
||||
assert_eq!("xmlo", xmlo);
|
||||
assert_eq!(ObjectFileKind::XmloStable, xmlo);
|
||||
}
|
||||
_ => panic!("Unexpected result"),
|
||||
}
|
||||
|
@ -696,7 +587,7 @@ mod test {
|
|||
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
||||
assert_eq!("foo.xml", infile);
|
||||
assert_eq!("foo.xmli", outfile);
|
||||
assert_eq!("xmlo", xmlo);
|
||||
assert_eq!(ObjectFileKind::XmloStable, xmlo);
|
||||
}
|
||||
_ => panic!("Unexpected result"),
|
||||
}
|
||||
|
@ -722,9 +613,31 @@ mod test {
|
|||
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
||||
assert_eq!("foo.xml", infile);
|
||||
assert_eq!("foo.xmli", outfile);
|
||||
assert_eq!("xmlo", xmlo);
|
||||
assert_eq!(ObjectFileKind::XmloStable, xmlo);
|
||||
}
|
||||
_ => panic!("Unexpected result"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_options_xmlo_experimetal() {
|
||||
let opts = get_opts();
|
||||
let xmlo = String::from("xmlo-experimental");
|
||||
let result = parse_options(
|
||||
opts,
|
||||
vec![
|
||||
String::from("program"),
|
||||
String::from("foo.xml"),
|
||||
String::from("--emit"),
|
||||
xmlo,
|
||||
String::from("--output"),
|
||||
String::from("foo.xmli"),
|
||||
],
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
result,
|
||||
Ok(Command::Compile(_, ObjectFileKind::XmloExperimental, _)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -332,3 +332,55 @@ impl<S: Into<Span>> Annotate for S {
|
|||
AnnotatedSpan(self.into(), level, label)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a variant-less error enum akin to [`Infallible`].
|
||||
///
|
||||
/// This is used to create [`Infallible`]-like newtypes where unique error
|
||||
/// types are beneficial.
|
||||
/// For example,
|
||||
/// this can be used so that [`From`] implementations can be exclusively
|
||||
/// used to widen errors
|
||||
/// (or lack thereof)
|
||||
/// into error sum variants,
|
||||
/// and is especially useful when code generation is involved to avoid
|
||||
/// generation of overlapping [`From`] `impl`s.
|
||||
///
|
||||
/// The generated enum is convertable [`Into`] and [`From`] [`Infallible`].
|
||||
macro_rules! diagnostic_infallible {
|
||||
($vis:vis enum $name:ident {}) => {
|
||||
/// A unique [`Infallible`](std::convert::Infallible) type.
|
||||
#[derive(Debug, PartialEq)]
|
||||
$vis enum $name {}
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, stringify!($name))
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::diagnose::Diagnostic for $name {
|
||||
fn describe(&self) -> Vec<$crate::diagnose::AnnotatedSpan> {
|
||||
// This is a unit struct and should not be able to be
|
||||
// instantiated!
|
||||
unreachable!(
|
||||
concat!(
|
||||
stringify!($name),
|
||||
" should be unreachable!"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for $name {
|
||||
fn from(_: std::convert::Infallible) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for std::convert::Infallible {
|
||||
fn from(_: $name) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
160
tamer/src/f.rs
160
tamer/src/f.rs
|
@ -33,11 +33,12 @@
|
|||
|
||||
/// A type providing a `map` function from inner type `T` to `U`.
|
||||
///
|
||||
/// In an abuse of terminology,
|
||||
/// this functor is polymorphic over the entire trait,
|
||||
/// This used to be called `Functor`,
|
||||
/// but was renamed because it was an abuse of terminology;
|
||||
/// this is polymorphic over the entire trait,
|
||||
/// rather than just the definition of `map`,
|
||||
/// allowing implementations to provide multiple specialized `map`s
|
||||
/// without having to define individual `map_*` methods.
|
||||
/// allowing implementations to provide multiple specialized `map`s
|
||||
/// without having to define individual `map_*` methods.
|
||||
/// Rust will often be able to infer the proper types and invoke the
|
||||
/// intended `map` function within a given context,
|
||||
/// but methods may still be explicitly disambiguated using the
|
||||
|
@ -46,16 +47,16 @@
|
|||
/// if a functor requires a monomorphic function
|
||||
/// (so `T = U`),
|
||||
/// then it's not really a functor.
|
||||
/// We'll refer to these structures informally as monomorphic functors,
|
||||
/// since they provide the same type of API as a functor,
|
||||
/// but cannot change the underlying type.
|
||||
///
|
||||
/// This trait also provides a number of convenience methods that can be
|
||||
/// implemented in terms of [`Functor::map`].
|
||||
/// implemented in terms of [`Map::map`].
|
||||
///
|
||||
/// If a mapping can fail,
|
||||
/// see [`TryMap`].
|
||||
///
|
||||
/// Why a primitive `map` instead of `fmap`?
|
||||
/// ========================================
|
||||
/// One of the methods of this trait is [`Functor::fmap`],
|
||||
/// One of the methods of this trait is [`Map::fmap`],
|
||||
/// which [is motivated by Haskell][haskell-functor].
|
||||
/// This trait implements methods in terms of [`map`](Self::map) rather than
|
||||
/// [`fmap`](Self::fmap) because `map` is a familiar idiom in Rust and
|
||||
|
@ -66,8 +67,8 @@
|
|||
/// which is additional boilerplate relative to `map`.
|
||||
///
|
||||
/// [haskell-functor]: https://hackage.haskell.org/package/base/docs/Data-Functor.html
|
||||
pub trait Functor<T, U = T>: Sized {
|
||||
/// Type of object resulting from [`Functor::map`] operation.
|
||||
pub trait Map<T, U = T>: Sized {
|
||||
/// Type of object resulting from [`Map::map`] operation.
|
||||
///
|
||||
/// The term "target" originates from category theory,
|
||||
/// representing the codomain of the functor.
|
||||
|
@ -83,7 +84,7 @@ pub trait Functor<T, U = T>: Sized {
|
|||
/// all others are implemented in terms of it.
|
||||
fn map(self, f: impl FnOnce(T) -> U) -> Self::Target;
|
||||
|
||||
/// Curried form of [`Functor::map`],
|
||||
/// Curried form of [`Map::map`],
|
||||
/// with arguments reversed.
|
||||
///
|
||||
/// `fmap` returns a unary closure that accepts an object of type
|
||||
|
@ -106,26 +107,153 @@ pub trait Functor<T, U = T>: Sized {
|
|||
///
|
||||
/// This is intended for cases where there's a single element that will
|
||||
/// be replaced,
|
||||
/// taking advantage of [`Functor`]'s trait-level polymorphism.
|
||||
/// taking advantage of [`Map`]'s trait-level polymorphism.
|
||||
fn overwrite(self, value: U) -> Self::Target {
|
||||
self.map(|_| value)
|
||||
}
|
||||
|
||||
/// Curried form of [`Functor::overwrite`],
|
||||
/// Curried form of [`Map::overwrite`],
|
||||
/// with arguments reversed.
|
||||
fn foverwrite(value: U) -> impl FnOnce(Self) -> Self::Target {
|
||||
move |x| x.overwrite(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Functor<T, U> for Option<T> {
|
||||
impl<T, U> Map<T, U> for Option<T> {
|
||||
type Target = Option<U>;
|
||||
|
||||
fn map(self, f: impl FnOnce(T) -> U) -> <Self as Functor<T, U>>::Target {
|
||||
fn map(self, f: impl FnOnce(T) -> U) -> <Self as Map<T, U>>::Target {
|
||||
Option::map(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type providing a `try_map` function from inner type `T` to `U`.
|
||||
///
|
||||
/// This is a fallible version of [`Map`];
|
||||
/// see that trait for more information.
|
||||
pub trait TryMap<T, U = T>: Sized {
|
||||
/// Type of object resulting from [`TryMap::try_map`] operation.
|
||||
///
|
||||
/// The term "target" originates from category theory,
|
||||
/// representing the codomain of the functor.
|
||||
type Target = Self;
|
||||
|
||||
/// Result of the mapping function.
|
||||
type FnResult<E> = Result<T, (T, E)>;
|
||||
|
||||
/// Result of the entire map operation.
|
||||
type Result<E> = Result<Self, (Self, E)>;
|
||||
|
||||
/// A structure-preserving map between types `T` and `U`.
|
||||
///
|
||||
/// This unwraps any number of `T` from `Self` and applies the
|
||||
/// function `f` to transform it into `U`,
|
||||
/// wrapping the result back up into [`Self`].
|
||||
///
|
||||
/// Since this method takes ownership over `self` rather than a mutable
|
||||
/// reference,
|
||||
/// [`Self::FnResult`] is expected to return some version of `T`
|
||||
/// alongside the error `E`;
|
||||
/// this is usually the original `self`,
|
||||
/// but does not have to be.
|
||||
/// Similarly,
|
||||
/// [`Self::Result`] will also return [`Self`] in the event of an
|
||||
/// error.
|
||||
///
|
||||
/// This is the only method that needs to be implemented on this trait;
|
||||
/// all others are implemented in terms of it.
|
||||
fn try_map<E>(
|
||||
self,
|
||||
f: impl FnOnce(T) -> Self::FnResult<E>,
|
||||
) -> Self::Result<E>;
|
||||
|
||||
/// Curried form of [`TryMap::try_map`],
|
||||
/// with arguments reversed.
|
||||
///
|
||||
/// `try_fmap` returns a unary closure that accepts an object of type
|
||||
/// [`Self`].
|
||||
/// This is more amenable to function composition and a point-free style.
|
||||
fn try_fmap<E>(
|
||||
f: impl FnOnce(T) -> Self::FnResult<E>,
|
||||
) -> impl FnOnce(Self) -> Self::Result<E> {
|
||||
move |x| x.try_map(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate monomorphic [`TryMap`] and [`Map`] implementations for the
|
||||
/// provided type.
|
||||
///
|
||||
/// This macro is suitable for otherwise-boilerplate `impl`s for these
|
||||
/// traits.
|
||||
/// If you expect anything more than a generic `map` or `try_map` operation,
|
||||
/// then you should implement the traits manually.
|
||||
///
|
||||
/// Only tuple structs are supported at present.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate tamer;
|
||||
/// # use tamer::impl_mono_map;
|
||||
/// # use tamer::f::Map;
|
||||
/// # fn main() {
|
||||
/// #[derive(Debug, PartialEq)]
|
||||
/// struct Foo(u8, Bar);
|
||||
///
|
||||
/// #[derive(Debug, PartialEq)]
|
||||
/// enum Bar { A, B };
|
||||
///
|
||||
/// impl_mono_map! {
|
||||
/// u8 => Foo(@, bar),
|
||||
/// Bar => Foo(n, @),
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(5, Bar::A).overwrite(Bar::B), Foo(5, Bar::B));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Each line above generates a pair of `impl`s,
|
||||
/// each for `Foo`,
|
||||
/// where `@` represents the tuple item being mapped over.
|
||||
#[macro_export] // for doc test above
|
||||
macro_rules! impl_mono_map {
|
||||
($($t:ty => $tup:ident( $($pre:ident,)* @ $(, $post:ident),* ),)+) => {
|
||||
$(
|
||||
impl $crate::f::TryMap<$t> for $tup {
|
||||
fn try_map<E>(
|
||||
self,
|
||||
f: impl FnOnce($t) -> Self::FnResult<E>,
|
||||
) -> Self::Result<E> {
|
||||
match self {
|
||||
Self($($pre,)* x $(, $post),*) => match f(x) {
|
||||
Ok(y) => Ok(Self($($pre,)* y $(, $post),*)),
|
||||
Err((y, e)) => Err((
|
||||
Self($($pre,)* y $(, $post),*),
|
||||
e,
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::f::Map<$t> for $tup {
|
||||
fn map(self, f: impl FnOnce($t) -> $t) -> Self::Target {
|
||||
use std::convert::Infallible;
|
||||
use $crate::f::TryMap;
|
||||
|
||||
// `unwrap()` requires `E: Debug`,
|
||||
// so this avoids that bound.
|
||||
match self.try_map::<Infallible>(|x| Ok(f(x))) {
|
||||
Ok(y) => y,
|
||||
// Verbosely emphasize unreachability
|
||||
Err::<_, (_, Infallible)>(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
/// A nullary [`Fn`] delaying some computation.
|
||||
///
|
||||
/// For the history and usage of this term in computing,
|
||||
|
|
|
@ -26,30 +26,25 @@ use super::xmle::{
|
|||
XmleSections,
|
||||
};
|
||||
use crate::{
|
||||
asg::{
|
||||
air::{Air, AirAggregateCtx},
|
||||
AsgError, DefaultAsg,
|
||||
},
|
||||
asg::{air::AirAggregateCtx, DefaultAsg},
|
||||
diagnose::{AnnotatedSpan, Diagnostic},
|
||||
fs::{
|
||||
Filesystem, FsCanonicalizer, PathFile, VisitOnceFile,
|
||||
VisitOnceFilesystem,
|
||||
},
|
||||
ld::xmle::Sections,
|
||||
obj::xmlo::{XmloAirContext, XmloAirError, XmloError, XmloToken},
|
||||
parse::{lowerable, FinalizeError, ParseError, UnknownToken},
|
||||
pipeline,
|
||||
obj::xmlo::XmloAirContext,
|
||||
parse::{lowerable, FinalizeError},
|
||||
pipeline::{self, LoadXmloError},
|
||||
sym::{GlobalSymbolResolve, SymbolId},
|
||||
xir::{
|
||||
flat::{Text, XirToXirfError, XirfToken},
|
||||
reader::XmlXirReader,
|
||||
writer::{Error as XirWriterError, XmlWriter},
|
||||
DefaultEscaper, Error as XirError, Escaper, Token as XirToken,
|
||||
DefaultEscaper, Error as XirError, Escaper,
|
||||
},
|
||||
};
|
||||
use fxhash::FxBuildHasher;
|
||||
use std::{
|
||||
convert::identity,
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
fs,
|
||||
|
@ -106,9 +101,10 @@ fn load_xmlo<P: AsRef<Path>, S: Escaper>(
|
|||
|
||||
let src = &mut lowerable(XmlXirReader::new(file, escaper, ctx));
|
||||
|
||||
let (mut state, mut air_ctx) = pipeline::load_xmlo::<_, TameldError, _>(
|
||||
src, state, air_ctx, identity,
|
||||
)?;
|
||||
let (mut state, mut air_ctx) =
|
||||
pipeline::load_xmlo(state, air_ctx)(src, |result| {
|
||||
result.map_err(TameldError::from)
|
||||
})?;
|
||||
|
||||
let mut dir = path;
|
||||
dir.pop();
|
||||
|
@ -159,11 +155,7 @@ fn output_xmle<'a, X: XmleSections<'a>, S: Escaper>(
|
|||
pub enum TameldError {
|
||||
Io(NeqIoError),
|
||||
SortError(SortError),
|
||||
XirParseError(ParseError<UnknownToken, XirError>),
|
||||
XirfParseError(ParseError<XirToken, XirToXirfError>),
|
||||
XmloParseError(ParseError<XirfToken<Text>, XmloError>),
|
||||
XmloLowerError(ParseError<XmloToken, XmloAirError>),
|
||||
AirLowerError(ParseError<Air, AsgError>),
|
||||
LoadXmloError(LoadXmloError<XirError>),
|
||||
XirWriterError(XirWriterError),
|
||||
FinalizeError(FinalizeError),
|
||||
Fmt(fmt::Error),
|
||||
|
@ -206,33 +198,9 @@ impl From<SortError> for TameldError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<UnknownToken, XirError>> for TameldError {
|
||||
fn from(e: ParseError<UnknownToken, XirError>) -> Self {
|
||||
Self::XirParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<XirfToken<Text>, XmloError>> for TameldError {
|
||||
fn from(e: ParseError<XirfToken<Text>, XmloError>) -> Self {
|
||||
Self::XmloParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<XirToken, XirToXirfError>> for TameldError {
|
||||
fn from(e: ParseError<XirToken, XirToXirfError>) -> Self {
|
||||
Self::XirfParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<XmloToken, XmloAirError>> for TameldError {
|
||||
fn from(e: ParseError<XmloToken, XmloAirError>) -> Self {
|
||||
Self::XmloLowerError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError<Air, AsgError>> for TameldError {
|
||||
fn from(e: ParseError<Air, AsgError>) -> Self {
|
||||
Self::AirLowerError(e)
|
||||
impl From<LoadXmloError<XirError>> for TameldError {
|
||||
fn from(e: LoadXmloError<XirError>) -> Self {
|
||||
Self::LoadXmloError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,11 +227,7 @@ impl Display for TameldError {
|
|||
match self {
|
||||
Self::Io(e) => Display::fmt(e, f),
|
||||
Self::SortError(e) => Display::fmt(e, f),
|
||||
Self::XirParseError(e) => Display::fmt(e, f),
|
||||
Self::XirfParseError(e) => Display::fmt(e, f),
|
||||
Self::XmloParseError(e) => Display::fmt(e, f),
|
||||
Self::XmloLowerError(e) => Display::fmt(e, f),
|
||||
Self::AirLowerError(e) => Display::fmt(e, f),
|
||||
Self::LoadXmloError(e) => Display::fmt(e, f),
|
||||
Self::XirWriterError(e) => Display::fmt(e, f),
|
||||
Self::FinalizeError(e) => Display::fmt(e, f),
|
||||
Self::Fmt(e) => Display::fmt(e, f),
|
||||
|
@ -271,31 +235,12 @@ impl Display for TameldError {
|
|||
}
|
||||
}
|
||||
|
||||
impl Error for TameldError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
Self::Io(e) => Some(e),
|
||||
Self::SortError(e) => Some(e),
|
||||
Self::XirParseError(e) => Some(e),
|
||||
Self::XirfParseError(e) => Some(e),
|
||||
Self::XmloParseError(e) => Some(e),
|
||||
Self::XmloLowerError(e) => Some(e),
|
||||
Self::AirLowerError(e) => Some(e),
|
||||
Self::XirWriterError(e) => Some(e),
|
||||
Self::FinalizeError(e) => Some(e),
|
||||
Self::Fmt(e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for TameldError {}
|
||||
|
||||
impl Diagnostic for TameldError {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
match self {
|
||||
Self::XirParseError(e) => e.describe(),
|
||||
Self::XirfParseError(e) => e.describe(),
|
||||
Self::XmloParseError(e) => e.describe(),
|
||||
Self::XmloLowerError(e) => e.describe(),
|
||||
Self::AirLowerError(e) => e.describe(),
|
||||
Self::LoadXmloError(e) => e.describe(),
|
||||
Self::FinalizeError(e) => e.describe(),
|
||||
Self::SortError(e) => e.describe(),
|
||||
|
||||
|
|
|
@ -105,12 +105,13 @@ fn graph_sort() -> SortResult<()> {
|
|||
let asg = asg_from_toks(toks);
|
||||
let sections = sort(&asg, StubSections { pushed: Vec::new() })?;
|
||||
|
||||
let expected = vec![
|
||||
let expected = [
|
||||
// Post-order
|
||||
name_a_dep_dep,
|
||||
name_a_dep,
|
||||
name_a,
|
||||
]
|
||||
.map(Some)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
|
@ -113,10 +113,10 @@ impl<'a> XmleSections<'a> for Sections<'a> {
|
|||
fn push(&mut self, ident: &'a Ident) -> PushResult {
|
||||
self.deps.push(ident);
|
||||
|
||||
let name = ident.name();
|
||||
let frag = ident.fragment();
|
||||
let (resolved, name) = ident.resolved()?;
|
||||
|
||||
match ident.resolved()?.kind() {
|
||||
match resolved.kind() {
|
||||
Some(kind) => match kind {
|
||||
IdentKind::Cgen(..)
|
||||
| IdentKind::Gen(..)
|
||||
|
@ -156,7 +156,7 @@ impl<'a> XmleSections<'a> for Sections<'a> {
|
|||
// compiler bug and there is no use in trying to be nice
|
||||
// about a situation where something went terribly, horribly
|
||||
// wrong.
|
||||
return Err(SectionsError::MissingObjectKind(ident.name()));
|
||||
return Err(SectionsError::MissingObjectKind(name));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -256,7 +256,7 @@ fn test_writes_deps() -> TestResult {
|
|||
|
||||
assert_eq!(
|
||||
attrs.find(QN_NAME).map(|a| a.value()),
|
||||
Some(ident.name().symbol()),
|
||||
ident.name().map(|name| name.symbol()),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
//!
|
||||
//! - [`tamec`](../tamec), the TAME compiler; and
|
||||
//! - [`tameld`](../tameld), the TAME linker.
|
||||
//!
|
||||
//! The [`pipeline`] module contains declarative definitions and
|
||||
//! documentation for TAMER's _lowering pipelines_;
|
||||
//! you should start there if you are looking for how a particular
|
||||
//! component of parsing or code generation is integrated.
|
||||
|
||||
// Constant functions are still in their infancy as of the time of writing
|
||||
// (October 2021).
|
||||
|
@ -76,6 +81,8 @@
|
|||
// If this is not stabalized,
|
||||
// then we can do without by changing the abstraction;
|
||||
// this is largely experimentation to see if it's useful.
|
||||
// See `rust-toolchain.toml` for information on how this blocks more recent
|
||||
// nightly versions as of 2023-06.
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(adt_const_params)]
|
||||
// Used for traits returning functions,
|
||||
|
@ -173,19 +180,88 @@
|
|||
// which can be inscrutable if you are not very familiar with Rust's
|
||||
// borrow checker.
|
||||
#![allow(clippy::needless_lifetimes)]
|
||||
// Uh oh. Trait specialization, you say?
|
||||
// This deserves its own section.
|
||||
//
|
||||
// Rust has two trait specialization feature flags:
|
||||
// - min_specialization; and
|
||||
// - specialization.
|
||||
//
|
||||
// Both are unstable,
|
||||
// but _the latter has soundness holes when it comes to lifetimes_.
|
||||
// A viable subset of `specialization` was introduced for use in the Rust
|
||||
// compiler itself,
|
||||
// dubbed `min_specialization`.
|
||||
// That hopefully-not-unsound subset is what has been adopted here.
|
||||
//
|
||||
// Here's the problem:
|
||||
// TAMER makes _heavy_ use of the type system for various guarantees,
|
||||
// operating as proofs.
|
||||
// This static information means that we're able to determine a lot of
|
||||
// behavior statically.
|
||||
// However,
|
||||
// we also have to support various operations dynamically,
|
||||
// and marry to the two together.
|
||||
// The best example of this at the time of writing is AIR,
|
||||
// which uses static types for graph construction and manipulation
|
||||
// whenever it can,
|
||||
// but sometimes has to rely on runtime information to determine which
|
||||
// types are applicable.
|
||||
// In that case,
|
||||
// we have to match on runtime type information and branch into various
|
||||
// static paths based on that information.
|
||||
//
|
||||
// Furthermore,
|
||||
// this type information often exhibits specialized behavior for certain
|
||||
// cases,
|
||||
// and fallback behavior for all others.
|
||||
//
|
||||
// This conversion back and fourth in various direction results in either a
|
||||
// maintenance burden
|
||||
// (e.g. any time new types or variants are introduced,
|
||||
// branching code has to be manually updated),
|
||||
// or complex macros that attempt to generate that code.
|
||||
// It's all boilerplate,
|
||||
// and it's messy.
|
||||
//
|
||||
// Trait specialization allows for a simple and declarative approach to
|
||||
// solving these problems without all of the boilerplate;
|
||||
// the type system can be used to match on relevant types and will fall
|
||||
// back to specialization in situations where we are not concerned with
|
||||
// other types.
|
||||
// In situations where we _do_ want to comprehensively match all types,
|
||||
// we still have that option in the traditional way.
|
||||
//
|
||||
// TAMER will begin to slowly and carefully utilize `min_specialization` in
|
||||
// isolated areas to experiment with the stability and soundness of the
|
||||
// system.
|
||||
// You can search for its uses by searching for `default fn`.
|
||||
//
|
||||
// If it is decided to _not_ utilize this feature in the future,
|
||||
// then specialization must be replaced with burdensome branching code as
|
||||
// mentioned above.
|
||||
// It is doable without sacrificing type safety,
|
||||
// but it makes many changes very time-consuming and therefore very
|
||||
// expensive.
|
||||
//
|
||||
// (At the time of writing,
|
||||
// there is no clear path to stabalization of this feature.)
|
||||
#![feature(min_specialization)]
|
||||
|
||||
pub mod global;
|
||||
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
|
||||
#[macro_use]
|
||||
pub mod f;
|
||||
#[macro_use]
|
||||
pub mod diagnose;
|
||||
#[macro_use]
|
||||
pub mod xir;
|
||||
|
||||
pub mod asg;
|
||||
pub mod convert;
|
||||
pub mod diagnose;
|
||||
pub mod f;
|
||||
pub mod fmt;
|
||||
pub mod fs;
|
||||
pub mod iter;
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
//! The entry point for NIR in the lowering pipeline is exported as
|
||||
//! [`XirfToNir`].
|
||||
|
||||
mod abstract_bind;
|
||||
mod air;
|
||||
mod interp;
|
||||
mod parse;
|
||||
|
@ -56,7 +57,7 @@ mod tplshort;
|
|||
|
||||
use crate::{
|
||||
diagnose::{Annotate, Diagnostic},
|
||||
f::Functor,
|
||||
f::Map,
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
parse::{util::SPair, Object, Token},
|
||||
span::Span,
|
||||
|
@ -72,12 +73,13 @@ use std::{
|
|||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
pub use air::{NirToAir, NirToAirError};
|
||||
pub use abstract_bind::{AbstractBindTranslate, AbstractBindTranslateError};
|
||||
pub use air::{NirToAir, NirToAirError, NirToAirParseType};
|
||||
pub use interp::{InterpError, InterpState as InterpolateNir};
|
||||
pub use parse::{
|
||||
NirParseState as XirfToNir, NirParseStateError_ as XirfToNirError,
|
||||
};
|
||||
pub use tplshort::TplShortDesugar;
|
||||
pub use tplshort::{TplShortDesugar, TplShortDesugarError};
|
||||
|
||||
/// IR that is "near" the source code.
|
||||
///
|
||||
|
@ -96,12 +98,33 @@ pub enum Nir {
|
|||
/// Finish definition of a [`NirEntity`] atop of the stack and pop it.
|
||||
Close(NirEntity, Span),
|
||||
|
||||
/// Bind the given name as an identifier for the entity atop of the
|
||||
/// stack.
|
||||
/// Bind the given name as a concrete identifier for the entity atop of
|
||||
/// the stack.
|
||||
///
|
||||
/// [`Self::Ref`] references identifiers created using this token.
|
||||
///
|
||||
/// See also [`Self::BindIdentAbstract`].
|
||||
BindIdent(SPair),
|
||||
|
||||
/// Bind entity atop of the stack to an abstract identifier whose name
|
||||
/// will eventually be derived from the metavariable identifier by the
|
||||
/// given [`SPair`].
|
||||
///
|
||||
/// The identifier is intended to become concrete when a lexical value
|
||||
/// for the metavariable becomes available during expansion,
|
||||
/// which is outside of the scope of NIR.
|
||||
///
|
||||
/// See also [`Self::BindIdent`] for a concrete identifier.
|
||||
BindIdentAbstract(SPair),
|
||||
|
||||
/// The name should be interpreted as a concrete name of a
|
||||
/// metavariable.
|
||||
///
|
||||
/// This is broadly equivalent to [`Self::BindIdent`] but is intended to
|
||||
/// convey that no NIR operation should ever translate this token into
|
||||
/// [`Self::BindIdentAbstract`].
|
||||
BindIdentMeta(SPair),
|
||||
|
||||
/// Reference the value of the given identifier as the subject of the
|
||||
/// current expression.
|
||||
///
|
||||
|
@ -163,15 +186,23 @@ pub enum Nir {
|
|||
}
|
||||
|
||||
impl Nir {
|
||||
/// Retrieve inner [`SymbolId`] that this token represents,
|
||||
/// Retrieve a _concrete_ inner [`SymbolId`] that this token represents,
|
||||
/// if any.
|
||||
///
|
||||
/// Not all NIR tokens contain associated symbols;
|
||||
/// a token's [`SymbolId`] is retained only if it provides additional
|
||||
/// information over the token itself.
|
||||
///
|
||||
/// See also [`Nir::map`] if you wish to change the symbol.
|
||||
pub fn symbol(&self) -> Option<SymbolId> {
|
||||
/// An abstract identifier will yield [`None`],
|
||||
/// since its concrete symbol has yet to be defined;
|
||||
/// the available symbol instead represents the name of the
|
||||
/// metavariable from which the concrete symbol will eventually
|
||||
/// have its value derived.
|
||||
///
|
||||
/// See also [`Nir::map`] if you wish to change the symbol,
|
||||
/// noting however that it does not distinguish between notions of
|
||||
/// concrete and abstract as this method does.
|
||||
pub fn concrete_symbol(&self) -> Option<SymbolId> {
|
||||
use Nir::*;
|
||||
|
||||
match self {
|
||||
|
@ -180,15 +211,24 @@ impl Nir {
|
|||
|
||||
Open(_, _) | Close(_, _) => None,
|
||||
|
||||
BindIdent(spair) | RefSubject(spair) | Ref(spair) | Desc(spair)
|
||||
| Text(spair) | Import(spair) => Some(spair.symbol()),
|
||||
BindIdent(spair) | BindIdentMeta(spair) | RefSubject(spair)
|
||||
| Ref(spair) | Desc(spair) | Text(spair) | Import(spair) => {
|
||||
Some(spair.symbol())
|
||||
}
|
||||
|
||||
// An abstract identifier does not yet have a concrete symbol
|
||||
// assigned;
|
||||
// the available symbol represents the metavariable from
|
||||
// which a symbol will eventually be derived during
|
||||
// expansion.
|
||||
BindIdentAbstract(_) => None,
|
||||
|
||||
Noop(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Functor<SymbolId> for Nir {
|
||||
impl Map<SymbolId> for Nir {
|
||||
/// Map over a token's [`SymbolId`].
|
||||
///
|
||||
/// This allows modifying a token's [`SymbolId`] while retaining the
|
||||
|
@ -201,8 +241,7 @@ impl Functor<SymbolId> for Nir {
|
|||
/// If a token does not contain a symbol,
|
||||
/// this returns the token unchanged.
|
||||
///
|
||||
/// See also [`Nir::symbol`] if you only wish to retrieve the symbol
|
||||
/// rather than map over it.
|
||||
/// See also [`Nir::concrete_symbol`].
|
||||
fn map(self, f: impl FnOnce(SymbolId) -> SymbolId) -> Self {
|
||||
use Nir::*;
|
||||
|
||||
|
@ -213,6 +252,8 @@ impl Functor<SymbolId> for Nir {
|
|||
Open(_, _) | Close(_, _) => self,
|
||||
|
||||
BindIdent(spair) => BindIdent(spair.map(f)),
|
||||
BindIdentAbstract(spair) => BindIdentAbstract(spair.map(f)),
|
||||
BindIdentMeta(spair) => BindIdentMeta(spair.map(f)),
|
||||
RefSubject(spair) => RefSubject(spair.map(f)),
|
||||
Ref(spair) => Ref(spair.map(f)),
|
||||
Desc(spair) => Desc(spair.map(f)),
|
||||
|
@ -339,8 +380,14 @@ impl Token for Nir {
|
|||
Open(_, span) => *span,
|
||||
Close(_, span) => *span,
|
||||
|
||||
BindIdent(spair) | RefSubject(spair) | Ref(spair) | Desc(spair)
|
||||
| Text(spair) | Import(spair) => spair.span(),
|
||||
BindIdent(spair)
|
||||
| BindIdentAbstract(spair)
|
||||
| BindIdentMeta(spair)
|
||||
| RefSubject(spair)
|
||||
| Ref(spair)
|
||||
| Desc(spair)
|
||||
| Text(spair)
|
||||
| Import(spair) => spair.span(),
|
||||
|
||||
// A no-op is discarding user input,
|
||||
// so we still want to know where that is so that we can
|
||||
|
@ -363,7 +410,26 @@ impl Display for Nir {
|
|||
Open(entity, _) => write!(f, "open {entity} entity"),
|
||||
Close(entity, _) => write!(f, "close {entity} entity"),
|
||||
BindIdent(spair) => {
|
||||
write!(f, "bind identifier {}", TtQuote::wrap(spair))
|
||||
write!(
|
||||
f,
|
||||
"bind to concrete identifier {}",
|
||||
TtQuote::wrap(spair)
|
||||
)
|
||||
}
|
||||
BindIdentAbstract(spair) => {
|
||||
write!(
|
||||
f,
|
||||
"bind to abstract identifier with future value of \
|
||||
metavariable {}",
|
||||
TtQuote::wrap(spair)
|
||||
)
|
||||
}
|
||||
BindIdentMeta(spair) => {
|
||||
write!(
|
||||
f,
|
||||
"bind metavariable to concreate identifier {}",
|
||||
TtQuote::wrap(spair)
|
||||
)
|
||||
}
|
||||
RefSubject(spair) => {
|
||||
write!(f, "subject ref {}", TtQuote::wrap(spair))
|
||||
|
|
|
@ -0,0 +1,295 @@
|
|||
// Abstract binding translation for NIR
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
//! Translation of non-interpolated abstract bindings for NIR.
|
||||
//!
|
||||
//! Metavariables can be used in the context of an identifier binding to
|
||||
//! produce identifiers dynamically via template expansion.
|
||||
//! For example:
|
||||
//!
|
||||
//! ```xml
|
||||
//! <classify as="@as@" yields="@yields@">
|
||||
//! <!-- ^^^^ ^^^^^^^^
|
||||
//! Ident Ident -->
|
||||
//!
|
||||
//! <match on="@match@" />
|
||||
//! <!-- ~~~~~~~
|
||||
//! Ref -->
|
||||
//! </classify>
|
||||
//! ```
|
||||
//!
|
||||
//! In the above example,
|
||||
//! both `@as@` and `@yields@` represent identifier bindings,
|
||||
//! but the concrete names are not known until the expansion of the
|
||||
//! respective metavariables.
|
||||
//! This is equivalently expressed using interpolation:
|
||||
//!
|
||||
//! ```xml
|
||||
//! <classify as="{@as@}" yields="{@yields@}">
|
||||
//! <!-- ^^^^^^ ^^^^^^^^^^ -->
|
||||
//! ```
|
||||
//!
|
||||
//! The above interpolation would cause the generation of an abstract
|
||||
//! identifier via [`super::interp`].
|
||||
//! However,
|
||||
//! because metavariables historically have an exclusive naming convention
|
||||
//! that requires a `@` prefix and suffix,
|
||||
//! the curly braces can be optionally omitted.
|
||||
//! This works just fine for the `@match@` _reference_ above,
|
||||
//! because that reference is unambiguously referring to a metavariable of
|
||||
//! that name.
|
||||
//!
|
||||
//! But binding contexts are different,
|
||||
//! because they assert that the provided lexical symbol should serve as
|
||||
//! the name of an identifier.
|
||||
//! We need an additional and explicit level of indirection,
|
||||
//! otherwise we run into the following ambiguity:
|
||||
//!
|
||||
//! ```xml
|
||||
//! <template name="_foo_">
|
||||
//! <param name="@as@" />
|
||||
//! <!-- ^^^^
|
||||
//! !
|
||||
//! vvv -->
|
||||
//! <classify as="@as@" />
|
||||
//! </template>
|
||||
//! ```
|
||||
//!
|
||||
//! In this case,
|
||||
//! if we interpret `@as@` in both contexts to be bindings,
|
||||
//! then there is a redefinition,
|
||||
//! which is an error.
|
||||
//! We instead want the equivalent of this:
|
||||
//!
|
||||
//! ```xml
|
||||
//! <template name="_foo_">
|
||||
//! <param name="@as@" desc="Name of classification" />
|
||||
//! <!-- ^^^^ -->
|
||||
//!
|
||||
//! <classify as="{@as@}" />
|
||||
//! <!-- ~~~~ -->
|
||||
//! </template>
|
||||
//! ```
|
||||
//!
|
||||
//! This creates an awkward ambiguity,
|
||||
//! because what if we instead want this?
|
||||
//!
|
||||
//! ```xml
|
||||
//! <template name="_foo_">
|
||||
//! <param name="{@as@}" desc="Name of classification" />
|
||||
//! <!-- ~~~~ -->
|
||||
//!
|
||||
//! <classify as="{@as@}" />
|
||||
//! <!-- ~~~~ -->
|
||||
//! </template>
|
||||
//! ```
|
||||
//!
|
||||
//! This was not possible in the XSLT-based TAME.
|
||||
//! TAMER instead adopts this awkward convention,
|
||||
//! implemented in this module:
|
||||
//!
|
||||
//! 1. Template parameters treat all symbols in binding position
|
||||
//! (`@name`)
|
||||
//! as concrete identifiers.
|
||||
//! This behavior can be overridden using curly braces to trigger
|
||||
//! interpolation.
|
||||
//!
|
||||
//! 2. All other bindings treat symbols matching the `@`-prefix-suffix
|
||||
//! metavariable naming convention as abstract bindings.
|
||||
//! This is equivalent to the interpolation behavior.
|
||||
//!
|
||||
//! To support this interpretation,
|
||||
//! this lowering operation requires that all names of
|
||||
//! [`Nir::BindIdentMeta`] be padded with a single `@` on each side,
|
||||
//! as shown in the examples above.
|
||||
//!
|
||||
//! Lowering Pipeline Ordering
|
||||
//! ==========================
|
||||
//! This module is an _optional_ syntactic feature of TAME.
|
||||
//! If desired,
|
||||
//! this module could be omitted from the lowering pipeline in favor of
|
||||
//! explicitly utilizing interpolation for all abstract identifiers.
|
||||
//!
|
||||
//! Interpolation via [`super::interp`] its own [`Nir::BindIdentAbstract`]
|
||||
//! tokens,
|
||||
//! and shorthand template application via [`super::tplshort`] desugars
|
||||
//! into both the proper `@`-padded naming convention and
|
||||
//! [`Nir::BindIdentAbstract`].
|
||||
//! It should therefore be possible to place this operation in any order
|
||||
//! relative to those two.
|
||||
|
||||
use super::Nir;
|
||||
use crate::{
|
||||
fmt::TtQuote, parse::prelude::*, span::Span, sym::GlobalSymbolResolve,
|
||||
};
|
||||
use memchr::memchr;
|
||||
|
||||
use Nir::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct AbstractBindTranslate;
|
||||
|
||||
impl Display for AbstractBindTranslate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"scanning for abstract binding via metavariable naming convention"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseState for AbstractBindTranslate {
|
||||
type Token = Nir;
|
||||
type Object = Nir;
|
||||
type Error = AbstractBindTranslateError;
|
||||
|
||||
fn parse_token(
|
||||
self,
|
||||
tok: Self::Token,
|
||||
_: &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
match tok {
|
||||
BindIdent(name) if needs_translation(name) => {
|
||||
Transition(self).ok(BindIdentAbstract(name))
|
||||
}
|
||||
|
||||
BindIdentMeta(name) => validate_meta_name(name)
|
||||
.map(BindIdentMeta)
|
||||
.map(ParseStatus::Object)
|
||||
.transition(self),
|
||||
|
||||
_ => Transition(self).ok(tok),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepting(&self, _: &Self::Context) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate that the provided name is padded with a single `@` on both
|
||||
/// sides.
|
||||
///
|
||||
/// This check is necessary to ensure that we can properly infer when a
|
||||
/// metavariable is in use in a bind position without having to rely on
|
||||
/// interpolation.
|
||||
///
|
||||
/// TODO: This does not yet place any other restrictions on the name of a
|
||||
/// metavariable;
|
||||
/// we'll take care of that when we decide on an approach for other
|
||||
/// names.
|
||||
fn validate_meta_name(
|
||||
meta: SPair,
|
||||
) -> Result<SPair, AbstractBindTranslateError> {
|
||||
let name = meta.symbol().lookup_str();
|
||||
|
||||
if !name.starts_with('@') {
|
||||
Err(AbstractBindTranslateError::MetaNamePadMissing(
|
||||
meta,
|
||||
meta.span().slice_head(0),
|
||||
))
|
||||
} else if !name.ends_with('@') {
|
||||
Err(AbstractBindTranslateError::MetaNamePadMissing(
|
||||
meta,
|
||||
meta.span().slice_tail(0),
|
||||
))
|
||||
} else {
|
||||
Ok(meta)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether the given name requires translation into an abstract
|
||||
/// identifier.
|
||||
///
|
||||
/// It's important to understand how the naming convention is utilized;
|
||||
/// this assumes that:
|
||||
///
|
||||
/// 1. Metavariables are `@`-prefixed.
|
||||
/// The convention is actually to have a suffix too,
|
||||
/// but since `@` is not permitted at the time of writing for any
|
||||
/// other types of identifiers,
|
||||
/// it should be the case that a prefix also implies a suffix,
|
||||
/// otherwise some other portion of the system will fail.
|
||||
/// 2. This should not be consulted for metavariable definitions,
|
||||
/// like template parameters.
|
||||
fn needs_translation(name: SPair) -> bool {
|
||||
// Unlike the interpolation module which must check many symbols,
|
||||
// we assume here that it's not necessary
|
||||
// (and may be even be determental)
|
||||
// for a "quick" check version given that this is invoked for
|
||||
// bindings,
|
||||
// and bindings will very likely introduce something new.
|
||||
// It'd be worth verifying this assumption at some point in the future,
|
||||
// but is unlikely to make a significant different either way.
|
||||
#[rustfmt::skip]
|
||||
matches!(
|
||||
memchr(b'@', name.symbol().lookup_str().as_bytes()),
|
||||
Some(0),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AbstractBindTranslateError {
|
||||
/// A metavariable does not adhere to the naming convention requiring
|
||||
/// `@`-padding.
|
||||
///
|
||||
/// The provided [`Span`] is the first occurrence of such a violation.
|
||||
/// If `@` is missing from both the beginning and end of the name,
|
||||
/// then one of them is chosen.
|
||||
MetaNamePadMissing(SPair, Span),
|
||||
}
|
||||
|
||||
impl Display for AbstractBindTranslateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use AbstractBindTranslateError::*;
|
||||
|
||||
match self {
|
||||
MetaNamePadMissing(name, _) => write!(
|
||||
f,
|
||||
"metavariable {} must both begin and end with `@`",
|
||||
TtQuote::wrap(name),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for AbstractBindTranslateError {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
use AbstractBindTranslateError::*;
|
||||
|
||||
match self {
|
||||
MetaNamePadMissing(_, at) => vec![
|
||||
at.error("missing `@` here"),
|
||||
at.help(
|
||||
"metavariables (such as template parameters) must \
|
||||
have names that both begin and end with the \
|
||||
character `@`",
|
||||
),
|
||||
at.help(
|
||||
"this naming requirement is necessary to make curly \
|
||||
braces optional when referencing metavariables \
|
||||
without requiring interpolation",
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
|
@ -0,0 +1,171 @@
|
|||
// Test abstract binding translation for NIR
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
use super::*;
|
||||
use crate::span::dummy::{DUMMY_CONTEXT as DC, *};
|
||||
|
||||
type Sut = AbstractBindTranslate;
|
||||
use Parsed::Object as O;
|
||||
|
||||
#[test]
|
||||
fn already_abstract_bind_ignored_despite_metavar_naming() {
|
||||
// This is named as a metavariable.
|
||||
let name = "@foo@".into();
|
||||
|
||||
assert_tok_translate(
|
||||
// Identifier is already abstract...
|
||||
BindIdentAbstract(SPair(name, S1)),
|
||||
// ...so this acts as an identity operation.
|
||||
BindIdentAbstract(SPair(name, S1)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concrete_bind_without_metavar_naming_ignored_non_meta() {
|
||||
// This is _not_ named as a metavariable.
|
||||
let name = "concrete".into();
|
||||
|
||||
assert_tok_translate(
|
||||
// Identifier is concrete and the name does not follow a
|
||||
// metavariable naming convention...
|
||||
BindIdent(SPair(name, S1)),
|
||||
// ...and so this acts as an identity operation.
|
||||
BindIdent(SPair(name, S1)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_meta_concrete_bind_with_metavar_naming_translated_to_abstract_bind() {
|
||||
// This is named as a metavariable.
|
||||
let name = "@foo@".into();
|
||||
|
||||
assert_tok_translate(
|
||||
// Identifier is concrete...
|
||||
BindIdent(SPair(name, S1)),
|
||||
// ...and so gets translated into an abstract binding.
|
||||
// Its data are otherwise the same.
|
||||
BindIdentAbstract(SPair(name, S1)),
|
||||
);
|
||||
}
|
||||
|
||||
// Metavariable definitions must be left concrete since they produce the
|
||||
// identifiers that are utilized by other abstract identifiers.
|
||||
#[test]
|
||||
fn meta_concrete_bind_with_metavar_naming_ignored() {
|
||||
// This is named as a metavariable.
|
||||
let name = "@param@".into();
|
||||
|
||||
assert_tok_translate(
|
||||
// This identifier utilizes a metavariable naming convention,
|
||||
// but we're in a metavariable definition context.
|
||||
BindIdentMeta(SPair(name, S2)),
|
||||
// And so the bind stays concrete.
|
||||
BindIdentMeta(SPair(name, S2)),
|
||||
);
|
||||
}
|
||||
|
||||
// This lowering operation utilizes a naming convention to infer user intent
|
||||
// and lift the requirement for curly braces;
|
||||
// they go hand-in-hand.
|
||||
// To utilize this feature,
|
||||
// we must also require adherence to the naming convention so that we know
|
||||
// that our assumptions hold.
|
||||
//
|
||||
// We can't check for the opposite---
|
||||
// that non-meta identifiers must _not_ follow that convention---
|
||||
// because we interpet such occurrences as abstract identifiers.
|
||||
// In practice,
|
||||
// users will get an error because the conversion into a reference will
|
||||
// yield an error when the metavariable does not exist already as a
|
||||
// reference,
|
||||
// or a duplicate definition error if it was already defined.
|
||||
#[test]
|
||||
fn rejects_metavariable_without_naming_convention() {
|
||||
let name_a = "@missing-end".into();
|
||||
// | |
|
||||
// | 11
|
||||
// | B
|
||||
// [----------]
|
||||
// 0 11
|
||||
// A
|
||||
|
||||
let a_a = DC.span(10, 12);
|
||||
let a_b = DC.span(22, 0); // _after_ last char
|
||||
|
||||
let name_b = "missing-start@".into();
|
||||
// | |
|
||||
// 0 |
|
||||
// A |
|
||||
// [------------]
|
||||
// 0 13
|
||||
// A
|
||||
let b_a = DC.span(10, 14);
|
||||
let b_b = DC.span(10, 0); // _before_ first char
|
||||
|
||||
let name_c = "missing-both".into();
|
||||
// | |
|
||||
// 0 |
|
||||
// B |
|
||||
// [----------]
|
||||
// 0 11
|
||||
// A
|
||||
let c_a = DC.span(10, 12);
|
||||
let c_b = DC.span(10, 0); // _before_ first char
|
||||
|
||||
// Each of these will result in slightly different failures.
|
||||
#[rustfmt::skip]
|
||||
let toks = [
|
||||
BindIdentMeta(SPair(name_a, a_a)),
|
||||
BindIdentMeta(SPair(name_b, b_a)),
|
||||
BindIdentMeta(SPair(name_c, c_a)),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
#[rustfmt::skip]
|
||||
vec![
|
||||
Err(ParseError::StateError(
|
||||
AbstractBindTranslateError::MetaNamePadMissing(
|
||||
SPair(name_a, a_a),
|
||||
a_b,
|
||||
),
|
||||
)),
|
||||
Err(ParseError::StateError(
|
||||
AbstractBindTranslateError::MetaNamePadMissing(
|
||||
SPair(name_b, b_a),
|
||||
b_b,
|
||||
),
|
||||
)),
|
||||
Err(ParseError::StateError(
|
||||
AbstractBindTranslateError::MetaNamePadMissing(
|
||||
SPair(name_c, c_a),
|
||||
c_b,
|
||||
),
|
||||
)),
|
||||
],
|
||||
Sut::parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_tok_translate(tok: Nir, expect: Nir) {
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
Ok(vec![O(expect)]),
|
||||
Sut::parse([tok].into_iter()).collect()
|
||||
);
|
||||
}
|
|
@ -21,22 +21,56 @@
|
|||
|
||||
use super::Nir;
|
||||
use crate::{
|
||||
asg::air::Air,
|
||||
diagnose::{Annotate, Diagnostic},
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
asg::{air::Air, ExprOp},
|
||||
fmt::TtQuote,
|
||||
nir::{Nir::*, NirEntity::*},
|
||||
parse::prelude::*,
|
||||
span::Span,
|
||||
};
|
||||
use arrayvec::ArrayVec;
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
// These are also used by the `test` module which imports `super`.
|
||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
||||
use crate::{
|
||||
asg::ExprOp,
|
||||
nir::{Nir::*, NirEntity::*},
|
||||
sym::{st::raw::U_TRUE, SymbolId},
|
||||
};
|
||||
use arrayvec::ArrayVec;
|
||||
|
||||
/// Dynamic [`NirToAir`] parser configuration.
|
||||
///
|
||||
/// This acts as a runtime feature flag while this portions of TAMER is
|
||||
/// under development.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum NirToAirParseType {
|
||||
/// Discard incoming tokens instead of lowering them.
|
||||
Noop,
|
||||
|
||||
/// Lower known tokens,
|
||||
/// but produce errors for everything else that is not yet supported.
|
||||
///
|
||||
/// It is expected that this will fail on at least some packages;
|
||||
/// this should be enabled only on packages known to compile with the
|
||||
/// new system.
|
||||
LowerKnownErrorRest,
|
||||
}
|
||||
|
||||
impl Display for NirToAirParseType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Noop => write!(f, "discarding all tokens (noop)"),
|
||||
Self::LowerKnownErrorRest => write!(
|
||||
f,
|
||||
"lowering only supported tokens and failing on all others"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NirToAirParseType> for (NirToAirParseType, ObjStack) {
|
||||
fn from(ty: NirToAirParseType) -> Self {
|
||||
(ty, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(NirToAirParseType, ObjStack)> for NirToAirParseType {
|
||||
fn from((ty, _): (NirToAirParseType, ObjStack)) -> Self {
|
||||
ty
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Default)]
|
||||
pub enum NirToAir {
|
||||
|
@ -78,37 +112,27 @@ type ObjStack = ArrayVec<Air, 2>;
|
|||
|
||||
/// The symbol to use when lexically expanding shorthand notations to
|
||||
/// compare against values of `1`.
|
||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
||||
pub const SYM_TRUE: SymbolId = U_TRUE;
|
||||
|
||||
impl ParseState for NirToAir {
|
||||
type Token = Nir;
|
||||
type Object = Air;
|
||||
type Error = NirToAirError;
|
||||
type Context = ObjStack;
|
||||
type Context = (NirToAirParseType, ObjStack);
|
||||
type PubContext = NirToAirParseType;
|
||||
|
||||
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
||||
fn parse_token(
|
||||
self,
|
||||
tok: Self::Token,
|
||||
_queue: &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
use NirToAir::*;
|
||||
|
||||
let _ = tok; // prevent `unused_variables` warning
|
||||
Transition(Ready).ok(Air::Todo(crate::span::UNKNOWN_SPAN))
|
||||
}
|
||||
|
||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
||||
fn parse_token(
|
||||
self,
|
||||
tok: Self::Token,
|
||||
stack: &mut Self::Context,
|
||||
(parse_type, stack): &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
use NirToAir::*;
|
||||
use NirToAirError::*;
|
||||
|
||||
use crate::diagnostic_panic;
|
||||
match parse_type {
|
||||
NirToAirParseType::Noop => return Transition(Ready).incomplete(),
|
||||
NirToAirParseType::LowerKnownErrorRest => (),
|
||||
}
|
||||
|
||||
if let Some(obj) = stack.pop() {
|
||||
return Transition(Ready).ok(obj).with_lookahead(tok);
|
||||
|
@ -228,6 +252,12 @@ impl ParseState for NirToAir {
|
|||
(Ready, Open(TplParam, span)) => {
|
||||
Transition(Meta(span)).ok(Air::MetaStart(span))
|
||||
}
|
||||
(Meta(mspan), BindIdentMeta(spair)) => {
|
||||
Transition(Meta(mspan)).ok(Air::BindIdent(spair))
|
||||
}
|
||||
(Meta(mspan), Ref(spair)) => {
|
||||
Transition(Meta(mspan)).ok(Air::RefIdent(spair))
|
||||
}
|
||||
(Meta(mspan), Text(lexeme)) => {
|
||||
Transition(Meta(mspan)).ok(Air::MetaLexeme(lexeme))
|
||||
}
|
||||
|
@ -237,10 +267,9 @@ impl ParseState for NirToAir {
|
|||
// Some of these will be permitted in the future.
|
||||
(
|
||||
Meta(mspan),
|
||||
tok @ (Open(..) | Close(..) | Ref(..) | RefSubject(..)
|
||||
| Desc(..)),
|
||||
tok @ (Open(..) | Close(..) | BindIdent(..) | RefSubject(..)),
|
||||
) => Transition(Meta(mspan))
|
||||
.err(NirToAirError::UnexpectedMetaToken(mspan, tok)),
|
||||
.err(NirToAirError::ExpectedMetaToken(mspan, tok)),
|
||||
|
||||
(Ready, Text(text)) => Transition(Ready).ok(Air::DocText(text)),
|
||||
|
||||
|
@ -252,15 +281,18 @@ impl ParseState for NirToAir {
|
|||
),
|
||||
) => Transition(Ready).ok(Air::ExprEnd(span)),
|
||||
|
||||
(st @ (Ready | Meta(_)), BindIdent(spair)) => {
|
||||
Transition(st).ok(Air::BindIdent(spair))
|
||||
(Ready, BindIdent(spair)) => {
|
||||
Transition(Ready).ok(Air::BindIdent(spair))
|
||||
}
|
||||
(st @ (Ready | Meta(_)), BindIdentAbstract(spair)) => {
|
||||
Transition(st).ok(Air::BindIdentAbstract(spair))
|
||||
}
|
||||
(Ready, Ref(spair) | RefSubject(spair)) => {
|
||||
Transition(Ready).ok(Air::RefIdent(spair))
|
||||
}
|
||||
|
||||
(Ready, Desc(clause)) => {
|
||||
Transition(Ready).ok(Air::DocIndepClause(clause))
|
||||
(st @ (Ready | Meta(_)), Desc(clause)) => {
|
||||
Transition(st).ok(Air::DocIndepClause(clause))
|
||||
}
|
||||
|
||||
(Ready, Import(namespec)) => {
|
||||
|
@ -272,27 +304,48 @@ impl ParseState for NirToAir {
|
|||
// This assumption is only valid so long as that's the only
|
||||
// thing producing NIR.
|
||||
(st @ Meta(..), tok @ Import(_)) => Transition(st).dead(tok),
|
||||
(st @ Ready, tok @ BindIdentMeta(_)) => Transition(st).dead(tok),
|
||||
|
||||
(_, tok @ (Todo(..) | TodoAttr(..))) => {
|
||||
crate::diagnostic_todo!(
|
||||
vec![tok.internal_error(
|
||||
"this token is not yet supported in TAMER"
|
||||
)],
|
||||
"unsupported token: {tok}",
|
||||
)
|
||||
// Unsupported tokens yield errors.
|
||||
// There _is_ a risk that this will put us in a wildly
|
||||
// inconsistent state,
|
||||
// yielding nonsense errors in the future.
|
||||
// This used to panic,
|
||||
// but yielding errors allows compilation to continue and
|
||||
// discover further problems,
|
||||
// so that this new parser can be run on a given package
|
||||
// (with e.g. `--emit xmlo-experimental`)
|
||||
// to get some idea of what type of missing features may
|
||||
// be needed to support the compilation of that package.
|
||||
// Note also that,
|
||||
// at the time of writing,
|
||||
// large numbers of diagnostic spans may be quite slow to
|
||||
// output on large files because the system does not cache
|
||||
// newline locations and requires re-calculating from the
|
||||
// beginning of the file for earlier spans.
|
||||
(st, tok @ (Todo(..) | TodoAttr(..))) => {
|
||||
Transition(st).err(NirToAirError::UnsupportedToken(tok))
|
||||
}
|
||||
|
||||
(st, Noop(_)) => Transition(st).incomplete(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepting(&self, stack: &Self::Context) -> bool {
|
||||
fn is_accepting(&self, (_, stack): &Self::Context) -> bool {
|
||||
matches!(self, Self::Ready) && stack.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum NirToAirError {
|
||||
/// The provided token is not yet supported by TAMER.
|
||||
///
|
||||
/// This means that a token was recognized by NIR but it makes no
|
||||
/// guarantees about _whether_ a token will be supported in the
|
||||
/// future;
|
||||
/// explicit rejection is still a future possibility.
|
||||
UnsupportedToken(Nir),
|
||||
|
||||
/// Expected a match subject,
|
||||
/// but encountered some other token.
|
||||
///
|
||||
|
@ -306,7 +359,7 @@ pub enum NirToAirError {
|
|||
|
||||
/// The provided [`Nir`] token of input was unexpected for the body of a
|
||||
/// metavariable that was opened at the provided [`Span`].
|
||||
UnexpectedMetaToken(Span, Nir),
|
||||
ExpectedMetaToken(Span, Nir),
|
||||
}
|
||||
|
||||
impl Display for NirToAirError {
|
||||
|
@ -314,6 +367,10 @@ impl Display for NirToAirError {
|
|||
use NirToAirError::*;
|
||||
|
||||
match self {
|
||||
UnsupportedToken(tok) => {
|
||||
write!(f, "unsupported token: {tok}")
|
||||
}
|
||||
|
||||
MatchSubjectExpected(_, nir) => {
|
||||
write!(f, "expected match subject, found {nir}")
|
||||
}
|
||||
|
@ -322,7 +379,7 @@ impl Display for NirToAirError {
|
|||
write!(f, "match body is not yet supported by TAMER")
|
||||
}
|
||||
|
||||
UnexpectedMetaToken(_, tok) => {
|
||||
ExpectedMetaToken(_, tok) => {
|
||||
write!(
|
||||
f,
|
||||
"expected lexical token for metavariable, found {tok}"
|
||||
|
@ -341,6 +398,19 @@ impl Diagnostic for NirToAirError {
|
|||
use NirToAirError::*;
|
||||
|
||||
match self {
|
||||
UnsupportedToken(tok) => vec![
|
||||
tok.span().internal_error("this token is not yet supported in TAMER"),
|
||||
tok.span().help(
|
||||
"if this is unexpected, \
|
||||
are you unintentionally using the `--emit xmlo-experimental` \
|
||||
command line option?"
|
||||
),
|
||||
tok.span().help(
|
||||
"this package may also have a sibling `.experimental` file \
|
||||
that triggers `xmlo-experimental`"
|
||||
),
|
||||
],
|
||||
|
||||
MatchSubjectExpected(ospan, given) => vec![
|
||||
ospan.note("for this match"),
|
||||
given
|
||||
|
@ -358,7 +428,7 @@ impl Diagnostic for NirToAirError {
|
|||
// The user should have been preempted by the parent parser
|
||||
// (e.g. XML->Nir),
|
||||
// and so shouldn't see this.
|
||||
UnexpectedMetaToken(mspan, given) => vec![
|
||||
ExpectedMetaToken(mspan, given) => vec![
|
||||
mspan.note("while parsing the body of this metavariable"),
|
||||
given.span().error("expected a lexical token here"),
|
||||
],
|
||||
|
@ -366,5 +436,5 @@ impl Diagnostic for NirToAirError {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "wip-asg-derived-xmli"))]
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
|
|
@ -18,12 +18,33 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use crate::{parse::util::SPair, span::dummy::*};
|
||||
use crate::{
|
||||
parse::{util::SPair, Parser},
|
||||
span::dummy::*,
|
||||
};
|
||||
|
||||
type Sut = NirToAir;
|
||||
|
||||
use Parsed::{Incomplete, Object as O};
|
||||
|
||||
fn sut_parse<I: Iterator<Item = Nir>>(toks: I) -> Parser<Sut, I> {
|
||||
Sut::parse_with_context(
|
||||
toks.into_iter(),
|
||||
NirToAirParseType::LowerKnownErrorRest,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_input_when_parse_type_is_noop() {
|
||||
let toks = vec![Open(Package, S1), Close(Package, S2)];
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![Incomplete, Incomplete,]),
|
||||
Sut::parse_with_context(toks.into_iter(), NirToAirParseType::Noop)
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_to_pkg() {
|
||||
let toks = vec![Open(Package, S1), Close(Package, S2)];
|
||||
|
@ -33,7 +54,7 @@ fn package_to_pkg() {
|
|||
O(Air::PkgStart(S1, SPair("/TODO".into(), S1))),
|
||||
O(Air::PkgEnd(S2)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -55,7 +76,7 @@ fn rate_to_sum_expr() {
|
|||
O(Air::BindIdent(id)),
|
||||
O(Air::ExprEnd(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -77,7 +98,7 @@ fn calc_exprs() {
|
|||
O(Air::ExprEnd(S3)),
|
||||
O(Air::ExprEnd(S4)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -99,7 +120,7 @@ fn classify_to_conj_expr() {
|
|||
O(Air::BindIdent(id)),
|
||||
O(Air::ExprEnd(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -121,7 +142,7 @@ fn logic_exprs() {
|
|||
O(Air::ExprEnd(S3)),
|
||||
O(Air::ExprEnd(S4)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -148,7 +169,7 @@ fn desc_as_indep_clause() {
|
|||
O(Air::DocIndepClause(desc)),
|
||||
O(Air::ExprEnd(S4)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -170,7 +191,41 @@ fn tpl_with_name() {
|
|||
O(Air::BindIdent(name)),
|
||||
O(Air::TplEnd(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tpl_with_param() {
|
||||
let name_tpl = SPair("_tpl_".into(), S2);
|
||||
let name_param = SPair("@param@".into(), S4);
|
||||
let desc_param = SPair("param desc".into(), S5);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Open(Tpl, S1),
|
||||
BindIdent(name_tpl),
|
||||
|
||||
Open(TplParam, S3),
|
||||
BindIdentMeta(name_param),
|
||||
Desc(desc_param),
|
||||
Close(TplParam, S6),
|
||||
Close(Tpl, S7),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
#[rustfmt::skip]
|
||||
Ok(vec![
|
||||
O(Air::TplStart(S1)),
|
||||
O(Air::BindIdent(name_tpl)),
|
||||
|
||||
O(Air::MetaStart(S3)),
|
||||
O(Air::BindIdent(name_param)),
|
||||
O(Air::DocIndepClause(desc_param)),
|
||||
O(Air::MetaEnd(S6)),
|
||||
O(Air::TplEnd(S7)),
|
||||
]),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -194,7 +249,7 @@ fn apply_template_long_form_nullary() {
|
|||
O(Air::RefIdent(name)),
|
||||
O(Air::TplEndRef(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -212,12 +267,13 @@ fn apply_template_long_form_args() {
|
|||
RefSubject(name),
|
||||
|
||||
Open(TplParam, S3),
|
||||
BindIdent(p1),
|
||||
BindIdentMeta(p1),
|
||||
Text(v1),
|
||||
Ref(p2),
|
||||
Close(TplParam, S6),
|
||||
|
||||
Open(TplParam, S7),
|
||||
BindIdent(p2),
|
||||
BindIdentMeta(p2),
|
||||
Text(v2),
|
||||
Close(TplParam, S10),
|
||||
Close(TplApply, S11),
|
||||
|
@ -232,6 +288,7 @@ fn apply_template_long_form_args() {
|
|||
O(Air::MetaStart(S3)),
|
||||
O(Air::BindIdent(p1)),
|
||||
O(Air::MetaLexeme(v1)),
|
||||
O(Air::RefIdent(p2)),
|
||||
O(Air::MetaEnd(S6)),
|
||||
|
||||
O(Air::MetaStart(S7)),
|
||||
|
@ -240,7 +297,7 @@ fn apply_template_long_form_args() {
|
|||
O(Air::MetaEnd(S10)),
|
||||
O(Air::TplEndRef(S11)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -274,7 +331,7 @@ fn match_short_no_value() {
|
|||
O(Air::RefIdent(SPair(SYM_TRUE, S1))),
|
||||
O(Air::ExprEnd(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -306,7 +363,7 @@ fn match_short_with_value() {
|
|||
O(Air::RefIdent(value)),
|
||||
O(Air::ExprEnd(S4)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -345,7 +402,7 @@ fn match_short_value_before_subject_err() {
|
|||
Ok(O(Air::RefIdent(SPair(SYM_TRUE, S1)))),
|
||||
Ok(O(Air::ExprEnd(S3))),
|
||||
],
|
||||
Sut::parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||
sut_parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -370,7 +427,7 @@ fn match_no_args_err() {
|
|||
)),
|
||||
// RECOVERY: Useless match above discarded.
|
||||
],
|
||||
Sut::parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||
sut_parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -395,6 +452,52 @@ fn text_as_arbitrary_doc() {
|
|||
O(Air::DocText(text)),
|
||||
O(Air::PkgEnd(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
// NIR's concept of abstract identifiers exists for the sake of
|
||||
// disambiguation for AIR.
|
||||
// While NIR's grammar does not explicitly utilize it,
|
||||
// interpolation via `nir::interp` will desugar into it.
|
||||
#[test]
|
||||
fn abstract_idents_lowered_to_air_equivalent() {
|
||||
let meta_id = SPair("@foo@".into(), S2);
|
||||
let meta_meta_id = SPair("@bar@".into(), S5);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Open(Rate, S1),
|
||||
// NIR does not know or care that this metavariable does not
|
||||
// exist.
|
||||
BindIdentAbstract(meta_id),
|
||||
Close(Rate, S3),
|
||||
|
||||
// The XSLT-based TAME had a grammatical ambiguity that disallowed
|
||||
// for this type of construction,
|
||||
// but there's no reason we can't allow for abstract
|
||||
// metavariables
|
||||
// (which would make `meta_meta_id` a meta-metavariable).
|
||||
// (See `nir::interp` for more information on the handling of
|
||||
// `TplParam` and abstract identifiers.)
|
||||
Open(TplParam, S4),
|
||||
// NIR does not know or care that this metavariable does not
|
||||
// exist.
|
||||
BindIdentAbstract(meta_meta_id),
|
||||
Close(TplParam, S6),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
#[rustfmt::skip]
|
||||
Ok(vec![
|
||||
O(Air::ExprStart(ExprOp::Sum, S1)),
|
||||
O(Air::BindIdentAbstract(meta_id)),
|
||||
O(Air::ExprEnd(S3)),
|
||||
|
||||
O(Air::MetaStart(S4)),
|
||||
O(Air::BindIdentAbstract(meta_meta_id)),
|
||||
O(Air::MetaEnd(S6)),
|
||||
]),
|
||||
sut_parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
//!
|
||||
//! ```xml
|
||||
//! <param name="@___dsgr_01@"
|
||||
//! desc="Generated from interpolated string `foo{@bar@}baz`">
|
||||
//! desc="Generated from interpolated string">
|
||||
//! <text>foo</text>
|
||||
//! <param-value name="@bar@" />
|
||||
//! <text>baz</text>
|
||||
|
@ -69,6 +69,12 @@
|
|||
//! then it is interpreted as a literal within the context of the template
|
||||
//! system and is echoed back unchanged.
|
||||
//!
|
||||
//! There is currently no way to escape `{` within a string.
|
||||
//! Such a feature will be considered in the future,
|
||||
//! but for the meantime,
|
||||
//! this can be worked around by using metavariables that expand into the
|
||||
//! desired literal.
|
||||
//!
|
||||
//! Desugared Spans
|
||||
//! ---------------
|
||||
//! [`Span`]s for the generated tokens are derived from the specification
|
||||
|
@ -102,13 +108,13 @@ use memchr::memchr2;
|
|||
use super::{Nir, NirEntity};
|
||||
use crate::{
|
||||
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan, Diagnostic},
|
||||
f::Functor,
|
||||
f::Map,
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
parse::{prelude::*, util::SPair, NoContext},
|
||||
span::Span,
|
||||
sym::{
|
||||
st::quick_contains_byte, GlobalSymbolIntern, GlobalSymbolResolve,
|
||||
SymbolId,
|
||||
st::{quick_contains_byte, raw::S_GEN_FROM_INTERP},
|
||||
GlobalSymbolIntern, GlobalSymbolResolve, SymbolId,
|
||||
},
|
||||
};
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
@ -266,7 +272,7 @@ impl ParseState for InterpState {
|
|||
// filter out non-interpolated strings quickly,
|
||||
// before we start to parse.
|
||||
// Symbols that require no interpoolation are simply echoed back.
|
||||
Ready => match tok.symbol() {
|
||||
Ready => match tok.concrete_symbol() {
|
||||
Some(sym) if needs_interpolation(sym) => {
|
||||
Transition(GenIdent(sym))
|
||||
.ok(Nir::Open(NirEntity::TplParam, span))
|
||||
|
@ -282,29 +288,29 @@ impl ParseState for InterpState {
|
|||
let GenIdentSymbolId(ident_sym) = gen_ident;
|
||||
|
||||
Transition(GenDesc(sym, gen_ident))
|
||||
.ok(Nir::BindIdent(SPair(ident_sym, span)))
|
||||
.ok(Nir::BindIdentMeta(SPair(ident_sym, span)))
|
||||
.with_lookahead(tok)
|
||||
}
|
||||
|
||||
// Note: This historically generated a description containing
|
||||
// the interpolated string,
|
||||
// which was useful when looking at generated code.
|
||||
// But this ends up producing output that is not a fixpoint,
|
||||
// because if you run it back through the compiler,
|
||||
// it needs interpolation again,
|
||||
// but now in an incorrect context.
|
||||
// We can revisit this
|
||||
// (see commit introducing this comment)
|
||||
// when we introduce escaping of some form,
|
||||
// if it's worth doing.
|
||||
GenDesc(sym, gen_ident) => {
|
||||
let s = sym.lookup_str();
|
||||
|
||||
// Description is not interned since there's no use in
|
||||
// wasting time hashing something that will not be
|
||||
// referenced
|
||||
// (it's just informative for a human).
|
||||
// Note that this means that tests cannot compare SymbolId.
|
||||
let gen_desc = format!(
|
||||
"Generated from interpolated string {}",
|
||||
TtQuote::wrap(s)
|
||||
)
|
||||
.clone_uninterned();
|
||||
|
||||
// Begin parsing in a _literal_ context,
|
||||
// since interpolation is most commonly utilized with literal
|
||||
// prefixes.
|
||||
Transition(ParseLiteralAt(s, gen_ident, 0))
|
||||
.ok(Nir::Desc(SPair(gen_desc, span)))
|
||||
.ok(Nir::Desc(SPair(S_GEN_FROM_INTERP, span)))
|
||||
.with_lookahead(tok)
|
||||
}
|
||||
|
||||
|
@ -458,7 +464,19 @@ impl ParseState for InterpState {
|
|||
// generated.
|
||||
// We finally release the lookahead symbol.
|
||||
FinishSym(_, GenIdentSymbolId(gen_param)) => {
|
||||
Transition(Ready).ok(tok.map(|_| gen_param))
|
||||
let replacement = match tok.map(|_| gen_param) {
|
||||
// `BindIdent` represents a concrete identifier.
|
||||
// Our interpolation has generated a metavariable,
|
||||
// meaning that this identifier has become abstract
|
||||
// since its name will not be known until expansion-time.
|
||||
Nir::BindIdent(x) => Nir::BindIdentAbstract(x),
|
||||
|
||||
// All other tokens only have their symbols replaced by
|
||||
// the above.
|
||||
x => x,
|
||||
};
|
||||
|
||||
Transition(Ready).ok(replacement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ use crate::{
|
|||
nir::NirEntity,
|
||||
parse::{Parsed, ParsedResult, Parser},
|
||||
span::dummy::{DUMMY_CONTEXT as DC, *},
|
||||
sym::GlobalSymbolResolve,
|
||||
};
|
||||
use std::assert_matches::assert_matches;
|
||||
use Parsed::*;
|
||||
|
@ -81,7 +80,6 @@ fn does_not_desugar_text() {
|
|||
|
||||
fn expect_expanded_header(
|
||||
sut: &mut Parser<InterpState, std::vec::IntoIter<Nir>>,
|
||||
given_val: &str,
|
||||
span: Span,
|
||||
) -> SymbolId {
|
||||
let GenIdentSymbolId(expect_name) = gen_tpl_param_ident_at_offset(span);
|
||||
|
@ -99,18 +97,104 @@ fn expect_expanded_header(
|
|||
);
|
||||
assert_eq!(
|
||||
sut.next(),
|
||||
Some(Ok(Object(Nir::BindIdent(SPair(expect_name_sym, span))))),
|
||||
Some(Ok(Object(Nir::BindIdentMeta(SPair(expect_name_sym, span))))),
|
||||
);
|
||||
assert_matches!(
|
||||
sut.next(),
|
||||
Some(Ok(Object(Nir::Desc(SPair(desc_str, desc_span)))))
|
||||
if desc_str.lookup_str().contains(given_val)
|
||||
&& desc_span == span
|
||||
Some(Ok(Object(Nir::Desc(SPair(S_GEN_FROM_INTERP, desc_span)))))
|
||||
if desc_span == span
|
||||
);
|
||||
|
||||
expect_name_sym
|
||||
}
|
||||
|
||||
// This allows for unambiguously requesting desugaring in situations where
|
||||
// the default is to treat the name as concrete.
|
||||
#[test]
|
||||
fn desugars_spec_with_only_var() {
|
||||
let given_val = "{@foo@}";
|
||||
// |[---]|
|
||||
// |1 5|
|
||||
// | B |
|
||||
// [-----]
|
||||
// 0 6
|
||||
// A
|
||||
|
||||
// Non-zero span offset ensures that derived spans properly consider
|
||||
// parent offset.
|
||||
let a = DC.span(10, 7);
|
||||
let b = DC.span(11, 5);
|
||||
|
||||
let given_sym = Nir::Ref(SPair(given_val.into(), a));
|
||||
let toks = vec![given_sym];
|
||||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
// This is the actual metavariable reference, pulled out of the
|
||||
// interpolated portion of the given value.
|
||||
Object(Nir::Ref(SPair("@foo@".into(), b))),
|
||||
// This is an object generated from user input, so the closing
|
||||
// span has to identify what were generated from.
|
||||
Object(Nir::Close(NirEntity::TplParam, a)),
|
||||
// Finally,
|
||||
// we replace the original provided attribute
|
||||
// (the interpolation specification)
|
||||
// with a metavariable reference to the generated parameter.
|
||||
Object(Nir::Ref(SPair(expect_name, a))),
|
||||
]),
|
||||
sut.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
// This is like the above test,
|
||||
// but with a `BindIdent` instead of a `Ref`,
|
||||
// which desugars into `BindIdentAbstract`.
|
||||
// We could handle that translation in a later lowering operation,
|
||||
// but re-parsing the symbol would be wasteful.
|
||||
#[test]
|
||||
fn concrete_bind_ident_desugars_into_abstract_bind_after_interpolation() {
|
||||
let given_val = "{@bindme@}";
|
||||
// |[------]|
|
||||
// |1 8|
|
||||
// | B |
|
||||
// [--------]
|
||||
// 0 9
|
||||
// A
|
||||
|
||||
// Non-zero span offset ensures that derived spans properly consider
|
||||
// parent offset.
|
||||
let a = DC.span(10, 10);
|
||||
let b = DC.span(11, 8);
|
||||
|
||||
// This is a bind,
|
||||
// unlike above.
|
||||
let given_sym = Nir::BindIdent(SPair(given_val.into(), a));
|
||||
let toks = vec![given_sym];
|
||||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
// The interpolation occurs the same as above.
|
||||
Object(Nir::Ref(SPair("@bindme@".into(), b))),
|
||||
Object(Nir::Close(NirEntity::TplParam, a)),
|
||||
// But at the end,
|
||||
// instead of keeping the original `BindIdent` token,
|
||||
// we translate to `BindIdentAbstract`,
|
||||
// indicating that the name of this identifier depends on the
|
||||
// value of the metavariable during expansion
|
||||
Object(Nir::BindIdentAbstract(SPair(expect_name, a))),
|
||||
]),
|
||||
sut.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
// When ending with an interpolated variable,
|
||||
// the parser should recognize that we've returned to the outer literal
|
||||
// context and permit successful termination of the specification string.
|
||||
|
@ -135,7 +219,7 @@ fn desugars_literal_with_ending_var() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
|
@ -182,7 +266,7 @@ fn desugars_var_with_ending_literal() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
|
@ -219,7 +303,7 @@ fn desugars_many_vars_and_literals() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
|
@ -269,7 +353,7 @@ fn proper_multibyte_handling() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
|
@ -310,7 +394,7 @@ fn desugars_adjacent_interpolated_vars() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
|
@ -345,7 +429,7 @@ fn error_missing_closing_interp_delim() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
|
@ -391,7 +475,7 @@ fn error_nested_delim() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
|
@ -440,7 +524,7 @@ fn error_empty_interp() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
|
@ -482,7 +566,7 @@ fn error_close_before_open() {
|
|||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, given_val, a);
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
|
|
|
@ -1425,9 +1425,10 @@ ele_parse! {
|
|||
/// expanded.
|
||||
TplParamStmt := QN_PARAM(_, ospan) {
|
||||
@ {
|
||||
QN_NAME => TodoAttr,
|
||||
QN_DESC => TodoAttr,
|
||||
} => Todo(ospan.into()),
|
||||
QN_NAME => BindIdentMeta,
|
||||
QN_DESC => Desc,
|
||||
} => Nir::Open(NirEntity::TplParam, ospan.into()),
|
||||
/(cspan) => Nir::Close(NirEntity::TplParam, cspan.into()),
|
||||
|
||||
TplParamDefault,
|
||||
};
|
||||
|
@ -1457,10 +1458,16 @@ ele_parse! {
|
|||
/// providing constant values.
|
||||
/// The result will be as if the user typed the text themselves in the
|
||||
/// associated template application argument.
|
||||
///
|
||||
/// TODO: This just produces a no-op right now and lets the text hander
|
||||
/// produce text for the inner character data.
|
||||
/// This is consequently ambiguous with omitting this node entirely;
|
||||
/// this might be okay,
|
||||
/// but this needs explicit design.
|
||||
TplText := QN_TEXT(_, ospan) {
|
||||
@ {
|
||||
QN_UNIQUE => TodoAttr,
|
||||
} => Todo(ospan.into()),
|
||||
} => Noop(ospan.into()),
|
||||
};
|
||||
|
||||
/// Default the param to the value of another template param,
|
||||
|
@ -1474,7 +1481,7 @@ ele_parse! {
|
|||
/// cumbersome and slow
|
||||
TplParamValue := QN_PARAM_VALUE(_, ospan) {
|
||||
@ {
|
||||
QN_NAME => TodoAttr,
|
||||
QN_NAME => Ref,
|
||||
QN_DASH => TodoAttr,
|
||||
QN_UPPER => TodoAttr,
|
||||
QN_LOWER => TodoAttr,
|
||||
|
@ -1483,7 +1490,7 @@ ele_parse! {
|
|||
QN_RMUNDERSCORE => TodoAttr,
|
||||
QN_IDENTIFIER => TodoAttr,
|
||||
QN_SNAKE => TodoAttr,
|
||||
} => Todo(ospan.into()),
|
||||
} => Noop(ospan.into()),
|
||||
};
|
||||
|
||||
/// Inherit a default value from a metavalue.
|
||||
|
@ -1696,7 +1703,7 @@ ele_parse! {
|
|||
/// which gets desugared into this via [`super::tplshort`].
|
||||
ApplyTemplateParam := QN_WITH_PARAM(_, ospan) {
|
||||
@ {
|
||||
QN_NAME => BindIdent,
|
||||
QN_NAME => BindIdentMeta,
|
||||
QN_VALUE => Text,
|
||||
} => Nir::Open(NirEntity::TplParam, ospan.into()),
|
||||
/(cspan) => Nir::Close(NirEntity::TplParam, cspan.into()),
|
||||
|
|
|
@ -80,7 +80,7 @@ use arrayvec::ArrayVec;
|
|||
|
||||
use super::{Nir, NirEntity};
|
||||
use crate::{
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
fmt::TtQuote,
|
||||
parse::prelude::*,
|
||||
span::Span,
|
||||
sym::{
|
||||
|
@ -88,7 +88,6 @@ use crate::{
|
|||
SymbolId,
|
||||
},
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
|
||||
use Nir::*;
|
||||
use NirEntity::*;
|
||||
|
@ -130,10 +129,14 @@ impl Display for TplShortDesugar {
|
|||
}
|
||||
}
|
||||
|
||||
diagnostic_infallible! {
|
||||
pub enum TplShortDesugarError {}
|
||||
}
|
||||
|
||||
impl ParseState for TplShortDesugar {
|
||||
type Token = Nir;
|
||||
type Object = Nir;
|
||||
type Error = Infallible;
|
||||
type Error = TplShortDesugarError;
|
||||
type Context = Stack;
|
||||
|
||||
fn parse_token(
|
||||
|
@ -162,6 +165,8 @@ impl ParseState for TplShortDesugar {
|
|||
let tpl_name =
|
||||
format!("_{}_", qname.local_name().lookup_str()).intern();
|
||||
|
||||
// TODO: This should be emitted _after_ params to save work
|
||||
// for `crate::asg:graph::visit::ontree::SourceCompatibleTreeEdgeOrder`.
|
||||
let name = SPair(tpl_name, span);
|
||||
stack.push(Ref(name));
|
||||
|
||||
|
@ -176,7 +181,7 @@ impl ParseState for TplShortDesugar {
|
|||
// note: reversed (stack)
|
||||
stack.push(Close(TplParam, span));
|
||||
stack.push(Text(val));
|
||||
stack.push(BindIdent(SPair(pname, name.span())));
|
||||
stack.push(BindIdentMeta(SPair(pname, name.span())));
|
||||
Transition(DesugaringParams(ospan)).ok(Open(TplParam, span))
|
||||
}
|
||||
|
||||
|
@ -212,7 +217,7 @@ impl ParseState for TplShortDesugar {
|
|||
stack.push(Close(TplApply, ospan));
|
||||
stack.push(Close(TplParam, ospan));
|
||||
stack.push(Text(SPair(gen_name, ospan)));
|
||||
stack.push(BindIdent(SPair(L_TPLP_VALUES, ospan)));
|
||||
stack.push(BindIdentMeta(SPair(L_TPLP_VALUES, ospan)));
|
||||
|
||||
// Note that we must have `tok` as lookahead instead of
|
||||
// pushing directly on the stack in case it's a
|
||||
|
|
|
@ -86,7 +86,7 @@ fn desugars_unary() {
|
|||
|
||||
O(Open(TplParam, S2)),
|
||||
// Derived from `aname` (by padding)
|
||||
O(BindIdent(pname)),
|
||||
O(BindIdentMeta(pname)),
|
||||
// The value is left untouched.
|
||||
O(Text(pval)),
|
||||
// Close is derived from open.
|
||||
|
@ -137,7 +137,7 @@ fn desugars_body_into_tpl_with_ref_in_values_param() {
|
|||
// @values@ remains lexical by referencing the name of a
|
||||
// template we're about to generate.
|
||||
O(Open(TplParam, S1)),
|
||||
O(BindIdent(SPair(L_TPLP_VALUES, S1))),
|
||||
O(BindIdentMeta(SPair(L_TPLP_VALUES, S1))),
|
||||
O(Text(SPair(gen_name, S1))), //:-.
|
||||
O(Close(TplParam, S1)), // |
|
||||
O(Close(TplApply, S1)), // |
|
||||
|
@ -193,7 +193,7 @@ fn desugar_nested_apply() {
|
|||
|
||||
// @values@
|
||||
O(Open(TplParam, S1)),
|
||||
O(BindIdent(SPair(L_TPLP_VALUES, S1))),
|
||||
O(BindIdentMeta(SPair(L_TPLP_VALUES, S1))),
|
||||
O(Text(SPair(gen_name_outer, S1))), //:-.
|
||||
O(Close(TplParam, S1)), // |
|
||||
O(Close(TplApply, S1)), // |
|
||||
|
@ -227,7 +227,7 @@ fn does_not_desugar_long_form() {
|
|||
BindIdent(name),
|
||||
|
||||
Open(TplParam, S3),
|
||||
BindIdent(pname),
|
||||
BindIdentMeta(pname),
|
||||
Text(pval),
|
||||
Close(TplParam, S6),
|
||||
Close(TplApply, S7),
|
||||
|
@ -244,7 +244,7 @@ fn does_not_desugar_long_form() {
|
|||
O(BindIdent(name)),
|
||||
|
||||
O(Open(TplParam, S3)),
|
||||
O(BindIdent(pname)),
|
||||
O(BindIdentMeta(pname)),
|
||||
O(Text(pval)),
|
||||
O(Close(TplParam, S6)),
|
||||
O(Close(TplApply, S7)),
|
||||
|
|
|
@ -29,6 +29,7 @@ use fxhash::FxHashSet;
|
|||
use crate::{
|
||||
asg::{air::Air, IdentKind, Source},
|
||||
diagnose::{AnnotatedSpan, Diagnostic},
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
obj::xmlo::{SymAttrs, SymType},
|
||||
parse::{util::SPair, ParseState, ParseStatus, Transition, Transitionable},
|
||||
span::Span,
|
||||
|
@ -95,6 +96,7 @@ pub enum XmloToAir {
|
|||
PackageFound(Span),
|
||||
Package(PackageSPair),
|
||||
SymDep(PackageSPair, SPair),
|
||||
SymDepEnded(PackageSPair, Span),
|
||||
/// End of header (EOH) reached.
|
||||
Done(Span),
|
||||
}
|
||||
|
@ -212,13 +214,22 @@ impl ParseState for XmloToAir {
|
|||
.transition(Package(pkg_name))
|
||||
}
|
||||
|
||||
(Package(pkg_name) | SymDep(pkg_name, _), Fragment(name, text)) => {
|
||||
(Package(pkg_name) | SymDep(pkg_name, _), SymDepEnd(span)) => {
|
||||
Transition(SymDepEnded(pkg_name, span)).incomplete()
|
||||
}
|
||||
|
||||
(
|
||||
Package(pkg_name)
|
||||
| SymDep(pkg_name, _)
|
||||
| SymDepEnded(pkg_name, _),
|
||||
Fragment(name, text),
|
||||
) => {
|
||||
Transition(Package(pkg_name)).ok(Air::IdentFragment(name, text))
|
||||
}
|
||||
|
||||
// We don't need to read any further than the end of the
|
||||
// header (symtable, sym-deps, fragments).
|
||||
(Package(..) | SymDep(..), Eoh(span)) => {
|
||||
(Package(..) | SymDep(..) | SymDepEnded(..), Eoh(span)) => {
|
||||
// It's important to set this _after_ we're done processing,
|
||||
// otherwise our `first` checks above will be inaccurate.
|
||||
ctx.first = false;
|
||||
|
@ -234,15 +245,36 @@ impl ParseState for XmloToAir {
|
|||
tok @ (PkgStart(..) | PkgName(..) | Symbol(..)),
|
||||
) => Transition(st).dead(tok),
|
||||
|
||||
(st @ (PackageFound(..) | SymDep(..) | Done(..)), tok) => {
|
||||
Transition(st).dead(tok)
|
||||
}
|
||||
(
|
||||
st @ (PackageFound(..) | SymDep(..) | SymDepEnded(..)
|
||||
| Done(..)),
|
||||
tok,
|
||||
) => Transition(st).dead(tok),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepting(&self, _: &Self::Context) -> bool {
|
||||
matches!(*self, Self::Done(_))
|
||||
}
|
||||
|
||||
fn eof_tok(&self, _ctx: &Self::Context) -> Option<Self::Token> {
|
||||
use XmloToAir::*;
|
||||
|
||||
match self {
|
||||
// We are able to stop parsing immediately after symbol
|
||||
// dependencies have ended if the caller wishes to ignore
|
||||
// fragments.
|
||||
// Pretend that we received an `Eoh` token in this case so that
|
||||
// we can conclude parsing.
|
||||
SymDepEnded(_, span) => Some(XmloToken::Eoh(*span)),
|
||||
|
||||
Package(_)
|
||||
| PackageExpected
|
||||
| PackageFound(_)
|
||||
| SymDep(_, _)
|
||||
| Done(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for XmloToAir {
|
||||
|
@ -258,6 +290,13 @@ impl Display for XmloToAir {
|
|||
SymDep(pkg_name, sym) => {
|
||||
write!(f, "expecting dependency for symbol `/{pkg_name}/{sym}`")
|
||||
}
|
||||
SymDepEnded(pkg_name, _) => {
|
||||
write!(
|
||||
f,
|
||||
"expecting fragments or end of header for package {}",
|
||||
TtQuote::wrap(pkg_name)
|
||||
)
|
||||
}
|
||||
Done(_) => write!(f, "done lowering xmlo into AIR"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,9 +102,10 @@ fn adds_sym_deps() {
|
|||
PkgName(SPair(name, S2)),
|
||||
|
||||
SymDepStart(SPair(sym_from, S3)),
|
||||
Symbol(SPair(sym_to1, S4)),
|
||||
Symbol(SPair(sym_to2, S5)),
|
||||
Eoh(S6),
|
||||
Symbol(SPair(sym_to1, S4)),
|
||||
Symbol(SPair(sym_to2, S5)),
|
||||
SymDepEnd(S6),
|
||||
Eoh(S7),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
|
@ -115,7 +116,42 @@ fn adds_sym_deps() {
|
|||
Incomplete, // SymDepStart
|
||||
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))),
|
||||
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to2, S5))),
|
||||
O(Air::PkgEnd(S6)),
|
||||
Incomplete, // EndOfDeps
|
||||
O(Air::PkgEnd(S7)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepting_state_after_sym_deps() {
|
||||
let name = "name".into();
|
||||
let sym_from = "from".into();
|
||||
let sym_to1 = "to1".into();
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1),
|
||||
PkgName(SPair(name, S2)),
|
||||
|
||||
SymDepStart(SPair(sym_from, S3)),
|
||||
Symbol(SPair(sym_to1, S4)),
|
||||
SymDepEnd(S5),
|
||||
// Missing EOH; this should be a valid accepting state so that
|
||||
// parsing can end early.
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
#[rustfmt::skip]
|
||||
Ok(vec![
|
||||
Incomplete, // PkgStart
|
||||
O(Air::PkgStart(S1, SPair(name, S2))),
|
||||
Incomplete, // SymDepStart
|
||||
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))),
|
||||
Incomplete, // EndOfDeps
|
||||
// The missing EOH is added automatically.
|
||||
// TODO: Span of last-encountered token.
|
||||
O(Air::PkgEnd(S5)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
);
|
||||
|
|
|
@ -25,8 +25,8 @@ use crate::{
|
|||
num::{Dim, Dtype},
|
||||
obj::xmlo::SymType,
|
||||
parse::{
|
||||
self, util::SPair, ClosedParseState, EmptyContext, NoContext,
|
||||
ParseState, Token, Transition, TransitionResult, Transitionable,
|
||||
self, util::SPair, NoContext, ParseState, Token, Transition,
|
||||
TransitionResult, Transitionable,
|
||||
},
|
||||
span::Span,
|
||||
sym::{st::raw, GlobalSymbolIntern, GlobalSymbolResolve, SymbolId},
|
||||
|
@ -80,6 +80,17 @@ pub enum XmloToken {
|
|||
/// object file representing the source location of this symbol.
|
||||
Symbol(SPair),
|
||||
|
||||
/// End of symbol dependencies.
|
||||
///
|
||||
/// This token indicates that all symbols and their dependencies have
|
||||
/// been parsed.
|
||||
/// This is a safe stopping point for subsystems that do not wish to
|
||||
/// load fragments.
|
||||
///
|
||||
/// (This is not named `Eos` because that is not a commonly used
|
||||
/// initialism and is not clear.)
|
||||
SymDepEnd(Span),
|
||||
|
||||
/// Text (compiled code) fragment for a given symbol.
|
||||
///
|
||||
/// This contains the compiler output for a given symbol,
|
||||
|
@ -120,6 +131,7 @@ impl Token for XmloToken {
|
|||
| SymDecl(SPair(_, span), _)
|
||||
| SymDepStart(SPair(_, span))
|
||||
| Symbol(SPair(_, span))
|
||||
| SymDepEnd(span)
|
||||
| Fragment(SPair(_, span), _)
|
||||
| Eoh(span) => *span,
|
||||
}
|
||||
|
@ -155,6 +167,7 @@ impl Display for XmloToken {
|
|||
)
|
||||
}
|
||||
Symbol(sym) => write!(f, "symbol {}", TtQuote::wrap(sym)),
|
||||
SymDepEnd(_) => write!(f, "end of symbol dependencies"),
|
||||
Fragment(sym, _) => {
|
||||
write!(f, "symbol {} code fragment", TtQuote::wrap(sym))
|
||||
}
|
||||
|
@ -163,44 +176,30 @@ impl Display for XmloToken {
|
|||
}
|
||||
}
|
||||
|
||||
/// A parser capable of being composed with [`XmloReader`].
|
||||
pub trait XmloState =
|
||||
ClosedParseState<Token = Xirf<Text>, Context = EmptyContext>
|
||||
where
|
||||
Self: Default,
|
||||
<Self as ParseState>::Error: Into<XmloError>,
|
||||
<Self as ParseState>::Object: Into<XmloToken>;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub enum XmloReader<
|
||||
SS: XmloState = SymtableState,
|
||||
SD: XmloState = SymDepsState,
|
||||
SF: XmloState = FragmentsState,
|
||||
> {
|
||||
pub enum XmloReader {
|
||||
/// Parser has not yet processed any input.
|
||||
#[default]
|
||||
Ready,
|
||||
/// Processing `package` attributes.
|
||||
Package(Span),
|
||||
/// Expecting a symbol declaration or closing `preproc:symtable`.
|
||||
Symtable(Span, SS),
|
||||
Symtable(Span, SymtableState),
|
||||
/// Symbol dependencies are expected next.
|
||||
SymDepsExpected,
|
||||
/// Expecting symbol dependency list or closing `preproc:sym-deps`.
|
||||
SymDeps(Span, SD),
|
||||
SymDeps(Span, SymDepsState),
|
||||
/// Compiled text fragments are expected next.
|
||||
FragmentsExpected,
|
||||
/// Expecting text fragment or closing `preproc:fragments`.
|
||||
Fragments(Span, SF),
|
||||
Fragments(Span, FragmentsState),
|
||||
/// End of header parsing.
|
||||
Eoh,
|
||||
/// `xmlo` file has been fully read.
|
||||
Done,
|
||||
}
|
||||
|
||||
impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
||||
for XmloReader<SS, SD, SF>
|
||||
{
|
||||
impl ParseState for XmloReader {
|
||||
type Token = Xirf<Text>;
|
||||
type Object = XmloToken;
|
||||
type Error = XmloError;
|
||||
|
@ -249,7 +248,7 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
|||
(Package(_), Xirf::Close(..)) => Transition(Done).incomplete(),
|
||||
|
||||
(Package(_), Xirf::Open(QN_P_SYMTABLE, span, ..)) => {
|
||||
Transition(Symtable(span.tag_span(), SS::default()))
|
||||
Transition(Symtable(span.tag_span(), SymtableState::default()))
|
||||
.incomplete()
|
||||
}
|
||||
|
||||
|
@ -269,14 +268,15 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
|||
),
|
||||
|
||||
(SymDepsExpected, Xirf::Open(QN_P_SYM_DEPS, span, _)) => {
|
||||
Transition(SymDeps(span.tag_span(), SD::default())).incomplete()
|
||||
Transition(SymDeps(span.tag_span(), SymDepsState::default()))
|
||||
.incomplete()
|
||||
}
|
||||
|
||||
(SymDeps(_, sd), Xirf::Close(None | Some(QN_P_SYM_DEPS), ..))
|
||||
if sd.is_accepting(ctx) =>
|
||||
{
|
||||
Transition(FragmentsExpected).incomplete()
|
||||
}
|
||||
(
|
||||
SymDeps(_, sd),
|
||||
Xirf::Close(None | Some(QN_P_SYM_DEPS), cspan, _),
|
||||
) if sd.is_accepting(ctx) => Transition(FragmentsExpected)
|
||||
.ok(XmloToken::SymDepEnd(cspan.span())),
|
||||
|
||||
(SymDeps(span, sd), tok) => sd.delegate(
|
||||
tok,
|
||||
|
@ -286,8 +286,11 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
|||
),
|
||||
|
||||
(FragmentsExpected, Xirf::Open(QN_P_FRAGMENTS, span, _)) => {
|
||||
Transition(Fragments(span.tag_span(), SF::default()))
|
||||
.incomplete()
|
||||
Transition(Fragments(
|
||||
span.tag_span(),
|
||||
FragmentsState::default(),
|
||||
))
|
||||
.incomplete()
|
||||
}
|
||||
|
||||
(
|
||||
|
@ -318,7 +321,7 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
|||
}
|
||||
|
||||
fn is_accepting(&self, _: &Self::Context) -> bool {
|
||||
*self == Self::Eoh || *self == Self::Done
|
||||
matches!(self, Self::FragmentsExpected | Self::Eoh | Self::Done)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,9 +346,7 @@ fn canonical_slash(name: SymbolId) -> SymbolId {
|
|||
}
|
||||
}
|
||||
|
||||
impl<SS: XmloState, SD: XmloState, SF: XmloState> Display
|
||||
for XmloReader<SS, SD, SF>
|
||||
{
|
||||
impl Display for XmloReader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use XmloReader::*;
|
||||
|
||||
|
|
|
@ -701,6 +701,7 @@ fn xmlo_composite_parsers_header() {
|
|||
O(PkgStart(S1)),
|
||||
O(SymDecl(SPair(sym_name, S3), Default::default(),)),
|
||||
O(SymDepStart(SPair(symdep_name, S3))),
|
||||
O(SymDepEnd(S3)),
|
||||
O(Fragment(SPair(symfrag_id, S4), frag)),
|
||||
O(Eoh(S3)),
|
||||
]),
|
||||
|
@ -711,3 +712,45 @@ fn xmlo_composite_parsers_header() {
|
|||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xmlo_end_after_sym_deps_before_fragments() {
|
||||
let sym_name = "sym".into();
|
||||
let symdep_name = "symdep".into();
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks_header = [
|
||||
open(QN_PACKAGE, S1, Depth(0)),
|
||||
open(QN_P_SYMTABLE, S2, Depth(1)),
|
||||
open(QN_P_SYM, S3, Depth(2)),
|
||||
attr(QN_NAME, sym_name, (S2, S3)),
|
||||
close_empty(S4, Depth(2)),
|
||||
close(Some(QN_P_SYMTABLE), S4, Depth(1)),
|
||||
|
||||
open(QN_P_SYM_DEPS, S2, Depth(1)),
|
||||
open(QN_P_SYM_DEP, S3, Depth(3)),
|
||||
attr(QN_NAME, symdep_name, (S2, S3)),
|
||||
close(Some(QN_P_SYM_DEP), S4, Depth(3)),
|
||||
close(Some(QN_P_SYM_DEPS), S3, Depth(1)),
|
||||
|
||||
// End before fragments.
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
let sut = Sut::parse(toks_header);
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
O(PkgStart(S1)),
|
||||
O(SymDecl(SPair(sym_name, S3), Default::default(),)),
|
||||
O(SymDepStart(SPair(symdep_name, S3))),
|
||||
O(SymDepEnd(S3)),
|
||||
]),
|
||||
sut.filter(|parsed| match parsed {
|
||||
Ok(Incomplete) => false,
|
||||
_ => true,
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ pub mod util;
|
|||
pub use error::{FinalizeError, ParseError};
|
||||
pub use lower::{
|
||||
lowerable, terminal, FromParseError, Lower, LowerIter, LowerSource,
|
||||
ParsedObject,
|
||||
ParseStateError, ParsedObject,
|
||||
};
|
||||
pub use parser::{FinalizedParser, Parsed, ParsedResult, Parser};
|
||||
pub use state::{
|
||||
|
@ -58,8 +58,13 @@ pub mod prelude {
|
|||
TransitionResult, Transitionable,
|
||||
};
|
||||
|
||||
// Every `Token` must implement `Display`.
|
||||
pub use std::fmt::Display;
|
||||
// Every `Token`.
|
||||
pub use crate::fmt::DisplayWrapper;
|
||||
pub use std::fmt::{Debug, Display};
|
||||
|
||||
// Every `ParseState::Error`.
|
||||
pub use crate::diagnose::{Annotate, AnnotatedSpan, Diagnostic};
|
||||
pub use std::error::Error;
|
||||
}
|
||||
|
||||
/// A single datum from a streaming IR with an associated [`Span`].
|
||||
|
|
|
@ -221,6 +221,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Short-hand [`ParseError`] with types derived from the provided
|
||||
/// [`ParseState`] `S`.
|
||||
///
|
||||
/// The reason that [`ParseError`] does not accept [`ParseState`] is because
|
||||
/// a [`ParseState`] may carry a lot of additional type baggage---
|
||||
/// including lifetimes and other generics---
|
||||
/// that are irrelevant to the error type.
|
||||
pub type ParseStateError<S> =
|
||||
ParseError<<S as ParseState>::Token, <S as ParseState>::Error>;
|
||||
|
||||
/// A [`Diagnostic`] error type common to both `S` and `LS`.
|
||||
///
|
||||
/// This error type must be able to accommodate error variants from all
|
||||
|
@ -235,9 +245,17 @@ where
|
|||
/// which may then decide what to do
|
||||
/// (e.g. report errors and permit recovery,
|
||||
/// or terminate at the first sign of trouble).
|
||||
pub trait WidenedError<S: ParseState, LS: ParseState> = Diagnostic
|
||||
+ From<ParseError<<S as ParseState>::Token, <S as ParseState>::Error>>
|
||||
+ From<ParseError<<LS as ParseState>::Token, <LS as ParseState>::Error>>;
|
||||
///
|
||||
/// Note that the [`From`] trait bound utilizing `S` is purely a development
|
||||
/// aid to help guide the user (developer) in deriving the necessary
|
||||
/// types,
|
||||
/// since lowering pipelines are deeply complex with all the types
|
||||
/// involved.
|
||||
/// It can be safely removed in the future,
|
||||
/// at least at the time of writing,
|
||||
/// and have no effect on compilation.
|
||||
pub trait WidenedError<S: ParseState, LS: ParseState> =
|
||||
Diagnostic + From<ParseStateError<S>> + From<ParseStateError<LS>>;
|
||||
|
||||
/// Convenience trait for converting [`From`] a [`ParseError`] for the
|
||||
/// provided [`ParseState`] `S`.
|
||||
|
@ -246,8 +264,7 @@ pub trait WidenedError<S: ParseState, LS: ParseState> = Diagnostic
|
|||
/// that is almost certainly already utilized,
|
||||
/// rather than having to either import more types or use the verbose
|
||||
/// associated type.
|
||||
pub trait FromParseError<S: ParseState> =
|
||||
From<ParseError<<S as ParseState>::Token, <S as ParseState>::Error>>;
|
||||
pub trait FromParseError<S: ParseState> = From<ParseStateError<S>>;
|
||||
|
||||
/// A [`ParsedResult`](super::ParsedResult) with a [`WidenedError`].
|
||||
pub type WidenedParsedResult<S, E> =
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
//! to use outside of the domain of the parsing system itself.
|
||||
|
||||
use super::{prelude::*, state::TransitionData};
|
||||
use crate::{f::Functor, span::Span, sym::SymbolId};
|
||||
use crate::{f::Map, span::Span, sym::SymbolId};
|
||||
use std::fmt::Display;
|
||||
|
||||
/// A [`SymbolId`] with a corresponding [`Span`].
|
||||
|
@ -56,6 +56,22 @@ use std::fmt::Display;
|
|||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct SPair(pub SymbolId, pub Span);
|
||||
|
||||
/// More concisely construct an [`SPair`] from [`SymbolId`]- and
|
||||
/// [`Span`]-like things.
|
||||
///
|
||||
/// This is restricted to `cfg(test)` because it can cause unexpected
|
||||
/// internment if you're not careful.
|
||||
/// For example,
|
||||
/// if a [`str`] is assigned to a variable and then that variable is
|
||||
/// supplied to multiple calls to this function,
|
||||
/// each call will invoke the internment system.
|
||||
/// This isn't much of a concern for short-running tests,
|
||||
/// but is not acceptable elsewhere.
|
||||
#[cfg(test)]
|
||||
pub fn spair(sym: impl Into<SymbolId>, span: impl Into<Span>) -> SPair {
|
||||
SPair(sym.into(), span.into())
|
||||
}
|
||||
|
||||
impl SPair {
|
||||
/// Retrieve the [`SymbolId`] of this pair.
|
||||
///
|
||||
|
@ -67,7 +83,7 @@ impl SPair {
|
|||
}
|
||||
}
|
||||
|
||||
impl Functor<SymbolId> for SPair {
|
||||
impl Map<SymbolId> for SPair {
|
||||
/// Map over the [`SymbolId`] of the pair while retaining the original
|
||||
/// associated [`Span`].
|
||||
///
|
||||
|
@ -82,7 +98,7 @@ impl Functor<SymbolId> for SPair {
|
|||
}
|
||||
}
|
||||
|
||||
impl Functor<Span> for SPair {
|
||||
impl Map<Span> for SPair {
|
||||
/// Map over the [`Span`] of the pair while retaining the associated
|
||||
/// [`SymbolId`].
|
||||
///
|
||||
|
|
|
@ -39,15 +39,85 @@
|
|||
//! The module is responsible for pipeline composition.
|
||||
//! For information on the lowering pipeline as an abstraction,
|
||||
//! see [`Lower`].
|
||||
//!
|
||||
//! Error Widening
|
||||
//! ==============
|
||||
//! Every lowering pipeline will have an associated error sum type generated
|
||||
//! for it;
|
||||
//! this is necessary to maintain an appropriate level of encapsulation
|
||||
//! and keep implementation details away from the caller.
|
||||
//! All of the individual errors types is otherwise significant source of
|
||||
//! complexity.
|
||||
//!
|
||||
//! Since all [`ParseState`]s in the lowering pipeline are expected to
|
||||
//! support error recovery,
|
||||
//! this generated error sum type represents a _recoverable_ error.
|
||||
//! It is up to the sink to deermine whether the error should be promoted
|
||||
//! into an unrecoverable error `EU`,
|
||||
//! which is the error type yielded by the lowering operation.
|
||||
//! Error reporting and recovery should be utilized whenever it makes sense
|
||||
//! to present the user with as many errors as possible rather than
|
||||
//! aborting the process immediately,
|
||||
//! which would otherwise force the user to correct errors one at a
|
||||
//! time.
|
||||
//!
|
||||
//! [`ParseState`] Requirements
|
||||
//! ---------------------------
|
||||
//! Each [`ParseState`] in the pipeline is expected to have its own unique
|
||||
//! error type,
|
||||
//! utilizing newtypes if necessary;
|
||||
//! this ensures that errors are able to be uniquely paired with each
|
||||
//! [`ParseState`] that produced it without having to perform an
|
||||
//! explicit mapping at the call site.
|
||||
//! This uniqueness property allows for generation of [`From`]
|
||||
//! implementations that will not overlap,
|
||||
//! and remains compatible with the API of [`Lower`].
|
||||
//!
|
||||
//! [`ParseState::Error`] Lifetime Requirements and Workarounds
|
||||
//! -----------------------------------------------------------
|
||||
//! Error types in TAMER _never_ have lifetime bounds;
|
||||
//! this is necessary to allow error types to be propapgated all the way
|
||||
//! up the stack regardless of dependencies.[^lifetime-alt]
|
||||
//!
|
||||
//! [^lifetime-alt]: Rather than utilizing references with lifetimes,
|
||||
//! TAMER error types may hold symbols representing interned values,
|
||||
//! or may instead [`Copy`] data that has no interner.
|
||||
//!
|
||||
//! However,
|
||||
//! [`ParseState::Error`] is an associated type on [`ParseState`],
|
||||
//! which _may_ have lifetimes.[^parse-state-lifetime-ex]
|
||||
//! At the time of writing,
|
||||
//! even though the associated error type does not utilize the lifetime
|
||||
//! bounds of the [`ParseState`],
|
||||
//! Rust still requires some lifetime specification and will not elide
|
||||
//! it or allow for anonymous lifetimes.
|
||||
//!
|
||||
//! [^parse-state-lifetime-ex]: One example of a [`ParseState`] with
|
||||
//! an associated lifetime is [`AsgTreeToXirf`].
|
||||
//!
|
||||
//! We want to be able to derive error types from the provided
|
||||
//! [`ParseState`]s along so that the caller does not have to peel back
|
||||
//! layers of abstraction in order to determine how the error type ought
|
||||
//! to be specified.
|
||||
//! To handle this,
|
||||
//! the `lower_pipeline!` macro will _rewrite all lifetimes to `'static`'_
|
||||
//! in the provided pipeline types.
|
||||
//! Since no [`ParseState::Error`] type should have a lifetime,
|
||||
//! and therefore should not reference the lifetime of its parent
|
||||
//! [`ParseState`],
|
||||
//! this should have no practical effect on the error type itself.
|
||||
|
||||
use crate::{
|
||||
asg::{air::AirAggregate, AsgTreeToXirf},
|
||||
diagnose::Diagnostic,
|
||||
nir::{InterpolateNir, NirToAir, TplShortDesugar, XirfToNir},
|
||||
nir::{
|
||||
AbstractBindTranslate, InterpolateNir, NirToAir, TplShortDesugar,
|
||||
XirfToNir,
|
||||
},
|
||||
obj::xmlo::{XmloReader, XmloToAir, XmloToken},
|
||||
parse::{
|
||||
terminal, FinalizeError, Lower, LowerSource, ParseError, ParseState,
|
||||
Parsed, ParsedObject, UnknownToken,
|
||||
ParseStateError, Parsed, ParsedObject, UnknownToken,
|
||||
},
|
||||
xir::{
|
||||
autoclose::XirfAutoClose,
|
||||
|
@ -61,38 +131,58 @@ use crate::{
|
|||
mod r#macro;
|
||||
|
||||
lower_pipeline! {
|
||||
/// Load an `xmlo` file represented by `src` into the graph held
|
||||
/// by `air_ctx`.
|
||||
/// Parse a source package into the [ASG](crate::asg) using TAME's XML
|
||||
/// source language.
|
||||
///
|
||||
/// Source XML is represented by [XIR](crate::xir).
|
||||
/// This is parsed into [NIR`](crate::nir),
|
||||
/// which extracts TAME's high-level source language from the document
|
||||
/// format (XML).
|
||||
/// NIR is then desugared in various ways,
|
||||
/// producing a more verbose NIR than what the user originally
|
||||
/// entered.
|
||||
///
|
||||
/// NIR is then lowered into [AIR](crate::asg::air),
|
||||
/// which is then aggregated into the [ASG](crate::asg) to await
|
||||
/// further processing.
|
||||
/// It is after this point that package imports should be processed and
|
||||
/// also aggregated into the same ASG so that all needed dependencies
|
||||
/// definitions are available.
|
||||
pub parse_package_xml -> ParsePackageXml
|
||||
|> XirToXirf<64, RefinedText>
|
||||
|> XirfToNir
|
||||
|> TplShortDesugar
|
||||
|> InterpolateNir
|
||||
|> AbstractBindTranslate
|
||||
|> NirToAir[nir_air_ty]
|
||||
|> AirAggregate[air_ctx];
|
||||
|
||||
/// Load an `xmlo` file into the graph held by `air_ctx`.
|
||||
///
|
||||
/// Loading an object file will result in opaque objects being added to the
|
||||
/// graph.
|
||||
/// graph;
|
||||
/// no sources will be parsed.
|
||||
///
|
||||
/// TODO: To re-use this in `tamec` we want to be able to ignore fragments.
|
||||
///
|
||||
/// TODO: More documentation once this has been further cleaned up.
|
||||
pub load_xmlo
|
||||
/// To parse sources instead,
|
||||
/// see [`parse_package_xml`].
|
||||
pub load_xmlo -> LoadXmlo
|
||||
|> PartialXirToXirf<4, Text>
|
||||
|> XmloReader
|
||||
|> XmloToAir[xmlo_ctx], until (XmloToken::Eoh(..))
|
||||
|> AirAggregate[air_ctx];
|
||||
|
||||
/// Parse a source package into the [ASG](crate::asg) using TAME's XML
|
||||
/// source language.
|
||||
///
|
||||
/// TODO: More documentation once this has been further cleaned up.
|
||||
pub parse_package_xml
|
||||
|> XirToXirf<64, RefinedText>
|
||||
|> XirfToNir
|
||||
|> TplShortDesugar
|
||||
|> InterpolateNir
|
||||
|> NirToAir
|
||||
|> AirAggregate[air_ctx];
|
||||
|
||||
/// Lower an [`Asg`](crate::asg::Asg)-derived token stream into an
|
||||
/// `xmli` file.
|
||||
///
|
||||
/// TODO: More documentation once this has been further cleaned up.
|
||||
pub lower_xmli<'a>
|
||||
/// After a package has been parsed with [`parse_package_xml`] and
|
||||
/// further processing has taken place,
|
||||
/// this pipeline will re-generate TAME sources from the ASG for the
|
||||
/// purpose of serving as source input to the XSLT-based compiler.
|
||||
/// This allows us to incrementally replace that compiler's
|
||||
/// functionality by having the XSLT-based system pick up where we
|
||||
/// leave off,
|
||||
/// skipping anything that we have already done.
|
||||
pub lower_xmli<'a> -> LowerXmli
|
||||
|> AsgTreeToXirf<'a>[asg]
|
||||
|> XirfAutoClose
|
||||
|> XirfToXir<Text>;
|
||||
|
|
|
@ -25,6 +25,103 @@
|
|||
//! and to see TAMER's pipelines,
|
||||
//! see the [parent module](super).
|
||||
|
||||
#[cfg(doc)]
|
||||
use crate::parse::ParseState;
|
||||
|
||||
/// Generate an error sum type for a lowering pipeline.
|
||||
///
|
||||
/// Given a series of [`ParseState`] types,
|
||||
/// this derives a sum type capable of representing the associated
|
||||
/// [`ParseState::Error`] of each.
|
||||
/// See the [parent module](super) for more information,
|
||||
/// including the challenges/concerns with this approach.
|
||||
/// In particular,
|
||||
/// note that all lifetimes on the [`ParseState`] type are rewritten to be
|
||||
/// `'static';
|
||||
/// all associated `Error` types must not contain non-static lifetimes,
|
||||
/// as is the standard convention in TAMER.
|
||||
macro_rules! lower_error_sum {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$vis:vis $name:ident = $(
|
||||
$st:ident $(<$($l:lifetime,)* $($c:literal,)* $($t:ident,)*>)?
|
||||
)+
|
||||
) => {
|
||||
// Pair `'static` with each lifetime so that it may be used to
|
||||
// replace the respective lifetime in `@gen`
|
||||
// (we need an iteration token).
|
||||
lower_error_sum!(
|
||||
@gen
|
||||
$(#[$meta])*
|
||||
$vis $name = $($st$(<$($l: 'static,)* $($c,)* $($t,)*>)?)+
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
@gen
|
||||
$(#[$meta:meta])*
|
||||
$vis:vis $name:ident = $(
|
||||
$st:ident $(<
|
||||
$($_:lifetime: $l:lifetime,)*
|
||||
// ^^ `'static` (see above)
|
||||
$($c:literal,)*
|
||||
$($t:ident,)*
|
||||
>)?
|
||||
)+
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, PartialEq)]
|
||||
$vis enum $name<ES: Diagnostic + PartialEq + 'static> {
|
||||
Src(ParseError<UnknownToken, ES>),
|
||||
$(
|
||||
$st(ParseStateError<$st$(<$($l,)* $($c,)* $($t),* >)?>)
|
||||
// ^^ `'static`
|
||||
),+
|
||||
}
|
||||
|
||||
impl<ES: Diagnostic + PartialEq + 'static> From<ParseError<UnknownToken, ES>>
|
||||
for $name<ES>
|
||||
{
|
||||
fn from(e: ParseError<UnknownToken, ES>) -> Self {
|
||||
Self::Src(e)
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl<ES: Diagnostic + PartialEq + 'static>
|
||||
From<ParseStateError<$st$(<$($l,)* $($c,)* $($t),*>)?>>
|
||||
for $name<ES>
|
||||
{
|
||||
fn from(e: ParseStateError<$st$(<$($l,)* $($c,)* $($t),*>)?>) -> Self {
|
||||
Self::$st(e)
|
||||
}
|
||||
}
|
||||
)+
|
||||
|
||||
impl<ES: Diagnostic + PartialEq + 'static> std::fmt::Display for $name<ES> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Src(e) => std::fmt::Display::fmt(e, f),
|
||||
$(
|
||||
Self::$st(e) => std::fmt::Display::fmt(e, f),
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ES: Diagnostic + PartialEq + 'static> Diagnostic for $name<ES> {
|
||||
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
|
||||
match self {
|
||||
Self::Src(e) => e.describe(),
|
||||
$(
|
||||
Self::$st(e) => e.describe(),
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Declaratively define a lowering pipeline.
|
||||
///
|
||||
/// A lowering pipeline stitches together parsers such that the objects of
|
||||
|
@ -42,9 +139,49 @@
|
|||
macro_rules! lower_pipeline {
|
||||
($(
|
||||
$(#[$meta:meta])*
|
||||
$vis:vis $fn:ident$(<$l:lifetime>)?
|
||||
$(|> $lower:ty $([$ctx:ident])? $(, until ($until:pat))?)*;
|
||||
$vis:vis $fn:ident$(<$l:lifetime>)? -> $struct:ident
|
||||
$(|>
|
||||
$lower_name:tt$(<$($lower_t:tt),+>)?
|
||||
$([$ctx:ident])?
|
||||
$(, until ($until:pat))?
|
||||
)*
|
||||
;
|
||||
)*) => {$(
|
||||
paste::paste! {
|
||||
lower_error_sum! {
|
||||
/// Recoverable error for
|
||||
#[doc=concat!("[`", stringify!($fn), "`]")]
|
||||
/// lowering pipeline.
|
||||
///
|
||||
/// This represents an error that occurred from one of the
|
||||
/// [`ParseState`]s in the lowering pipeline.
|
||||
/// Since all [`ParseState`]s are expected to attempt
|
||||
/// recovery on failure,
|
||||
/// this error represents a _recoverable_ error.
|
||||
/// Whether or not the error should be treated as
|
||||
/// recoverable is entirely at the discretion of the sink
|
||||
/// provided to the pipeline;
|
||||
/// a sink may choose to promote all errors to
|
||||
/// unrecoverable.
|
||||
$vis [<$struct Error>] = $(
|
||||
$lower_name$(<$($lower_t,)+>)?
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
lower_pipeline!(
|
||||
@pipeline
|
||||
$(#[$meta])*
|
||||
$vis $fn$(<$l>)? -> $struct
|
||||
$(|> $lower_name$(<$($lower_t),+>)? $([$ctx])? $(, until ($until))?)*
|
||||
);
|
||||
)*};
|
||||
|
||||
(@pipeline
|
||||
$(#[$meta:meta])*
|
||||
$vis:vis $fn:ident$(<$l:lifetime>)? -> $struct:ident
|
||||
$(|> $lower:ty $([$ctx:ident])? $(, until ($until:pat))?)*
|
||||
) => {paste::paste!{
|
||||
$(#[$meta])*
|
||||
///
|
||||
/// Pipeline Definition
|
||||
|
@ -72,47 +209,35 @@ macro_rules! lower_pipeline {
|
|||
/// of the types of all parsers in the pipeline.
|
||||
/// It can be understood as:
|
||||
///
|
||||
/// 1. A function accepting three classes of arguments:
|
||||
/// 1. The _source_ token stream,
|
||||
/// which consists of tokens expected by the first parser
|
||||
/// in the pipeline;
|
||||
/// 2. _Context_ for certain parsers that request it,
|
||||
/// allowing for state to persist between separate
|
||||
/// pipelines; and
|
||||
/// 3. A _sink_ that serves as the final destination for the
|
||||
/// token stream.
|
||||
/// 2. A [`Result`] consisting of the updated context that was
|
||||
/// 1. A function accepting _context_ for whatever parsers request
|
||||
/// it,
|
||||
/// allowing both for configuration and for state to
|
||||
/// persist between separate pipelines.
|
||||
/// This returns a closure representing a configured pipeline.
|
||||
/// 2. The _source_ token stream is accepted by the closure,
|
||||
/// which consists of tokens expected by the first parser
|
||||
/// in the pipeline;
|
||||
/// 3. A _sink_ serves as the final destination for the token
|
||||
/// stream.
|
||||
/// 4. A [`Result`] consisting of the updated context that was
|
||||
/// originally passed into the function,
|
||||
/// so that it may be utilized in future pipelines.
|
||||
/// 3. A _recoverable error_ type `ER` that may be utilized when
|
||||
/// 5. A _recoverable error_ type
|
||||
#[doc=concat!("[`", stringify!([<$struct Error>]), "`]")]
|
||||
/// that may be utilized when
|
||||
/// compilation should continue despite an error.
|
||||
/// All parsers are expected to perform their own error
|
||||
/// recovery in an attempt to continue parsing to discover
|
||||
/// further errors;
|
||||
/// as such,
|
||||
/// this error type `ER` must be able to contain the
|
||||
/// errors of any parser in the pipeline,
|
||||
/// which is the reason for the large block of
|
||||
/// [`From`]s in this function's `where` clause.
|
||||
/// 4. An _unrecoverable error_ type `EU` that may be yielded by
|
||||
/// See [`crate::pipeline`] for more information.
|
||||
/// 6. An _unrecoverable error_ type `EU` that may be yielded by
|
||||
/// the sink to terminate compilation immediately.
|
||||
/// This is a component of the [`Result`] type that is
|
||||
/// ultimately yielded as the result of this function.
|
||||
$vis fn $fn<$($l,)? ES: Diagnostic, ER: Diagnostic, EU: Diagnostic>(
|
||||
src: impl LowerSource<
|
||||
UnknownToken,
|
||||
lower_pipeline!(@first_tok_ty $($lower),*),
|
||||
ES
|
||||
>,
|
||||
$vis fn $fn<$($l,)? ES: Diagnostic + 'static, EU: Diagnostic, SA, SB>(
|
||||
$(
|
||||
// Each parser may optionally receive context from an
|
||||
// earlier run.
|
||||
$($ctx: impl Into<<$lower as ParseState>::PubContext>,)?
|
||||
)*
|
||||
sink: impl FnMut(
|
||||
Result<lower_pipeline!(@last_obj_ty $($lower),*), ER>
|
||||
) -> Result<(), EU>,
|
||||
) -> Result<
|
||||
) -> impl FnOnce(SA, SB) -> Result<
|
||||
(
|
||||
$(
|
||||
// Any context that is passed in is also returned so
|
||||
|
@ -124,21 +249,6 @@ macro_rules! lower_pipeline {
|
|||
EU
|
||||
>
|
||||
where
|
||||
// Recoverable errors (ER) are errors that could potentially be
|
||||
// handled by the sink.
|
||||
// Parsers are always expected to perform error recovery to the
|
||||
// best of their ability.
|
||||
// We need to support widening into this error type from every
|
||||
// individual ParseState in this pipeline,
|
||||
// plus the source.
|
||||
ER: From<ParseError<UnknownToken, ES>>
|
||||
$(
|
||||
+ From<ParseError<
|
||||
<$lower as ParseState>::Token,
|
||||
<$lower as ParseState>::Error,
|
||||
>>
|
||||
)*,
|
||||
|
||||
// Unrecoverable errors (EU) are errors that the sink chooses
|
||||
// not to handle.
|
||||
// It is constructed explicitly from the sink,
|
||||
|
@ -147,17 +257,33 @@ macro_rules! lower_pipeline {
|
|||
// which is _not_ an error that parsers are expected to
|
||||
// recover from.
|
||||
EU: From<FinalizeError>,
|
||||
{
|
||||
let lower_pipeline!(@ret_pat $($($ctx)?)*) = lower_pipeline!(
|
||||
@body_head(src, sink)
|
||||
$((|> $lower $([$ctx])? $(, until ($until))?))*
|
||||
)?;
|
||||
|
||||
Ok(($(
|
||||
$($ctx,)?
|
||||
)*))
|
||||
SA: LowerSource<
|
||||
UnknownToken,
|
||||
lower_pipeline!(@first_tok_ty $($lower),*),
|
||||
ES
|
||||
>,
|
||||
|
||||
SB: FnMut(
|
||||
Result<lower_pipeline!(@last_obj_ty $($lower),*), [<$struct Error>]<ES>>
|
||||
) -> Result<(), EU>
|
||||
{
|
||||
move |src, sink| {
|
||||
// Recoverable error type (for brevity).
|
||||
#[doc(hidden)]
|
||||
type ER<T> = [<$struct Error>]<T>;
|
||||
|
||||
let lower_pipeline!(@ret_pat $($($ctx)?)*) = lower_pipeline!(
|
||||
@body_head(src, sink)
|
||||
$((|> $lower $([$ctx])? $(, until ($until))?))*
|
||||
)?;
|
||||
|
||||
Ok(($(
|
||||
$($ctx,)?
|
||||
)*))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}};
|
||||
|
||||
(@ret_ctx_ty $lower:ty, $_ctx:ident) => {
|
||||
<$lower as ParseState>::PubContext
|
||||
|
@ -200,8 +326,8 @@ macro_rules! lower_pipeline {
|
|||
Lower::<
|
||||
ParsedObject<UnknownToken, _, ES>,
|
||||
$head,
|
||||
ER,
|
||||
>::lower::<_, EU>(&mut $src.map(|result| result.map_err(ER::from)), |next| {
|
||||
ER<ES>,
|
||||
>::lower::<_, EU>(&mut $src.map(|result| result.map_err(ER::Src)), |next| {
|
||||
lower_pipeline!(
|
||||
@body_inner(next, $head, $sink)
|
||||
$($rest)*
|
||||
|
@ -217,9 +343,9 @@ macro_rules! lower_pipeline {
|
|||
Lower::<
|
||||
ParsedObject<UnknownToken, _, ES>,
|
||||
$head,
|
||||
ER,
|
||||
ER<ES>,
|
||||
>::lower_with_context::<_, EU>(
|
||||
&mut $src.map(|result| result.map_err(ER::from)),
|
||||
&mut $src.map(|result| result.map_err(ER::Src)),
|
||||
$ctx,
|
||||
|next| {
|
||||
lower_pipeline!(
|
||||
|
|
|
@ -733,6 +733,8 @@ pub mod st {
|
|||
URI_LV_TPL: uri "http://www.lovullo.com/rater/apply-template",
|
||||
URI_LV_WORKSHEET: uri "http://www.lovullo.com/rater/worksheet",
|
||||
|
||||
S_GEN_FROM_INTERP: str "Generated from interpolated string",
|
||||
|
||||
// Common whitespace.
|
||||
//
|
||||
// _This does not represent all forms of whitespace!_
|
||||
|
|
|
@ -101,12 +101,11 @@ use super::{
|
|||
CloseSpan, OpenSpan, QName,
|
||||
};
|
||||
use crate::{
|
||||
f::Functor,
|
||||
f::Map,
|
||||
parse::prelude::*,
|
||||
span::{Span, UNKNOWN_SPAN},
|
||||
xir::EleSpan,
|
||||
};
|
||||
use std::{convert::Infallible, fmt::Display};
|
||||
|
||||
use XirfAutoClose::*;
|
||||
|
||||
|
@ -138,10 +137,14 @@ impl Display for XirfAutoClose {
|
|||
}
|
||||
}
|
||||
|
||||
diagnostic_infallible! {
|
||||
pub enum XirfAutoCloseError {}
|
||||
}
|
||||
|
||||
impl ParseState for XirfAutoClose {
|
||||
type Token = XirfToken<Text>;
|
||||
type Object = XirfToken<Text>;
|
||||
type Error = Infallible;
|
||||
type Error = XirfAutoCloseError;
|
||||
type Context = AutoCloseStack;
|
||||
|
||||
fn parse_token(
|
||||
|
|
|
@ -46,20 +46,14 @@ use super::{
|
|||
CloseSpan, OpenSpan, QName, Token as XirToken, TokenStream,
|
||||
};
|
||||
use crate::{
|
||||
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
|
||||
f::Functor,
|
||||
f::Map,
|
||||
parse::prelude::*,
|
||||
span::Span,
|
||||
sym::{st::is_common_whitespace, GlobalSymbolResolve, SymbolId},
|
||||
xir::EleSpan,
|
||||
};
|
||||
use arrayvec::ArrayVec;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error,
|
||||
fmt::{Debug, Display},
|
||||
marker::PhantomData,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// Used for organization.
|
||||
pub use accept::*;
|
||||
|
@ -254,7 +248,7 @@ impl<T: TextType> From<Attr> for XirfToken<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: TextType> Functor<Depth> for XirfToken<T> {
|
||||
impl<T: TextType> Map<Depth> for XirfToken<T> {
|
||||
fn map(self, f: impl FnOnce(Depth) -> Depth) -> Self::Target {
|
||||
use XirfToken::*;
|
||||
|
||||
|
@ -926,10 +920,14 @@ impl<T: TextType> Display for XirfToXir<T> {
|
|||
}
|
||||
}
|
||||
|
||||
diagnostic_infallible! {
|
||||
pub enum XirfToXirError {}
|
||||
}
|
||||
|
||||
impl<T: TextType> ParseState for XirfToXir<T> {
|
||||
type Token = XirfToken<T>;
|
||||
type Object = XirToken;
|
||||
type Error = Infallible;
|
||||
type Error = XirfToXirError;
|
||||
|
||||
fn parse_token(
|
||||
self,
|
||||
|
|
|
@ -619,10 +619,10 @@ fn xirf_to_xir() {
|
|||
);
|
||||
|
||||
// The lowering pipeline above requires compatible errors.
|
||||
impl From<ParseError<XirfToken<Text>, Infallible>>
|
||||
impl From<ParseError<XirfToken<Text>, XirfToXirError>>
|
||||
for ParseError<XirToken, XirToXirfError>
|
||||
{
|
||||
fn from(_value: ParseError<XirfToken<Text>, Infallible>) -> Self {
|
||||
fn from(_value: ParseError<XirfToken<Text>, XirfToXirError>) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ slightly different representation than the original sources, but it must
|
|||
express an equivalent program, and the program must be at least as
|
||||
performant when emitted by TAME XSLT.
|
||||
|
||||
# Experimental Features
|
||||
If a file `is-experimental` exists in a test directory, then
|
||||
`--emit xmlo-experimental` will be used in place of `--emit xmlo`.
|
||||
|
||||
# Running Tests
|
||||
Test are prefixed with `test-*` and are executable. They must be invoked
|
||||
with the environment variable `PATH_TAMEC` set to the path of `tamec`
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
<package xmlns="http://www.lovullo.com/rater"
|
||||
xmlns:c="http://www.lovullo.com/calc"
|
||||
xmlns:t="http://www.lovullo.com/rater/apply-template">
|
||||
|
||||
This simply verifies that the file is copied to the destination.
|
||||
This can be removed once more of tamec becomes stable.
|
||||
|
||||
<t:foo-bar />
|
||||
</package>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0"?>
|
||||
<package xmlns="http://www.lovullo.com/rater"
|
||||
xmlns:c="http://www.lovullo.com/calc"
|
||||
xmlns:t="http://www.lovullo.com/rater/apply-template">
|
||||
|
||||
This simply verifies that the file is copied to the destination.
|
||||
This can be removed once more of tamec becomes stable.
|
||||
|
||||
<t:foo-bar />
|
||||
</package>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
|
||||
<package xmlns="http://www.lovullo.com/rater"
|
||||
xmlns:c="http://www.lovullo.com/calc"
|
||||
xmlns:t="http://www.lovullo.com/rater/apply-template">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<template name="_interp-non-bind_"
|
||||
desc="Interpolation in non-binding position">
|
||||
<param name="@___dsgr_335@"
|
||||
desc="Generated from interpolated string">
|
||||
<param-value name="@bar@" />
|
||||
</param>
|
||||
<param name="@___dsgr_368@"
|
||||
desc="Generated from interpolated string">
|
||||
<text>Prefix </text>
|
||||
<param-value name="@bar@" />
|
||||
</param>
|
||||
<param name="@___dsgr_3a3@"
|
||||
desc="Generated from interpolated string">
|
||||
<param-value name="@bar@" />
|
||||
<text> suffix</text>
|
||||
</param>
|
||||
<param name="@___dsgr_3da@"
|
||||
desc="Generated from interpolated string">
|
||||
<text>Prefix </text>
|
||||
<param-value name="@bar@" />
|
||||
<text> suffix</text>
|
||||
</param>
|
||||
|
||||
<classify as="only" desc="@___dsgr_335@"/>
|
||||
<classify as="prefixed" desc="@___dsgr_368@"/>
|
||||
<classify as="suffixed" desc="@___dsgr_3a3@"/>
|
||||
<classify as="both" desc="@___dsgr_3da@" />
|
||||
</template>
|
||||
|
||||
|
||||
<template name="_with-abstract-ident_"
|
||||
desc="Metavariable interpolation in binding position">
|
||||
<param name="@___dsgr_4a8@"
|
||||
desc="Generated from interpolated string">
|
||||
<param-value name="@as@" />
|
||||
</param>
|
||||
<param name="@___dsgr_4ca@"
|
||||
desc="Generated from interpolated string">
|
||||
<text>prefix-</text>
|
||||
<param-value name="@as@" />
|
||||
</param>
|
||||
<param name="@___dsgr_4f4@"
|
||||
desc="Generated from interpolated string">
|
||||
<param-value name="@as@" />
|
||||
<text>-suffix</text>
|
||||
</param>
|
||||
<param name="@___dsgr_51e@"
|
||||
desc="Generated from interpolated string">
|
||||
<text>prefix-</text>
|
||||
<param-value name="@as@" />
|
||||
<text>-suffix</text>
|
||||
</param>
|
||||
|
||||
<classify as="@___dsgr_4a8@" />
|
||||
<classify as="@___dsgr_4ca@" />
|
||||
<classify as="@___dsgr_4f4@" />
|
||||
<classify as="@___dsgr_51e@" />
|
||||
</template>
|
||||
</package>
|
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0"?>
|
||||
<package xmlns="http://www.lovullo.com/rater"
|
||||
xmlns:c="http://www.lovullo.com/calc"
|
||||
xmlns:t="http://www.lovullo.com/rater/apply-template">
|
||||
|
||||
<!-- note: the extra vertical space is for alignment with expected.xml;
|
||||
open them side-by-side in your editor of choice -->
|
||||
|
||||
<!-- because the output contains identifiers derived from spans, this test
|
||||
is exceptionally fragile; if you add or remove a single byte, you're
|
||||
bound to break things. If that happens, it is safe to update the
|
||||
span portion of identifier names. In the future, a tool may be
|
||||
created to help with this tedious chore. -->
|
||||
|
||||
<template name="_interp-non-bind_"
|
||||
desc="Interpolation in non-binding position">
|
||||
<!-- note the `{}` here -->
|
||||
<classify as="only" desc="{@bar@}" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<classify as="prefixed" desc="Prefix {@bar@}" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<classify as="suffixed" desc="{@bar@} suffix" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<classify as="both" desc="Prefix {@bar@} suffix" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<template name="_with-abstract-ident_"
|
||||
desc="Metavariable interpolation in binding position">
|
||||
<!-- note the `{}` here -->
|
||||
<classify as="{@as@}" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<classify as="prefix-{@as@}" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<classify as="{@as@}-suffix" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<classify as="prefix-{@as@}-suffix" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
</package>
|
||||
|
|
@ -20,19 +20,11 @@
|
|||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product>
|
||||
<c:sum />
|
||||
</c:product>
|
||||
</template>
|
||||
|
||||
<template name="_with-static-mix_"
|
||||
desc="Both identified and unidentified that may or may
|
||||
not be reachable in expansion context">
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product>
|
||||
<c:sum />
|
||||
</c:product>
|
||||
|
@ -48,9 +40,9 @@
|
|||
|
||||
<rate yields="tplStaticMix" />
|
||||
|
||||
<c:sum>
|
||||
<rate yields="tplStaticMixEnd">
|
||||
<c:product />
|
||||
</c:sum>
|
||||
</rate>
|
||||
</template>
|
||||
|
||||
|
||||
|
@ -100,9 +92,9 @@
|
|||
|
||||
<template name="_short-hand-nullary-body_" desc="Nullary with body" />
|
||||
<apply-template name="_short-hand-nullary-body_">
|
||||
<with-param name="@values@" value="___dsgr-bfe___" />
|
||||
<with-param name="@values@" value="___dsgr-bb5___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-bfe___"
|
||||
<template name="___dsgr-bb5___"
|
||||
desc="Desugared body of shorthand template application of `_short-hand-nullary-body_`">
|
||||
<c:product>
|
||||
<c:sum />
|
||||
|
@ -113,9 +105,9 @@
|
|||
<apply-template name="_short-hand-nary-body_">
|
||||
<with-param name="@bar@" value="baz" />
|
||||
<with-param name="@baz@" value="quux" />
|
||||
<with-param name="@values@" value="___dsgr-cb5___" />
|
||||
<with-param name="@values@" value="___dsgr-c6c___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-cb5___"
|
||||
<template name="___dsgr-c6c___"
|
||||
desc="Desugared body of shorthand template application of `_short-hand-nary-body_`">
|
||||
<c:sum>
|
||||
<c:product />
|
||||
|
@ -125,9 +117,9 @@
|
|||
<template name="_short-hand-nullary-outer_"
|
||||
desc="Outer template holding an inner" />
|
||||
<apply-template name="_short-hand-nullary-outer_">
|
||||
<with-param name="@values@" value="___dsgr-d99___" />
|
||||
<with-param name="@values@" value="___dsgr-d50___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-d99___"
|
||||
<template name="___dsgr-d50___"
|
||||
desc="Desugared body of shorthand template application of `_short-hand-nullary-outer_`">
|
||||
<template name="_short-hand-nullary-inner-dfn-inner_"
|
||||
desc="Inner template applied inner" />
|
||||
|
@ -137,9 +129,9 @@
|
|||
<template name="_short-hand-nullary-inner-dfn-outer_"
|
||||
desc="Define template outer but apply inner" />
|
||||
<apply-template name="_short-hand-nullary-outer_">
|
||||
<with-param name="@values@" value="___dsgr-eed___" />
|
||||
<with-param name="@values@" value="___dsgr-ea4___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-eed___"
|
||||
<template name="___dsgr-ea4___"
|
||||
desc="Desugared body of shorthand template application of `_short-hand-nullary-outer_`">
|
||||
<apply-template name="_short-hand-nullary-inner-dfn-outer_" />
|
||||
</template>
|
||||
|
@ -148,9 +140,9 @@
|
|||
desc="Unary with body" />
|
||||
<apply-template name="_short-hand-unary-with-body_">
|
||||
<with-param name="@foo@" value="bar" />
|
||||
<with-param name="@values@" value="___dsgr-fb4___" />
|
||||
<with-param name="@values@" value="___dsgr-f6b___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-fb4___"
|
||||
<template name="___dsgr-f6b___"
|
||||
desc="Desugared body of shorthand template application of `_short-hand-unary-with-body_`">
|
||||
<template name="_short-hand-unary-with-body-inner_"
|
||||
desc="Inner template" />
|
||||
|
@ -190,5 +182,28 @@
|
|||
<template name="_match-child_" desc="Template with a match child">
|
||||
<match on="foo" value="TRUE" />
|
||||
</template>
|
||||
|
||||
<template name="_tpl-param_" desc="Template with a param">
|
||||
<param name="@foo@" desc="A parameter" />
|
||||
<param name="@bar@" desc="Another parameter" />
|
||||
</template>
|
||||
|
||||
|
||||
<template name="_tpl-param_body_"
|
||||
desc="Template with params with bodies">
|
||||
<param name="@text@" desc="A param with a literal">
|
||||
<text>lonely foo</text>
|
||||
</param>
|
||||
|
||||
<param name="@ref@" desc="A param with a ref">
|
||||
<param-value name="@text@" />
|
||||
</param>
|
||||
|
||||
<param name="@both@" desc="A param with both literal and ref">
|
||||
<text>foo </text>
|
||||
<param-value name="@text@" />
|
||||
<text> bar</text>
|
||||
</param>
|
||||
</template>
|
||||
</package>
|
||||
|
||||
|
|
|
@ -20,19 +20,11 @@
|
|||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product>
|
||||
<c:sum />
|
||||
</c:product>
|
||||
</template>
|
||||
|
||||
<template name="_with-static-mix_"
|
||||
desc="Both identified and unidentified that may or may
|
||||
not be reachable in expansion context">
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product> <!-- depth N -->
|
||||
<c:sum /> <!-- depth N+1 -->
|
||||
</c:product>
|
||||
|
@ -48,9 +40,9 @@
|
|||
|
||||
<rate yields="tplStaticMix" /> <!-- begins at depth N+1 -->
|
||||
|
||||
<c:sum>
|
||||
<rate yields="tplStaticMixEnd">
|
||||
<c:product />
|
||||
</c:sum>
|
||||
</rate>
|
||||
</template>
|
||||
|
||||
Short-hand template application.
|
||||
|
@ -186,8 +178,32 @@
|
|||
we cannot support the generation of each of those things within
|
||||
templates.
|
||||
|
||||
|
||||
<template name="_match-child_" desc="Template with a match child">
|
||||
<match on="foo" />
|
||||
</template>
|
||||
|
||||
<template name="_tpl-param_" desc="Template with a param">
|
||||
<param name="@foo@" desc="A parameter" />
|
||||
<param name="@bar@" desc="Another parameter" />
|
||||
</template>
|
||||
|
||||
|
||||
<template name="_tpl-param_body_"
|
||||
desc="Template with params with bodies">
|
||||
<param name="@text@" desc="A param with a literal">
|
||||
<text>lonely foo</text>
|
||||
</param>
|
||||
|
||||
<param name="@ref@" desc="A param with a ref">
|
||||
<param-value name="@text@" />
|
||||
</param>
|
||||
|
||||
<param name="@both@" desc="A param with both literal and ref">
|
||||
<text>foo </text>
|
||||
<param-value name="@text@" />
|
||||
<text> bar</text>
|
||||
</param>
|
||||
</template>
|
||||
</package>
|
||||
|
||||
|
|
|
@ -11,8 +11,6 @@ mypath=$(dirname "$0")
|
|||
# Performing this check within `<()` below won't cause a failure.
|
||||
: "${P_XMLLINT?}" # conf.sh
|
||||
|
||||
tamer-flag-or-exit-ok wip-asg-derived-xmli
|
||||
|
||||
run-test() {
|
||||
local name="${1?Missing test name}"
|
||||
local dir="${2?Missing dir}"
|
||||
|
@ -45,8 +43,13 @@ timed-tamec() {
|
|||
# but it'll be close enough.
|
||||
local -i start_ns=$(date +%s%N)
|
||||
|
||||
local objty=xmlo
|
||||
if [ -f "$dir/is-experimental" ]; then
|
||||
objty=xmlo-experimental
|
||||
fi
|
||||
|
||||
command time -f "%F/%Rfault %I/%Oio %Mrss %c/%wctx \n%C" -o "$dir/time.log" \
|
||||
"${TAMER_PATH_TAMEC?}" -o "$dir/$out" --emit xmlo "$dir/$in" \
|
||||
"${TAMER_PATH_TAMEC?}" -o "$dir/$out" --emit "$objty" "$dir/$in" \
|
||||
&> "$dir/tamec-$out.log" \
|
||||
|| ret=$?
|
||||
|
||||
|
@ -66,7 +69,7 @@ timed-tamec() {
|
|||
header() {
|
||||
# allocate enough space based on the path we'll output
|
||||
local -i mypath_len=${#mypath}
|
||||
local -i dirlen=$((mypath_len + 12))
|
||||
local -i dirlen=$((mypath_len + 14))
|
||||
|
||||
# newline intentionally omitted
|
||||
printf "%-${dirlen}s %-20s " "$@"
|
||||
|
@ -101,6 +104,11 @@ test-derive-from-src() {
|
|||
test-fixpoint() {
|
||||
local dir="${1?Missing directory name}"
|
||||
|
||||
if [ -f "$dir/no-fixpoint" ]; then
|
||||
echo -n '!!!WARNING!!! test skipped: `no-fixpoint` file '
|
||||
return
|
||||
fi
|
||||
|
||||
timed-tamec "$dir" out.xmli out-2.xmli || return
|
||||
|
||||
diff <("$P_XMLLINT" --format "$dir/expected.xml" || echo 'ERR expected.xml') \
|
||||
|
|
Loading…
Reference in New Issue