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
|
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
|
%.xmli: %.xml
|
||||||
$(path_tame)/tamer/target/release/tamec --emit xmlo -o $@ $<
|
$(path_tame)/tamer/target/release/tamec --emit xmlo -o $@ $<
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,7 @@
|
||||||
|
|
||||||
<!-- make the name of the supplier available -->
|
<!-- make the name of the supplier available -->
|
||||||
<text>/**@expose*/rater.supplier = '</text>
|
<text>/**@expose*/rater.supplier = '</text>
|
||||||
<value-of select="substring-after( $name, '/' )" />
|
<value-of select="( tokenize( $name, '/' ) )[ last() ]" />
|
||||||
<text>'; </text>
|
<text>'; </text>
|
||||||
|
|
||||||
<text>/**@expose*/rater.meta = meta;</text>
|
<text>/**@expose*/rater.meta = meta;</text>
|
||||||
|
|
|
@ -53,9 +53,3 @@ unicode-width = "0.1.5"
|
||||||
# This is enabled automatically for the `test` profile.
|
# This is enabled automatically for the `test` profile.
|
||||||
parser-trace-stderr = []
|
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
|
# This script expects to receive a list of files containing such
|
||||||
# definitions.
|
# definitions.
|
||||||
# It will output,
|
# It will output,
|
||||||
|
@ -81,7 +99,8 @@ BEGINFILE {
|
||||||
/^object_rel! {$/, /^}$/ { in_block = 1 }
|
/^object_rel! {$/, /^}$/ { in_block = 1 }
|
||||||
|
|
||||||
# `Foo -> {` line declares the source of the relation.
|
# `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
|
block_src = $1
|
||||||
|
|
||||||
printf " # `%s` from `%s:%d`\n", block_src, FILENAME, FNR
|
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
|
# A closing curly brace always means that we've finished with the current
|
||||||
# source relation,
|
# source relation,
|
||||||
# since we're at the innermost level of nesting.
|
# since we're at the innermost level of nesting.
|
||||||
block_src && /}/ {
|
block_src && /^ }$/ {
|
||||||
block_src = ""
|
block_src = ""
|
||||||
print ""
|
print ""
|
||||||
}
|
}
|
||||||
|
@ -116,7 +135,12 @@ block_src && /^ *\/\/ empty$/ {
|
||||||
# we must independently define each one.
|
# we must independently define each one.
|
||||||
# But that's okay;
|
# But that's okay;
|
||||||
# the output is quite legible.
|
# 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)
|
# Edge type (cross, tree)
|
||||||
ty = $(NF-1)
|
ty = $(NF-1)
|
||||||
|
|
||||||
|
@ -139,8 +163,6 @@ block_src && $NF ~ /\w+,$/ {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
gsub(/,$/, "")
|
|
||||||
|
|
||||||
# This may need updating over time as object names in Rust sources
|
# This may need updating over time as object names in Rust sources
|
||||||
# exceed the fixed-width definition here.
|
# exceed the fixed-width definition here.
|
||||||
# This output is intended to form a table that is easy to read and
|
# 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.
|
# you will need to modify `configure.ac` / `Makefile.am` to do your bidding.
|
||||||
|
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2023-04-15"
|
channel = "nightly-2023-04-15" #¹
|
||||||
|
|
||||||
# The components should be checked in `configure.ac`
|
# The components should be checked in `configure.ac`
|
||||||
# - Note that `cargo-fmt` is `rustfmt`.
|
# - Note that `cargo-fmt` is `rustfmt`.
|
||||||
|
@ -50,3 +50,26 @@ components = ["rustfmt", "clippy"]
|
||||||
# another version of Rust already available elsewhere), you may use the
|
# another version of Rust already available elsewhere), you may use the
|
||||||
# `TAMER_RUST_TOOLCHAIN` `configure` parameter.
|
# `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::{
|
use crate::{
|
||||||
diagnose::Annotate,
|
diagnose::Annotate,
|
||||||
diagnostic_unreachable,
|
diagnostic_unreachable,
|
||||||
f::Functor,
|
f::Map,
|
||||||
parse::{prelude::*, StateStack},
|
parse::{prelude::*, StateStack},
|
||||||
span::Span,
|
span::Span,
|
||||||
sym::SymbolId,
|
sym::SymbolId,
|
||||||
|
@ -46,6 +46,9 @@ use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use super::graph::object::ObjectRelTo;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod ir;
|
mod ir;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
|
@ -223,13 +226,14 @@ impl ParseState for AirAggregate {
|
||||||
(PkgTpl(tplst), AirBind(ttok)) => ctx.proxy(tplst, ttok),
|
(PkgTpl(tplst), AirBind(ttok)) => ctx.proxy(tplst, ttok),
|
||||||
(PkgTpl(tplst), AirDoc(ttok)) => ctx.proxy(tplst, ttok),
|
(PkgTpl(tplst), AirDoc(ttok)) => ctx.proxy(tplst, ttok),
|
||||||
|
|
||||||
// Metasyntactic variables (metavariables)
|
// Metavariables
|
||||||
(st @ PkgTpl(_), tok @ AirMeta(..)) => {
|
(st @ (PkgTpl(_) | PkgExpr(_)), tok @ AirMeta(..)) => {
|
||||||
ctx.ret_or_transfer(st, tok, AirMetaAggregate::new())
|
ctx.ret_or_transfer(st, tok, AirMetaAggregate::new())
|
||||||
}
|
}
|
||||||
(PkgMeta(meta), AirMeta(mtok)) => ctx.proxy(meta, mtok),
|
(PkgMeta(meta), AirMeta(mtok)) => ctx.proxy(meta, mtok),
|
||||||
(PkgMeta(meta), AirBind(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)
|
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,
|
// TODO: We will need to be more intelligent about this,
|
||||||
// since desugaring will produce metavariables in nested contexts,
|
// since desugaring will produce metavariables in nested contexts,
|
||||||
// e.g. within an expression within a template.
|
// 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()))
|
Transition(st).err(AsgError::UnexpectedMeta(tok.span()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,6 +433,42 @@ impl AirAggregate {
|
||||||
Root(_) => Filter,
|
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
|
/// 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);
|
self.ooi_pkg.replace(oi_pkg);
|
||||||
|
|
||||||
Ok(oi_pkg)
|
Ok(oi_pkg)
|
||||||
|
@ -847,11 +887,33 @@ impl AirAggregateCtx {
|
||||||
///
|
///
|
||||||
/// A value of [`None`] indicates that no bindings are permitted in the
|
/// A value of [`None`] indicates that no bindings are permitted in the
|
||||||
/// current context.
|
/// current context.
|
||||||
fn rooting_oi(&self) -> Option<ObjectIndexToTree<Ident>> {
|
fn rooting_oi(&self) -> Option<(&AirAggregate, ObjectIndexToTree<Ident>)> {
|
||||||
self.stack
|
self.stack
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.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.
|
/// 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.
|
/// Root a concrete identifier using the [`Self::rooting_oi`] atop of
|
||||||
fn defines(&mut self, name: SPair) -> Result<ObjectIndex<Ident>, AsgError> {
|
/// the stack.
|
||||||
let oi_root = self
|
///
|
||||||
|
/// 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()
|
.rooting_oi()
|
||||||
.ok_or(AsgError::InvalidBindContext(name))?;
|
.ok_or(AsgError::InvalidBindContext(binding_name))?;
|
||||||
|
|
||||||
Ok(self.lookup_lexical_or_missing(name).add_edge_from(
|
self.lookup_lexical_or_missing(binding_name)
|
||||||
self.asg_mut(),
|
.defined_by(self.asg_mut(), oi_root)
|
||||||
oi_root,
|
}
|
||||||
None,
|
|
||||||
))
|
/// 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
|
/// Attempt to retrieve an identifier and its scope information from the
|
||||||
|
@ -966,6 +1088,26 @@ impl AirAggregateCtx {
|
||||||
.map(EnvScopeKind::into_inner)
|
.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
|
/// Attempt to retrieve an identifier from the graph by name relative to
|
||||||
/// the immediate environment `imm_env`.
|
/// 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>;
|
type Target = EnvScopeKind<U>;
|
||||||
|
|
||||||
fn map(self, f: impl FnOnce(T) -> U) -> Self::Target {
|
fn map(self, f: impl FnOnce(T) -> U) -> Self::Target {
|
||||||
|
|
|
@ -30,11 +30,8 @@ use super::{
|
||||||
AirAggregate, AirAggregateCtx,
|
AirAggregate, AirAggregateCtx,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
asg::{
|
asg::{graph::object::ObjectIndexTo, ObjectKind},
|
||||||
graph::object::{ObjectIndexRelTo, ObjectIndexTo},
|
f::Map,
|
||||||
ObjectKind,
|
|
||||||
},
|
|
||||||
f::Functor,
|
|
||||||
parse::prelude::*,
|
parse::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,8 +93,12 @@ impl ParseState for AirExprAggregate {
|
||||||
}
|
}
|
||||||
|
|
||||||
(BuildingExpr(es, poi), AirExpr(ExprStart(op, span))) => {
|
(BuildingExpr(es, poi), AirExpr(ExprStart(op, span))) => {
|
||||||
let oi = poi.create_subexpr(ctx.asg_mut(), Expr::new(op, span));
|
match poi.create_subexpr(ctx.asg_mut(), Expr::new(op, span)) {
|
||||||
Transition(BuildingExpr(es.push(poi), oi)).incomplete()
|
Ok(oi) => {
|
||||||
|
Transition(BuildingExpr(es.push(poi), oi)).incomplete()
|
||||||
|
}
|
||||||
|
Err(e) => Transition(BuildingExpr(es, poi)).err(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(BuildingExpr(es, oi), AirExpr(ExprEnd(end))) => {
|
(BuildingExpr(es, oi), AirExpr(ExprEnd(end))) => {
|
||||||
|
@ -123,7 +124,7 @@ impl ParseState for AirExprAggregate {
|
||||||
}
|
}
|
||||||
|
|
||||||
(BuildingExpr(es, oi), AirBind(BindIdent(id))) => {
|
(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)
|
oi_ident.bind_definition(ctx.asg_mut(), id, oi)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -138,20 +139,38 @@ impl ParseState for AirExprAggregate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(BuildingExpr(es, oi), AirBind(RefIdent(name))) => {
|
(BuildingExpr(es, oi), AirBind(BindIdentAbstract(meta_name))) => {
|
||||||
let oi_ident = ctx.lookup_lexical_or_missing(name);
|
let result =
|
||||||
Transition(BuildingExpr(
|
ctx.defines_abstract(meta_name).and_then(|oi_ident| {
|
||||||
es,
|
oi_ident.bind_definition(ctx.asg_mut(), meta_name, oi)
|
||||||
oi.ref_expr(ctx.asg_mut(), oi_ident),
|
});
|
||||||
))
|
|
||||||
.incomplete()
|
// 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))) => {
|
(BuildingExpr(es, oi), AirBind(RefIdent(name))) => {
|
||||||
oi.desc_short(ctx.asg_mut(), clause);
|
let oi_ident = ctx.lookup_lexical_or_missing(name);
|
||||||
Transition(BuildingExpr(es, oi)).incomplete()
|
|
||||||
|
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), AirDoc(DocText(text))) => Transition(
|
||||||
BuildingExpr(es, oi),
|
BuildingExpr(es, oi),
|
||||||
)
|
)
|
||||||
|
@ -188,9 +207,10 @@ impl AirExprAggregate {
|
||||||
oi_root: Option<ObjectIndexTo<Expr>>,
|
oi_root: Option<ObjectIndexTo<Expr>>,
|
||||||
oi_expr: ObjectIndex<Expr>,
|
oi_expr: ObjectIndex<Expr>,
|
||||||
) -> Result<(), AsgError> {
|
) -> Result<(), AsgError> {
|
||||||
oi_root
|
let oi_container = oi_root
|
||||||
.ok_or(AsgError::DanglingExpr(oi_expr.resolve(asg).span()))?
|
.ok_or(AsgError::DanglingExpr(oi_expr.resolve(asg).span()))?;
|
||||||
.add_edge_to(asg, oi_expr, None);
|
|
||||||
|
oi_expr.held_by(asg, oi_container)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,18 +18,23 @@
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::*;
|
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::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;
|
use std::assert_matches::assert_matches;
|
||||||
|
|
||||||
type Sut = AirAggregate;
|
type Sut = AirAggregate;
|
||||||
|
@ -46,13 +51,13 @@ pub fn collect_subexprs(
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_empty_ident() {
|
fn expr_empty_ident() {
|
||||||
let id = SPair("foo".into(), S2);
|
let id = spair("foo", S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S3),
|
ExprEnd(S3),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||||
|
@ -65,13 +70,13 @@ fn expr_empty_ident() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_without_pkg() {
|
fn expr_without_pkg() {
|
||||||
let toks = vec![
|
let toks = [
|
||||||
// No package
|
// No package
|
||||||
// (because we're not parsing with `parse_as_pkg_body` below)
|
// (because we're not parsing with `parse_as_pkg_body` below)
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
// RECOVERY
|
// RECOVERY
|
||||||
Air::PkgStart(S2, SPair("/pkg".into(), S2)),
|
PkgStart(S2, spair("/pkg", S2)),
|
||||||
Air::PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -88,18 +93,18 @@ fn expr_without_pkg() {
|
||||||
// Note that this can't happen in e.g. NIR / TAME's source XML.
|
// Note that this can't happen in e.g. NIR / TAME's source XML.
|
||||||
#[test]
|
#[test]
|
||||||
fn close_pkg_mid_expr() {
|
fn close_pkg_mid_expr() {
|
||||||
let id = SPair("foo".into(), S4);
|
let id = spair("foo", S4);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
Air::ExprStart(ExprOp::Sum, S2),
|
ExprStart(ExprOp::Sum, S2),
|
||||||
Air::PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
// RECOVERY: Let's finish the expression first...
|
// RECOVERY: Let's finish the expression first...
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
// ...and then try to close again.
|
// ...and then try to close again.
|
||||||
Air::PkgEnd(S6),
|
PkgEnd(S6),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -123,20 +128,20 @@ fn close_pkg_mid_expr() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_pkg_mid_expr() {
|
fn open_pkg_mid_expr() {
|
||||||
let pkg_a = SPair("/pkg".into(), S1);
|
let pkg_a = spair("/pkg", S1);
|
||||||
let pkg_nested = SPair("/pkg-nested".into(), S3);
|
let pkg_nested = spair("/pkg-nested", S3);
|
||||||
let id = SPair("foo".into(), S4);
|
let id = spair("foo", S4);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::PkgStart(S1, pkg_a),
|
PkgStart(S1, pkg_a),
|
||||||
Air::ExprStart(ExprOp::Sum, S2),
|
ExprStart(ExprOp::Sum, S2),
|
||||||
Air::PkgStart(S3, pkg_nested),
|
PkgStart(S3, pkg_nested),
|
||||||
// RECOVERY: We should still be able to complete successfully.
|
// RECOVERY: We should still be able to complete successfully.
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
// Closes the _original_ package.
|
// Closes the _original_ package.
|
||||||
Air::PkgEnd(S6),
|
PkgEnd(S6),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -163,23 +168,23 @@ fn open_pkg_mid_expr() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_non_empty_ident_root() {
|
fn expr_non_empty_ident_root() {
|
||||||
let id_a = SPair("foo".into(), S2);
|
let id_a = spair("foo", S2);
|
||||||
let id_b = SPair("bar".into(), S2);
|
let id_b = spair("bar", S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
// Identifier while still empty...
|
// 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
|
// (note that the inner expression _does not_ have an ident
|
||||||
// binding)
|
// binding)
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
|
|
||||||
// ...and an identifier non-empty.
|
// ...and an identifier non-empty.
|
||||||
Air::BindIdent(id_b),
|
BindIdent(id_b),
|
||||||
Air::ExprEnd(S6),
|
ExprEnd(S6),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
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.
|
// which only becomes reachable at the end.
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_non_empty_bind_only_after() {
|
fn expr_non_empty_bind_only_after() {
|
||||||
let id = SPair("foo".into(), S2);
|
let id = spair("foo", S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
// Expression root is still dangling at this point.
|
// Expression root is still dangling at this point.
|
||||||
Air::ExprStart(ExprOp::Sum, S2),
|
ExprStart(ExprOp::Sum, S2),
|
||||||
Air::ExprEnd(S3),
|
ExprEnd(S3),
|
||||||
|
|
||||||
// We only bind an identifier _after_ we've created the expression,
|
// We only bind an identifier _after_ we've created the expression,
|
||||||
// which should cause the still-dangling root to become
|
// which should cause the still-dangling root to become
|
||||||
// reachable.
|
// reachable.
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
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.
|
// since they're either mistakes or misconceptions.
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_dangling_no_subexpr() {
|
fn expr_dangling_no_subexpr() {
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
// No `BindIdent`,
|
// No `BindIdent`,
|
||||||
// so this expression is dangling.
|
// so this expression is dangling.
|
||||||
Air::ExprEnd(S2),
|
ExprEnd(S2),
|
||||||
];
|
];
|
||||||
|
|
||||||
// The error span should encompass the entire expression.
|
// The error span should encompass the entire expression.
|
||||||
|
@ -251,14 +256,14 @@ fn expr_dangling_no_subexpr() {
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_dangling_with_subexpr() {
|
fn expr_dangling_with_subexpr() {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
// Expression root is still dangling at this point.
|
// Expression root is still dangling at this point.
|
||||||
Air::ExprStart(ExprOp::Sum, S2),
|
ExprStart(ExprOp::Sum, S2),
|
||||||
Air::ExprEnd(S3),
|
ExprEnd(S3),
|
||||||
// Still no ident binding,
|
// Still no ident binding,
|
||||||
// so root should still be dangling.
|
// so root should still be dangling.
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
];
|
];
|
||||||
|
|
||||||
let full_span = S1.merge(S4).unwrap();
|
let full_span = S1.merge(S4).unwrap();
|
||||||
|
@ -280,23 +285,23 @@ fn expr_dangling_with_subexpr() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_dangling_with_subexpr_ident() {
|
fn expr_dangling_with_subexpr_ident() {
|
||||||
let id = SPair("foo".into(), S3);
|
let id = spair("foo", S3);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
// Expression root is still dangling at this point.
|
// Expression root is still dangling at this point.
|
||||||
Air::ExprStart(ExprOp::Sum, S2),
|
ExprStart(ExprOp::Sum, S2),
|
||||||
// The _inner_ expression receives an identifier,
|
// The _inner_ expression receives an identifier,
|
||||||
// but that should have no impact on the dangling status of
|
// but that should have no impact on the dangling status of
|
||||||
// the root,
|
// the root,
|
||||||
// especially given that subexpressions are always reachable
|
// especially given that subexpressions are always reachable
|
||||||
// anyway.
|
// anyway.
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
// But the root still has no ident binding,
|
// But the root still has no ident binding,
|
||||||
// and so should still be dangling.
|
// and so should still be dangling.
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
];
|
];
|
||||||
|
|
||||||
let full_span = S1.merge(S5).unwrap();
|
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.
|
// but this also protects against potential future breakages.
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_reachable_subsequent_dangling() {
|
fn expr_reachable_subsequent_dangling() {
|
||||||
let id = SPair("foo".into(), S2);
|
let id = spair("foo", S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
// Reachable
|
// Reachable
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S3),
|
ExprEnd(S3),
|
||||||
|
|
||||||
// Dangling
|
// Dangling
|
||||||
Air::ExprStart(ExprOp::Sum, S4),
|
ExprStart(ExprOp::Sum, S4),
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
];
|
];
|
||||||
|
|
||||||
// The error span should encompass the entire expression.
|
// The error span should encompass the entire expression.
|
||||||
|
@ -362,18 +367,18 @@ fn expr_reachable_subsequent_dangling() {
|
||||||
// Recovery from dangling expression.
|
// Recovery from dangling expression.
|
||||||
#[test]
|
#[test]
|
||||||
fn recovery_expr_reachable_after_dangling() {
|
fn recovery_expr_reachable_after_dangling() {
|
||||||
let id = SPair("foo".into(), S4);
|
let id = spair("foo", S4);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
// Dangling
|
// Dangling
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
Air::ExprEnd(S2),
|
ExprEnd(S2),
|
||||||
|
|
||||||
// Reachable, after error from dangling.
|
// Reachable, after error from dangling.
|
||||||
Air::ExprStart(ExprOp::Sum, S3),
|
ExprStart(ExprOp::Sum, S3),
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
];
|
];
|
||||||
|
|
||||||
// The error span should encompass the entire expression.
|
// The error span should encompass the entire expression.
|
||||||
|
@ -415,21 +420,21 @@ fn recovery_expr_reachable_after_dangling() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_close_unbalanced() {
|
fn expr_close_unbalanced() {
|
||||||
let id = SPair("foo".into(), S3);
|
let id = spair("foo", S3);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
// Close before _any_ open.
|
// Close before _any_ open.
|
||||||
Air::ExprEnd(S1),
|
ExprEnd(S1),
|
||||||
|
|
||||||
// Should recover,
|
// Should recover,
|
||||||
// allowing for a normal expr.
|
// allowing for a normal expr.
|
||||||
Air::ExprStart(ExprOp::Sum, S2),
|
ExprStart(ExprOp::Sum, S2),
|
||||||
Air::BindIdent(id),
|
BindIdent(id),
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
|
|
||||||
// And now an extra close _after_ a valid expr.
|
// And now an extra close _after_ a valid expr.
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut sut = parse_as_pkg_body(toks);
|
let mut sut = parse_as_pkg_body(toks);
|
||||||
|
@ -467,26 +472,26 @@ fn expr_close_unbalanced() {
|
||||||
// for non-associative expressions.
|
// for non-associative expressions.
|
||||||
#[test]
|
#[test]
|
||||||
fn sibling_subexprs_have_ordered_edges_to_parent() {
|
fn sibling_subexprs_have_ordered_edges_to_parent() {
|
||||||
let id_root = SPair("root".into(), S1);
|
let id_root = spair("root", S1);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
// Identify the root so that it is not dangling.
|
// Identify the root so that it is not dangling.
|
||||||
Air::BindIdent(id_root),
|
BindIdent(id_root),
|
||||||
|
|
||||||
// Sibling A
|
// Sibling A
|
||||||
Air::ExprStart(ExprOp::Sum, S3),
|
ExprStart(ExprOp::Sum, S3),
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
|
|
||||||
// Sibling B
|
// Sibling B
|
||||||
Air::ExprStart(ExprOp::Sum, S5),
|
ExprStart(ExprOp::Sum, S5),
|
||||||
Air::ExprEnd(S6),
|
ExprEnd(S6),
|
||||||
|
|
||||||
// Sibling C
|
// Sibling C
|
||||||
Air::ExprStart(ExprOp::Sum, S7),
|
ExprStart(ExprOp::Sum, S7),
|
||||||
Air::ExprEnd(S8),
|
ExprEnd(S8),
|
||||||
Air::ExprEnd(S9),
|
ExprEnd(S9),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||||
|
@ -516,21 +521,21 @@ fn sibling_subexprs_have_ordered_edges_to_parent() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_subexprs_related_to_relative_parent() {
|
fn nested_subexprs_related_to_relative_parent() {
|
||||||
let id_root = SPair("root".into(), S1);
|
let id_root = spair("root", S1);
|
||||||
let id_suba = SPair("suba".into(), S2);
|
let id_suba = spair("suba", S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1), // 0
|
ExprStart(ExprOp::Sum, S1), // 0
|
||||||
Air::BindIdent(id_root),
|
BindIdent(id_root),
|
||||||
|
|
||||||
Air::ExprStart(ExprOp::Sum, S2), // 1
|
ExprStart(ExprOp::Sum, S2), // 1
|
||||||
Air::BindIdent(id_suba),
|
BindIdent(id_suba),
|
||||||
|
|
||||||
Air::ExprStart(ExprOp::Sum, S3), // 2
|
ExprStart(ExprOp::Sum, S3), // 2
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
Air::ExprEnd(S6),
|
ExprEnd(S6),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||||
|
@ -556,18 +561,18 @@ fn nested_subexprs_related_to_relative_parent() {
|
||||||
fn expr_redefine_ident() {
|
fn expr_redefine_ident() {
|
||||||
// Same identifier but with different spans
|
// Same identifier but with different spans
|
||||||
// (which would be the case in the real world).
|
// (which would be the case in the real world).
|
||||||
let id_first = SPair("foo".into(), S2);
|
let id_first = spair("foo", S2);
|
||||||
let id_dup = SPair("foo".into(), S3);
|
let id_dup = spair("foo", S3);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
Air::BindIdent(id_first),
|
BindIdent(id_first),
|
||||||
|
|
||||||
Air::ExprStart(ExprOp::Sum, S3),
|
ExprStart(ExprOp::Sum, S3),
|
||||||
Air::BindIdent(id_dup),
|
BindIdent(id_dup),
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
Air::ExprEnd(S5),
|
ExprEnd(S5),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut sut = parse_as_pkg_body(toks);
|
let mut sut = parse_as_pkg_body(toks);
|
||||||
|
@ -606,32 +611,32 @@ fn expr_redefine_ident() {
|
||||||
fn expr_still_dangling_on_redefine() {
|
fn expr_still_dangling_on_redefine() {
|
||||||
// Same identifier but with different spans
|
// Same identifier but with different spans
|
||||||
// (which would be the case in the real world).
|
// (which would be the case in the real world).
|
||||||
let id_first = SPair("foo".into(), S2);
|
let id_first = spair("foo", S2);
|
||||||
let id_dup = SPair("foo".into(), S5);
|
let id_dup = spair("foo", S5);
|
||||||
let id_dup2 = SPair("foo".into(), S8);
|
let id_dup2 = spair("foo", S8);
|
||||||
let id_second = SPair("bar".into(), S9);
|
let id_second = spair("bar", S9);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
// First expr (OK)
|
// First expr (OK)
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
Air::BindIdent(id_first),
|
BindIdent(id_first),
|
||||||
Air::ExprEnd(S3),
|
ExprEnd(S3),
|
||||||
|
|
||||||
// Second expr should still dangle due to use of duplicate
|
// Second expr should still dangle due to use of duplicate
|
||||||
// identifier
|
// identifier
|
||||||
Air::ExprStart(ExprOp::Sum, S4),
|
ExprStart(ExprOp::Sum, S4),
|
||||||
Air::BindIdent(id_dup),
|
BindIdent(id_dup),
|
||||||
Air::ExprEnd(S6),
|
ExprEnd(S6),
|
||||||
|
|
||||||
// Third expr will error on redefine but then be successful.
|
// Third expr will error on redefine but then be successful.
|
||||||
// This probably won't happen in practice with TAME's original
|
// This probably won't happen in practice with TAME's original
|
||||||
// source language,
|
// source language,
|
||||||
// but could happen at e.g. a REPL.
|
// but could happen at e.g. a REPL.
|
||||||
Air::ExprStart(ExprOp::Sum, S7),
|
ExprStart(ExprOp::Sum, S7),
|
||||||
Air::BindIdent(id_dup2), // fail
|
BindIdent(id_dup2), // fail
|
||||||
Air::BindIdent(id_second), // succeed
|
BindIdent(id_second), // succeed
|
||||||
Air::ExprEnd(S10),
|
ExprEnd(S10),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut sut = parse_as_pkg_body(toks);
|
let mut sut = parse_as_pkg_body(toks);
|
||||||
|
@ -691,27 +696,27 @@ fn expr_still_dangling_on_redefine() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_ref_to_ident() {
|
fn expr_ref_to_ident() {
|
||||||
let id_foo = SPair("foo".into(), S2);
|
let id_foo = spair("foo", S2);
|
||||||
let id_bar = SPair("bar".into(), S6);
|
let id_bar = spair("bar", S6);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
Air::BindIdent(id_foo),
|
BindIdent(id_foo),
|
||||||
|
|
||||||
// Reference to an as-of-yet-undefined id (okay),
|
// Reference to an as-of-yet-undefined id (okay),
|
||||||
// with a different span than `id_bar`.
|
// with a different span than `id_bar`.
|
||||||
Air::RefIdent(SPair("bar".into(), S3)),
|
RefIdent(spair("bar", S3)),
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
|
|
||||||
//
|
//
|
||||||
// Another expression to reference the first
|
// Another expression to reference the first
|
||||||
// (we don't handle cyclic references until a topological sort,
|
// (we don't handle cyclic references until a topological sort,
|
||||||
// so no point in referencing ourselves;
|
// so no point in referencing ourselves;
|
||||||
// it'd work just fine here.)
|
// it'd work just fine here.)
|
||||||
Air::ExprStart(ExprOp::Sum, S5),
|
ExprStart(ExprOp::Sum, S5),
|
||||||
Air::BindIdent(id_bar),
|
BindIdent(id_bar),
|
||||||
Air::ExprEnd(S7),
|
ExprEnd(S7),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||||
|
@ -748,23 +753,23 @@ fn expr_ref_to_ident() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn idents_share_defining_pkg() {
|
fn idents_share_defining_pkg() {
|
||||||
let id_foo = SPair("foo".into(), S3);
|
let id_foo = spair("foo", S3);
|
||||||
let id_bar = SPair("bar".into(), S5);
|
let id_bar = spair("bar", S5);
|
||||||
let id_baz = SPair("baz".into(), S6);
|
let id_baz = spair("baz", S6);
|
||||||
|
|
||||||
// An expression nested within another.
|
// An expression nested within another.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
Air::ExprStart(ExprOp::Sum, S2),
|
ExprStart(ExprOp::Sum, S2),
|
||||||
Air::BindIdent(id_foo),
|
BindIdent(id_foo),
|
||||||
|
|
||||||
Air::ExprStart(ExprOp::Sum, S4),
|
ExprStart(ExprOp::Sum, S4),
|
||||||
Air::BindIdent(id_bar),
|
BindIdent(id_bar),
|
||||||
Air::RefIdent(id_baz),
|
RefIdent(id_baz),
|
||||||
Air::ExprEnd(S7),
|
ExprEnd(S7),
|
||||||
Air::ExprEnd(S8),
|
ExprEnd(S8),
|
||||||
Air::PkgEnd(S9),
|
PkgEnd(S9),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_toks(toks);
|
let ctx = air_ctx_from_toks(toks);
|
||||||
|
@ -789,15 +794,15 @@ fn idents_share_defining_pkg() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_doc_short_desc() {
|
fn expr_doc_short_desc() {
|
||||||
let id_expr = SPair("foo".into(), S2);
|
let id_expr = spair("foo", S2);
|
||||||
let clause = SPair("short desc".into(), S3);
|
let clause = spair("short desc", S3);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
Air::ExprStart(ExprOp::Sum, S1),
|
ExprStart(ExprOp::Sum, S1),
|
||||||
Air::BindIdent(id_expr),
|
BindIdent(id_expr),
|
||||||
Air::DocIndepClause(clause),
|
DocIndepClause(clause),
|
||||||
Air::ExprEnd(S4),
|
ExprEnd(S4),
|
||||||
];
|
];
|
||||||
|
|
||||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||||
|
@ -813,3 +818,73 @@ fn expr_doc_short_desc() {
|
||||||
oi_docs.collect::<Vec<_>>(),
|
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.
|
/// Assign an identifier to the active object.
|
||||||
///
|
///
|
||||||
/// The "active" object depends on the current parsing state.
|
/// 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) => {
|
BindIdent(id: SPair) => {
|
||||||
span: id,
|
span: id,
|
||||||
display: |f| write!(
|
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`].
|
/// Reference another object identified by the given [`SPair`].
|
||||||
///
|
///
|
||||||
/// Objects can be referenced before they are declared or defined,
|
/// Objects can be referenced before they are declared or defined,
|
||||||
|
@ -646,7 +670,7 @@ sum_ir! {
|
||||||
/// Subset of [`Air`] tokens for defining [`Tpl`]s.
|
/// Subset of [`Air`] tokens for defining [`Tpl`]s.
|
||||||
///
|
///
|
||||||
/// Templates serve as containers for objects that reference
|
/// Templates serve as containers for objects that reference
|
||||||
/// metasyntactic variables,
|
/// metavariables,
|
||||||
/// defined by [`AirMeta::MetaStart`].
|
/// defined by [`AirMeta::MetaStart`].
|
||||||
///
|
///
|
||||||
/// Template Application
|
/// 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 {
|
enum AirMeta {
|
||||||
/// Begin a metavariable definition.
|
/// Begin a metavariable definition.
|
||||||
///
|
///
|
||||||
|
@ -769,7 +805,7 @@ sum_ir! {
|
||||||
span: span,
|
span: span,
|
||||||
display: |f| write!(
|
display: |f| write!(
|
||||||
f,
|
f,
|
||||||
"open definition of metasyntactic variable",
|
"open definition of metavariable",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -787,7 +823,7 @@ sum_ir! {
|
||||||
span: span,
|
span: span,
|
||||||
display: |f| write!(
|
display: |f| write!(
|
||||||
f,
|
f,
|
||||||
"close definition of metasyntactic variable",
|
"close definition of metavariable",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -854,18 +890,7 @@ sum_ir! {
|
||||||
pub sum enum AirBindableTpl = AirTpl | AirBind | AirDoc;
|
pub sum enum AirBindableTpl = AirTpl | AirBind | AirDoc;
|
||||||
|
|
||||||
/// Tokens that may be used to define metavariables.
|
/// Tokens that may be used to define metavariables.
|
||||||
pub sum enum AirBindableMeta = AirMeta | AirBind;
|
pub sum enum AirBindableMeta = AirMeta | AirBind | AirDoc;
|
||||||
}
|
|
||||||
|
|
||||||
impl AirBind {
|
|
||||||
/// Name of the identifier described by this token.
|
|
||||||
pub fn name(&self) -> SPair {
|
|
||||||
use AirBind::*;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
BindIdent(name) | RefIdent(name) => *name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AirIdent {
|
impl AirIdent {
|
||||||
|
|
|
@ -26,12 +26,9 @@ use super::{
|
||||||
ir::AirBindableMeta,
|
ir::AirBindableMeta,
|
||||||
AirAggregate, AirAggregateCtx,
|
AirAggregate, AirAggregateCtx,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{asg::graph::object::Meta, diagnostic_todo, parse::prelude::*};
|
||||||
asg::graph::object::Meta, diagnose::Annotate, diagnostic_todo,
|
|
||||||
parse::prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Metasyntactic variable (metavariable) parser.
|
/// Metalinguistic variable (metavariable) parser.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum AirMetaAggregate {
|
pub enum AirMetaAggregate {
|
||||||
/// Ready for the start of a metavariable.
|
/// Ready for the start of a metavariable.
|
||||||
|
@ -64,7 +61,7 @@ impl ParseState for AirMetaAggregate {
|
||||||
tok: Self::Token,
|
tok: Self::Token,
|
||||||
ctx: &mut Self::Context,
|
ctx: &mut Self::Context,
|
||||||
) -> TransitionResult<Self::Super> {
|
) -> TransitionResult<Self::Super> {
|
||||||
use super::ir::{AirBind::*, AirMeta::*};
|
use super::ir::{AirBind::*, AirDoc::*, AirMeta::*};
|
||||||
use AirBindableMeta::*;
|
use AirBindableMeta::*;
|
||||||
use AirMetaAggregate::*;
|
use AirMetaAggregate::*;
|
||||||
|
|
||||||
|
@ -78,26 +75,58 @@ impl ParseState for AirMetaAggregate {
|
||||||
Transition(Ready).incomplete()
|
Transition(Ready).incomplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
(TplMeta(oi_meta), AirMeta(MetaLexeme(lexeme))) => Transition(
|
(TplMeta(oi_meta), AirMeta(MetaLexeme(lexeme))) => oi_meta
|
||||||
TplMeta(oi_meta.assign_lexeme(ctx.asg_mut(), lexeme)),
|
.append_lexeme(ctx.asg_mut(), lexeme)
|
||||||
)
|
.map(|_| ())
|
||||||
.incomplete(),
|
.transition(TplMeta(oi_meta)),
|
||||||
|
|
||||||
(TplMeta(oi_meta), AirBind(BindIdent(name))) => ctx
|
(TplMeta(oi_meta), AirBind(BindIdent(name))) => ctx
|
||||||
.defines(name)
|
.defines_concrete(name)
|
||||||
.and_then(|oi_ident| {
|
.and_then(|oi_ident| {
|
||||||
oi_ident.bind_definition(ctx.asg_mut(), name, oi_meta)
|
oi_ident.bind_definition(ctx.asg_mut(), name, oi_meta)
|
||||||
})
|
})
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.transition(TplMeta(oi_meta)),
|
.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!(
|
diagnostic_todo!(
|
||||||
vec![tok.note("this token")],
|
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(..))) => {
|
(TplMeta(..), tok @ AirMeta(MetaStart(..))) => {
|
||||||
diagnostic_todo!(
|
diagnostic_todo!(
|
||||||
vec![tok.note("this token")],
|
vec![tok.note("this token")],
|
||||||
|
@ -119,8 +148,10 @@ impl ParseState for AirMetaAggregate {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maybe the bind can be handled by the parent frame.
|
// Maybe the token can be handled by the parent frame.
|
||||||
(Ready, tok @ AirBind(..)) => Transition(Ready).dead(tok),
|
(Ready, tok @ (AirBind(..) | AirDoc(..))) => {
|
||||||
|
Transition(Ready).dead(tok)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,3 +165,6 @@ impl AirMetaAggregate {
|
||||||
Self::Ready
|
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 super::{super::AsgError, ir::AirIdent, AirAggregate, AirAggregateCtx};
|
||||||
use crate::parse::prelude::*;
|
use crate::parse::prelude::*;
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum AirOpaqueAggregate {
|
pub enum AirOpaqueAggregate {
|
||||||
|
@ -74,9 +73,11 @@ impl ParseState for AirOpaqueAggregate {
|
||||||
(Ready, IdentDep(name, dep)) => {
|
(Ready, IdentDep(name, dep)) => {
|
||||||
let oi_from = ctx.lookup_lexical_or_missing(name);
|
let oi_from = ctx.lookup_lexical_or_missing(name);
|
||||||
let oi_to = ctx.lookup_lexical_or_missing(dep);
|
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
|
(Ready, IdentFragment(name, text)) => ctx
|
||||||
|
@ -85,11 +86,11 @@ impl ParseState for AirOpaqueAggregate {
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.transition(Ready),
|
.transition(Ready),
|
||||||
|
|
||||||
(Ready, IdentRoot(name)) => {
|
(Ready, IdentRoot(name)) => ctx
|
||||||
ctx.lookup_lexical_or_missing(name).root(ctx.asg_mut());
|
.lookup_lexical_or_missing(name)
|
||||||
|
.root(ctx.asg_mut())
|
||||||
Transition(Ready).incomplete()
|
.map(|_| ())
|
||||||
}
|
.transition(Ready),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ use super::{
|
||||||
ir::AirLiteratePkg,
|
ir::AirLiteratePkg,
|
||||||
AirAggregate, AirAggregateCtx,
|
AirAggregate, AirAggregateCtx,
|
||||||
};
|
};
|
||||||
use crate::{diagnose::Annotate, diagnostic_todo, parse::prelude::*};
|
use crate::{diagnostic_todo, parse::prelude::*};
|
||||||
|
|
||||||
/// Package parsing with support for loaded identifiers.
|
/// Package parsing with support for loaded identifiers.
|
||||||
///
|
///
|
||||||
|
@ -112,10 +112,10 @@ impl ParseState for AirPkgAggregate {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
(Toplevel(oi_pkg), AirDoc(DocText(text))) => {
|
(Toplevel(oi_pkg), AirDoc(DocText(text))) => oi_pkg
|
||||||
oi_pkg.append_doc_text(ctx.asg_mut(), text);
|
.append_doc_text(ctx.asg_mut(), text)
|
||||||
Transition(Toplevel(oi_pkg)).incomplete()
|
.map(|_| ())
|
||||||
}
|
.transition(Toplevel(oi_pkg)),
|
||||||
|
|
||||||
// Package import
|
// Package import
|
||||||
(Toplevel(oi_pkg), AirPkg(PkgImport(namespec))) => oi_pkg
|
(Toplevel(oi_pkg), AirPkg(PkgImport(namespec))) => oi_pkg
|
||||||
|
|
|
@ -26,11 +26,11 @@ use crate::{
|
||||||
graph::object::{ObjectRel, ObjectRelFrom, ObjectRelatable},
|
graph::object::{ObjectRel, ObjectRelFrom, ObjectRelatable},
|
||||||
IdentKind, ObjectIndexRelTo, Source, TransitionError,
|
IdentKind, ObjectIndexRelTo, Source, TransitionError,
|
||||||
},
|
},
|
||||||
parse::{ParseError, Parsed, Parser},
|
parse::{util::spair, ParseError, Parsed, Parser},
|
||||||
span::dummy::*,
|
span::dummy::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Sut = AirAggregate;
|
pub type Sut = AirAggregate;
|
||||||
|
|
||||||
use Air::*;
|
use Air::*;
|
||||||
use Parsed::Incomplete;
|
use Parsed::Incomplete;
|
||||||
|
@ -39,7 +39,7 @@ mod scope;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ident_decl() {
|
fn ident_decl() {
|
||||||
let id = SPair("foo".into(), S2);
|
let id = spair("foo", S2);
|
||||||
let kind = IdentKind::Tpl;
|
let kind = IdentKind::Tpl;
|
||||||
let src = Source {
|
let src = Source {
|
||||||
src: Some("test/decl".into()),
|
src: Some("test/decl".into()),
|
||||||
|
@ -47,8 +47,8 @@ fn ident_decl() {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
IdentDecl(id, kind.clone(), src.clone()),
|
IdentDecl(id, kind.clone(), src.clone()),
|
||||||
// Attempt re-declaration.
|
// Attempt re-declaration.
|
||||||
IdentDecl(id, kind.clone(), src.clone()),
|
IdentDecl(id, kind.clone(), src.clone()),
|
||||||
|
@ -88,8 +88,8 @@ fn ident_decl() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ident_extern_decl() {
|
fn ident_extern_decl() {
|
||||||
let id = SPair("foo".into(), S2);
|
let id = spair("foo", S2);
|
||||||
let re_id = SPair("foo".into(), S3);
|
let re_id = spair("foo", S3);
|
||||||
let kind = IdentKind::Tpl;
|
let kind = IdentKind::Tpl;
|
||||||
let different_kind = IdentKind::Meta;
|
let different_kind = IdentKind::Meta;
|
||||||
let src = Source {
|
let src = Source {
|
||||||
|
@ -98,8 +98,8 @@ fn ident_extern_decl() {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
IdentExternDecl(id, kind.clone(), src.clone()),
|
IdentExternDecl(id, kind.clone(), src.clone()),
|
||||||
// Redeclare with a different kind
|
// Redeclare with a different kind
|
||||||
IdentExternDecl(re_id, different_kind.clone(), src.clone()),
|
IdentExternDecl(re_id, different_kind.clone(), src.clone()),
|
||||||
|
@ -141,12 +141,12 @@ fn ident_extern_decl() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ident_dep() {
|
fn ident_dep() {
|
||||||
let id = SPair("foo".into(), S2);
|
let id = spair("foo", S2);
|
||||||
let dep = SPair("dep".into(), S3);
|
let dep = spair("dep", S3);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
IdentDep(id, dep),
|
IdentDep(id, dep),
|
||||||
PkgEnd(S4),
|
PkgEnd(S4),
|
||||||
].into_iter();
|
].into_iter();
|
||||||
|
@ -174,7 +174,7 @@ fn ident_dep() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ident_fragment() {
|
fn ident_fragment() {
|
||||||
let id = SPair("frag".into(), S2);
|
let id = spair("frag", S2);
|
||||||
let kind = IdentKind::Tpl;
|
let kind = IdentKind::Tpl;
|
||||||
let src = Source {
|
let src = Source {
|
||||||
src: Some("test/frag".into()),
|
src: Some("test/frag".into()),
|
||||||
|
@ -183,8 +183,8 @@ fn ident_fragment() {
|
||||||
let frag = "fragment text".into();
|
let frag = "fragment text".into();
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
// Identifier must be declared before it can be given a
|
// Identifier must be declared before it can be given a
|
||||||
// fragment.
|
// fragment.
|
||||||
IdentDecl(id, kind.clone(), src.clone()),
|
IdentDecl(id, kind.clone(), src.clone()),
|
||||||
|
@ -232,11 +232,11 @@ fn ident_fragment() {
|
||||||
// `Ident::Missing`.
|
// `Ident::Missing`.
|
||||||
#[test]
|
#[test]
|
||||||
fn ident_root_missing() {
|
fn ident_root_missing() {
|
||||||
let id = SPair("toroot".into(), S2);
|
let id = spair("toroot", S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
IdentRoot(id),
|
IdentRoot(id),
|
||||||
PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
].into_iter();
|
].into_iter();
|
||||||
|
@ -269,7 +269,7 @@ fn ident_root_missing() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ident_root_existing() {
|
fn ident_root_existing() {
|
||||||
let id = SPair("toroot".into(), S2);
|
let id = spair("toroot", S2);
|
||||||
let kind = IdentKind::Tpl;
|
let kind = IdentKind::Tpl;
|
||||||
let src = Source {
|
let src = Source {
|
||||||
src: Some("test/root-existing".into()),
|
src: Some("test/root-existing".into()),
|
||||||
|
@ -281,10 +281,10 @@ fn ident_root_existing() {
|
||||||
assert!(!kind.is_auto_root());
|
assert!(!kind.is_auto_root());
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
IdentDecl(id, kind.clone(), src.clone()),
|
IdentDecl(id, kind.clone(), src.clone()),
|
||||||
IdentRoot(SPair(id.symbol(), S3)),
|
IdentRoot(spair(id, S3)),
|
||||||
PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
]
|
]
|
||||||
.into_iter();
|
.into_iter();
|
||||||
|
@ -329,8 +329,8 @@ fn declare_kind_auto_root() {
|
||||||
assert!(auto_kind.is_auto_root());
|
assert!(auto_kind.is_auto_root());
|
||||||
assert!(!no_auto_kind.is_auto_root());
|
assert!(!no_auto_kind.is_auto_root());
|
||||||
|
|
||||||
let id_auto = SPair("auto_root".into(), S2);
|
let id_auto = spair("auto_root", S2);
|
||||||
let id_no_auto = SPair("no_auto_root".into(), S3);
|
let id_no_auto = spair("no_auto_root", S3);
|
||||||
|
|
||||||
let src = Source {
|
let src = Source {
|
||||||
src: Some("src/pkg".into()),
|
src: Some("src/pkg".into()),
|
||||||
|
@ -339,7 +339,7 @@ fn declare_kind_auto_root() {
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = [
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
// auto-rooting
|
// auto-rooting
|
||||||
IdentDecl(id_auto, auto_kind, src.clone()),
|
IdentDecl(id_auto, auto_kind, src.clone()),
|
||||||
// non-auto-rooting
|
// non-auto-rooting
|
||||||
|
@ -372,8 +372,8 @@ fn declare_kind_auto_root() {
|
||||||
#[test]
|
#[test]
|
||||||
fn pkg_is_rooted() {
|
fn pkg_is_rooted() {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, spair("/pkg", S1)),
|
||||||
PkgEnd(S2),
|
PkgEnd(S2),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -394,10 +394,10 @@ fn pkg_is_rooted() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn close_pkg_without_open() {
|
fn close_pkg_without_open() {
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgEnd(S1),
|
PkgEnd(S1),
|
||||||
// RECOVERY: Try again.
|
// RECOVERY: Try again.
|
||||||
PkgStart(S2, SPair("/pkg".into(), S2)),
|
PkgStart(S2, spair("/pkg", S2)),
|
||||||
PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -414,11 +414,11 @@ fn close_pkg_without_open() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_open_pkg() {
|
fn nested_open_pkg() {
|
||||||
let name_a = SPair("/pkg-a".into(), S2);
|
let name_a = spair("/pkg-a", S2);
|
||||||
let name_b = SPair("/pkg-b".into(), S4);
|
let name_b = spair("/pkg-b", S4);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, name_a),
|
PkgStart(S1, name_a),
|
||||||
// Cannot nest package
|
// Cannot nest package
|
||||||
PkgStart(S3, name_b),
|
PkgStart(S3, name_b),
|
||||||
|
@ -442,10 +442,10 @@ fn nested_open_pkg() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pkg_canonical_name() {
|
fn pkg_canonical_name() {
|
||||||
let name = SPair("/foo/bar".into(), S2);
|
let name = spair("/foo/bar", S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, name),
|
PkgStart(S1, name),
|
||||||
PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
];
|
];
|
||||||
|
@ -477,12 +477,12 @@ fn pkg_canonical_name() {
|
||||||
// filenames.
|
// filenames.
|
||||||
#[test]
|
#[test]
|
||||||
fn pkg_cannot_redeclare() {
|
fn pkg_cannot_redeclare() {
|
||||||
let name = SPair("/foo/bar".into(), S2);
|
let name = spair("/foo/bar", S2);
|
||||||
let name2 = SPair("/foo/bar".into(), S5);
|
let name2 = spair("/foo/bar", S5);
|
||||||
let namefix = SPair("/foo/fix".into(), S7);
|
let namefix = spair("/foo/fix", S7);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, name),
|
PkgStart(S1, name),
|
||||||
PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
|
|
||||||
|
@ -527,11 +527,11 @@ fn pkg_cannot_redeclare() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pkg_import_canonicalized_against_current_pkg() {
|
fn pkg_import_canonicalized_against_current_pkg() {
|
||||||
let pkg_name = SPair("/foo/bar".into(), S2);
|
let pkg_name = spair("/foo/bar", S2);
|
||||||
let pkg_rel = SPair("baz/quux".into(), S3);
|
let pkg_rel = spair("baz/quux", S3);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
PkgStart(S1, pkg_name),
|
PkgStart(S1, pkg_name),
|
||||||
PkgImport(pkg_rel),
|
PkgImport(pkg_rel),
|
||||||
PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
|
@ -553,18 +553,18 @@ fn pkg_import_canonicalized_against_current_pkg() {
|
||||||
.resolve(&asg);
|
.resolve(&asg);
|
||||||
|
|
||||||
// TODO
|
// 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.
|
// Documentation can be mixed in with objects in a literate style.
|
||||||
#[test]
|
#[test]
|
||||||
fn pkg_doc() {
|
fn pkg_doc() {
|
||||||
let doc_a = SPair("first".into(), S2);
|
let doc_a = spair("first", S2);
|
||||||
let id_import = SPair("import".into(), S3);
|
let id_import = spair("import", S3);
|
||||||
let doc_b = SPair("first".into(), S4);
|
let doc_b = spair("first", S4);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
DocText(doc_a),
|
DocText(doc_a),
|
||||||
|
|
||||||
// Some object to place in-between the two
|
// Some object to place in-between the two
|
||||||
|
@ -597,19 +597,19 @@ fn pkg_doc() {
|
||||||
// index.
|
// index.
|
||||||
#[test]
|
#[test]
|
||||||
fn resume_previous_parsing_context() {
|
fn resume_previous_parsing_context() {
|
||||||
let name_foo = SPair("foo".into(), S2);
|
let name_foo = spair("foo", S2);
|
||||||
let name_bar = SPair("bar".into(), S5);
|
let name_bar = spair("bar", S5);
|
||||||
let name_baz = SPair("baz".into(), S6);
|
let name_baz = spair("baz", S6);
|
||||||
let kind = IdentKind::Tpl;
|
let kind = IdentKind::Tpl;
|
||||||
let src = Source::default();
|
let src = Source::default();
|
||||||
|
|
||||||
// We're going to test with opaque objects as if we are the linker.
|
// We're going to test with opaque objects as if we are the linker.
|
||||||
// This is the first parse.
|
// This is the first parse.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
// The first package will reference an identifier from another
|
// The first package will reference an identifier from another
|
||||||
// package.
|
// package.
|
||||||
PkgStart(S1, SPair("/pkg-a".into(), S1)),
|
PkgStart(S1, spair("/pkg-a", S1)),
|
||||||
IdentDep(name_foo, name_bar),
|
IdentDep(name_foo, name_bar),
|
||||||
PkgEnd(S3),
|
PkgEnd(S3),
|
||||||
];
|
];
|
||||||
|
@ -620,11 +620,11 @@ fn resume_previous_parsing_context() {
|
||||||
// This is the token stream for the second parser,
|
// This is the token stream for the second parser,
|
||||||
// which will re-use the above context.
|
// which will re-use the above context.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let toks = vec![
|
let toks = [
|
||||||
// This package will define that identifier,
|
// This package will define that identifier,
|
||||||
// which should also find the identifier having been placed into
|
// which should also find the identifier having been placed into
|
||||||
// the global environment.
|
// the global environment.
|
||||||
PkgStart(S4, SPair("/pkg-b".into(), S4)),
|
PkgStart(S4, spair("/pkg-b", S4)),
|
||||||
IdentDecl(name_bar, kind.clone(), src.clone()),
|
IdentDecl(name_bar, kind.clone(), src.clone()),
|
||||||
|
|
||||||
// This is a third identifier that is unique to this package.
|
// 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>>(
|
pub fn parse_as_pkg_body<I: IntoIterator<Item = Air>>(
|
||||||
toks: I,
|
toks: I,
|
||||||
) -> Parser<Sut, impl Iterator<Item = Air> + Debug>
|
) -> 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
|
where
|
||||||
<I as IntoIterator>::IntoIter: Debug,
|
<I as IntoIterator>::IntoIter: Debug,
|
||||||
{
|
{
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
Sut::parse(
|
iter::once(PkgStart(S1, spair("/incidental-pkg", S1)))
|
||||||
iter::once(PkgStart(S1, SPair("/pkg".into(), S1)))
|
.chain(toks.into_iter())
|
||||||
.chain(toks.into_iter())
|
.chain(iter::once(PkgEnd(S1)))
|
||||||
.chain(iter::once(PkgEnd(S1))),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn asg_from_pkg_body_toks<I: IntoIterator<Item = Air>>(
|
pub(super) fn asg_from_pkg_body_toks<I: IntoIterator<Item = Air>>(
|
||||||
|
|
|
@ -51,6 +51,7 @@ use crate::{
|
||||||
visit::{tree_reconstruction, TreeWalkRel},
|
visit::{tree_reconstruction, TreeWalkRel},
|
||||||
ExprOp,
|
ExprOp,
|
||||||
},
|
},
|
||||||
|
parse::util::spair,
|
||||||
span::UNKNOWN_SPAN,
|
span::UNKNOWN_SPAN,
|
||||||
};
|
};
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
@ -120,9 +121,9 @@ macro_rules! test_scopes {
|
||||||
|
|
||||||
test_scopes! {
|
test_scopes! {
|
||||||
setup {
|
setup {
|
||||||
let pkg_name = SPair("/pkg".into(), S1);
|
let pkg_name = spair("/pkg", S1);
|
||||||
let outer = SPair("outer".into(), S3);
|
let outer = spair("outer", S3);
|
||||||
let inner = SPair("inner".into(), S5);
|
let inner = spair("inner", S5);
|
||||||
}
|
}
|
||||||
|
|
||||||
air {
|
air {
|
||||||
|
@ -166,15 +167,15 @@ test_scopes! {
|
||||||
|
|
||||||
test_scopes! {
|
test_scopes! {
|
||||||
setup {
|
setup {
|
||||||
let pkg_name = SPair("/pkg".into(), S1);
|
let pkg_name = spair("/pkg", S1);
|
||||||
|
|
||||||
let tpl_outer = SPair("_tpl-outer_".into(), S3);
|
let tpl_outer = spair("_tpl-outer_", S3);
|
||||||
let meta_outer = SPair("@param_outer@".into(), S5);
|
let meta_outer = spair("@param_outer@", S5);
|
||||||
let expr_outer = SPair("exprOuter".into(), S8);
|
let expr_outer = spair("exprOuter", S8);
|
||||||
|
|
||||||
let tpl_inner = SPair("_tpl-inner_".into(), S11);
|
let tpl_inner = spair("_tpl-inner_", S11);
|
||||||
let meta_inner = SPair("@param_inner@".into(), S13);
|
let meta_inner = spair("@param_inner@", S13);
|
||||||
let expr_inner = SPair("exprInner".into(), S16);
|
let expr_inner = spair("exprInner", S16);
|
||||||
}
|
}
|
||||||
|
|
||||||
air {
|
air {
|
||||||
|
@ -329,18 +330,18 @@ test_scopes! {
|
||||||
|
|
||||||
test_scopes! {
|
test_scopes! {
|
||||||
setup {
|
setup {
|
||||||
let pkg_name = SPair("/pkg".into(), S1);
|
let pkg_name = spair("/pkg", S1);
|
||||||
|
|
||||||
let tpl_outer = SPair("_tpl-outer_".into(), S3);
|
let tpl_outer = spair("_tpl-outer_", S3);
|
||||||
let tpl_inner = SPair("_tpl-inner_".into(), S9);
|
let tpl_inner = spair("_tpl-inner_", S9);
|
||||||
|
|
||||||
// Note how these have the _same name_.
|
// Note how these have the _same name_.
|
||||||
let meta_name = "@param@".into();
|
let meta_name = "@param@";
|
||||||
let meta_same_a = SPair(meta_name, S5);
|
let meta_same_a = spair(meta_name, S5);
|
||||||
let meta_same_b = SPair(meta_name, S11);
|
let meta_same_b = spair(meta_name, S11);
|
||||||
|
|
||||||
// This one will be used for asserting.
|
// This one will be used for asserting.
|
||||||
let meta_same = SPair(meta_name, S11);
|
let meta_same = spair(meta_name, S11);
|
||||||
}
|
}
|
||||||
|
|
||||||
air {
|
air {
|
||||||
|
@ -433,11 +434,11 @@ test_scopes! {
|
||||||
// From the perspective of the linker (tameld):
|
// From the perspective of the linker (tameld):
|
||||||
test_scopes! {
|
test_scopes! {
|
||||||
setup {
|
setup {
|
||||||
let pkg_a = SPair("/pkg/a".into(), S1);
|
let pkg_a = spair("/pkg/a", S1);
|
||||||
let opaque_a = SPair("opaque_a".into(), S2);
|
let opaque_a = spair("opaque_a", S2);
|
||||||
|
|
||||||
let pkg_b = SPair("/pkg/b".into(), S4);
|
let pkg_b = spair("/pkg/b", S4);
|
||||||
let opaque_b = SPair("opaque_b".into(), S5);
|
let opaque_b = spair("opaque_b", S5);
|
||||||
}
|
}
|
||||||
|
|
||||||
air {
|
air {
|
||||||
|
|
|
@ -26,11 +26,7 @@ use super::{
|
||||||
ir::AirBindableTpl,
|
ir::AirBindableTpl,
|
||||||
AirAggregate, AirAggregateCtx,
|
AirAggregate, AirAggregateCtx,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{fmt::TtQuote, parse::prelude::*, span::Span};
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
|
||||||
parse::prelude::*,
|
|
||||||
span::Span,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Template parser and token aggregator.
|
/// Template parser and token aggregator.
|
||||||
///
|
///
|
||||||
|
@ -179,31 +175,47 @@ impl ParseState for AirTplAggregate {
|
||||||
}
|
}
|
||||||
|
|
||||||
(Toplevel(tpl), AirBind(BindIdent(id))) => ctx
|
(Toplevel(tpl), AirBind(BindIdent(id))) => ctx
|
||||||
.defines(id)
|
.defines_concrete(id)
|
||||||
.and_then(|oi_ident| {
|
.and_then(|oi_ident| {
|
||||||
oi_ident.bind_definition(ctx.asg_mut(), id, tpl.oi())
|
oi_ident.bind_definition(ctx.asg_mut(), id, tpl.oi())
|
||||||
})
|
})
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.transition(Toplevel(tpl.identify(id))),
|
.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))) => {
|
(Toplevel(tpl), AirBind(RefIdent(name))) => {
|
||||||
let tpl_oi = tpl.oi();
|
let tpl_oi = tpl.oi();
|
||||||
let ref_oi = ctx.lookup_lexical_or_missing(name);
|
let ref_oi = ctx.lookup_lexical_or_missing(name);
|
||||||
|
|
||||||
tpl_oi.apply_named_tpl(ctx.asg_mut(), ref_oi, name.span());
|
tpl_oi
|
||||||
|
.apply_named_tpl(ctx.asg_mut(), ref_oi, name.span())
|
||||||
Transition(Toplevel(tpl)).incomplete()
|
.map(|_| ())
|
||||||
|
.transition(Toplevel(tpl))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Toplevel(tpl), AirDoc(DocIndepClause(clause))) => {
|
(Toplevel(tpl), AirDoc(DocIndepClause(clause))) => tpl
|
||||||
tpl.oi().desc_short(ctx.asg_mut(), clause);
|
.oi()
|
||||||
Transition(Toplevel(tpl)).incomplete()
|
.add_desc_short(ctx.asg_mut(), clause)
|
||||||
}
|
.map(|_| ())
|
||||||
|
.transition(Toplevel(tpl)),
|
||||||
|
|
||||||
(Toplevel(tpl), AirDoc(DocText(text))) => {
|
(Toplevel(tpl), AirDoc(DocText(text))) => tpl
|
||||||
tpl.oi().append_doc_text(ctx.asg_mut(), text);
|
.oi()
|
||||||
Transition(Toplevel(tpl)).incomplete()
|
.append_doc_text(ctx.asg_mut(), text)
|
||||||
}
|
.map(|_| ())
|
||||||
|
.transition(Toplevel(tpl)),
|
||||||
|
|
||||||
(Toplevel(tpl), AirTpl(TplEnd(span))) => {
|
(Toplevel(tpl), AirTpl(TplEnd(span))) => {
|
||||||
tpl.close(ctx.asg_mut(), span).transition(Done)
|
tpl.close(ctx.asg_mut(), span).transition(Done)
|
||||||
|
@ -215,12 +227,12 @@ impl ParseState for AirTplAggregate {
|
||||||
// we are effectively discarding the ref and translating
|
// we are effectively discarding the ref and translating
|
||||||
// into a `TplEnd`.
|
// into a `TplEnd`.
|
||||||
match ctx.expansion_oi() {
|
match ctx.expansion_oi() {
|
||||||
Some(oi_target) => {
|
Some(oi_target) => tpl
|
||||||
tpl.oi().expand_into(ctx.asg_mut(), oi_target);
|
.oi()
|
||||||
|
.close(ctx.asg_mut(), span)
|
||||||
Transition(Toplevel(tpl.anonymous_reachable()))
|
.expand_into(ctx.asg_mut(), oi_target)
|
||||||
.incomplete()
|
.map(|_| ())
|
||||||
}
|
.transition(Toplevel(tpl.anonymous_reachable())),
|
||||||
None => Transition(Toplevel(tpl))
|
None => Transition(Toplevel(tpl))
|
||||||
.err(AsgError::InvalidExpansionContext(span)),
|
.err(AsgError::InvalidExpansionContext(span)),
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -117,13 +117,20 @@ pub enum AsgError {
|
||||||
/// delimiter.
|
/// delimiter.
|
||||||
UnbalancedTpl(Span),
|
UnbalancedTpl(Span),
|
||||||
|
|
||||||
/// Attempted to bind an identifier to an object while not in a context
|
/// Attempted to bind a concrete identifier to an object while not in a
|
||||||
/// that can receive an identifier binding.
|
/// context that can receive an identifier binding.
|
||||||
///
|
///
|
||||||
/// Note that the user may encounter an error from a higher-level IR
|
/// Note that the user may encounter an error from a higher-level IR
|
||||||
/// instead of this one.
|
/// instead of this one.
|
||||||
InvalidBindContext(SPair),
|
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
|
/// Attempted to reference an identifier while not in a context that can
|
||||||
/// receive an identifier reference.
|
/// receive an identifier reference.
|
||||||
///
|
///
|
||||||
|
@ -160,8 +167,26 @@ pub enum AsgError {
|
||||||
/// The provided [`Span`] indicates the location of the start of the
|
/// The provided [`Span`] indicates the location of the start of the
|
||||||
/// metavariable definition.
|
/// metavariable definition.
|
||||||
UnexpectedMeta(Span),
|
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 {
|
impl Display for AsgError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
use AsgError::*;
|
use AsgError::*;
|
||||||
|
@ -202,8 +227,20 @@ impl Display for AsgError {
|
||||||
),
|
),
|
||||||
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
|
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
|
||||||
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
|
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
|
||||||
InvalidBindContext(_) => {
|
InvalidBindContext(name) => {
|
||||||
write!(f, "invalid identifier binding context")
|
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) => {
|
InvalidRefContext(ident) => {
|
||||||
write!(
|
write!(
|
||||||
|
@ -231,6 +268,18 @@ impl Display for AsgError {
|
||||||
UnexpectedMeta(_) => {
|
UnexpectedMeta(_) => {
|
||||||
write!(f, "unexpected metavariable definition")
|
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
|
InvalidBindContext(name) => vec![name
|
||||||
.error("an identifier binding is not valid in this context")],
|
.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(
|
InvalidRefContext(ident) => vec![ident.error(
|
||||||
"cannot reference the value of an expression from outside \
|
"cannot reference the value of an expression from outside \
|
||||||
of an expression context",
|
of an expression context",
|
||||||
|
@ -407,6 +475,42 @@ impl Diagnostic for AsgError {
|
||||||
span.help("metavariables are expected to occur in a template context"),
|
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 super::{AsgError, Object, ObjectIndex, ObjectKind};
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
||||||
f::Functor,
|
f::Map,
|
||||||
global,
|
global,
|
||||||
span::Span,
|
span::Span,
|
||||||
};
|
};
|
||||||
|
@ -40,6 +40,9 @@ use petgraph::{
|
||||||
};
|
};
|
||||||
use std::{fmt::Debug, result::Result};
|
use std::{fmt::Debug, result::Result};
|
||||||
|
|
||||||
|
#[cfg(doc)]
|
||||||
|
use object::{ObjectIndexTo, Tpl};
|
||||||
|
|
||||||
pub mod object;
|
pub mod object;
|
||||||
pub mod visit;
|
pub mod visit;
|
||||||
pub mod xmli;
|
pub mod xmli;
|
||||||
|
@ -197,17 +200,24 @@ impl Asg {
|
||||||
///
|
///
|
||||||
/// For more information on how the ASG's ontology is enforced statically,
|
/// For more information on how the ASG's ontology is enforced statically,
|
||||||
/// see [`ObjectRelTo`](object::ObjectRelTo).
|
/// 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,
|
&mut self,
|
||||||
from_oi: impl ObjectIndexRelTo<OB>,
|
from_oi: OA,
|
||||||
to_oi: ObjectIndex<OB>,
|
to_oi: ObjectIndex<OB>,
|
||||||
ctx_span: Option<Span>,
|
ctx_span: Option<Span>,
|
||||||
) {
|
) -> Result<(), AsgError> {
|
||||||
self.graph.add_edge(
|
from_oi.pre_add_edge(self, to_oi, ctx_span).map(|()| {
|
||||||
from_oi.widen().into(),
|
self.graph.add_edge(
|
||||||
to_oi.into(),
|
from_oi.widen().into(),
|
||||||
(from_oi.src_rel_ty(), OB::rel_ty(), ctx_span),
|
to_oi.into(),
|
||||||
);
|
(from_oi.src_rel_ty(), OB::rel_ty(), ctx_span),
|
||||||
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve an object from the graph by [`ObjectIndex`].
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
|
@ -112,12 +112,18 @@
|
||||||
//! Since [`ObjectRel`] narrows into an [`ObjectIndex`],
|
//! Since [`ObjectRel`] narrows into an [`ObjectIndex`],
|
||||||
//! the system will produce runtime panics if there is ever any attempt to
|
//! the system will produce runtime panics if there is ever any attempt to
|
||||||
//! follow an edge to an unexpected [`ObjectKind`].
|
//! 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::{
|
use crate::{
|
||||||
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
||||||
diagnostic_panic,
|
diagnostic_panic,
|
||||||
f::Functor,
|
f::{Map, TryMap},
|
||||||
parse::util::SPair,
|
parse::util::SPair,
|
||||||
span::{Span, UNKNOWN_SPAN},
|
span::{Span, UNKNOWN_SPAN},
|
||||||
};
|
};
|
||||||
|
@ -156,7 +162,7 @@ pub use tpl::Tpl;
|
||||||
/// Often-needed exports for [`ObjectKind`]s.
|
/// Often-needed exports for [`ObjectKind`]s.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{
|
pub use super::{
|
||||||
super::{super::error::AsgError, Asg},
|
super::{super::error::AsgError, Asg, AsgRelMut},
|
||||||
Object, ObjectIndex, ObjectIndexRelTo, ObjectKind, ObjectRel,
|
Object, ObjectIndex, ObjectIndexRelTo, ObjectKind, ObjectRel,
|
||||||
ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable,
|
ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable,
|
||||||
ObjectTreeRelTo,
|
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`].
|
/// The collection of potential objects of [`Object`].
|
||||||
pub trait ObjectInner {
|
pub trait ObjectInner {
|
||||||
$(type $kind;)+
|
$(type $kind;)+
|
||||||
|
@ -363,7 +383,7 @@ object_gen! {
|
||||||
/// A template definition.
|
/// A template definition.
|
||||||
Tpl,
|
Tpl,
|
||||||
|
|
||||||
/// Metasyntactic variable (metavariable).
|
/// Metalinguistic variable (metavariable).
|
||||||
Meta,
|
Meta,
|
||||||
|
|
||||||
/// Documentation.
|
/// 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`].
|
/// Add an edge from `from_oi` to `self` on the provided [`Asg`].
|
||||||
///
|
///
|
||||||
/// An edge can only be added if ontologically valid;
|
/// An edge can only be added if ontologically valid;
|
||||||
/// see [`ObjectRelTo`] for more information.
|
/// see [`ObjectRelTo`] for more information.
|
||||||
///
|
///
|
||||||
/// See also [`Self::add_edge_to`].
|
/// 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,
|
self,
|
||||||
asg: &mut Asg,
|
asg: &mut Asg,
|
||||||
from_oi: OF,
|
from_oi: OA,
|
||||||
ctx_span: Option<Span>,
|
ctx_span: Option<Span>,
|
||||||
) -> Self
|
) -> Result<Self, AsgError>
|
||||||
where
|
where
|
||||||
O: ObjectRelatable,
|
O: ObjectRelatable,
|
||||||
{
|
{
|
||||||
asg.add_edge(from_oi, self, ctx_span);
|
asg.add_edge(from_oi, self, ctx_span).map(|()| self)
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an iterator over the [`ObjectIndex`]es of the outgoing edges
|
/// 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)
|
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
|
/// Resolve the identifier and infallibly map over the resulting
|
||||||
/// [`Object`] narrowed to [`ObjectKind`] `O`,
|
/// [`Object`] narrowed to [`ObjectKind`] `O`,
|
||||||
/// replacing the object on the given [`Asg`].
|
/// 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
|
/// Lift [`Self`] into [`Option`] and [`filter`](Option::filter) based
|
||||||
/// on whether the [`ObjectRelatable::rel_ty`] of [`Self`]'s `O`
|
/// on whether the [`ObjectRelatable::rel_ty`] of [`Self`]'s `O`
|
||||||
/// matches that of `OB`.
|
/// matches that of `OB`.
|
||||||
|
@ -736,12 +821,13 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
||||||
/// objects.
|
/// objects.
|
||||||
/// Forcing objects to be reachable can prevent them from being
|
/// Forcing objects to be reachable can prevent them from being
|
||||||
/// optimized away if they are not used.
|
/// 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
|
where
|
||||||
Root: ObjectRelTo<O>,
|
Root: ObjectRelTo<O>,
|
||||||
{
|
{
|
||||||
asg.root(self.span()).add_edge_to(asg, self, None);
|
asg.root(self.span())
|
||||||
self
|
.add_edge_to(asg, self, None)
|
||||||
|
.map(|_| self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this object has been rooted in the ASG's [`Root`] object.
|
/// Whether this object has been rooted in the ASG's [`Root`] object.
|
||||||
|
@ -810,13 +896,23 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
||||||
/// on the graph,
|
/// on the graph,
|
||||||
/// like common subexpression elimination,
|
/// like common subexpression elimination,
|
||||||
/// in which case it's best not to rely on following edges in reverse.
|
/// 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
|
where
|
||||||
O: ObjectRelFrom<Ident>,
|
O: ObjectRelFrom<Ident>,
|
||||||
{
|
{
|
||||||
self.incoming_edges_filtered(asg)
|
self.incoming_edges_filtered(asg).next()
|
||||||
.map(ObjectIndex::cresolve(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.
|
/// 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.
|
/// simple sentence or as part of a compound sentence.
|
||||||
/// There should only be one such clause for any given object,
|
/// There should only be one such clause for any given object,
|
||||||
/// but that is not enforced here.
|
/// 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
|
where
|
||||||
O: ObjectRelTo<Doc>,
|
O: ObjectRelTo<Doc>,
|
||||||
{
|
{
|
||||||
let oi_doc = asg.create(Doc::new_indep_clause(clause));
|
let oi_doc = asg.create(Doc::new_indep_clause(clause));
|
||||||
self.add_edge_to(asg, oi_doc, None)
|
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> {
|
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 {
|
fn map(self, f: impl FnOnce(Span) -> Span) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self(index, span, ph) => Self(index, f(span), ph),
|
Self(index, span, ph) => Self(index, f(span), ph),
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
|
|
||||||
//! Expressions on the ASG.
|
//! Expressions on the ASG.
|
||||||
|
|
||||||
use super::{prelude::*, Doc, Ident, Tpl};
|
use super::{prelude::*, Doc, Ident, ObjectIndexTo, Tpl};
|
||||||
use crate::{f::Functor, num::Dim, span::Span};
|
use crate::{f::Map, num::Dim, span::Span};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[cfg(doc)]
|
#[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 {
|
fn map(self, f: impl FnOnce(Span) -> Span) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self(op, dim, span) => Self(op, dim, f(span)),
|
Self(op, dim, span) => Self(op, dim, f(span)),
|
||||||
|
@ -233,14 +233,38 @@ impl ObjectIndex<Expr> {
|
||||||
self,
|
self,
|
||||||
asg: &mut Asg,
|
asg: &mut Asg,
|
||||||
expr: Expr,
|
expr: Expr,
|
||||||
) -> ObjectIndex<Expr> {
|
) -> Result<ObjectIndex<Expr>, AsgError> {
|
||||||
let oi_subexpr = asg.create(expr);
|
let oi_subexpr = asg.create(expr);
|
||||||
oi_subexpr.add_edge_from(asg, self, None)
|
oi_subexpr.add_edge_from(asg, self, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reference the value of the expression identified by `oi_ident` as if
|
/// Reference the value of the expression identified by `oi_ident` as if
|
||||||
/// it were a subexpression.
|
/// 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()))
|
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 super::{prelude::*, Expr, Meta, Pkg, Tpl};
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnose::{Annotate, Diagnostic},
|
diagnose::{panic::DiagnosticPanic, Annotate, Diagnostic},
|
||||||
diagnostic_todo,
|
diagnostic_todo,
|
||||||
f::Functor,
|
f::Map,
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
num::{Dim, Dtype},
|
num::{Dim, Dtype},
|
||||||
parse::{util::SPair, Token},
|
parse::{util::SPair, Token},
|
||||||
|
@ -143,6 +143,20 @@ pub enum Ident {
|
||||||
/// itself;
|
/// itself;
|
||||||
/// this is safe since identifiers in TAME are immutable.
|
/// this is safe since identifiers in TAME are immutable.
|
||||||
Transparent(SPair),
|
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 {
|
impl Display for Ident {
|
||||||
|
@ -165,19 +179,28 @@ impl Display for Ident {
|
||||||
Transparent(id) => {
|
Transparent(id) => {
|
||||||
write!(f, "transparent identifier {}", TtQuote::wrap(id))
|
write!(f, "transparent identifier {}", TtQuote::wrap(id))
|
||||||
}
|
}
|
||||||
|
Abstract(_) => {
|
||||||
|
write!(f, "pending identifier (to be named during expansion)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ident {
|
impl Ident {
|
||||||
/// Identifier name.
|
/// Concrete identifier name.
|
||||||
pub fn name(&self) -> SPair {
|
///
|
||||||
|
/// 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 {
|
match self {
|
||||||
Missing(name)
|
Missing(name)
|
||||||
| Opaque(name, ..)
|
| Opaque(name, ..)
|
||||||
| Extern(name, ..)
|
| Extern(name, ..)
|
||||||
| IdentFragment(name, ..)
|
| IdentFragment(name, ..)
|
||||||
| Transparent(name) => *name,
|
| Transparent(name) => Some(*name),
|
||||||
|
|
||||||
|
Abstract(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +211,8 @@ impl Ident {
|
||||||
| Extern(name, ..)
|
| Extern(name, ..)
|
||||||
| IdentFragment(name, ..)
|
| IdentFragment(name, ..)
|
||||||
| Transparent(name) => name.span(),
|
| Transparent(name) => name.span(),
|
||||||
|
|
||||||
|
Abstract(span) => *span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +223,7 @@ impl Ident {
|
||||||
/// [`None`] is returned.
|
/// [`None`] is returned.
|
||||||
pub fn kind(&self) -> Option<&IdentKind> {
|
pub fn kind(&self) -> Option<&IdentKind> {
|
||||||
match self {
|
match self {
|
||||||
Missing(_) | Transparent(_) => None,
|
Missing(_) | Transparent(_) | Abstract(_) => None,
|
||||||
|
|
||||||
Opaque(_, kind, _)
|
Opaque(_, kind, _)
|
||||||
| Extern(_, kind, _)
|
| Extern(_, kind, _)
|
||||||
|
@ -213,7 +238,7 @@ impl Ident {
|
||||||
/// [`None`] is returned.
|
/// [`None`] is returned.
|
||||||
pub fn src(&self) -> Option<&Source> {
|
pub fn src(&self) -> Option<&Source> {
|
||||||
match self {
|
match self {
|
||||||
Missing(_) | Extern(_, _, _) | Transparent(_) => None,
|
Missing(_) | Extern(_, _, _) | Transparent(_) | Abstract(_) => None,
|
||||||
|
|
||||||
Opaque(_, _, src) | IdentFragment(_, _, src, _) => Some(src),
|
Opaque(_, _, src) | IdentFragment(_, _, src, _) => Some(src),
|
||||||
}
|
}
|
||||||
|
@ -225,9 +250,11 @@ impl Ident {
|
||||||
/// [`None`] is returned.
|
/// [`None`] is returned.
|
||||||
pub fn fragment(&self) -> Option<FragmentText> {
|
pub fn fragment(&self) -> Option<FragmentText> {
|
||||||
match self {
|
match self {
|
||||||
Missing(_) | Opaque(_, _, _) | Extern(_, _, _) | Transparent(_) => {
|
Missing(_)
|
||||||
None
|
| Opaque(_, _, _)
|
||||||
}
|
| Extern(_, _, _)
|
||||||
|
| Transparent(_)
|
||||||
|
| Abstract(_) => None,
|
||||||
|
|
||||||
IdentFragment(_, _, _, text) => Some(*text),
|
IdentFragment(_, _, _, text) => Some(*text),
|
||||||
}
|
}
|
||||||
|
@ -244,6 +271,17 @@ impl Ident {
|
||||||
Missing(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.
|
/// Attempt to redeclare an identifier with additional information.
|
||||||
///
|
///
|
||||||
/// If an existing identifier is an [`Ident::Extern`],
|
/// If an existing identifier is an [`Ident::Extern`],
|
||||||
|
@ -357,12 +395,24 @@ impl Ident {
|
||||||
|
|
||||||
Missing(name) => Ok(Opaque(name.overwrite(span), kind, src)),
|
Missing(name) => Ok(Opaque(name.overwrite(span), kind, src)),
|
||||||
|
|
||||||
// TODO: Remove guards and catch-all for exhaustiveness check.
|
// TODO: Remove guards for better exhaustiveness check
|
||||||
_ => {
|
Opaque(name, _, _)
|
||||||
let err = TransitionError::Redeclare(self.name(), span);
|
| IdentFragment(name, _, _, _)
|
||||||
|
| Transparent(name) => {
|
||||||
|
let err = TransitionError::Redeclare(name, span);
|
||||||
Err((self, err))
|
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,
|
/// At present,
|
||||||
/// both [`Ident::Missing`] and [`Ident::Extern`] are
|
/// both [`Ident::Missing`] and [`Ident::Extern`] are
|
||||||
/// considered to be unresolved.
|
/// considered to be unresolved.
|
||||||
pub fn resolved(&self) -> Result<&Ident, UnresolvedError> {
|
pub fn resolved(&self) -> Result<(&Ident, SPair), UnresolvedError> {
|
||||||
match self {
|
match self {
|
||||||
Missing(name) => Err(UnresolvedError::Missing(*name)),
|
Missing(name) => Err(UnresolvedError::Missing(*name)),
|
||||||
|
|
||||||
|
@ -389,7 +439,11 @@ impl Ident {
|
||||||
Err(UnresolvedError::Extern(*name, kind.clone()))
|
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,
|
kind: IdentKind,
|
||||||
src: Source,
|
src: Source,
|
||||||
) -> TransitionResult<Ident> {
|
) -> TransitionResult<Ident> {
|
||||||
match self.kind() {
|
match self {
|
||||||
None => Ok(Extern(self.name().overwrite(span), kind, src)),
|
Missing(name) | Transparent(name) => {
|
||||||
Some(cur_kind) => {
|
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 {
|
if cur_kind != &kind {
|
||||||
let err = TransitionError::ExternResolution(
|
let err = TransitionError::ExternResolution(
|
||||||
self.name(),
|
name,
|
||||||
cur_kind.clone(),
|
cur_kind.clone(),
|
||||||
(kind, span),
|
(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
|
/// Note, however, that an identifier's fragment may be cleared under
|
||||||
/// certain circumstances (such as symbol overrides),
|
/// certain circumstances (such as symbol overrides),
|
||||||
/// making way for a new fragment to be set.
|
/// 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> {
|
pub fn set_fragment(self, text: FragmentText) -> TransitionResult<Ident> {
|
||||||
match self {
|
match self {
|
||||||
Opaque(sym, kind, src) => Ok(IdentFragment(sym, kind, src, text)),
|
Opaque(sym, kind, src) => Ok(IdentFragment(sym, kind, src, text)),
|
||||||
|
@ -457,6 +527,9 @@ impl Ident {
|
||||||
IdentFragment(_, _, ref src, ..) if src.override_ => Ok(self),
|
IdentFragment(_, _, ref src, ..) if src.override_ => Ok(self),
|
||||||
|
|
||||||
// These represent the prologue and epilogue of maps.
|
// These represent the prologue and epilogue of maps.
|
||||||
|
//
|
||||||
|
// TODO: Is this arm still needed after having eliminated their
|
||||||
|
// fragments from xmlo files?
|
||||||
IdentFragment(
|
IdentFragment(
|
||||||
_,
|
_,
|
||||||
IdentKind::MapHead
|
IdentKind::MapHead
|
||||||
|
@ -466,10 +539,16 @@ impl Ident {
|
||||||
..,
|
..,
|
||||||
) => Ok(self),
|
) => Ok(self),
|
||||||
|
|
||||||
_ => {
|
Missing(name)
|
||||||
let name = self.name();
|
| Extern(name, _, _)
|
||||||
|
| IdentFragment(name, _, _, _)
|
||||||
|
| Transparent(name) => {
|
||||||
Err((self, TransitionError::BadFragmentDest(name)))
|
Err((self, TransitionError::BadFragmentDest(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Abstract(span) => {
|
||||||
|
Err((self, TransitionError::AbstractFragmentDest(span)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,6 +582,16 @@ pub enum TransitionError {
|
||||||
///
|
///
|
||||||
/// See [`Ident::set_fragment`].
|
/// See [`Ident::set_fragment`].
|
||||||
BadFragmentDest(SPair),
|
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 {
|
impl std::fmt::Display for TransitionError {
|
||||||
|
@ -540,7 +629,15 @@ impl std::fmt::Display for TransitionError {
|
||||||
|
|
||||||
BadFragmentDest(name) => {
|
BadFragmentDest(name) => {
|
||||||
write!(fmt, "bad fragment destination: {}", TtQuote::wrap(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."),
|
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
|
/// Expected identifier has not yet been resolved with a concrete
|
||||||
/// definition.
|
/// definition.
|
||||||
Extern(SPair, IdentKind),
|
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 {
|
impl std::fmt::Display for UnresolvedError {
|
||||||
|
@ -646,6 +771,8 @@ impl std::fmt::Display for UnresolvedError {
|
||||||
TtQuote::wrap(name),
|
TtQuote::wrap(name),
|
||||||
TtQuote::wrap(kind),
|
TtQuote::wrap(kind),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
Abstract(_) => write!(fmt, "abstract (unexpanded) identifier"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -684,6 +811,16 @@ impl Diagnostic for UnresolvedError {
|
||||||
" later provide a concrete definition for it."
|
" 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;
|
/// This is a legacy feature expected to be removed in the future;
|
||||||
/// see [`ObjectRel::can_recurse`] for more information.
|
/// see [`ObjectRel::can_recurse`] for more information.
|
||||||
Ident -> {
|
Ident -> {
|
||||||
tree Ident,
|
// Could represent an opaque dependency or an abstract identifier's
|
||||||
|
// metavariable reference.
|
||||||
|
dyn Ident,
|
||||||
|
|
||||||
tree Expr,
|
tree Expr,
|
||||||
tree Tpl,
|
tree Tpl,
|
||||||
tree Meta,
|
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`.
|
// We are okay to proceed to add an edge to the `definition`.
|
||||||
// Discard the original span
|
// Discard the original span
|
||||||
// (which is the location of the first reference _to_ this
|
// (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.
|
// and use the newly provided `id` and its span.
|
||||||
Missing(_) => Ok(Transparent(id)),
|
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.
|
/// Set the fragment associated with a concrete identifier.
|
||||||
|
@ -1159,14 +1316,94 @@ impl ObjectIndex<Ident> {
|
||||||
self.incoming_edges_filtered(asg).next()
|
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`.
|
/// Declare that `oi_dep` is an opaque dependency of `self`.
|
||||||
pub fn add_opaque_dep(
|
pub fn add_opaque_dep(
|
||||||
&self,
|
&self,
|
||||||
asg: &mut Asg,
|
asg: &mut Asg,
|
||||||
oi_dep: ObjectIndex<Ident>,
|
oi_dep: ObjectIndex<Ident>,
|
||||||
) -> Self {
|
) -> Result<Self, AsgError> {
|
||||||
self.add_edge_to(asg, oi_dep, None)
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -30,20 +30,20 @@ fn ident_name() {
|
||||||
let name = "name".into();
|
let name = "name".into();
|
||||||
let spair = SPair(name, S1);
|
let spair = SPair(name, S1);
|
||||||
|
|
||||||
assert_eq!(spair, Ident::Missing(spair).name());
|
assert_eq!(Some(spair), Ident::Missing(spair).name());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spair,
|
Some(spair),
|
||||||
Ident::Opaque(spair, IdentKind::Meta, Source::default()).name()
|
Ident::Opaque(spair, IdentKind::Meta, Source::default()).name()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spair,
|
Some(spair),
|
||||||
Ident::Extern(spair, IdentKind::Meta, Source::default()).name()
|
Ident::Extern(spair, IdentKind::Meta, Source::default()).name()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spair,
|
Some(spair),
|
||||||
Ident::IdentFragment(
|
Ident::IdentFragment(
|
||||||
spair,
|
spair,
|
||||||
IdentKind::Meta,
|
IdentKind::Meta,
|
||||||
|
@ -182,7 +182,10 @@ fn resolved_on_ident() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.resolved()
|
.resolved()
|
||||||
.unwrap(),
|
.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!(
|
assert_eq!(
|
||||||
ident.set_fragment(text.clone()).unwrap().resolved(),
|
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.
|
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
|
||||||
//
|
//
|
||||||
|
@ -17,25 +17,33 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Metasyntactic variables on the ASG.
|
//! Metalinguistic objects on the ASG.
|
||||||
//!
|
//!
|
||||||
//! Metasyntactic variables
|
//! Metalinguistic variables[^w],
|
||||||
//! (sometimes called "metavariables" herein for short)
|
//! called "metavariables" for short,
|
||||||
//! have historically been a feature of the template system.
|
//! have historically been a feature of the template system.
|
||||||
//! The canonical metavariable is the template parameter.
|
//! 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::{
|
use crate::{
|
||||||
diagnose::Annotate,
|
diagnose::Annotate,
|
||||||
diagnostic_todo,
|
diagnostic_todo,
|
||||||
f::Functor,
|
f::Map,
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
parse::util::SPair,
|
parse::{util::SPair, Token},
|
||||||
span::Span,
|
span::Span,
|
||||||
};
|
};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
/// Metasyntactic variable (metavariable).
|
/// Metalinguistic variable (metavariable).
|
||||||
///
|
///
|
||||||
/// A metavariable is a lexical construct.
|
/// A metavariable is a lexical construct.
|
||||||
/// Its value is a lexeme that represents an [`Ident`],
|
/// 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.
|
/// the symbol representing that identifier then acts as a metavariable.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Meta {
|
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),
|
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),
|
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 {
|
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 {
|
impl From<&Meta> for Span {
|
||||||
|
@ -105,10 +144,10 @@ impl Display for Meta {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Required(_) => {
|
Self::Required(_) => {
|
||||||
write!(f, "metasyntactic parameter with required value")
|
write!(f, "metalinguistic parameter with required value")
|
||||||
}
|
}
|
||||||
Self::ConcatList(_) => {
|
Self::ConcatList(_) => {
|
||||||
write!(f, "metasyntactic concatenation list")
|
write!(f, "metalinguistic concatenation list")
|
||||||
}
|
}
|
||||||
Self::Lexeme(_, spair) => {
|
Self::Lexeme(_, spair) => {
|
||||||
write!(f, "lexeme {}", TtQuote::wrap(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 {
|
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
|
||||||
match self {
|
match self {
|
||||||
Self::Required(span) => Self::Required(f(span)),
|
Self::Required(span) => Self::Required(f(span)),
|
||||||
|
@ -131,14 +170,83 @@ object_rel! {
|
||||||
/// Metavariables contain lexical data and references to other
|
/// Metavariables contain lexical data and references to other
|
||||||
/// metavariables.
|
/// metavariables.
|
||||||
Meta -> {
|
Meta -> {
|
||||||
tree Meta, // TODO: do we need tree?
|
// References to other metavariables
|
||||||
|
// (e.g. `<param-value>` in XML-based sources).
|
||||||
cross Ident,
|
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> {
|
impl ObjectIndex<Meta> {
|
||||||
pub fn assign_lexeme(self, asg: &mut Asg, lexeme: SPair) -> Self {
|
/// Append a lexeme to this metavariable.
|
||||||
self.map_obj(asg, |meta| meta.assign_lexeme(lexeme))
|
///
|
||||||
|
/// 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 {
|
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 super::{prelude::*, Doc, Ident, Tpl};
|
||||||
use crate::{
|
use crate::{
|
||||||
f::Functor,
|
f::Map,
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
parse::{util::SPair, Token},
|
parse::{util::SPair, Token},
|
||||||
span::Span,
|
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 {
|
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
|
||||||
match self {
|
match self {
|
||||||
Self(span, path) => Self(f(span), path),
|
Self(span, path) => Self(f(span), path),
|
||||||
|
@ -134,11 +134,15 @@ impl ObjectIndex<Pkg> {
|
||||||
let parent = self.resolve(asg);
|
let parent = self.resolve(asg);
|
||||||
let oi_import = asg.create(Pkg::new_imported(parent, namespec)?);
|
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.
|
/// 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));
|
let oi_doc = asg.create(Doc::new_text(text));
|
||||||
self.add_edge_to(asg, oi_doc, None)
|
self.add_edge_to(asg, oi_doc, None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,15 @@
|
||||||
//! See (parent module)[super] for more information.
|
//! See (parent module)[super] for more information.
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Doc, Expr, Ident, Meta, Object, ObjectIndex, ObjectKind, OiPairObjectInner,
|
Doc, Expr, Ident, Meta, Object, ObjectIndex, ObjectIndexRefined,
|
||||||
Pkg, Root,
|
ObjectKind, OiPairObjectInner, Pkg, Root,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
asg::{graph::object::Tpl, Asg},
|
asg::{
|
||||||
f::Functor,
|
graph::{object::Tpl, AsgRelMut, ProposedRel},
|
||||||
|
Asg, AsgError,
|
||||||
|
},
|
||||||
|
f::Map,
|
||||||
span::Span,
|
span::Span,
|
||||||
};
|
};
|
||||||
use std::{fmt::Display, marker::PhantomData};
|
use std::{fmt::Display, marker::PhantomData};
|
||||||
|
@ -55,7 +58,7 @@ macro_rules! object_rel {
|
||||||
(
|
(
|
||||||
$(#[$attr:meta])+
|
$(#[$attr:meta])+
|
||||||
$from:ident -> {
|
$from:ident -> {
|
||||||
$($ety:ident $kind:ident,)*
|
$($ety:ident $kind:ident $({$($impl:tt)*})?,)*
|
||||||
}
|
}
|
||||||
$(can_recurse($rec_obj:ident) if $rec_expr:expr)?
|
$(can_recurse($rec_obj:ident) if $rec_expr:expr)?
|
||||||
) => {paste::paste! {
|
) => {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.
|
// Static edge types.
|
||||||
|
@ -270,12 +284,47 @@ impl<S> DynObjectRel<S, ObjectIndex<Object>> {
|
||||||
/// Attempt to narrow the target into the [`ObjectRel`] of `O`.
|
/// Attempt to narrow the target into the [`ObjectRel`] of `O`.
|
||||||
///
|
///
|
||||||
/// See [`ObjectRelatable::new_rel_dyn`] for more information.
|
/// 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>(
|
pub fn narrow_target<O: ObjectKind + ObjectRelatable>(
|
||||||
&self,
|
&self,
|
||||||
) -> Option<O::Rel> {
|
) -> Option<O::Rel> {
|
||||||
O::new_rel_dyn(self.target_ty(), *self.target())
|
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
|
/// Attempt to convert [`Self`] into an [`ObjectIndex`] with an
|
||||||
/// [`ObjectKind`] of type `O`.
|
/// [`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>;
|
type Target = DynObjectRel<U, V>;
|
||||||
|
|
||||||
fn map(self, f: impl FnOnce((S, T)) -> (U, V)) -> Self::Target {
|
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
|
/// statically analyzed by the type system to ensure that they only
|
||||||
/// construct graphs that adhere to this schema.
|
/// construct graphs that adhere to this schema.
|
||||||
pub trait ObjectRelTo<OB: ObjectKind + ObjectRelatable> =
|
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`].
|
/// Reverse of [`ObjectRelTo`].
|
||||||
///
|
///
|
||||||
|
@ -791,39 +841,36 @@ pub trait ObjectIndexRelTo<OB: ObjectRelatable>: Sized + Clone + Copy {
|
||||||
/// See [`ObjectIndex::widen`] for more information.
|
/// See [`ObjectIndex::widen`] for more information.
|
||||||
fn widen(&self) -> ObjectIndex<Object>;
|
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
|
/// This gives the object ownership over the edges that are created,
|
||||||
/// it may be related to `OB`,
|
/// in addition to the static guarantees provided by
|
||||||
/// this method will only permit edges to `OB`;
|
/// [`ObjectIndexRelTo`].
|
||||||
/// nothing else about the inner object is statically known.
|
/// Since [`ObjectIndexRelTo` supports dynamic source objects,
|
||||||
/// To create edges to other types of objects,
|
/// this allows calling code to be written in a concise manner that is
|
||||||
/// and for more information about this operation
|
/// agnostic to the source type,
|
||||||
/// (including `ctx_span`),
|
/// without sacrificing edge ownership.
|
||||||
/// see [`ObjectIndex::add_edge_to`].
|
///
|
||||||
fn add_edge_to(
|
/// For more information,
|
||||||
self,
|
/// 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,
|
asg: &mut Asg,
|
||||||
to_oi: ObjectIndex<OB>,
|
to_oi: ObjectIndex<OB>,
|
||||||
ctx_span: Option<Span>,
|
ctx_span: Option<Span>,
|
||||||
) -> Self {
|
) -> Result<(), AsgError>;
|
||||||
asg.add_edge(self, to_oi, ctx_span);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether an edge exists from `self` to `to_oi`.
|
/// Check whether an edge exists from `self` to `to_oi`.
|
||||||
fn has_edge_to(&self, asg: &Asg, to_oi: ObjectIndex<OB>) -> bool {
|
fn has_edge_to(&self, asg: &Asg, to_oi: ObjectIndex<OB>) -> bool {
|
||||||
asg.has_edge(*self, to_oi)
|
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`
|
/// Iterate over the [`ObjectIndex`]es of the outgoing edges of `self`
|
||||||
/// that match the [`ObjectKind`] `OB`.
|
/// that match the [`ObjectKind`] `OB`.
|
||||||
///
|
///
|
||||||
|
@ -875,8 +922,10 @@ pub trait ObjectIndexRelTo<OB: ObjectRelatable>: Sized + Clone + Copy {
|
||||||
Self: ObjectIndexRelTo<Ident>,
|
Self: ObjectIndexRelTo<Ident>,
|
||||||
{
|
{
|
||||||
// Rust fails to infer OB with `self.edges_rel_to` as of 2023-03
|
// Rust fails to infer OB with `self.edges_rel_to` as of 2023-03
|
||||||
ObjectIndexRelTo::<Ident>::edges_rel_to(self, asg)
|
ObjectIndexRelTo::<Ident>::edges_rel_to(self, asg).find(|oi| {
|
||||||
.find(|oi| oi.resolve(asg).name().symbol() == name.symbol())
|
oi.resolve(asg).name().map(|name| name.symbol())
|
||||||
|
== Some(name.symbol())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -892,6 +941,22 @@ where
|
||||||
fn widen(&self) -> ObjectIndex<Object> {
|
fn widen(&self) -> ObjectIndex<Object> {
|
||||||
ObjectIndex::<O>::widen(*self)
|
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> {
|
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> {
|
fn widen(&self) -> ObjectIndex<Object> {
|
||||||
*self.as_ref()
|
*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> {
|
impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexToTree<OB> {
|
||||||
|
@ -918,6 +1013,17 @@ impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexToTree<OB> {
|
||||||
Self(oito) => oito.widen(),
|
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> {
|
impl<OB: ObjectRelatable> From<ObjectIndexTo<OB>> for ObjectIndex<Object> {
|
||||||
|
|
|
@ -66,7 +66,7 @@ impl ObjectIndex<Root> {
|
||||||
&self,
|
&self,
|
||||||
asg: &mut Asg,
|
asg: &mut Asg,
|
||||||
oi: ObjectIndex<Ident>,
|
oi: ObjectIndex<Ident>,
|
||||||
) -> ObjectIndex<Ident> {
|
) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||||
oi.add_edge_from(asg, *self, None)
|
oi.add_edge_from(asg, *self, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,35 +22,192 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::{prelude::*, Doc, Expr, Ident};
|
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.
|
/// Template with associated name.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Tpl(Span);
|
pub struct Tpl(Span, TplShape);
|
||||||
|
|
||||||
impl Tpl {
|
impl Tpl {
|
||||||
|
pub fn new(span: Span) -> Self {
|
||||||
|
Self(span, TplShape::default())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn span(&self) -> Span {
|
pub fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
Self(span) => *span,
|
Self(span, _) => *span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(span: Span) -> Self {
|
pub fn shape(&self) -> TplShape {
|
||||||
Self(span)
|
match self {
|
||||||
|
Self(_, shape) => *shape,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Functor<Span> for Tpl {
|
impl_mono_map! {
|
||||||
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
|
Span => Tpl(@, shape),
|
||||||
match self {
|
TplShape => Tpl(span, @),
|
||||||
Self(span) => Self(f(span)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Tpl {
|
impl Display for Tpl {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
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 -> {
|
Tpl -> {
|
||||||
// Expressions must be able to be anonymous to allow templates in
|
// Expressions must be able to be anonymous to allow templates in
|
||||||
// any `Expr` context.
|
// 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
|
// Identifiers are used for both references and identifiers that
|
||||||
// will expand into an application site.
|
// will expand into an application site.
|
||||||
dyn Ident,
|
dyn Ident,
|
||||||
|
|
||||||
// Template application.
|
// 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
|
// Short template description and arbitrary documentation to be
|
||||||
// expanded into the application site.
|
// expanded into the application site.
|
||||||
|
@ -76,13 +262,23 @@ object_rel! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectIndex<Tpl> {
|
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.
|
/// Attempt to complete a template definition.
|
||||||
///
|
///
|
||||||
/// This updates the span of the template to encompass the entire
|
/// This updates the span of the template to encompass the entire
|
||||||
/// definition.
|
/// definition.
|
||||||
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
|
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
|
||||||
self.map_obj(asg, |tpl| {
|
self.map_obj(asg, |tpl| {
|
||||||
tpl.map(|open_span| {
|
tpl.map(|open_span: Span| {
|
||||||
open_span.merge(close_span).unwrap_or(open_span)
|
open_span.merge(close_span).unwrap_or(open_span)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -98,7 +294,7 @@ impl ObjectIndex<Tpl> {
|
||||||
asg: &mut Asg,
|
asg: &mut Asg,
|
||||||
oi_apply: ObjectIndex<Ident>,
|
oi_apply: ObjectIndex<Ident>,
|
||||||
ref_span: Span,
|
ref_span: Span,
|
||||||
) -> Self {
|
) -> Result<Self, AsgError> {
|
||||||
self.add_edge_to(asg, oi_apply, Some(ref_span))
|
self.add_edge_to(asg, oi_apply, Some(ref_span))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,13 +312,17 @@ impl ObjectIndex<Tpl> {
|
||||||
self,
|
self,
|
||||||
asg: &mut Asg,
|
asg: &mut Asg,
|
||||||
oi_target_parent: OP,
|
oi_target_parent: OP,
|
||||||
) -> Self {
|
) -> Result<Self, AsgError> {
|
||||||
self.add_edge_from(asg, oi_target_parent, None)
|
self.add_edge_from(asg, oi_target_parent, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arbitrary text serving as documentation in a literate style,
|
/// Arbitrary text serving as documentation in a literate style,
|
||||||
/// to be expanded into the application site.
|
/// 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));
|
let oi_doc = asg.create(Doc::new_text(text));
|
||||||
self.add_edge_to(asg, oi_doc, None)
|
self.add_edge_to(asg, oi_doc, None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
//! This is a [depth-first search][w-depth-first-search]
|
//! This is a [depth-first search][w-depth-first-search]
|
||||||
//! visiting all nodes that are _reachable_ from the graph root
|
//! visiting all nodes that are _reachable_ from the graph root
|
||||||
//! (see [`Asg::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
|
//! and may be emitted more than once if
|
||||||
//! (a) they are the destination of cross edges or
|
//! (a) they are the destination of cross edges or
|
||||||
//! (b) they are shared between trees
|
//! (b) they are shared between trees
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
//!
|
//!
|
||||||
//! Depth Tracking
|
//! 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
|
//! [`Depth`] representing the length of the current path relative to the
|
||||||
//! [`Asg`] root.
|
//! [`Asg`] root.
|
||||||
//! Since the ASG root is never emitted,
|
//! Since the ASG root is never emitted,
|
||||||
|
@ -135,11 +135,34 @@
|
||||||
//! because the [`Depth`] represents the current _path_,
|
//! because the [`Depth`] represents the current _path_,
|
||||||
//! the same [`ObjectIndex`] may be emitted multiple times with different
|
//! the same [`ObjectIndex`] may be emitted multiple times with different
|
||||||
//! [`Depth`]s.
|
//! [`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::{
|
use crate::{
|
||||||
|
asg::graph::object::ObjectTy,
|
||||||
parse::{self, Token},
|
parse::{self, Token},
|
||||||
span::{Span, UNKNOWN_SPAN},
|
span::{Span, UNKNOWN_SPAN},
|
||||||
};
|
};
|
||||||
|
@ -149,7 +172,9 @@ use crate::{
|
||||||
pub use crate::xir::flat::Depth;
|
pub use crate::xir::flat::Depth;
|
||||||
|
|
||||||
#[cfg(doc)]
|
#[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
|
/// Produce an iterator suitable for reconstructing a source tree based on
|
||||||
/// the contents of the [`Asg`].
|
/// the contents of the [`Asg`].
|
||||||
|
@ -160,7 +185,9 @@ use super::super::object::ObjectRel;
|
||||||
///
|
///
|
||||||
/// See the [module-level documentation](super) for important information
|
/// See the [module-level documentation](super) for important information
|
||||||
/// about this traversal.
|
/// about this traversal.
|
||||||
pub fn tree_reconstruction(asg: &Asg) -> TreePreOrderDfs {
|
pub fn tree_reconstruction(
|
||||||
|
asg: &Asg,
|
||||||
|
) -> TreePreOrderDfs<SourceCompatibleTreeEdgeOrder> {
|
||||||
TreePreOrderDfs::new(asg)
|
TreePreOrderDfs::new(asg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,11 +197,11 @@ pub fn tree_reconstruction(asg: &Asg) -> TreePreOrderDfs {
|
||||||
/// _it does not track visited nodes_,
|
/// _it does not track visited nodes_,
|
||||||
/// relying instead on the ontology and recognition of cross edges to
|
/// relying instead on the ontology and recognition of cross edges to
|
||||||
/// produce the intended spanning tree.
|
/// 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.
|
/// more than once.
|
||||||
///
|
///
|
||||||
/// See [`tree_reconstruction`] for more information.
|
/// See [`tree_reconstruction`] for more information.
|
||||||
pub struct TreePreOrderDfs<'a> {
|
pub struct TreePreOrderDfs<'a, O: TreeEdgeOrder> {
|
||||||
/// Reference [`Asg`].
|
/// Reference [`Asg`].
|
||||||
///
|
///
|
||||||
/// Holding a reference to the [`Asg`] allows us to serve conveniently
|
/// 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.
|
/// The traversal ends once the stack becomes empty.
|
||||||
stack: Vec<(DynObjectRel, Depth)>,
|
stack: Vec<(DynObjectRel, Depth)>,
|
||||||
|
|
||||||
|
_phantom: PhantomData<O>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initial size of the DFS stack for [`TreePreOrderDfs`].
|
/// Initial size of the DFS stack for [`TreePreOrderDfs`].
|
||||||
|
@ -196,34 +225,40 @@ pub struct TreePreOrderDfs<'a> {
|
||||||
/// TODO: Derive a heuristic from our systems.
|
/// TODO: Derive a heuristic from our systems.
|
||||||
const TREE_INITIAL_STACK_SIZE: usize = 8;
|
const TREE_INITIAL_STACK_SIZE: usize = 8;
|
||||||
|
|
||||||
impl<'a> TreePreOrderDfs<'a> {
|
impl<'a, O: TreeEdgeOrder> TreePreOrderDfs<'a, O> {
|
||||||
fn new(asg: &'a Asg) -> Self {
|
fn new(asg: &'a Asg) -> Self {
|
||||||
let span = UNKNOWN_SPAN;
|
let span = UNKNOWN_SPAN;
|
||||||
|
|
||||||
let mut dfs = Self {
|
let mut dfs = Self {
|
||||||
asg,
|
asg,
|
||||||
stack: Vec::with_capacity(TREE_INITIAL_STACK_SIZE),
|
stack: Vec::with_capacity(TREE_INITIAL_STACK_SIZE),
|
||||||
|
_phantom: PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
let root = asg.root(span);
|
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
|
dfs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_edges_of(&mut self, oi: ObjectIndex<Object>, depth: Depth) {
|
fn push_edges_of(&mut self, rel: &DynObjectRel, depth: Depth) {
|
||||||
self.asg
|
O::push_edges_of(self.asg, rel, depth, &mut self.stack)
|
||||||
.edges_dyn(oi)
|
|
||||||
.map(|rel| (rel, depth.child_depth()))
|
|
||||||
.collect_into(&mut self.stack);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for TreePreOrderDfs<'a> {
|
impl<'a, O: TreeEdgeOrder> Iterator for TreePreOrderDfs<'a, O> {
|
||||||
type Item = TreeWalkRel;
|
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.
|
/// see [`tree_reconstruction`] for more information.
|
||||||
///
|
///
|
||||||
/// Each item contains a corresponding [`Depth`],
|
/// 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
|
/// This depth is the only way to derive the tree structure from this
|
||||||
/// iterator.
|
/// iterator.
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
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()?;
|
let (rel, depth) = self.stack.pop()?;
|
||||||
|
|
||||||
// We want to output information about references to other trees,
|
// We want to output information about references to other trees,
|
||||||
// but we must not traverse into them.
|
// but we must not traverse into them.
|
||||||
if !rel.is_cross_edge() {
|
if !rel.is_cross_edge() {
|
||||||
self.push_edges_of(*rel.target(), depth);
|
self.push_edges_of(&rel, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(TreeWalkRel(rel, depth))
|
Some(TreeWalkRel(rel, depth))
|
||||||
|
@ -283,5 +323,196 @@ impl Token for TreeWalkRel {
|
||||||
|
|
||||||
impl parse::Object 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)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
|
@ -24,8 +24,11 @@ use crate::{
|
||||||
graph::object::ObjectRelTy,
|
graph::object::ObjectRelTy,
|
||||||
ExprOp,
|
ExprOp,
|
||||||
},
|
},
|
||||||
f::Functor,
|
f::Map,
|
||||||
parse::{util::SPair, ParseState},
|
parse::{
|
||||||
|
util::{spair, SPair},
|
||||||
|
ParseState,
|
||||||
|
},
|
||||||
span::{dummy::*, Span},
|
span::{dummy::*, Span},
|
||||||
};
|
};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -204,21 +207,21 @@ fn traverses_ontological_tree_tpl_apply() {
|
||||||
PkgStart(S1, SPair("/pkg".into(), S1)),
|
PkgStart(S1, SPair("/pkg".into(), S1)),
|
||||||
// The template that will be applied.
|
// The template that will be applied.
|
||||||
TplStart(S2),
|
TplStart(S2),
|
||||||
BindIdent(id_tpl),
|
BindIdent(id_tpl), // <-,
|
||||||
|
// |
|
||||||
// This test is light for now,
|
// This test is light for now, // |
|
||||||
// until we develop the ASG further.
|
// until we develop the ASG further. // |
|
||||||
TplEnd(S4),
|
TplEnd(S4), // |
|
||||||
|
// |
|
||||||
// Apply the above template.
|
// Apply the above template. // |
|
||||||
TplStart(S5),
|
TplStart(S5), // |
|
||||||
RefIdent(ref_tpl),
|
RefIdent(ref_tpl), // |
|
||||||
|
// |
|
||||||
MetaStart(S7),
|
MetaStart(S7), // |
|
||||||
BindIdent(id_param),
|
BindIdent(id_param), // |
|
||||||
MetaLexeme(value_param),
|
MetaLexeme(value_param), // |
|
||||||
MetaEnd(S10),
|
MetaEnd(S10), // |
|
||||||
TplEndRef(S11), // notice the `Ref` at the end
|
TplEndRef(S11), // notice the `Ref` at the end --'
|
||||||
PkgEnd(S12),
|
PkgEnd(S12),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -234,9 +237,13 @@ fn traverses_ontological_tree_tpl_apply() {
|
||||||
(d(Pkg, Ident, m(S1, S12), S3, None ), Depth(2)),
|
(d(Pkg, Ident, m(S1, S12), S3, None ), Depth(2)),
|
||||||
(d(Ident, Tpl, S3, m(S2, S4), None ), Depth(3)),
|
(d(Ident, Tpl, S3, m(S2, S4), None ), Depth(3)),
|
||||||
(d(Pkg, Tpl, m(S1, S12), m(S5, S11), None ), Depth(2)),
|
(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(Tpl, Ident, m(S5, S11), S8, None ), Depth(3)),
|
||||||
(d(Ident, Meta, S8, m(S7, S10), None ), Depth(4)),
|
(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),
|
tree_reconstruction_report(toks),
|
||||||
);
|
);
|
||||||
|
@ -328,3 +335,147 @@ fn traverses_ontological_tree_tpl_within_template() {
|
||||||
tree_reconstruction_report(toks),
|
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 arrayvec::ArrayVec;
|
||||||
use std::{convert::Infallible, fmt::Display, marker::PhantomData};
|
use std::{fmt::Display, marker::PhantomData};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum AsgTreeToXirf<'a> {
|
pub enum AsgTreeToXirf<'a> {
|
||||||
|
@ -75,10 +75,14 @@ impl<'a> Display for AsgTreeToXirf<'a> {
|
||||||
|
|
||||||
type Xirf = XirfToken<Text>;
|
type Xirf = XirfToken<Text>;
|
||||||
|
|
||||||
|
diagnostic_infallible! {
|
||||||
|
pub enum AsgTreeToXirfError {}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ParseState for AsgTreeToXirf<'a> {
|
impl<'a> ParseState for AsgTreeToXirf<'a> {
|
||||||
type Token = TreeWalkRel;
|
type Token = TreeWalkRel;
|
||||||
type Object = Xirf;
|
type Object = Xirf;
|
||||||
type Error = Infallible;
|
type Error = AsgTreeToXirfError;
|
||||||
type Context = TreeContext<'a>;
|
type Context = TreeContext<'a>;
|
||||||
|
|
||||||
fn parse_token(
|
fn parse_token(
|
||||||
|
@ -150,6 +154,10 @@ type TokenStack = ArrayVec<Xirf, TOK_STACK_SIZE>;
|
||||||
pub struct TreeContext<'a> {
|
pub struct TreeContext<'a> {
|
||||||
stack: TokenStack,
|
stack: TokenStack,
|
||||||
asg: &'a Asg,
|
asg: &'a Asg,
|
||||||
|
|
||||||
|
/// Whether the most recently encountered template has been interpreted
|
||||||
|
/// as an application.
|
||||||
|
tpl_apply: Option<ObjectIndex<Tpl>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TreeContext<'a> {
|
impl<'a> TreeContext<'a> {
|
||||||
|
@ -185,18 +193,30 @@ impl<'a> TreeContext<'a> {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Identifiers will be considered in context;
|
Object::Ident((ident, oi_ident)) => match paired_rel.source() {
|
||||||
// pass over it for now.
|
Object::Meta(..) => {
|
||||||
// But we must not skip over its depth,
|
self.emit_tpl_param_value(ident, oi_ident, depth)
|
||||||
// otherwise we parent a following sibling at a matching
|
}
|
||||||
// depth;
|
|
||||||
// this close will force the auto-closing system to close
|
// All other identifiers will be considered in context;
|
||||||
// any siblings in preparation for the object to follow.
|
// pass over it for now.
|
||||||
Object::Ident((ident, _)) => Some(Xirf::Close(
|
// But we must not skip over its depth,
|
||||||
None,
|
// otherwise we parent a following sibling at a matching
|
||||||
CloseSpan::without_name_span(ident.span()),
|
// depth;
|
||||||
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)) => {
|
Object::Expr((expr, oi_expr)) => {
|
||||||
self.emit_expr(expr, *oi_expr, paired_rel.source(), depth)
|
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)
|
self.emit_template(tpl, *oi_tpl, paired_rel.source(), depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
Object::Meta((meta, oi_meta)) => {
|
Object::Meta((meta, oi_meta)) => match self.tpl_apply {
|
||||||
self.emit_tpl_arg(meta, *oi_meta, depth)
|
Some(_) => self.emit_tpl_arg(meta, *oi_meta, depth),
|
||||||
}
|
None => self.emit_tpl_param(meta, *oi_meta, depth),
|
||||||
|
},
|
||||||
|
|
||||||
Object::Doc((doc, oi_doc)) => {
|
Object::Doc((doc, oi_doc)) => {
|
||||||
self.emit_doc(doc, *oi_doc, paired_rel.source(), depth)
|
self.emit_doc(doc, *oi_doc, paired_rel.source(), depth)
|
||||||
|
@ -273,8 +294,9 @@ impl<'a> TreeContext<'a> {
|
||||||
depth: Depth,
|
depth: Depth,
|
||||||
) -> Option<Xirf> {
|
) -> Option<Xirf> {
|
||||||
match src {
|
match src {
|
||||||
Object::Ident((ident, _)) => {
|
Object::Ident((_, oi_ident)) => {
|
||||||
self.emit_expr_ident(expr, ident, depth)
|
let name = oi_ident.name_or_meta(self.asg);
|
||||||
|
self.emit_expr_ident(expr, name, depth)
|
||||||
}
|
}
|
||||||
Object::Expr((pexpr, _)) => match (pexpr.op(), expr.op()) {
|
Object::Expr((pexpr, _)) => match (pexpr.op(), expr.op()) {
|
||||||
(ExprOp::Conj | ExprOp::Disj, ExprOp::Eq) => {
|
(ExprOp::Conj | ExprOp::Disj, ExprOp::Eq) => {
|
||||||
|
@ -306,7 +328,7 @@ impl<'a> TreeContext<'a> {
|
||||||
fn emit_expr_ident(
|
fn emit_expr_ident(
|
||||||
&mut self,
|
&mut self,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
ident: &Ident,
|
name: SPair,
|
||||||
depth: Depth,
|
depth: Depth,
|
||||||
) -> Option<Xirf> {
|
) -> Option<Xirf> {
|
||||||
let (qname, ident_qname) = match expr.op() {
|
let (qname, ident_qname) = match expr.op() {
|
||||||
|
@ -322,8 +344,8 @@ impl<'a> TreeContext<'a> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ispan = ident.span();
|
let span = name.span();
|
||||||
self.push(Xirf::attr(ident_qname, ident.name(), (ispan, ispan)));
|
self.push(Xirf::attr(ident_qname, name, (span, span)));
|
||||||
|
|
||||||
Some(Xirf::open(
|
Some(Xirf::open(
|
||||||
qname,
|
qname,
|
||||||
|
@ -344,21 +366,15 @@ impl<'a> TreeContext<'a> {
|
||||||
let mut edges = oi_expr.edges_filtered::<Ident>(self.asg);
|
let mut edges = oi_expr.edges_filtered::<Ident>(self.asg);
|
||||||
|
|
||||||
// note: the edges are reversed (TODO?)
|
// note: the edges are reversed (TODO?)
|
||||||
let value = edges
|
let value = edges.next().diagnostic_expect(
|
||||||
.next()
|
|| vec![oi_expr.note("for this match")],
|
||||||
.diagnostic_expect(
|
"missing @value ref",
|
||||||
|| vec![oi_expr.note("for this match")],
|
);
|
||||||
"missing @value ref",
|
|
||||||
)
|
|
||||||
.resolve(self.asg);
|
|
||||||
|
|
||||||
let on = edges
|
let on = edges.next().diagnostic_expect(
|
||||||
.next()
|
|| vec![oi_expr.note("for this match")],
|
||||||
.diagnostic_expect(
|
"missing @on ref",
|
||||||
|| vec![oi_expr.note("for this match")],
|
);
|
||||||
"missing @on ref",
|
|
||||||
)
|
|
||||||
.resolve(self.asg);
|
|
||||||
|
|
||||||
if let Some(unexpected) = edges.next() {
|
if let Some(unexpected) = edges.next() {
|
||||||
diagnostic_panic!(
|
diagnostic_panic!(
|
||||||
|
@ -367,8 +383,8 @@ impl<'a> TreeContext<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push(attr_value(value.name()));
|
self.push(attr_value(value.name_or_meta(self.asg)));
|
||||||
self.push(attr_on(on.name()));
|
self.push(attr_on(on.name_or_meta(self.asg)));
|
||||||
|
|
||||||
Xirf::open(QN_MATCH, OpenSpan::without_name_span(expr.span()), depth)
|
Xirf::open(QN_MATCH, OpenSpan::without_name_span(expr.span()), depth)
|
||||||
}
|
}
|
||||||
|
@ -382,8 +398,9 @@ impl<'a> TreeContext<'a> {
|
||||||
depth: Depth,
|
depth: Depth,
|
||||||
) -> Option<Xirf> {
|
) -> Option<Xirf> {
|
||||||
match src {
|
match src {
|
||||||
Object::Ident((ident, _)) => {
|
Object::Ident((_, oi_ident)) => {
|
||||||
self.push(attr_name(ident.name()));
|
self.tpl_apply = None;
|
||||||
|
self.push(attr_name(oi_ident.name_or_meta(self.asg)));
|
||||||
|
|
||||||
Some(Xirf::open(
|
Some(Xirf::open(
|
||||||
QN_TEMPLATE,
|
QN_TEMPLATE,
|
||||||
|
@ -400,6 +417,12 @@ impl<'a> TreeContext<'a> {
|
||||||
// do not have to deal with converting underscore-padded
|
// do not have to deal with converting underscore-padded
|
||||||
// template names back into short-hand form.
|
// template names back into short-hand form.
|
||||||
Object::Pkg(..) | Object::Tpl(..) | Object::Expr(..) => {
|
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,
|
// [`Ident`]s are skipped during traversal,
|
||||||
// so we'll handle it ourselves here.
|
// so we'll handle it ourselves here.
|
||||||
// This also gives us the opportunity to make sure that
|
// 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",
|
"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(
|
Some(Xirf::open(
|
||||||
QN_APPLY_TEMPLATE,
|
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.
|
/// Emit a long-form template argument.
|
||||||
///
|
///
|
||||||
/// For the parent template application,
|
/// For the parent template application,
|
||||||
|
@ -450,7 +566,7 @@ impl<'a> TreeContext<'a> {
|
||||||
oi_meta: ObjectIndex<Meta>,
|
oi_meta: ObjectIndex<Meta>,
|
||||||
depth: Depth,
|
depth: Depth,
|
||||||
) -> Option<Xirf> {
|
) -> 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(
|
.diagnostic_unwrap(|| vec![meta.internal_error(
|
||||||
"anonymous metavariables are not supported as template arguments"
|
"anonymous metavariables are not supported as template arguments"
|
||||||
)]);
|
)]);
|
||||||
|
@ -497,6 +613,14 @@ impl<'a> TreeContext<'a> {
|
||||||
Some(attr_desc(*desc))
|
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)) => {
|
(_, Doc::Text(_text)) => {
|
||||||
// TODO: This isn't utilized by the XSLT parser and
|
// TODO: This isn't utilized by the XSLT parser and
|
||||||
// `xmllint` for system tests does not format with mixed
|
// `xmllint` for system tests does not format with mixed
|
||||||
|
@ -560,6 +684,7 @@ impl<'a> From<&'a Asg> for TreeContext<'a> {
|
||||||
TreeContext {
|
TreeContext {
|
||||||
stack: Default::default(),
|
stack: Default::default(),
|
||||||
asg,
|
asg,
|
||||||
|
tpl_apply: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ pub use graph::{
|
||||||
ObjectKind,
|
ObjectKind,
|
||||||
},
|
},
|
||||||
visit,
|
visit,
|
||||||
xmli::AsgTreeToXirf,
|
xmli::{AsgTreeToXirf, AsgTreeToXirfError},
|
||||||
Asg, AsgResult, IndexType,
|
Asg, AsgResult, IndexType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
// Use your judgment;
|
// Use your judgment;
|
||||||
// a `match` may be more clear within a given context.
|
// a `match` may be more clear within a given context.
|
||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
#![feature(assert_matches)]
|
||||||
|
|
||||||
//! This is the TAME compiler.
|
//! This is the TAME compiler.
|
||||||
//!
|
//!
|
||||||
|
@ -39,27 +40,49 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
use tamer::{
|
use tamer::{
|
||||||
asg::{air::Air, AsgError, DefaultAsg},
|
asg::DefaultAsg,
|
||||||
diagnose::{
|
diagnose::{
|
||||||
AnnotatedSpan, Diagnostic, FsSpanResolver, Reporter, VisualReporter,
|
AnnotatedSpan, Diagnostic, FsSpanResolver, Reporter, VisualReporter,
|
||||||
},
|
},
|
||||||
nir::{InterpError, Nir, NirToAirError, XirfToNirError},
|
nir::NirToAirParseType,
|
||||||
parse::{lowerable, FinalizeError, ParseError, Token, UnknownToken},
|
parse::{lowerable, FinalizeError, ParseError, Token},
|
||||||
pipeline::parse_package_xml,
|
pipeline::{parse_package_xml, LowerXmliError, ParsePackageXmlError},
|
||||||
xir::{
|
xir::{self, reader::XmlXirReader, writer::XmlWriter, DefaultEscaper},
|
||||||
self,
|
|
||||||
flat::{RefinedText, XirToXirfError, XirfToken},
|
|
||||||
reader::XmlXirReader,
|
|
||||||
DefaultEscaper, Token as XirToken,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Types of commands
|
/// Types of commands
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
enum Command {
|
enum Command {
|
||||||
Compile(String, String, String),
|
Compile(String, ObjectFileKind, String),
|
||||||
Usage,
|
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.
|
/// Create a [`XmlXirReader`] for a source file.
|
||||||
///
|
///
|
||||||
/// The provided escaper must be shared between all readers and writers in
|
/// 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.
|
/// transition period between the XSLT-based TAME and TAMER.
|
||||||
/// Writing XIR proves that the source file is being successfully parsed and
|
/// Writing XIR proves that the source file is being successfully parsed and
|
||||||
/// helps to evaluate system performance.
|
/// helps to evaluate system performance.
|
||||||
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
|
||||||
fn copy_xml_to<'e, W: io::Write + 'e>(
|
fn copy_xml_to<'e, W: io::Write + 'e>(
|
||||||
mut fout: W,
|
mut fout: Option<W>,
|
||||||
escaper: &'e DefaultEscaper,
|
escaper: &'e DefaultEscaper,
|
||||||
) -> impl FnMut(&Result<XirToken, tamer::xir::Error>) + 'e {
|
) -> impl FnMut(&Result<tamer::xir::Token, tamer::xir::Error>) + 'e {
|
||||||
use tamer::xir::writer::XmlWriter;
|
|
||||||
|
|
||||||
let mut xmlwriter = Default::default();
|
let mut xmlwriter = Default::default();
|
||||||
|
|
||||||
move |tok_result| match tok_result {
|
move |tok_result| match (fout.as_mut(), tok_result) {
|
||||||
Ok(tok) => {
|
(Some(mut dest), Ok(tok)) => {
|
||||||
xmlwriter = tok.write(&mut fout, xmlwriter, escaper).unwrap();
|
xmlwriter = tok.write(&mut dest, xmlwriter, escaper).unwrap();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -110,16 +130,34 @@ fn compile<R: Reporter>(
|
||||||
src_path: &String,
|
src_path: &String,
|
||||||
dest_path: &String,
|
dest_path: &String,
|
||||||
reporter: &mut R,
|
reporter: &mut R,
|
||||||
|
kind: ObjectFileKind,
|
||||||
) -> Result<(), UnrecoverableError> {
|
) -> Result<(), UnrecoverableError> {
|
||||||
let dest = Path::new(&dest_path);
|
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 escaper = DefaultEscaper::default();
|
||||||
|
|
||||||
let mut ebuf = String::new();
|
let mut ebuf = String::new();
|
||||||
|
|
||||||
let report_err = |result: Result<(), RecoverableError>| {
|
let report_err = |result: Result<(), ParsePackageXmlError<_>>| {
|
||||||
result.or_else(|e| {
|
result.or_else(|e| {
|
||||||
// See below note about buffering.
|
// See below note about buffering.
|
||||||
ebuf.clear();
|
ebuf.clear();
|
||||||
|
@ -130,44 +168,26 @@ fn compile<R: Reporter>(
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: We're just echoing back out XIR,
|
let src = &mut lowerable(
|
||||||
// which will be the same sans some formatting.
|
src_reader(src_path, &escaper)?.inspect(copy_xml_to(fcopy, &escaper)),
|
||||||
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")]
|
|
||||||
{
|
|
||||||
|_| ()
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// TODO: Determine a good default capacity once we have this populated
|
// TODO: Determine a good default capacity once we have this populated
|
||||||
// and can come up with some heuristics.
|
// and can come up with some heuristics.
|
||||||
let (air_ctx,) = parse_package_xml(
|
let (_, air_ctx) = parse_package_xml(
|
||||||
src,
|
parse_type,
|
||||||
DefaultAsg::with_capacity(1024, 2048),
|
DefaultAsg::with_capacity(1024, 2048),
|
||||||
report_err,
|
)(src, report_err)?;
|
||||||
)?;
|
|
||||||
|
|
||||||
match reporter.has_errors() {
|
if reporter.has_errors() {
|
||||||
false => {
|
Err(UnrecoverableError::ErrorsDuringLowering(
|
||||||
#[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(
|
|
||||||
reporter.error_count(),
|
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,
|
/// and must be an equivalent program,
|
||||||
/// but will look different;
|
/// but will look different;
|
||||||
/// TAMER reasons about the system using a different paradigm.
|
/// TAMER reasons about the system using a different paradigm.
|
||||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
|
||||||
fn derive_xmli(
|
fn derive_xmli(
|
||||||
asg: tamer::asg::Asg,
|
asg: tamer::asg::Asg,
|
||||||
mut fout: impl std::io::Write,
|
mut fout: impl std::io::Write,
|
||||||
escaper: &DefaultEscaper,
|
escaper: &DefaultEscaper,
|
||||||
) -> Result<(), UnrecoverableError> {
|
) -> Result<(), UnrecoverableError> {
|
||||||
use tamer::{
|
use tamer::{
|
||||||
asg::visit::tree_reconstruction,
|
asg::visit::tree_reconstruction, pipeline, xir::writer::WriterState,
|
||||||
pipeline,
|
|
||||||
xir::writer::{WriterState, XmlWriter},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let src = lowerable(tree_reconstruction(&asg).map(Ok));
|
let src = lowerable(tree_reconstruction(&asg).map(Ok));
|
||||||
|
@ -198,15 +215,17 @@ fn derive_xmli(
|
||||||
// TODO: Remove bad file?
|
// TODO: Remove bad file?
|
||||||
// Let make do it?
|
// Let make do it?
|
||||||
let mut st = WriterState::default();
|
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;
|
// Write failures should immediately bail out;
|
||||||
// we can't skip writing portions of the file and
|
// we can't skip writing portions of the file and
|
||||||
// just keep going!
|
// just keep going!
|
||||||
result.and_then(|tok| {
|
result
|
||||||
tok.write(&mut fout, st, escaper)
|
.map_err(Into::<UnrecoverableError>::into)
|
||||||
.map(|newst| st = newst)
|
.and_then(|tok| {
|
||||||
.map_err(Into::<UnrecoverableError>::into)
|
tok.write(&mut fout, st, escaper)
|
||||||
})
|
.map(|newst| st = newst)
|
||||||
|
.map_err(Into::<UnrecoverableError>::into)
|
||||||
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -220,10 +239,10 @@ pub fn main() -> Result<(), UnrecoverableError> {
|
||||||
let usage = opts.usage(&format!("Usage: {program} [OPTIONS] INPUT"));
|
let usage = opts.usage(&format!("Usage: {program} [OPTIONS] INPUT"));
|
||||||
|
|
||||||
match parse_options(opts, args) {
|
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);
|
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| {
|
|e: UnrecoverableError| {
|
||||||
// Rendering to a string ensures buffering so that we
|
// Rendering to a string ensures buffering so that we
|
||||||
// don't interleave output between processes.
|
// 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") {
|
let emit = match matches.opt_str("emit") {
|
||||||
Some(m) => match &m[..] {
|
Some(m) => match &m[..] {
|
||||||
"xmlo" => m,
|
"xmlo" => Ok(ObjectFileKind::XmloStable),
|
||||||
_ => {
|
"xmlo-experimental" => Ok(ObjectFileKind::XmloExperimental),
|
||||||
return Err(Fail::ArgumentMissing(String::from("--emit xmlo")))
|
_ => Err(Fail::ArgumentMissing(String::from("--emit xmlo"))),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => {
|
None => Err(Fail::OptionMissing(String::from("--emit xmlo"))),
|
||||||
return Err(Fail::OptionMissing(String::from("--emit xmlo")));
|
}?;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let output = match matches.opt_str("o") {
|
let output = match matches.opt_str("o") {
|
||||||
Some(m) => m,
|
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
|
/// These are errors that will result in aborting execution and exiting with
|
||||||
/// a non-zero status.
|
/// 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
|
/// which is reported real-time to the user and _does not_ cause the
|
||||||
/// program to abort until the end of the compilation unit.
|
/// 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)]
|
#[derive(Debug)]
|
||||||
pub enum UnrecoverableError {
|
pub enum UnrecoverableError {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
Fmt(fmt::Error),
|
Fmt(fmt::Error),
|
||||||
XirWriterError(xir::writer::Error),
|
XirWriterError(xir::writer::Error),
|
||||||
|
LowerXmliError(LowerXmliError<Infallible>),
|
||||||
ErrorsDuringLowering(ErrorCount),
|
ErrorsDuringLowering(ErrorCount),
|
||||||
FinalizeError(FinalizeError),
|
FinalizeError(FinalizeError),
|
||||||
}
|
}
|
||||||
|
@ -327,37 +349,6 @@ pub enum UnrecoverableError {
|
||||||
/// have in your code.
|
/// have in your code.
|
||||||
type ErrorCount = usize;
|
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 {
|
impl From<io::Error> for UnrecoverableError {
|
||||||
fn from(e: io::Error) -> Self {
|
fn from(e: io::Error) -> Self {
|
||||||
Self::Io(e)
|
Self::Io(e)
|
||||||
|
@ -376,15 +367,15 @@ impl From<xir::writer::Error> for UnrecoverableError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FinalizeError> for UnrecoverableError {
|
impl From<LowerXmliError<Infallible>> for UnrecoverableError {
|
||||||
fn from(e: FinalizeError) -> Self {
|
fn from(e: LowerXmliError<Infallible>) -> Self {
|
||||||
Self::FinalizeError(e)
|
Self::LowerXmliError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Infallible> for UnrecoverableError {
|
impl From<FinalizeError> for UnrecoverableError {
|
||||||
fn from(_: Infallible) -> Self {
|
fn from(e: FinalizeError) -> Self {
|
||||||
unreachable!("<UnrecoverableError as From<Infallible>>::from")
|
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 {
|
impl Display for UnrecoverableError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use UnrecoverableError::*;
|
use UnrecoverableError::*;
|
||||||
|
@ -449,6 +394,7 @@ impl Display for UnrecoverableError {
|
||||||
match self {
|
match self {
|
||||||
Io(e) => Display::fmt(e, f),
|
Io(e) => Display::fmt(e, f),
|
||||||
Fmt(e) => Display::fmt(e, f),
|
Fmt(e) => Display::fmt(e, f),
|
||||||
|
LowerXmliError(e) => Display::fmt(e, f),
|
||||||
XirWriterError(e) => Display::fmt(e, f),
|
XirWriterError(e) => Display::fmt(e, f),
|
||||||
FinalizeError(e) => Display::fmt(e, f),
|
FinalizeError(e) => Display::fmt(e, f),
|
||||||
|
|
||||||
|
@ -460,55 +406,14 @@ impl Display for UnrecoverableError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for RecoverableError {
|
impl Error for UnrecoverableError {}
|
||||||
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 Diagnostic for UnrecoverableError {
|
impl Diagnostic for UnrecoverableError {
|
||||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||||
use UnrecoverableError::*;
|
use UnrecoverableError::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
LowerXmliError(e) => e.describe(),
|
||||||
FinalizeError(e) => e.describe(),
|
FinalizeError(e) => e.describe(),
|
||||||
|
|
||||||
// Fall back to `Display`
|
// 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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::assert_matches::assert_matches;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_options_help() {
|
fn parse_options_help() {
|
||||||
|
@ -670,7 +561,7 @@ mod test {
|
||||||
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
||||||
assert_eq!("foo.xml", infile);
|
assert_eq!("foo.xml", infile);
|
||||||
assert_eq!("foo.xmlo", outfile);
|
assert_eq!("foo.xmlo", outfile);
|
||||||
assert_eq!("xmlo", xmlo);
|
assert_eq!(ObjectFileKind::XmloStable, xmlo);
|
||||||
}
|
}
|
||||||
_ => panic!("Unexpected result"),
|
_ => panic!("Unexpected result"),
|
||||||
}
|
}
|
||||||
|
@ -696,7 +587,7 @@ mod test {
|
||||||
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
||||||
assert_eq!("foo.xml", infile);
|
assert_eq!("foo.xml", infile);
|
||||||
assert_eq!("foo.xmli", outfile);
|
assert_eq!("foo.xmli", outfile);
|
||||||
assert_eq!("xmlo", xmlo);
|
assert_eq!(ObjectFileKind::XmloStable, xmlo);
|
||||||
}
|
}
|
||||||
_ => panic!("Unexpected result"),
|
_ => panic!("Unexpected result"),
|
||||||
}
|
}
|
||||||
|
@ -722,9 +613,31 @@ mod test {
|
||||||
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
Ok(Command::Compile(infile, xmlo, outfile)) => {
|
||||||
assert_eq!("foo.xml", infile);
|
assert_eq!("foo.xml", infile);
|
||||||
assert_eq!("foo.xmli", outfile);
|
assert_eq!("foo.xmli", outfile);
|
||||||
assert_eq!("xmlo", xmlo);
|
assert_eq!(ObjectFileKind::XmloStable, xmlo);
|
||||||
}
|
}
|
||||||
_ => panic!("Unexpected result"),
|
_ => 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)
|
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`.
|
/// A type providing a `map` function from inner type `T` to `U`.
|
||||||
///
|
///
|
||||||
/// In an abuse of terminology,
|
/// This used to be called `Functor`,
|
||||||
/// this functor is polymorphic over the entire trait,
|
/// but was renamed because it was an abuse of terminology;
|
||||||
|
/// this is polymorphic over the entire trait,
|
||||||
/// rather than just the definition of `map`,
|
/// rather than just the definition of `map`,
|
||||||
/// allowing implementations to provide multiple specialized `map`s
|
/// allowing implementations to provide multiple specialized `map`s
|
||||||
/// without having to define individual `map_*` methods.
|
/// without having to define individual `map_*` methods.
|
||||||
/// Rust will often be able to infer the proper types and invoke the
|
/// Rust will often be able to infer the proper types and invoke the
|
||||||
/// intended `map` function within a given context,
|
/// intended `map` function within a given context,
|
||||||
/// but methods may still be explicitly disambiguated using the
|
/// but methods may still be explicitly disambiguated using the
|
||||||
|
@ -46,16 +47,16 @@
|
||||||
/// if a functor requires a monomorphic function
|
/// if a functor requires a monomorphic function
|
||||||
/// (so `T = U`),
|
/// (so `T = U`),
|
||||||
/// then it's not really a functor.
|
/// 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
|
/// 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`?
|
/// 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].
|
/// which [is motivated by Haskell][haskell-functor].
|
||||||
/// This trait implements methods in terms of [`map`](Self::map) rather than
|
/// This trait implements methods in terms of [`map`](Self::map) rather than
|
||||||
/// [`fmap`](Self::fmap) because `map` is a familiar idiom in Rust and
|
/// [`fmap`](Self::fmap) because `map` is a familiar idiom in Rust and
|
||||||
|
@ -66,8 +67,8 @@
|
||||||
/// which is additional boilerplate relative to `map`.
|
/// which is additional boilerplate relative to `map`.
|
||||||
///
|
///
|
||||||
/// [haskell-functor]: https://hackage.haskell.org/package/base/docs/Data-Functor.html
|
/// [haskell-functor]: https://hackage.haskell.org/package/base/docs/Data-Functor.html
|
||||||
pub trait Functor<T, U = T>: Sized {
|
pub trait Map<T, U = T>: Sized {
|
||||||
/// Type of object resulting from [`Functor::map`] operation.
|
/// Type of object resulting from [`Map::map`] operation.
|
||||||
///
|
///
|
||||||
/// The term "target" originates from category theory,
|
/// The term "target" originates from category theory,
|
||||||
/// representing the codomain of the functor.
|
/// 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.
|
/// all others are implemented in terms of it.
|
||||||
fn map(self, f: impl FnOnce(T) -> U) -> Self::Target;
|
fn map(self, f: impl FnOnce(T) -> U) -> Self::Target;
|
||||||
|
|
||||||
/// Curried form of [`Functor::map`],
|
/// Curried form of [`Map::map`],
|
||||||
/// with arguments reversed.
|
/// with arguments reversed.
|
||||||
///
|
///
|
||||||
/// `fmap` returns a unary closure that accepts an object of type
|
/// `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
|
/// This is intended for cases where there's a single element that will
|
||||||
/// be replaced,
|
/// 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 {
|
fn overwrite(self, value: U) -> Self::Target {
|
||||||
self.map(|_| value)
|
self.map(|_| value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Curried form of [`Functor::overwrite`],
|
/// Curried form of [`Map::overwrite`],
|
||||||
/// with arguments reversed.
|
/// with arguments reversed.
|
||||||
fn foverwrite(value: U) -> impl FnOnce(Self) -> Self::Target {
|
fn foverwrite(value: U) -> impl FnOnce(Self) -> Self::Target {
|
||||||
move |x| x.overwrite(value)
|
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>;
|
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)
|
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.
|
/// A nullary [`Fn`] delaying some computation.
|
||||||
///
|
///
|
||||||
/// For the history and usage of this term in computing,
|
/// For the history and usage of this term in computing,
|
||||||
|
|
|
@ -26,30 +26,25 @@ use super::xmle::{
|
||||||
XmleSections,
|
XmleSections,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
asg::{
|
asg::{air::AirAggregateCtx, DefaultAsg},
|
||||||
air::{Air, AirAggregateCtx},
|
|
||||||
AsgError, DefaultAsg,
|
|
||||||
},
|
|
||||||
diagnose::{AnnotatedSpan, Diagnostic},
|
diagnose::{AnnotatedSpan, Diagnostic},
|
||||||
fs::{
|
fs::{
|
||||||
Filesystem, FsCanonicalizer, PathFile, VisitOnceFile,
|
Filesystem, FsCanonicalizer, PathFile, VisitOnceFile,
|
||||||
VisitOnceFilesystem,
|
VisitOnceFilesystem,
|
||||||
},
|
},
|
||||||
ld::xmle::Sections,
|
ld::xmle::Sections,
|
||||||
obj::xmlo::{XmloAirContext, XmloAirError, XmloError, XmloToken},
|
obj::xmlo::XmloAirContext,
|
||||||
parse::{lowerable, FinalizeError, ParseError, UnknownToken},
|
parse::{lowerable, FinalizeError},
|
||||||
pipeline,
|
pipeline::{self, LoadXmloError},
|
||||||
sym::{GlobalSymbolResolve, SymbolId},
|
sym::{GlobalSymbolResolve, SymbolId},
|
||||||
xir::{
|
xir::{
|
||||||
flat::{Text, XirToXirfError, XirfToken},
|
|
||||||
reader::XmlXirReader,
|
reader::XmlXirReader,
|
||||||
writer::{Error as XirWriterError, XmlWriter},
|
writer::{Error as XirWriterError, XmlWriter},
|
||||||
DefaultEscaper, Error as XirError, Escaper, Token as XirToken,
|
DefaultEscaper, Error as XirError, Escaper,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use fxhash::FxBuildHasher;
|
use fxhash::FxBuildHasher;
|
||||||
use std::{
|
use std::{
|
||||||
convert::identity,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
fs,
|
fs,
|
||||||
|
@ -106,9 +101,10 @@ fn load_xmlo<P: AsRef<Path>, S: Escaper>(
|
||||||
|
|
||||||
let src = &mut lowerable(XmlXirReader::new(file, escaper, ctx));
|
let src = &mut lowerable(XmlXirReader::new(file, escaper, ctx));
|
||||||
|
|
||||||
let (mut state, mut air_ctx) = pipeline::load_xmlo::<_, TameldError, _>(
|
let (mut state, mut air_ctx) =
|
||||||
src, state, air_ctx, identity,
|
pipeline::load_xmlo(state, air_ctx)(src, |result| {
|
||||||
)?;
|
result.map_err(TameldError::from)
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut dir = path;
|
let mut dir = path;
|
||||||
dir.pop();
|
dir.pop();
|
||||||
|
@ -159,11 +155,7 @@ fn output_xmle<'a, X: XmleSections<'a>, S: Escaper>(
|
||||||
pub enum TameldError {
|
pub enum TameldError {
|
||||||
Io(NeqIoError),
|
Io(NeqIoError),
|
||||||
SortError(SortError),
|
SortError(SortError),
|
||||||
XirParseError(ParseError<UnknownToken, XirError>),
|
LoadXmloError(LoadXmloError<XirError>),
|
||||||
XirfParseError(ParseError<XirToken, XirToXirfError>),
|
|
||||||
XmloParseError(ParseError<XirfToken<Text>, XmloError>),
|
|
||||||
XmloLowerError(ParseError<XmloToken, XmloAirError>),
|
|
||||||
AirLowerError(ParseError<Air, AsgError>),
|
|
||||||
XirWriterError(XirWriterError),
|
XirWriterError(XirWriterError),
|
||||||
FinalizeError(FinalizeError),
|
FinalizeError(FinalizeError),
|
||||||
Fmt(fmt::Error),
|
Fmt(fmt::Error),
|
||||||
|
@ -206,33 +198,9 @@ impl From<SortError> for TameldError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseError<UnknownToken, XirError>> for TameldError {
|
impl From<LoadXmloError<XirError>> for TameldError {
|
||||||
fn from(e: ParseError<UnknownToken, XirError>) -> Self {
|
fn from(e: LoadXmloError<XirError>) -> Self {
|
||||||
Self::XirParseError(e)
|
Self::LoadXmloError(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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,11 +227,7 @@ impl Display for TameldError {
|
||||||
match self {
|
match self {
|
||||||
Self::Io(e) => Display::fmt(e, f),
|
Self::Io(e) => Display::fmt(e, f),
|
||||||
Self::SortError(e) => Display::fmt(e, f),
|
Self::SortError(e) => Display::fmt(e, f),
|
||||||
Self::XirParseError(e) => Display::fmt(e, f),
|
Self::LoadXmloError(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::XirWriterError(e) => Display::fmt(e, f),
|
Self::XirWriterError(e) => Display::fmt(e, f),
|
||||||
Self::FinalizeError(e) => Display::fmt(e, f),
|
Self::FinalizeError(e) => Display::fmt(e, f),
|
||||||
Self::Fmt(e) => Display::fmt(e, f),
|
Self::Fmt(e) => Display::fmt(e, f),
|
||||||
|
@ -271,31 +235,12 @@ impl Display for TameldError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error 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 Diagnostic for TameldError {
|
impl Diagnostic for TameldError {
|
||||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||||
match self {
|
match self {
|
||||||
Self::XirParseError(e) => e.describe(),
|
Self::LoadXmloError(e) => e.describe(),
|
||||||
Self::XirfParseError(e) => e.describe(),
|
|
||||||
Self::XmloParseError(e) => e.describe(),
|
|
||||||
Self::XmloLowerError(e) => e.describe(),
|
|
||||||
Self::AirLowerError(e) => e.describe(),
|
|
||||||
Self::FinalizeError(e) => e.describe(),
|
Self::FinalizeError(e) => e.describe(),
|
||||||
Self::SortError(e) => e.describe(),
|
Self::SortError(e) => e.describe(),
|
||||||
|
|
||||||
|
|
|
@ -105,12 +105,13 @@ fn graph_sort() -> SortResult<()> {
|
||||||
let asg = asg_from_toks(toks);
|
let asg = asg_from_toks(toks);
|
||||||
let sections = sort(&asg, StubSections { pushed: Vec::new() })?;
|
let sections = sort(&asg, StubSections { pushed: Vec::new() })?;
|
||||||
|
|
||||||
let expected = vec![
|
let expected = [
|
||||||
// Post-order
|
// Post-order
|
||||||
name_a_dep_dep,
|
name_a_dep_dep,
|
||||||
name_a_dep,
|
name_a_dep,
|
||||||
name_a,
|
name_a,
|
||||||
]
|
]
|
||||||
|
.map(Some)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
|
@ -113,10 +113,10 @@ impl<'a> XmleSections<'a> for Sections<'a> {
|
||||||
fn push(&mut self, ident: &'a Ident) -> PushResult {
|
fn push(&mut self, ident: &'a Ident) -> PushResult {
|
||||||
self.deps.push(ident);
|
self.deps.push(ident);
|
||||||
|
|
||||||
let name = ident.name();
|
|
||||||
let frag = ident.fragment();
|
let frag = ident.fragment();
|
||||||
|
let (resolved, name) = ident.resolved()?;
|
||||||
|
|
||||||
match ident.resolved()?.kind() {
|
match resolved.kind() {
|
||||||
Some(kind) => match kind {
|
Some(kind) => match kind {
|
||||||
IdentKind::Cgen(..)
|
IdentKind::Cgen(..)
|
||||||
| IdentKind::Gen(..)
|
| 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
|
// compiler bug and there is no use in trying to be nice
|
||||||
// about a situation where something went terribly, horribly
|
// about a situation where something went terribly, horribly
|
||||||
// wrong.
|
// wrong.
|
||||||
return Err(SectionsError::MissingObjectKind(ident.name()));
|
return Err(SectionsError::MissingObjectKind(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -256,7 +256,7 @@ fn test_writes_deps() -> TestResult {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
attrs.find(QN_NAME).map(|a| a.value()),
|
attrs.find(QN_NAME).map(|a| a.value()),
|
||||||
Some(ident.name().symbol()),
|
ident.name().map(|name| name.symbol()),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -23,6 +23,11 @@
|
||||||
//!
|
//!
|
||||||
//! - [`tamec`](../tamec), the TAME compiler; and
|
//! - [`tamec`](../tamec), the TAME compiler; and
|
||||||
//! - [`tameld`](../tameld), the TAME linker.
|
//! - [`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
|
// Constant functions are still in their infancy as of the time of writing
|
||||||
// (October 2021).
|
// (October 2021).
|
||||||
|
@ -76,6 +81,8 @@
|
||||||
// If this is not stabalized,
|
// If this is not stabalized,
|
||||||
// then we can do without by changing the abstraction;
|
// then we can do without by changing the abstraction;
|
||||||
// this is largely experimentation to see if it's useful.
|
// 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)]
|
#![allow(incomplete_features)]
|
||||||
#![feature(adt_const_params)]
|
#![feature(adt_const_params)]
|
||||||
// Used for traits returning functions,
|
// Used for traits returning functions,
|
||||||
|
@ -173,19 +180,88 @@
|
||||||
// which can be inscrutable if you are not very familiar with Rust's
|
// which can be inscrutable if you are not very familiar with Rust's
|
||||||
// borrow checker.
|
// borrow checker.
|
||||||
#![allow(clippy::needless_lifetimes)]
|
#![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;
|
pub mod global;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate static_assertions;
|
extern crate static_assertions;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod f;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod diagnose;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod xir;
|
pub mod xir;
|
||||||
|
|
||||||
pub mod asg;
|
pub mod asg;
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod diagnose;
|
|
||||||
pub mod f;
|
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod iter;
|
pub mod iter;
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
//! The entry point for NIR in the lowering pipeline is exported as
|
//! The entry point for NIR in the lowering pipeline is exported as
|
||||||
//! [`XirfToNir`].
|
//! [`XirfToNir`].
|
||||||
|
|
||||||
|
mod abstract_bind;
|
||||||
mod air;
|
mod air;
|
||||||
mod interp;
|
mod interp;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
@ -56,7 +57,7 @@ mod tplshort;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnose::{Annotate, Diagnostic},
|
diagnose::{Annotate, Diagnostic},
|
||||||
f::Functor,
|
f::Map,
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
parse::{util::SPair, Object, Token},
|
parse::{util::SPair, Object, Token},
|
||||||
span::Span,
|
span::Span,
|
||||||
|
@ -72,12 +73,13 @@ use std::{
|
||||||
fmt::{Debug, Display},
|
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 interp::{InterpError, InterpState as InterpolateNir};
|
||||||
pub use parse::{
|
pub use parse::{
|
||||||
NirParseState as XirfToNir, NirParseStateError_ as XirfToNirError,
|
NirParseState as XirfToNir, NirParseStateError_ as XirfToNirError,
|
||||||
};
|
};
|
||||||
pub use tplshort::TplShortDesugar;
|
pub use tplshort::{TplShortDesugar, TplShortDesugarError};
|
||||||
|
|
||||||
/// IR that is "near" the source code.
|
/// 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.
|
/// Finish definition of a [`NirEntity`] atop of the stack and pop it.
|
||||||
Close(NirEntity, Span),
|
Close(NirEntity, Span),
|
||||||
|
|
||||||
/// Bind the given name as an identifier for the entity atop of the
|
/// Bind the given name as a concrete identifier for the entity atop of
|
||||||
/// stack.
|
/// the stack.
|
||||||
///
|
///
|
||||||
/// [`Self::Ref`] references identifiers created using this token.
|
/// [`Self::Ref`] references identifiers created using this token.
|
||||||
|
///
|
||||||
|
/// See also [`Self::BindIdentAbstract`].
|
||||||
BindIdent(SPair),
|
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
|
/// Reference the value of the given identifier as the subject of the
|
||||||
/// current expression.
|
/// current expression.
|
||||||
///
|
///
|
||||||
|
@ -163,15 +186,23 @@ pub enum Nir {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Nir {
|
impl Nir {
|
||||||
/// Retrieve inner [`SymbolId`] that this token represents,
|
/// Retrieve a _concrete_ inner [`SymbolId`] that this token represents,
|
||||||
/// if any.
|
/// if any.
|
||||||
///
|
///
|
||||||
/// Not all NIR tokens contain associated symbols;
|
/// Not all NIR tokens contain associated symbols;
|
||||||
/// a token's [`SymbolId`] is retained only if it provides additional
|
/// a token's [`SymbolId`] is retained only if it provides additional
|
||||||
/// information over the token itself.
|
/// information over the token itself.
|
||||||
///
|
///
|
||||||
/// See also [`Nir::map`] if you wish to change the symbol.
|
/// An abstract identifier will yield [`None`],
|
||||||
pub fn symbol(&self) -> Option<SymbolId> {
|
/// 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::*;
|
use Nir::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -180,15 +211,24 @@ impl Nir {
|
||||||
|
|
||||||
Open(_, _) | Close(_, _) => None,
|
Open(_, _) | Close(_, _) => None,
|
||||||
|
|
||||||
BindIdent(spair) | RefSubject(spair) | Ref(spair) | Desc(spair)
|
BindIdent(spair) | BindIdentMeta(spair) | RefSubject(spair)
|
||||||
| Text(spair) | Import(spair) => Some(spair.symbol()),
|
| 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,
|
Noop(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Functor<SymbolId> for Nir {
|
impl Map<SymbolId> for Nir {
|
||||||
/// Map over a token's [`SymbolId`].
|
/// Map over a token's [`SymbolId`].
|
||||||
///
|
///
|
||||||
/// This allows modifying a token's [`SymbolId`] while retaining the
|
/// 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,
|
/// If a token does not contain a symbol,
|
||||||
/// this returns the token unchanged.
|
/// this returns the token unchanged.
|
||||||
///
|
///
|
||||||
/// See also [`Nir::symbol`] if you only wish to retrieve the symbol
|
/// See also [`Nir::concrete_symbol`].
|
||||||
/// rather than map over it.
|
|
||||||
fn map(self, f: impl FnOnce(SymbolId) -> SymbolId) -> Self {
|
fn map(self, f: impl FnOnce(SymbolId) -> SymbolId) -> Self {
|
||||||
use Nir::*;
|
use Nir::*;
|
||||||
|
|
||||||
|
@ -213,6 +252,8 @@ impl Functor<SymbolId> for Nir {
|
||||||
Open(_, _) | Close(_, _) => self,
|
Open(_, _) | Close(_, _) => self,
|
||||||
|
|
||||||
BindIdent(spair) => BindIdent(spair.map(f)),
|
BindIdent(spair) => BindIdent(spair.map(f)),
|
||||||
|
BindIdentAbstract(spair) => BindIdentAbstract(spair.map(f)),
|
||||||
|
BindIdentMeta(spair) => BindIdentMeta(spair.map(f)),
|
||||||
RefSubject(spair) => RefSubject(spair.map(f)),
|
RefSubject(spair) => RefSubject(spair.map(f)),
|
||||||
Ref(spair) => Ref(spair.map(f)),
|
Ref(spair) => Ref(spair.map(f)),
|
||||||
Desc(spair) => Desc(spair.map(f)),
|
Desc(spair) => Desc(spair.map(f)),
|
||||||
|
@ -339,8 +380,14 @@ impl Token for Nir {
|
||||||
Open(_, span) => *span,
|
Open(_, span) => *span,
|
||||||
Close(_, span) => *span,
|
Close(_, span) => *span,
|
||||||
|
|
||||||
BindIdent(spair) | RefSubject(spair) | Ref(spair) | Desc(spair)
|
BindIdent(spair)
|
||||||
| Text(spair) | Import(spair) => spair.span(),
|
| BindIdentAbstract(spair)
|
||||||
|
| BindIdentMeta(spair)
|
||||||
|
| RefSubject(spair)
|
||||||
|
| Ref(spair)
|
||||||
|
| Desc(spair)
|
||||||
|
| Text(spair)
|
||||||
|
| Import(spair) => spair.span(),
|
||||||
|
|
||||||
// A no-op is discarding user input,
|
// A no-op is discarding user input,
|
||||||
// so we still want to know where that is so that we can
|
// 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"),
|
Open(entity, _) => write!(f, "open {entity} entity"),
|
||||||
Close(entity, _) => write!(f, "close {entity} entity"),
|
Close(entity, _) => write!(f, "close {entity} entity"),
|
||||||
BindIdent(spair) => {
|
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) => {
|
RefSubject(spair) => {
|
||||||
write!(f, "subject ref {}", TtQuote::wrap(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 super::Nir;
|
||||||
use crate::{
|
use crate::{
|
||||||
asg::air::Air,
|
asg::{air::Air, ExprOp},
|
||||||
diagnose::{Annotate, Diagnostic},
|
fmt::TtQuote,
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
nir::{Nir::*, NirEntity::*},
|
||||||
parse::prelude::*,
|
parse::prelude::*,
|
||||||
span::Span,
|
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},
|
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)]
|
#[derive(Debug, PartialEq, Eq, Default)]
|
||||||
pub enum NirToAir {
|
pub enum NirToAir {
|
||||||
|
@ -78,37 +112,27 @@ type ObjStack = ArrayVec<Air, 2>;
|
||||||
|
|
||||||
/// The symbol to use when lexically expanding shorthand notations to
|
/// The symbol to use when lexically expanding shorthand notations to
|
||||||
/// compare against values of `1`.
|
/// compare against values of `1`.
|
||||||
#[cfg(feature = "wip-asg-derived-xmli")]
|
|
||||||
pub const SYM_TRUE: SymbolId = U_TRUE;
|
pub const SYM_TRUE: SymbolId = U_TRUE;
|
||||||
|
|
||||||
impl ParseState for NirToAir {
|
impl ParseState for NirToAir {
|
||||||
type Token = Nir;
|
type Token = Nir;
|
||||||
type Object = Air;
|
type Object = Air;
|
||||||
type Error = NirToAirError;
|
type Error = NirToAirError;
|
||||||
type Context = ObjStack;
|
type Context = (NirToAirParseType, ObjStack);
|
||||||
|
type PubContext = NirToAirParseType;
|
||||||
|
|
||||||
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
|
||||||
fn parse_token(
|
fn parse_token(
|
||||||
self,
|
self,
|
||||||
tok: Self::Token,
|
tok: Self::Token,
|
||||||
_queue: &mut Self::Context,
|
(parse_type, stack): &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,
|
|
||||||
) -> TransitionResult<Self::Super> {
|
) -> TransitionResult<Self::Super> {
|
||||||
use NirToAir::*;
|
use NirToAir::*;
|
||||||
use NirToAirError::*;
|
use NirToAirError::*;
|
||||||
|
|
||||||
use crate::diagnostic_panic;
|
match parse_type {
|
||||||
|
NirToAirParseType::Noop => return Transition(Ready).incomplete(),
|
||||||
|
NirToAirParseType::LowerKnownErrorRest => (),
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(obj) = stack.pop() {
|
if let Some(obj) = stack.pop() {
|
||||||
return Transition(Ready).ok(obj).with_lookahead(tok);
|
return Transition(Ready).ok(obj).with_lookahead(tok);
|
||||||
|
@ -228,6 +252,12 @@ impl ParseState for NirToAir {
|
||||||
(Ready, Open(TplParam, span)) => {
|
(Ready, Open(TplParam, span)) => {
|
||||||
Transition(Meta(span)).ok(Air::MetaStart(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)) => {
|
(Meta(mspan), Text(lexeme)) => {
|
||||||
Transition(Meta(mspan)).ok(Air::MetaLexeme(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.
|
// Some of these will be permitted in the future.
|
||||||
(
|
(
|
||||||
Meta(mspan),
|
Meta(mspan),
|
||||||
tok @ (Open(..) | Close(..) | Ref(..) | RefSubject(..)
|
tok @ (Open(..) | Close(..) | BindIdent(..) | RefSubject(..)),
|
||||||
| Desc(..)),
|
|
||||||
) => Transition(Meta(mspan))
|
) => Transition(Meta(mspan))
|
||||||
.err(NirToAirError::UnexpectedMetaToken(mspan, tok)),
|
.err(NirToAirError::ExpectedMetaToken(mspan, tok)),
|
||||||
|
|
||||||
(Ready, Text(text)) => Transition(Ready).ok(Air::DocText(text)),
|
(Ready, Text(text)) => Transition(Ready).ok(Air::DocText(text)),
|
||||||
|
|
||||||
|
@ -252,15 +281,18 @@ impl ParseState for NirToAir {
|
||||||
),
|
),
|
||||||
) => Transition(Ready).ok(Air::ExprEnd(span)),
|
) => Transition(Ready).ok(Air::ExprEnd(span)),
|
||||||
|
|
||||||
(st @ (Ready | Meta(_)), BindIdent(spair)) => {
|
(Ready, BindIdent(spair)) => {
|
||||||
Transition(st).ok(Air::BindIdent(spair))
|
Transition(Ready).ok(Air::BindIdent(spair))
|
||||||
|
}
|
||||||
|
(st @ (Ready | Meta(_)), BindIdentAbstract(spair)) => {
|
||||||
|
Transition(st).ok(Air::BindIdentAbstract(spair))
|
||||||
}
|
}
|
||||||
(Ready, Ref(spair) | RefSubject(spair)) => {
|
(Ready, Ref(spair) | RefSubject(spair)) => {
|
||||||
Transition(Ready).ok(Air::RefIdent(spair))
|
Transition(Ready).ok(Air::RefIdent(spair))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Ready, Desc(clause)) => {
|
(st @ (Ready | Meta(_)), Desc(clause)) => {
|
||||||
Transition(Ready).ok(Air::DocIndepClause(clause))
|
Transition(st).ok(Air::DocIndepClause(clause))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Ready, Import(namespec)) => {
|
(Ready, Import(namespec)) => {
|
||||||
|
@ -272,27 +304,48 @@ impl ParseState for NirToAir {
|
||||||
// This assumption is only valid so long as that's the only
|
// This assumption is only valid so long as that's the only
|
||||||
// thing producing NIR.
|
// thing producing NIR.
|
||||||
(st @ Meta(..), tok @ Import(_)) => Transition(st).dead(tok),
|
(st @ Meta(..), tok @ Import(_)) => Transition(st).dead(tok),
|
||||||
|
(st @ Ready, tok @ BindIdentMeta(_)) => Transition(st).dead(tok),
|
||||||
|
|
||||||
(_, tok @ (Todo(..) | TodoAttr(..))) => {
|
// Unsupported tokens yield errors.
|
||||||
crate::diagnostic_todo!(
|
// There _is_ a risk that this will put us in a wildly
|
||||||
vec![tok.internal_error(
|
// inconsistent state,
|
||||||
"this token is not yet supported in TAMER"
|
// yielding nonsense errors in the future.
|
||||||
)],
|
// This used to panic,
|
||||||
"unsupported token: {tok}",
|
// 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(),
|
(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()
|
matches!(self, Self::Ready) && stack.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum NirToAirError {
|
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,
|
/// Expected a match subject,
|
||||||
/// but encountered some other token.
|
/// 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
|
/// The provided [`Nir`] token of input was unexpected for the body of a
|
||||||
/// metavariable that was opened at the provided [`Span`].
|
/// metavariable that was opened at the provided [`Span`].
|
||||||
UnexpectedMetaToken(Span, Nir),
|
ExpectedMetaToken(Span, Nir),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for NirToAirError {
|
impl Display for NirToAirError {
|
||||||
|
@ -314,6 +367,10 @@ impl Display for NirToAirError {
|
||||||
use NirToAirError::*;
|
use NirToAirError::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
UnsupportedToken(tok) => {
|
||||||
|
write!(f, "unsupported token: {tok}")
|
||||||
|
}
|
||||||
|
|
||||||
MatchSubjectExpected(_, nir) => {
|
MatchSubjectExpected(_, nir) => {
|
||||||
write!(f, "expected match subject, found {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")
|
write!(f, "match body is not yet supported by TAMER")
|
||||||
}
|
}
|
||||||
|
|
||||||
UnexpectedMetaToken(_, tok) => {
|
ExpectedMetaToken(_, tok) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"expected lexical token for metavariable, found {tok}"
|
"expected lexical token for metavariable, found {tok}"
|
||||||
|
@ -341,6 +398,19 @@ impl Diagnostic for NirToAirError {
|
||||||
use NirToAirError::*;
|
use NirToAirError::*;
|
||||||
|
|
||||||
match self {
|
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![
|
MatchSubjectExpected(ospan, given) => vec![
|
||||||
ospan.note("for this match"),
|
ospan.note("for this match"),
|
||||||
given
|
given
|
||||||
|
@ -358,7 +428,7 @@ impl Diagnostic for NirToAirError {
|
||||||
// The user should have been preempted by the parent parser
|
// The user should have been preempted by the parent parser
|
||||||
// (e.g. XML->Nir),
|
// (e.g. XML->Nir),
|
||||||
// and so shouldn't see this.
|
// and so shouldn't see this.
|
||||||
UnexpectedMetaToken(mspan, given) => vec![
|
ExpectedMetaToken(mspan, given) => vec![
|
||||||
mspan.note("while parsing the body of this metavariable"),
|
mspan.note("while parsing the body of this metavariable"),
|
||||||
given.span().error("expected a lexical token here"),
|
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;
|
mod test;
|
||||||
|
|
|
@ -18,12 +18,33 @@
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{parse::util::SPair, span::dummy::*};
|
use crate::{
|
||||||
|
parse::{util::SPair, Parser},
|
||||||
|
span::dummy::*,
|
||||||
|
};
|
||||||
|
|
||||||
type Sut = NirToAir;
|
type Sut = NirToAir;
|
||||||
|
|
||||||
use Parsed::{Incomplete, Object as O};
|
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]
|
#[test]
|
||||||
fn package_to_pkg() {
|
fn package_to_pkg() {
|
||||||
let toks = vec![Open(Package, S1), Close(Package, S2)];
|
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::PkgStart(S1, SPair("/TODO".into(), S1))),
|
||||||
O(Air::PkgEnd(S2)),
|
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::BindIdent(id)),
|
||||||
O(Air::ExprEnd(S3)),
|
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(S3)),
|
||||||
O(Air::ExprEnd(S4)),
|
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::BindIdent(id)),
|
||||||
O(Air::ExprEnd(S3)),
|
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(S3)),
|
||||||
O(Air::ExprEnd(S4)),
|
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::DocIndepClause(desc)),
|
||||||
O(Air::ExprEnd(S4)),
|
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::BindIdent(name)),
|
||||||
O(Air::TplEnd(S3)),
|
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::RefIdent(name)),
|
||||||
O(Air::TplEndRef(S3)),
|
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),
|
RefSubject(name),
|
||||||
|
|
||||||
Open(TplParam, S3),
|
Open(TplParam, S3),
|
||||||
BindIdent(p1),
|
BindIdentMeta(p1),
|
||||||
Text(v1),
|
Text(v1),
|
||||||
|
Ref(p2),
|
||||||
Close(TplParam, S6),
|
Close(TplParam, S6),
|
||||||
|
|
||||||
Open(TplParam, S7),
|
Open(TplParam, S7),
|
||||||
BindIdent(p2),
|
BindIdentMeta(p2),
|
||||||
Text(v2),
|
Text(v2),
|
||||||
Close(TplParam, S10),
|
Close(TplParam, S10),
|
||||||
Close(TplApply, S11),
|
Close(TplApply, S11),
|
||||||
|
@ -232,6 +288,7 @@ fn apply_template_long_form_args() {
|
||||||
O(Air::MetaStart(S3)),
|
O(Air::MetaStart(S3)),
|
||||||
O(Air::BindIdent(p1)),
|
O(Air::BindIdent(p1)),
|
||||||
O(Air::MetaLexeme(v1)),
|
O(Air::MetaLexeme(v1)),
|
||||||
|
O(Air::RefIdent(p2)),
|
||||||
O(Air::MetaEnd(S6)),
|
O(Air::MetaEnd(S6)),
|
||||||
|
|
||||||
O(Air::MetaStart(S7)),
|
O(Air::MetaStart(S7)),
|
||||||
|
@ -240,7 +297,7 @@ fn apply_template_long_form_args() {
|
||||||
O(Air::MetaEnd(S10)),
|
O(Air::MetaEnd(S10)),
|
||||||
O(Air::TplEndRef(S11)),
|
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::RefIdent(SPair(SYM_TRUE, S1))),
|
||||||
O(Air::ExprEnd(S3)),
|
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::RefIdent(value)),
|
||||||
O(Air::ExprEnd(S4)),
|
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::RefIdent(SPair(SYM_TRUE, S1)))),
|
||||||
Ok(O(Air::ExprEnd(S3))),
|
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.
|
// 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::DocText(text)),
|
||||||
O(Air::PkgEnd(S3)),
|
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
|
//! ```xml
|
||||||
//! <param name="@___dsgr_01@"
|
//! <param name="@___dsgr_01@"
|
||||||
//! desc="Generated from interpolated string `foo{@bar@}baz`">
|
//! desc="Generated from interpolated string">
|
||||||
//! <text>foo</text>
|
//! <text>foo</text>
|
||||||
//! <param-value name="@bar@" />
|
//! <param-value name="@bar@" />
|
||||||
//! <text>baz</text>
|
//! <text>baz</text>
|
||||||
|
@ -69,6 +69,12 @@
|
||||||
//! then it is interpreted as a literal within the context of the template
|
//! then it is interpreted as a literal within the context of the template
|
||||||
//! system and is echoed back unchanged.
|
//! 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
|
//! Desugared Spans
|
||||||
//! ---------------
|
//! ---------------
|
||||||
//! [`Span`]s for the generated tokens are derived from the specification
|
//! [`Span`]s for the generated tokens are derived from the specification
|
||||||
|
@ -102,13 +108,13 @@ use memchr::memchr2;
|
||||||
use super::{Nir, NirEntity};
|
use super::{Nir, NirEntity};
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan, Diagnostic},
|
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan, Diagnostic},
|
||||||
f::Functor,
|
f::Map,
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
parse::{prelude::*, util::SPair, NoContext},
|
parse::{prelude::*, util::SPair, NoContext},
|
||||||
span::Span,
|
span::Span,
|
||||||
sym::{
|
sym::{
|
||||||
st::quick_contains_byte, GlobalSymbolIntern, GlobalSymbolResolve,
|
st::{quick_contains_byte, raw::S_GEN_FROM_INTERP},
|
||||||
SymbolId,
|
GlobalSymbolIntern, GlobalSymbolResolve, SymbolId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::{error::Error, fmt::Display};
|
use std::{error::Error, fmt::Display};
|
||||||
|
@ -266,7 +272,7 @@ impl ParseState for InterpState {
|
||||||
// filter out non-interpolated strings quickly,
|
// filter out non-interpolated strings quickly,
|
||||||
// before we start to parse.
|
// before we start to parse.
|
||||||
// Symbols that require no interpoolation are simply echoed back.
|
// Symbols that require no interpoolation are simply echoed back.
|
||||||
Ready => match tok.symbol() {
|
Ready => match tok.concrete_symbol() {
|
||||||
Some(sym) if needs_interpolation(sym) => {
|
Some(sym) if needs_interpolation(sym) => {
|
||||||
Transition(GenIdent(sym))
|
Transition(GenIdent(sym))
|
||||||
.ok(Nir::Open(NirEntity::TplParam, span))
|
.ok(Nir::Open(NirEntity::TplParam, span))
|
||||||
|
@ -282,29 +288,29 @@ impl ParseState for InterpState {
|
||||||
let GenIdentSymbolId(ident_sym) = gen_ident;
|
let GenIdentSymbolId(ident_sym) = gen_ident;
|
||||||
|
|
||||||
Transition(GenDesc(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)
|
.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) => {
|
GenDesc(sym, gen_ident) => {
|
||||||
let s = sym.lookup_str();
|
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,
|
// Begin parsing in a _literal_ context,
|
||||||
// since interpolation is most commonly utilized with literal
|
// since interpolation is most commonly utilized with literal
|
||||||
// prefixes.
|
// prefixes.
|
||||||
Transition(ParseLiteralAt(s, gen_ident, 0))
|
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)
|
.with_lookahead(tok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +464,19 @@ impl ParseState for InterpState {
|
||||||
// generated.
|
// generated.
|
||||||
// We finally release the lookahead symbol.
|
// We finally release the lookahead symbol.
|
||||||
FinishSym(_, GenIdentSymbolId(gen_param)) => {
|
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,
|
nir::NirEntity,
|
||||||
parse::{Parsed, ParsedResult, Parser},
|
parse::{Parsed, ParsedResult, Parser},
|
||||||
span::dummy::{DUMMY_CONTEXT as DC, *},
|
span::dummy::{DUMMY_CONTEXT as DC, *},
|
||||||
sym::GlobalSymbolResolve,
|
|
||||||
};
|
};
|
||||||
use std::assert_matches::assert_matches;
|
use std::assert_matches::assert_matches;
|
||||||
use Parsed::*;
|
use Parsed::*;
|
||||||
|
@ -81,7 +80,6 @@ fn does_not_desugar_text() {
|
||||||
|
|
||||||
fn expect_expanded_header(
|
fn expect_expanded_header(
|
||||||
sut: &mut Parser<InterpState, std::vec::IntoIter<Nir>>,
|
sut: &mut Parser<InterpState, std::vec::IntoIter<Nir>>,
|
||||||
given_val: &str,
|
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> SymbolId {
|
) -> SymbolId {
|
||||||
let GenIdentSymbolId(expect_name) = gen_tpl_param_ident_at_offset(span);
|
let GenIdentSymbolId(expect_name) = gen_tpl_param_ident_at_offset(span);
|
||||||
|
@ -99,18 +97,104 @@ fn expect_expanded_header(
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sut.next(),
|
sut.next(),
|
||||||
Some(Ok(Object(Nir::BindIdent(SPair(expect_name_sym, span))))),
|
Some(Ok(Object(Nir::BindIdentMeta(SPair(expect_name_sym, span))))),
|
||||||
);
|
);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
sut.next(),
|
sut.next(),
|
||||||
Some(Ok(Object(Nir::Desc(SPair(desc_str, desc_span)))))
|
Some(Ok(Object(Nir::Desc(SPair(S_GEN_FROM_INTERP, desc_span)))))
|
||||||
if desc_str.lookup_str().contains(given_val)
|
if desc_span == span
|
||||||
&& desc_span == span
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect_name_sym
|
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,
|
// When ending with an interpolated variable,
|
||||||
// the parser should recognize that we've returned to the outer literal
|
// the parser should recognize that we've returned to the outer literal
|
||||||
// context and permit successful termination of the specification string.
|
// 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 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!(
|
assert_eq!(
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
|
@ -182,7 +266,7 @@ fn desugars_var_with_ending_literal() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
|
@ -219,7 +303,7 @@ fn desugars_many_vars_and_literals() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
|
@ -269,7 +353,7 @@ fn proper_multibyte_handling() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
|
@ -310,7 +394,7 @@ fn desugars_adjacent_interpolated_vars() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
|
@ -345,7 +429,7 @@ fn error_missing_closing_interp_delim() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
vec![
|
vec![
|
||||||
|
@ -391,7 +475,7 @@ fn error_nested_delim() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
vec![
|
vec![
|
||||||
|
@ -440,7 +524,7 @@ fn error_empty_interp() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
vec![
|
vec![
|
||||||
|
@ -482,7 +566,7 @@ fn error_close_before_open() {
|
||||||
|
|
||||||
let mut sut = Sut::parse(toks.into_iter());
|
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!(
|
assert_eq!(
|
||||||
vec![
|
vec![
|
||||||
|
|
|
@ -1425,9 +1425,10 @@ ele_parse! {
|
||||||
/// expanded.
|
/// expanded.
|
||||||
TplParamStmt := QN_PARAM(_, ospan) {
|
TplParamStmt := QN_PARAM(_, ospan) {
|
||||||
@ {
|
@ {
|
||||||
QN_NAME => TodoAttr,
|
QN_NAME => BindIdentMeta,
|
||||||
QN_DESC => TodoAttr,
|
QN_DESC => Desc,
|
||||||
} => Todo(ospan.into()),
|
} => Nir::Open(NirEntity::TplParam, ospan.into()),
|
||||||
|
/(cspan) => Nir::Close(NirEntity::TplParam, cspan.into()),
|
||||||
|
|
||||||
TplParamDefault,
|
TplParamDefault,
|
||||||
};
|
};
|
||||||
|
@ -1457,10 +1458,16 @@ ele_parse! {
|
||||||
/// providing constant values.
|
/// providing constant values.
|
||||||
/// The result will be as if the user typed the text themselves in the
|
/// The result will be as if the user typed the text themselves in the
|
||||||
/// associated template application argument.
|
/// 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) {
|
TplText := QN_TEXT(_, ospan) {
|
||||||
@ {
|
@ {
|
||||||
QN_UNIQUE => TodoAttr,
|
QN_UNIQUE => TodoAttr,
|
||||||
} => Todo(ospan.into()),
|
} => Noop(ospan.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Default the param to the value of another template param,
|
/// Default the param to the value of another template param,
|
||||||
|
@ -1474,7 +1481,7 @@ ele_parse! {
|
||||||
/// cumbersome and slow
|
/// cumbersome and slow
|
||||||
TplParamValue := QN_PARAM_VALUE(_, ospan) {
|
TplParamValue := QN_PARAM_VALUE(_, ospan) {
|
||||||
@ {
|
@ {
|
||||||
QN_NAME => TodoAttr,
|
QN_NAME => Ref,
|
||||||
QN_DASH => TodoAttr,
|
QN_DASH => TodoAttr,
|
||||||
QN_UPPER => TodoAttr,
|
QN_UPPER => TodoAttr,
|
||||||
QN_LOWER => TodoAttr,
|
QN_LOWER => TodoAttr,
|
||||||
|
@ -1483,7 +1490,7 @@ ele_parse! {
|
||||||
QN_RMUNDERSCORE => TodoAttr,
|
QN_RMUNDERSCORE => TodoAttr,
|
||||||
QN_IDENTIFIER => TodoAttr,
|
QN_IDENTIFIER => TodoAttr,
|
||||||
QN_SNAKE => TodoAttr,
|
QN_SNAKE => TodoAttr,
|
||||||
} => Todo(ospan.into()),
|
} => Noop(ospan.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Inherit a default value from a metavalue.
|
/// Inherit a default value from a metavalue.
|
||||||
|
@ -1696,7 +1703,7 @@ ele_parse! {
|
||||||
/// which gets desugared into this via [`super::tplshort`].
|
/// which gets desugared into this via [`super::tplshort`].
|
||||||
ApplyTemplateParam := QN_WITH_PARAM(_, ospan) {
|
ApplyTemplateParam := QN_WITH_PARAM(_, ospan) {
|
||||||
@ {
|
@ {
|
||||||
QN_NAME => BindIdent,
|
QN_NAME => BindIdentMeta,
|
||||||
QN_VALUE => Text,
|
QN_VALUE => Text,
|
||||||
} => Nir::Open(NirEntity::TplParam, ospan.into()),
|
} => Nir::Open(NirEntity::TplParam, ospan.into()),
|
||||||
/(cspan) => Nir::Close(NirEntity::TplParam, cspan.into()),
|
/(cspan) => Nir::Close(NirEntity::TplParam, cspan.into()),
|
||||||
|
|
|
@ -80,7 +80,7 @@ use arrayvec::ArrayVec;
|
||||||
|
|
||||||
use super::{Nir, NirEntity};
|
use super::{Nir, NirEntity};
|
||||||
use crate::{
|
use crate::{
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
fmt::TtQuote,
|
||||||
parse::prelude::*,
|
parse::prelude::*,
|
||||||
span::Span,
|
span::Span,
|
||||||
sym::{
|
sym::{
|
||||||
|
@ -88,7 +88,6 @@ use crate::{
|
||||||
SymbolId,
|
SymbolId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::convert::Infallible;
|
|
||||||
|
|
||||||
use Nir::*;
|
use Nir::*;
|
||||||
use NirEntity::*;
|
use NirEntity::*;
|
||||||
|
@ -130,10 +129,14 @@ impl Display for TplShortDesugar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diagnostic_infallible! {
|
||||||
|
pub enum TplShortDesugarError {}
|
||||||
|
}
|
||||||
|
|
||||||
impl ParseState for TplShortDesugar {
|
impl ParseState for TplShortDesugar {
|
||||||
type Token = Nir;
|
type Token = Nir;
|
||||||
type Object = Nir;
|
type Object = Nir;
|
||||||
type Error = Infallible;
|
type Error = TplShortDesugarError;
|
||||||
type Context = Stack;
|
type Context = Stack;
|
||||||
|
|
||||||
fn parse_token(
|
fn parse_token(
|
||||||
|
@ -162,6 +165,8 @@ impl ParseState for TplShortDesugar {
|
||||||
let tpl_name =
|
let tpl_name =
|
||||||
format!("_{}_", qname.local_name().lookup_str()).intern();
|
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);
|
let name = SPair(tpl_name, span);
|
||||||
stack.push(Ref(name));
|
stack.push(Ref(name));
|
||||||
|
|
||||||
|
@ -176,7 +181,7 @@ impl ParseState for TplShortDesugar {
|
||||||
// note: reversed (stack)
|
// note: reversed (stack)
|
||||||
stack.push(Close(TplParam, span));
|
stack.push(Close(TplParam, span));
|
||||||
stack.push(Text(val));
|
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))
|
Transition(DesugaringParams(ospan)).ok(Open(TplParam, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +217,7 @@ impl ParseState for TplShortDesugar {
|
||||||
stack.push(Close(TplApply, ospan));
|
stack.push(Close(TplApply, ospan));
|
||||||
stack.push(Close(TplParam, ospan));
|
stack.push(Close(TplParam, ospan));
|
||||||
stack.push(Text(SPair(gen_name, 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
|
// Note that we must have `tok` as lookahead instead of
|
||||||
// pushing directly on the stack in case it's a
|
// pushing directly on the stack in case it's a
|
||||||
|
|
|
@ -86,7 +86,7 @@ fn desugars_unary() {
|
||||||
|
|
||||||
O(Open(TplParam, S2)),
|
O(Open(TplParam, S2)),
|
||||||
// Derived from `aname` (by padding)
|
// Derived from `aname` (by padding)
|
||||||
O(BindIdent(pname)),
|
O(BindIdentMeta(pname)),
|
||||||
// The value is left untouched.
|
// The value is left untouched.
|
||||||
O(Text(pval)),
|
O(Text(pval)),
|
||||||
// Close is derived from open.
|
// 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
|
// @values@ remains lexical by referencing the name of a
|
||||||
// template we're about to generate.
|
// template we're about to generate.
|
||||||
O(Open(TplParam, S1)),
|
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(Text(SPair(gen_name, S1))), //:-.
|
||||||
O(Close(TplParam, S1)), // |
|
O(Close(TplParam, S1)), // |
|
||||||
O(Close(TplApply, S1)), // |
|
O(Close(TplApply, S1)), // |
|
||||||
|
@ -193,7 +193,7 @@ fn desugar_nested_apply() {
|
||||||
|
|
||||||
// @values@
|
// @values@
|
||||||
O(Open(TplParam, S1)),
|
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(Text(SPair(gen_name_outer, S1))), //:-.
|
||||||
O(Close(TplParam, S1)), // |
|
O(Close(TplParam, S1)), // |
|
||||||
O(Close(TplApply, S1)), // |
|
O(Close(TplApply, S1)), // |
|
||||||
|
@ -227,7 +227,7 @@ fn does_not_desugar_long_form() {
|
||||||
BindIdent(name),
|
BindIdent(name),
|
||||||
|
|
||||||
Open(TplParam, S3),
|
Open(TplParam, S3),
|
||||||
BindIdent(pname),
|
BindIdentMeta(pname),
|
||||||
Text(pval),
|
Text(pval),
|
||||||
Close(TplParam, S6),
|
Close(TplParam, S6),
|
||||||
Close(TplApply, S7),
|
Close(TplApply, S7),
|
||||||
|
@ -244,7 +244,7 @@ fn does_not_desugar_long_form() {
|
||||||
O(BindIdent(name)),
|
O(BindIdent(name)),
|
||||||
|
|
||||||
O(Open(TplParam, S3)),
|
O(Open(TplParam, S3)),
|
||||||
O(BindIdent(pname)),
|
O(BindIdentMeta(pname)),
|
||||||
O(Text(pval)),
|
O(Text(pval)),
|
||||||
O(Close(TplParam, S6)),
|
O(Close(TplParam, S6)),
|
||||||
O(Close(TplApply, S7)),
|
O(Close(TplApply, S7)),
|
||||||
|
|
|
@ -29,6 +29,7 @@ use fxhash::FxHashSet;
|
||||||
use crate::{
|
use crate::{
|
||||||
asg::{air::Air, IdentKind, Source},
|
asg::{air::Air, IdentKind, Source},
|
||||||
diagnose::{AnnotatedSpan, Diagnostic},
|
diagnose::{AnnotatedSpan, Diagnostic},
|
||||||
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
obj::xmlo::{SymAttrs, SymType},
|
obj::xmlo::{SymAttrs, SymType},
|
||||||
parse::{util::SPair, ParseState, ParseStatus, Transition, Transitionable},
|
parse::{util::SPair, ParseState, ParseStatus, Transition, Transitionable},
|
||||||
span::Span,
|
span::Span,
|
||||||
|
@ -95,6 +96,7 @@ pub enum XmloToAir {
|
||||||
PackageFound(Span),
|
PackageFound(Span),
|
||||||
Package(PackageSPair),
|
Package(PackageSPair),
|
||||||
SymDep(PackageSPair, SPair),
|
SymDep(PackageSPair, SPair),
|
||||||
|
SymDepEnded(PackageSPair, Span),
|
||||||
/// End of header (EOH) reached.
|
/// End of header (EOH) reached.
|
||||||
Done(Span),
|
Done(Span),
|
||||||
}
|
}
|
||||||
|
@ -212,13 +214,22 @@ impl ParseState for XmloToAir {
|
||||||
.transition(Package(pkg_name))
|
.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))
|
Transition(Package(pkg_name)).ok(Air::IdentFragment(name, text))
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't need to read any further than the end of the
|
// We don't need to read any further than the end of the
|
||||||
// header (symtable, sym-deps, fragments).
|
// 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,
|
// It's important to set this _after_ we're done processing,
|
||||||
// otherwise our `first` checks above will be inaccurate.
|
// otherwise our `first` checks above will be inaccurate.
|
||||||
ctx.first = false;
|
ctx.first = false;
|
||||||
|
@ -234,15 +245,36 @@ impl ParseState for XmloToAir {
|
||||||
tok @ (PkgStart(..) | PkgName(..) | Symbol(..)),
|
tok @ (PkgStart(..) | PkgName(..) | Symbol(..)),
|
||||||
) => Transition(st).dead(tok),
|
) => 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 {
|
fn is_accepting(&self, _: &Self::Context) -> bool {
|
||||||
matches!(*self, Self::Done(_))
|
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 {
|
impl Display for XmloToAir {
|
||||||
|
@ -258,6 +290,13 @@ impl Display for XmloToAir {
|
||||||
SymDep(pkg_name, sym) => {
|
SymDep(pkg_name, sym) => {
|
||||||
write!(f, "expecting dependency for symbol `/{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"),
|
Done(_) => write!(f, "done lowering xmlo into AIR"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,9 +102,10 @@ fn adds_sym_deps() {
|
||||||
PkgName(SPair(name, S2)),
|
PkgName(SPair(name, S2)),
|
||||||
|
|
||||||
SymDepStart(SPair(sym_from, S3)),
|
SymDepStart(SPair(sym_from, S3)),
|
||||||
Symbol(SPair(sym_to1, S4)),
|
Symbol(SPair(sym_to1, S4)),
|
||||||
Symbol(SPair(sym_to2, S5)),
|
Symbol(SPair(sym_to2, S5)),
|
||||||
Eoh(S6),
|
SymDepEnd(S6),
|
||||||
|
Eoh(S7),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -115,7 +116,42 @@ fn adds_sym_deps() {
|
||||||
Incomplete, // SymDepStart
|
Incomplete, // SymDepStart
|
||||||
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))),
|
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))),
|
||||||
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to2, S5))),
|
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(),
|
Sut::parse(toks.into_iter()).collect(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,8 +25,8 @@ use crate::{
|
||||||
num::{Dim, Dtype},
|
num::{Dim, Dtype},
|
||||||
obj::xmlo::SymType,
|
obj::xmlo::SymType,
|
||||||
parse::{
|
parse::{
|
||||||
self, util::SPair, ClosedParseState, EmptyContext, NoContext,
|
self, util::SPair, NoContext, ParseState, Token, Transition,
|
||||||
ParseState, Token, Transition, TransitionResult, Transitionable,
|
TransitionResult, Transitionable,
|
||||||
},
|
},
|
||||||
span::Span,
|
span::Span,
|
||||||
sym::{st::raw, GlobalSymbolIntern, GlobalSymbolResolve, SymbolId},
|
sym::{st::raw, GlobalSymbolIntern, GlobalSymbolResolve, SymbolId},
|
||||||
|
@ -80,6 +80,17 @@ pub enum XmloToken {
|
||||||
/// object file representing the source location of this symbol.
|
/// object file representing the source location of this symbol.
|
||||||
Symbol(SPair),
|
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.
|
/// Text (compiled code) fragment for a given symbol.
|
||||||
///
|
///
|
||||||
/// This contains the compiler output for a given symbol,
|
/// This contains the compiler output for a given symbol,
|
||||||
|
@ -120,6 +131,7 @@ impl Token for XmloToken {
|
||||||
| SymDecl(SPair(_, span), _)
|
| SymDecl(SPair(_, span), _)
|
||||||
| SymDepStart(SPair(_, span))
|
| SymDepStart(SPair(_, span))
|
||||||
| Symbol(SPair(_, span))
|
| Symbol(SPair(_, span))
|
||||||
|
| SymDepEnd(span)
|
||||||
| Fragment(SPair(_, span), _)
|
| Fragment(SPair(_, span), _)
|
||||||
| Eoh(span) => *span,
|
| Eoh(span) => *span,
|
||||||
}
|
}
|
||||||
|
@ -155,6 +167,7 @@ impl Display for XmloToken {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Symbol(sym) => write!(f, "symbol {}", TtQuote::wrap(sym)),
|
Symbol(sym) => write!(f, "symbol {}", TtQuote::wrap(sym)),
|
||||||
|
SymDepEnd(_) => write!(f, "end of symbol dependencies"),
|
||||||
Fragment(sym, _) => {
|
Fragment(sym, _) => {
|
||||||
write!(f, "symbol {} code fragment", TtQuote::wrap(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)]
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
pub enum XmloReader<
|
pub enum XmloReader {
|
||||||
SS: XmloState = SymtableState,
|
|
||||||
SD: XmloState = SymDepsState,
|
|
||||||
SF: XmloState = FragmentsState,
|
|
||||||
> {
|
|
||||||
/// Parser has not yet processed any input.
|
/// Parser has not yet processed any input.
|
||||||
#[default]
|
#[default]
|
||||||
Ready,
|
Ready,
|
||||||
/// Processing `package` attributes.
|
/// Processing `package` attributes.
|
||||||
Package(Span),
|
Package(Span),
|
||||||
/// Expecting a symbol declaration or closing `preproc:symtable`.
|
/// Expecting a symbol declaration or closing `preproc:symtable`.
|
||||||
Symtable(Span, SS),
|
Symtable(Span, SymtableState),
|
||||||
/// Symbol dependencies are expected next.
|
/// Symbol dependencies are expected next.
|
||||||
SymDepsExpected,
|
SymDepsExpected,
|
||||||
/// Expecting symbol dependency list or closing `preproc:sym-deps`.
|
/// Expecting symbol dependency list or closing `preproc:sym-deps`.
|
||||||
SymDeps(Span, SD),
|
SymDeps(Span, SymDepsState),
|
||||||
/// Compiled text fragments are expected next.
|
/// Compiled text fragments are expected next.
|
||||||
FragmentsExpected,
|
FragmentsExpected,
|
||||||
/// Expecting text fragment or closing `preproc:fragments`.
|
/// Expecting text fragment or closing `preproc:fragments`.
|
||||||
Fragments(Span, SF),
|
Fragments(Span, FragmentsState),
|
||||||
/// End of header parsing.
|
/// End of header parsing.
|
||||||
Eoh,
|
Eoh,
|
||||||
/// `xmlo` file has been fully read.
|
/// `xmlo` file has been fully read.
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
impl ParseState for XmloReader {
|
||||||
for XmloReader<SS, SD, SF>
|
|
||||||
{
|
|
||||||
type Token = Xirf<Text>;
|
type Token = Xirf<Text>;
|
||||||
type Object = XmloToken;
|
type Object = XmloToken;
|
||||||
type Error = XmloError;
|
type Error = XmloError;
|
||||||
|
@ -249,7 +248,7 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
||||||
(Package(_), Xirf::Close(..)) => Transition(Done).incomplete(),
|
(Package(_), Xirf::Close(..)) => Transition(Done).incomplete(),
|
||||||
|
|
||||||
(Package(_), Xirf::Open(QN_P_SYMTABLE, span, ..)) => {
|
(Package(_), Xirf::Open(QN_P_SYMTABLE, span, ..)) => {
|
||||||
Transition(Symtable(span.tag_span(), SS::default()))
|
Transition(Symtable(span.tag_span(), SymtableState::default()))
|
||||||
.incomplete()
|
.incomplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,14 +268,15 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
||||||
),
|
),
|
||||||
|
|
||||||
(SymDepsExpected, Xirf::Open(QN_P_SYM_DEPS, span, _)) => {
|
(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) =>
|
SymDeps(_, sd),
|
||||||
{
|
Xirf::Close(None | Some(QN_P_SYM_DEPS), cspan, _),
|
||||||
Transition(FragmentsExpected).incomplete()
|
) if sd.is_accepting(ctx) => Transition(FragmentsExpected)
|
||||||
}
|
.ok(XmloToken::SymDepEnd(cspan.span())),
|
||||||
|
|
||||||
(SymDeps(span, sd), tok) => sd.delegate(
|
(SymDeps(span, sd), tok) => sd.delegate(
|
||||||
tok,
|
tok,
|
||||||
|
@ -286,8 +286,11 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
|
||||||
),
|
),
|
||||||
|
|
||||||
(FragmentsExpected, Xirf::Open(QN_P_FRAGMENTS, span, _)) => {
|
(FragmentsExpected, Xirf::Open(QN_P_FRAGMENTS, span, _)) => {
|
||||||
Transition(Fragments(span.tag_span(), SF::default()))
|
Transition(Fragments(
|
||||||
.incomplete()
|
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 {
|
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
|
impl Display for XmloReader {
|
||||||
for XmloReader<SS, SD, SF>
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
use XmloReader::*;
|
use XmloReader::*;
|
||||||
|
|
||||||
|
|
|
@ -701,6 +701,7 @@ fn xmlo_composite_parsers_header() {
|
||||||
O(PkgStart(S1)),
|
O(PkgStart(S1)),
|
||||||
O(SymDecl(SPair(sym_name, S3), Default::default(),)),
|
O(SymDecl(SPair(sym_name, S3), Default::default(),)),
|
||||||
O(SymDepStart(SPair(symdep_name, S3))),
|
O(SymDepStart(SPair(symdep_name, S3))),
|
||||||
|
O(SymDepEnd(S3)),
|
||||||
O(Fragment(SPair(symfrag_id, S4), frag)),
|
O(Fragment(SPair(symfrag_id, S4), frag)),
|
||||||
O(Eoh(S3)),
|
O(Eoh(S3)),
|
||||||
]),
|
]),
|
||||||
|
@ -711,3 +712,45 @@ fn xmlo_composite_parsers_header() {
|
||||||
.collect(),
|
.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 error::{FinalizeError, ParseError};
|
||||||
pub use lower::{
|
pub use lower::{
|
||||||
lowerable, terminal, FromParseError, Lower, LowerIter, LowerSource,
|
lowerable, terminal, FromParseError, Lower, LowerIter, LowerSource,
|
||||||
ParsedObject,
|
ParseStateError, ParsedObject,
|
||||||
};
|
};
|
||||||
pub use parser::{FinalizedParser, Parsed, ParsedResult, Parser};
|
pub use parser::{FinalizedParser, Parsed, ParsedResult, Parser};
|
||||||
pub use state::{
|
pub use state::{
|
||||||
|
@ -58,8 +58,13 @@ pub mod prelude {
|
||||||
TransitionResult, Transitionable,
|
TransitionResult, Transitionable,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Every `Token` must implement `Display`.
|
// Every `Token`.
|
||||||
pub use std::fmt::Display;
|
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`].
|
/// 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`.
|
/// A [`Diagnostic`] error type common to both `S` and `LS`.
|
||||||
///
|
///
|
||||||
/// This error type must be able to accommodate error variants from all
|
/// This error type must be able to accommodate error variants from all
|
||||||
|
@ -235,9 +245,17 @@ where
|
||||||
/// which may then decide what to do
|
/// which may then decide what to do
|
||||||
/// (e.g. report errors and permit recovery,
|
/// (e.g. report errors and permit recovery,
|
||||||
/// or terminate at the first sign of trouble).
|
/// 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>>
|
/// Note that the [`From`] trait bound utilizing `S` is purely a development
|
||||||
+ From<ParseError<<LS as ParseState>::Token, <LS as ParseState>::Error>>;
|
/// 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
|
/// Convenience trait for converting [`From`] a [`ParseError`] for the
|
||||||
/// provided [`ParseState`] `S`.
|
/// provided [`ParseState`] `S`.
|
||||||
|
@ -246,8 +264,7 @@ pub trait WidenedError<S: ParseState, LS: ParseState> = Diagnostic
|
||||||
/// that is almost certainly already utilized,
|
/// that is almost certainly already utilized,
|
||||||
/// rather than having to either import more types or use the verbose
|
/// rather than having to either import more types or use the verbose
|
||||||
/// associated type.
|
/// associated type.
|
||||||
pub trait FromParseError<S: ParseState> =
|
pub trait FromParseError<S: ParseState> = From<ParseStateError<S>>;
|
||||||
From<ParseError<<S as ParseState>::Token, <S as ParseState>::Error>>;
|
|
||||||
|
|
||||||
/// A [`ParsedResult`](super::ParsedResult) with a [`WidenedError`].
|
/// A [`ParsedResult`](super::ParsedResult) with a [`WidenedError`].
|
||||||
pub type WidenedParsedResult<S, E> =
|
pub type WidenedParsedResult<S, E> =
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
//! to use outside of the domain of the parsing system itself.
|
//! to use outside of the domain of the parsing system itself.
|
||||||
|
|
||||||
use super::{prelude::*, state::TransitionData};
|
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;
|
use std::fmt::Display;
|
||||||
|
|
||||||
/// A [`SymbolId`] with a corresponding [`Span`].
|
/// A [`SymbolId`] with a corresponding [`Span`].
|
||||||
|
@ -56,6 +56,22 @@ use std::fmt::Display;
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub struct SPair(pub SymbolId, pub Span);
|
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 {
|
impl SPair {
|
||||||
/// Retrieve the [`SymbolId`] of this pair.
|
/// 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
|
/// Map over the [`SymbolId`] of the pair while retaining the original
|
||||||
/// associated [`Span`].
|
/// 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
|
/// Map over the [`Span`] of the pair while retaining the associated
|
||||||
/// [`SymbolId`].
|
/// [`SymbolId`].
|
||||||
///
|
///
|
||||||
|
|
|
@ -39,15 +39,85 @@
|
||||||
//! The module is responsible for pipeline composition.
|
//! The module is responsible for pipeline composition.
|
||||||
//! For information on the lowering pipeline as an abstraction,
|
//! For information on the lowering pipeline as an abstraction,
|
||||||
//! see [`Lower`].
|
//! 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::{
|
use crate::{
|
||||||
asg::{air::AirAggregate, AsgTreeToXirf},
|
asg::{air::AirAggregate, AsgTreeToXirf},
|
||||||
diagnose::Diagnostic,
|
diagnose::Diagnostic,
|
||||||
nir::{InterpolateNir, NirToAir, TplShortDesugar, XirfToNir},
|
nir::{
|
||||||
|
AbstractBindTranslate, InterpolateNir, NirToAir, TplShortDesugar,
|
||||||
|
XirfToNir,
|
||||||
|
},
|
||||||
obj::xmlo::{XmloReader, XmloToAir, XmloToken},
|
obj::xmlo::{XmloReader, XmloToAir, XmloToken},
|
||||||
parse::{
|
parse::{
|
||||||
terminal, FinalizeError, Lower, LowerSource, ParseError, ParseState,
|
terminal, FinalizeError, Lower, LowerSource, ParseError, ParseState,
|
||||||
Parsed, ParsedObject, UnknownToken,
|
ParseStateError, Parsed, ParsedObject, UnknownToken,
|
||||||
},
|
},
|
||||||
xir::{
|
xir::{
|
||||||
autoclose::XirfAutoClose,
|
autoclose::XirfAutoClose,
|
||||||
|
@ -61,38 +131,58 @@ use crate::{
|
||||||
mod r#macro;
|
mod r#macro;
|
||||||
|
|
||||||
lower_pipeline! {
|
lower_pipeline! {
|
||||||
/// Load an `xmlo` file represented by `src` into the graph held
|
/// Parse a source package into the [ASG](crate::asg) using TAME's XML
|
||||||
/// by `air_ctx`.
|
/// 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
|
/// 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.
|
/// To parse sources instead,
|
||||||
///
|
/// see [`parse_package_xml`].
|
||||||
/// TODO: More documentation once this has been further cleaned up.
|
pub load_xmlo -> LoadXmlo
|
||||||
pub load_xmlo
|
|
||||||
|> PartialXirToXirf<4, Text>
|
|> PartialXirToXirf<4, Text>
|
||||||
|> XmloReader
|
|> XmloReader
|
||||||
|> XmloToAir[xmlo_ctx], until (XmloToken::Eoh(..))
|
|> XmloToAir[xmlo_ctx], until (XmloToken::Eoh(..))
|
||||||
|> AirAggregate[air_ctx];
|
|> 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
|
/// Lower an [`Asg`](crate::asg::Asg)-derived token stream into an
|
||||||
/// `xmli` file.
|
/// `xmli` file.
|
||||||
///
|
///
|
||||||
/// TODO: More documentation once this has been further cleaned up.
|
/// After a package has been parsed with [`parse_package_xml`] and
|
||||||
pub lower_xmli<'a>
|
/// 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]
|
|> AsgTreeToXirf<'a>[asg]
|
||||||
|> XirfAutoClose
|
|> XirfAutoClose
|
||||||
|> XirfToXir<Text>;
|
|> XirfToXir<Text>;
|
||||||
|
|
|
@ -25,6 +25,103 @@
|
||||||
//! and to see TAMER's pipelines,
|
//! and to see TAMER's pipelines,
|
||||||
//! see the [parent module](super).
|
//! 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.
|
/// Declaratively define a lowering pipeline.
|
||||||
///
|
///
|
||||||
/// A lowering pipeline stitches together parsers such that the objects of
|
/// A lowering pipeline stitches together parsers such that the objects of
|
||||||
|
@ -42,9 +139,49 @@
|
||||||
macro_rules! lower_pipeline {
|
macro_rules! lower_pipeline {
|
||||||
($(
|
($(
|
||||||
$(#[$meta:meta])*
|
$(#[$meta:meta])*
|
||||||
$vis:vis $fn:ident$(<$l:lifetime>)?
|
$vis:vis $fn:ident$(<$l:lifetime>)? -> $struct:ident
|
||||||
$(|> $lower:ty $([$ctx:ident])? $(, until ($until:pat))?)*;
|
$(|>
|
||||||
|
$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])*
|
$(#[$meta])*
|
||||||
///
|
///
|
||||||
/// Pipeline Definition
|
/// Pipeline Definition
|
||||||
|
@ -72,47 +209,35 @@ macro_rules! lower_pipeline {
|
||||||
/// of the types of all parsers in the pipeline.
|
/// of the types of all parsers in the pipeline.
|
||||||
/// It can be understood as:
|
/// It can be understood as:
|
||||||
///
|
///
|
||||||
/// 1. A function accepting three classes of arguments:
|
/// 1. A function accepting _context_ for whatever parsers request
|
||||||
/// 1. The _source_ token stream,
|
/// it,
|
||||||
/// which consists of tokens expected by the first parser
|
/// allowing both for configuration and for state to
|
||||||
/// in the pipeline;
|
/// persist between separate pipelines.
|
||||||
/// 2. _Context_ for certain parsers that request it,
|
/// This returns a closure representing a configured pipeline.
|
||||||
/// allowing for state to persist between separate
|
/// 2. The _source_ token stream is accepted by the closure,
|
||||||
/// pipelines; and
|
/// which consists of tokens expected by the first parser
|
||||||
/// 3. A _sink_ that serves as the final destination for the
|
/// in the pipeline;
|
||||||
/// token stream.
|
/// 3. A _sink_ serves as the final destination for the token
|
||||||
/// 2. A [`Result`] consisting of the updated context that was
|
/// stream.
|
||||||
|
/// 4. A [`Result`] consisting of the updated context that was
|
||||||
/// originally passed into the function,
|
/// originally passed into the function,
|
||||||
/// so that it may be utilized in future pipelines.
|
/// 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.
|
/// compilation should continue despite an error.
|
||||||
/// All parsers are expected to perform their own error
|
/// See [`crate::pipeline`] for more information.
|
||||||
/// recovery in an attempt to continue parsing to discover
|
/// 6. An _unrecoverable error_ type `EU` that may be yielded by
|
||||||
/// 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
|
|
||||||
/// the sink to terminate compilation immediately.
|
/// the sink to terminate compilation immediately.
|
||||||
/// This is a component of the [`Result`] type that is
|
/// This is a component of the [`Result`] type that is
|
||||||
/// ultimately yielded as the result of this function.
|
/// ultimately yielded as the result of this function.
|
||||||
$vis fn $fn<$($l,)? ES: Diagnostic, ER: Diagnostic, EU: Diagnostic>(
|
$vis fn $fn<$($l,)? ES: Diagnostic + 'static, EU: Diagnostic, SA, SB>(
|
||||||
src: impl LowerSource<
|
|
||||||
UnknownToken,
|
|
||||||
lower_pipeline!(@first_tok_ty $($lower),*),
|
|
||||||
ES
|
|
||||||
>,
|
|
||||||
$(
|
$(
|
||||||
// Each parser may optionally receive context from an
|
// Each parser may optionally receive context from an
|
||||||
// earlier run.
|
// earlier run.
|
||||||
$($ctx: impl Into<<$lower as ParseState>::PubContext>,)?
|
$($ctx: impl Into<<$lower as ParseState>::PubContext>,)?
|
||||||
)*
|
)*
|
||||||
sink: impl FnMut(
|
) -> impl FnOnce(SA, SB) -> Result<
|
||||||
Result<lower_pipeline!(@last_obj_ty $($lower),*), ER>
|
|
||||||
) -> Result<(), EU>,
|
|
||||||
) -> Result<
|
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
// Any context that is passed in is also returned so
|
// Any context that is passed in is also returned so
|
||||||
|
@ -124,21 +249,6 @@ macro_rules! lower_pipeline {
|
||||||
EU
|
EU
|
||||||
>
|
>
|
||||||
where
|
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
|
// Unrecoverable errors (EU) are errors that the sink chooses
|
||||||
// not to handle.
|
// not to handle.
|
||||||
// It is constructed explicitly from the sink,
|
// 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
|
// which is _not_ an error that parsers are expected to
|
||||||
// recover from.
|
// recover from.
|
||||||
EU: From<FinalizeError>,
|
EU: From<FinalizeError>,
|
||||||
{
|
|
||||||
let lower_pipeline!(@ret_pat $($($ctx)?)*) = lower_pipeline!(
|
|
||||||
@body_head(src, sink)
|
|
||||||
$((|> $lower $([$ctx])? $(, until ($until))?))*
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(($(
|
SA: LowerSource<
|
||||||
$($ctx,)?
|
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) => {
|
(@ret_ctx_ty $lower:ty, $_ctx:ident) => {
|
||||||
<$lower as ParseState>::PubContext
|
<$lower as ParseState>::PubContext
|
||||||
|
@ -200,8 +326,8 @@ macro_rules! lower_pipeline {
|
||||||
Lower::<
|
Lower::<
|
||||||
ParsedObject<UnknownToken, _, ES>,
|
ParsedObject<UnknownToken, _, ES>,
|
||||||
$head,
|
$head,
|
||||||
ER,
|
ER<ES>,
|
||||||
>::lower::<_, EU>(&mut $src.map(|result| result.map_err(ER::from)), |next| {
|
>::lower::<_, EU>(&mut $src.map(|result| result.map_err(ER::Src)), |next| {
|
||||||
lower_pipeline!(
|
lower_pipeline!(
|
||||||
@body_inner(next, $head, $sink)
|
@body_inner(next, $head, $sink)
|
||||||
$($rest)*
|
$($rest)*
|
||||||
|
@ -217,9 +343,9 @@ macro_rules! lower_pipeline {
|
||||||
Lower::<
|
Lower::<
|
||||||
ParsedObject<UnknownToken, _, ES>,
|
ParsedObject<UnknownToken, _, ES>,
|
||||||
$head,
|
$head,
|
||||||
ER,
|
ER<ES>,
|
||||||
>::lower_with_context::<_, EU>(
|
>::lower_with_context::<_, EU>(
|
||||||
&mut $src.map(|result| result.map_err(ER::from)),
|
&mut $src.map(|result| result.map_err(ER::Src)),
|
||||||
$ctx,
|
$ctx,
|
||||||
|next| {
|
|next| {
|
||||||
lower_pipeline!(
|
lower_pipeline!(
|
||||||
|
|
|
@ -733,6 +733,8 @@ pub mod st {
|
||||||
URI_LV_TPL: uri "http://www.lovullo.com/rater/apply-template",
|
URI_LV_TPL: uri "http://www.lovullo.com/rater/apply-template",
|
||||||
URI_LV_WORKSHEET: uri "http://www.lovullo.com/rater/worksheet",
|
URI_LV_WORKSHEET: uri "http://www.lovullo.com/rater/worksheet",
|
||||||
|
|
||||||
|
S_GEN_FROM_INTERP: str "Generated from interpolated string",
|
||||||
|
|
||||||
// Common whitespace.
|
// Common whitespace.
|
||||||
//
|
//
|
||||||
// _This does not represent all forms of whitespace!_
|
// _This does not represent all forms of whitespace!_
|
||||||
|
|
|
@ -101,12 +101,11 @@ use super::{
|
||||||
CloseSpan, OpenSpan, QName,
|
CloseSpan, OpenSpan, QName,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
f::Functor,
|
f::Map,
|
||||||
parse::prelude::*,
|
parse::prelude::*,
|
||||||
span::{Span, UNKNOWN_SPAN},
|
span::{Span, UNKNOWN_SPAN},
|
||||||
xir::EleSpan,
|
xir::EleSpan,
|
||||||
};
|
};
|
||||||
use std::{convert::Infallible, fmt::Display};
|
|
||||||
|
|
||||||
use XirfAutoClose::*;
|
use XirfAutoClose::*;
|
||||||
|
|
||||||
|
@ -138,10 +137,14 @@ impl Display for XirfAutoClose {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diagnostic_infallible! {
|
||||||
|
pub enum XirfAutoCloseError {}
|
||||||
|
}
|
||||||
|
|
||||||
impl ParseState for XirfAutoClose {
|
impl ParseState for XirfAutoClose {
|
||||||
type Token = XirfToken<Text>;
|
type Token = XirfToken<Text>;
|
||||||
type Object = XirfToken<Text>;
|
type Object = XirfToken<Text>;
|
||||||
type Error = Infallible;
|
type Error = XirfAutoCloseError;
|
||||||
type Context = AutoCloseStack;
|
type Context = AutoCloseStack;
|
||||||
|
|
||||||
fn parse_token(
|
fn parse_token(
|
||||||
|
|
|
@ -46,20 +46,14 @@ use super::{
|
||||||
CloseSpan, OpenSpan, QName, Token as XirToken, TokenStream,
|
CloseSpan, OpenSpan, QName, Token as XirToken, TokenStream,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
|
f::Map,
|
||||||
f::Functor,
|
|
||||||
parse::prelude::*,
|
parse::prelude::*,
|
||||||
span::Span,
|
span::Span,
|
||||||
sym::{st::is_common_whitespace, GlobalSymbolResolve, SymbolId},
|
sym::{st::is_common_whitespace, GlobalSymbolResolve, SymbolId},
|
||||||
xir::EleSpan,
|
xir::EleSpan,
|
||||||
};
|
};
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use std::{
|
use std::marker::PhantomData;
|
||||||
convert::Infallible,
|
|
||||||
error::Error,
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
marker::PhantomData,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used for organization.
|
// Used for organization.
|
||||||
pub use accept::*;
|
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 {
|
fn map(self, f: impl FnOnce(Depth) -> Depth) -> Self::Target {
|
||||||
use XirfToken::*;
|
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> {
|
impl<T: TextType> ParseState for XirfToXir<T> {
|
||||||
type Token = XirfToken<T>;
|
type Token = XirfToken<T>;
|
||||||
type Object = XirToken;
|
type Object = XirToken;
|
||||||
type Error = Infallible;
|
type Error = XirfToXirError;
|
||||||
|
|
||||||
fn parse_token(
|
fn parse_token(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -619,10 +619,10 @@ fn xirf_to_xir() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// The lowering pipeline above requires compatible errors.
|
// The lowering pipeline above requires compatible errors.
|
||||||
impl From<ParseError<XirfToken<Text>, Infallible>>
|
impl From<ParseError<XirfToken<Text>, XirfToXirError>>
|
||||||
for ParseError<XirToken, XirToXirfError>
|
for ParseError<XirToken, XirToXirfError>
|
||||||
{
|
{
|
||||||
fn from(_value: ParseError<XirfToken<Text>, Infallible>) -> Self {
|
fn from(_value: ParseError<XirfToken<Text>, XirfToXirError>) -> Self {
|
||||||
unreachable!()
|
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
|
express an equivalent program, and the program must be at least as
|
||||||
performant when emitted by TAME XSLT.
|
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
|
# Running Tests
|
||||||
Test are prefixed with `test-*` and are executable. They must be invoked
|
Test are prefixed with `test-*` and are executable. They must be invoked
|
||||||
with the environment variable `PATH_TAMEC` set to the path of `tamec`
|
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:sum>
|
||||||
<c:product />
|
<c:product />
|
||||||
</c:sum>
|
</c:sum>
|
||||||
|
|
||||||
<c:product>
|
|
||||||
<c:sum />
|
|
||||||
</c:product>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="_with-static-mix_"
|
<template name="_with-static-mix_"
|
||||||
desc="Both identified and unidentified that may or may
|
desc="Both identified and unidentified that may or may
|
||||||
not be reachable in expansion context">
|
not be reachable in expansion context">
|
||||||
<c:sum>
|
|
||||||
<c:product />
|
|
||||||
</c:sum>
|
|
||||||
|
|
||||||
<c:product>
|
<c:product>
|
||||||
<c:sum />
|
<c:sum />
|
||||||
</c:product>
|
</c:product>
|
||||||
|
@ -48,9 +40,9 @@
|
||||||
|
|
||||||
<rate yields="tplStaticMix" />
|
<rate yields="tplStaticMix" />
|
||||||
|
|
||||||
<c:sum>
|
<rate yields="tplStaticMixEnd">
|
||||||
<c:product />
|
<c:product />
|
||||||
</c:sum>
|
</rate>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,9 +92,9 @@
|
||||||
|
|
||||||
<template name="_short-hand-nullary-body_" desc="Nullary with body" />
|
<template name="_short-hand-nullary-body_" desc="Nullary with body" />
|
||||||
<apply-template name="_short-hand-nullary-body_">
|
<apply-template name="_short-hand-nullary-body_">
|
||||||
<with-param name="@values@" value="___dsgr-bfe___" />
|
<with-param name="@values@" value="___dsgr-bb5___" />
|
||||||
</apply-template>
|
</apply-template>
|
||||||
<template name="___dsgr-bfe___"
|
<template name="___dsgr-bb5___"
|
||||||
desc="Desugared body of shorthand template application of `_short-hand-nullary-body_`">
|
desc="Desugared body of shorthand template application of `_short-hand-nullary-body_`">
|
||||||
<c:product>
|
<c:product>
|
||||||
<c:sum />
|
<c:sum />
|
||||||
|
@ -113,9 +105,9 @@
|
||||||
<apply-template name="_short-hand-nary-body_">
|
<apply-template name="_short-hand-nary-body_">
|
||||||
<with-param name="@bar@" value="baz" />
|
<with-param name="@bar@" value="baz" />
|
||||||
<with-param name="@baz@" value="quux" />
|
<with-param name="@baz@" value="quux" />
|
||||||
<with-param name="@values@" value="___dsgr-cb5___" />
|
<with-param name="@values@" value="___dsgr-c6c___" />
|
||||||
</apply-template>
|
</apply-template>
|
||||||
<template name="___dsgr-cb5___"
|
<template name="___dsgr-c6c___"
|
||||||
desc="Desugared body of shorthand template application of `_short-hand-nary-body_`">
|
desc="Desugared body of shorthand template application of `_short-hand-nary-body_`">
|
||||||
<c:sum>
|
<c:sum>
|
||||||
<c:product />
|
<c:product />
|
||||||
|
@ -125,9 +117,9 @@
|
||||||
<template name="_short-hand-nullary-outer_"
|
<template name="_short-hand-nullary-outer_"
|
||||||
desc="Outer template holding an inner" />
|
desc="Outer template holding an inner" />
|
||||||
<apply-template name="_short-hand-nullary-outer_">
|
<apply-template name="_short-hand-nullary-outer_">
|
||||||
<with-param name="@values@" value="___dsgr-d99___" />
|
<with-param name="@values@" value="___dsgr-d50___" />
|
||||||
</apply-template>
|
</apply-template>
|
||||||
<template name="___dsgr-d99___"
|
<template name="___dsgr-d50___"
|
||||||
desc="Desugared body of shorthand template application of `_short-hand-nullary-outer_`">
|
desc="Desugared body of shorthand template application of `_short-hand-nullary-outer_`">
|
||||||
<template name="_short-hand-nullary-inner-dfn-inner_"
|
<template name="_short-hand-nullary-inner-dfn-inner_"
|
||||||
desc="Inner template applied inner" />
|
desc="Inner template applied inner" />
|
||||||
|
@ -137,9 +129,9 @@
|
||||||
<template name="_short-hand-nullary-inner-dfn-outer_"
|
<template name="_short-hand-nullary-inner-dfn-outer_"
|
||||||
desc="Define template outer but apply inner" />
|
desc="Define template outer but apply inner" />
|
||||||
<apply-template name="_short-hand-nullary-outer_">
|
<apply-template name="_short-hand-nullary-outer_">
|
||||||
<with-param name="@values@" value="___dsgr-eed___" />
|
<with-param name="@values@" value="___dsgr-ea4___" />
|
||||||
</apply-template>
|
</apply-template>
|
||||||
<template name="___dsgr-eed___"
|
<template name="___dsgr-ea4___"
|
||||||
desc="Desugared body of shorthand template application of `_short-hand-nullary-outer_`">
|
desc="Desugared body of shorthand template application of `_short-hand-nullary-outer_`">
|
||||||
<apply-template name="_short-hand-nullary-inner-dfn-outer_" />
|
<apply-template name="_short-hand-nullary-inner-dfn-outer_" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -148,9 +140,9 @@
|
||||||
desc="Unary with body" />
|
desc="Unary with body" />
|
||||||
<apply-template name="_short-hand-unary-with-body_">
|
<apply-template name="_short-hand-unary-with-body_">
|
||||||
<with-param name="@foo@" value="bar" />
|
<with-param name="@foo@" value="bar" />
|
||||||
<with-param name="@values@" value="___dsgr-fb4___" />
|
<with-param name="@values@" value="___dsgr-f6b___" />
|
||||||
</apply-template>
|
</apply-template>
|
||||||
<template name="___dsgr-fb4___"
|
<template name="___dsgr-f6b___"
|
||||||
desc="Desugared body of shorthand template application of `_short-hand-unary-with-body_`">
|
desc="Desugared body of shorthand template application of `_short-hand-unary-with-body_`">
|
||||||
<template name="_short-hand-unary-with-body-inner_"
|
<template name="_short-hand-unary-with-body-inner_"
|
||||||
desc="Inner template" />
|
desc="Inner template" />
|
||||||
|
@ -190,5 +182,28 @@
|
||||||
<template name="_match-child_" desc="Template with a match child">
|
<template name="_match-child_" desc="Template with a match child">
|
||||||
<match on="foo" value="TRUE" />
|
<match on="foo" value="TRUE" />
|
||||||
</template>
|
</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>
|
</package>
|
||||||
|
|
||||||
|
|
|
@ -20,19 +20,11 @@
|
||||||
<c:sum>
|
<c:sum>
|
||||||
<c:product />
|
<c:product />
|
||||||
</c:sum>
|
</c:sum>
|
||||||
|
|
||||||
<c:product>
|
|
||||||
<c:sum />
|
|
||||||
</c:product>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="_with-static-mix_"
|
<template name="_with-static-mix_"
|
||||||
desc="Both identified and unidentified that may or may
|
desc="Both identified and unidentified that may or may
|
||||||
not be reachable in expansion context">
|
not be reachable in expansion context">
|
||||||
<c:sum>
|
|
||||||
<c:product />
|
|
||||||
</c:sum>
|
|
||||||
|
|
||||||
<c:product> <!-- depth N -->
|
<c:product> <!-- depth N -->
|
||||||
<c:sum /> <!-- depth N+1 -->
|
<c:sum /> <!-- depth N+1 -->
|
||||||
</c:product>
|
</c:product>
|
||||||
|
@ -48,9 +40,9 @@
|
||||||
|
|
||||||
<rate yields="tplStaticMix" /> <!-- begins at depth N+1 -->
|
<rate yields="tplStaticMix" /> <!-- begins at depth N+1 -->
|
||||||
|
|
||||||
<c:sum>
|
<rate yields="tplStaticMixEnd">
|
||||||
<c:product />
|
<c:product />
|
||||||
</c:sum>
|
</rate>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
Short-hand template application.
|
Short-hand template application.
|
||||||
|
@ -186,8 +178,32 @@
|
||||||
we cannot support the generation of each of those things within
|
we cannot support the generation of each of those things within
|
||||||
templates.
|
templates.
|
||||||
|
|
||||||
|
|
||||||
<template name="_match-child_" desc="Template with a match child">
|
<template name="_match-child_" desc="Template with a match child">
|
||||||
<match on="foo" />
|
<match on="foo" />
|
||||||
</template>
|
</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>
|
</package>
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,6 @@ mypath=$(dirname "$0")
|
||||||
# Performing this check within `<()` below won't cause a failure.
|
# Performing this check within `<()` below won't cause a failure.
|
||||||
: "${P_XMLLINT?}" # conf.sh
|
: "${P_XMLLINT?}" # conf.sh
|
||||||
|
|
||||||
tamer-flag-or-exit-ok wip-asg-derived-xmli
|
|
||||||
|
|
||||||
run-test() {
|
run-test() {
|
||||||
local name="${1?Missing test name}"
|
local name="${1?Missing test name}"
|
||||||
local dir="${2?Missing dir}"
|
local dir="${2?Missing dir}"
|
||||||
|
@ -45,8 +43,13 @@ timed-tamec() {
|
||||||
# but it'll be close enough.
|
# but it'll be close enough.
|
||||||
local -i start_ns=$(date +%s%N)
|
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" \
|
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" \
|
&> "$dir/tamec-$out.log" \
|
||||||
|| ret=$?
|
|| ret=$?
|
||||||
|
|
||||||
|
@ -66,7 +69,7 @@ timed-tamec() {
|
||||||
header() {
|
header() {
|
||||||
# allocate enough space based on the path we'll output
|
# allocate enough space based on the path we'll output
|
||||||
local -i mypath_len=${#mypath}
|
local -i mypath_len=${#mypath}
|
||||||
local -i dirlen=$((mypath_len + 12))
|
local -i dirlen=$((mypath_len + 14))
|
||||||
|
|
||||||
# newline intentionally omitted
|
# newline intentionally omitted
|
||||||
printf "%-${dirlen}s %-20s " "$@"
|
printf "%-${dirlen}s %-20s " "$@"
|
||||||
|
@ -101,6 +104,11 @@ test-derive-from-src() {
|
||||||
test-fixpoint() {
|
test-fixpoint() {
|
||||||
local dir="${1?Missing directory name}"
|
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
|
timed-tamec "$dir" out.xmli out-2.xmli || return
|
||||||
|
|
||||||
diff <("$P_XMLLINT" --format "$dir/expected.xml" || echo 'ERR expected.xml') \
|
diff <("$P_XMLLINT" --format "$dir/expected.xml" || echo 'ERR expected.xml') \
|
||||||
|
|
Loading…
Reference in New Issue