tamer: NIR->xmli template definition setup

This sets the stage for template parsing, and finally decides how we're
going to represent templates on the ASG.  This is going to start simple,
since my original plans for improving how templates are
handled (conceptually) is going to have to wait.

This is the last difficult object type to figure out, with respect to graph
representation and derivation, so I wanted to get it out of the way.

DEV-13708
main
Mike Gerwitz 2023-02-28 15:31:49 -05:00
parent 08278bc867
commit d42a46d2b8
12 changed files with 287 additions and 30 deletions

View File

@ -22,6 +22,7 @@ use super::{
Asg, AsgError, ExprOp, FragmentText, IdentKind, ObjectIndex, Source,
};
use crate::{
asg::graph::object::Tpl,
f::Functor,
fmt::{DisplayWrapper, TtQuote},
parse::{self, util::SPair, ParseState, Token, Transition, Transitionable},
@ -158,6 +159,23 @@ pub enum Air {
/// and the span is intended to aid in tracking down why rooting
/// occurred.
IdentRoot(SPair),
/// Create a new [`Tpl`] on the graph and switch to template parsing.
///
/// Until [`Self::TplClose`] is found,
/// all parsed objects will be parented to the [`Tpl`] rather than the
/// parent [`Pkg`].
/// Template parsing also recognizes additional nodes that can appear
/// only in this mode.
///
/// The [`ExprStack`] will be [`Held`],
/// to be restored after template parsing has concluded.
TplOpen(Span),
/// Close the active [`Tpl`] and exit template parsing.
///
/// The [`ExprStack`] will be restored to its prior state.
TplClose(Span),
}
impl Token for Air {
@ -174,7 +192,9 @@ impl Token for Air {
PkgOpen(span)
| PkgClose(span)
| ExprOpen(_, span)
| ExprClose(span) => *span,
| ExprClose(span)
| TplOpen(span)
| TplClose(span) => *span,
BindIdent(spair)
| ExprRef(spair)
@ -236,6 +256,14 @@ impl Display for Air {
IdentRoot(sym) => {
write!(f, "rooting of identifier {}", TtQuote::wrap(sym))
}
TplOpen(name) => {
write!(f, "open template {}", TtQuote::wrap(name))
}
TplClose(_) => {
write!(f, "close template")
}
}
}
}
@ -292,6 +320,13 @@ pub struct Dormant;
/// Expression stack is in use as part of an expression parse.
#[derive(Debug, PartialEq, Eq)]
pub struct Active(StackEdge);
/// Expression stack has been set aside temporarily for some other operation
/// and will be restored after that operation completes.
#[derive(Debug, PartialEq, Eq)]
pub enum Held {
Dormant,
Active(Active),
}
#[derive(Debug, PartialEq, Eq)]
pub enum StackEdge {
@ -334,6 +369,16 @@ impl ExprStack<Dormant> {
let Self(stack, _) = self;
ExprStack(stack, Active(StackEdge::Dangling))
}
/// Set the expression stack aside to perform another operation.
///
/// The stack can later be restored using [`ExprStack::release_st`],
/// and will restore to the same dormant state.
fn hold(self) -> ExprStack<Held> {
match self {
Self(stack, _st) => ExprStack(stack, Held::Dormant),
}
}
}
impl ExprStack<Active> {
@ -398,6 +443,24 @@ impl ExprStack<Active> {
}
}
impl ExprStack<Held> {
/// Produce an [`AirAggregate`] state from a prior expression stacks
/// state.
///
/// This marks the completion of whatever operation caused the stack to
/// be held using one of the `hold` implementations.
fn release_st(self, oi_pkg: ObjectIndex<Pkg>) -> AirAggregate {
match self {
Self(stack, Held::Dormant) => {
AirAggregate::PkgDfn(oi_pkg, ExprStack(stack, Dormant))
}
Self(_stack, Held::Active(_active)) => {
todo!("ExprStack<Held> -> Active")
}
}
}
}
impl Default for ExprStack<Dormant> {
fn default() -> Self {
// TODO: 16 is a generous guess that is very unlikely to be exceeded
@ -429,6 +492,19 @@ impl Display for ExprStack<Active> {
}
}
impl Display for ExprStack<Held> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self(_, Held::Dormant) => {
write!(f, "held dormant expression stack")
}
Self(_, Held::Active(..)) => {
write!(f, "held active expression stack")
}
}
}
}
/// AIR parser state.
#[derive(Debug, PartialEq, Eq)]
pub enum AirAggregate {
@ -442,6 +518,17 @@ pub enum AirAggregate {
///
/// Expressions may be nested arbitrarily deeply.
BuildingExpr(ObjectIndex<Pkg>, ExprStack<Active>, ObjectIndex<Expr>),
/// Parser is in template parsing mode.
///
/// All objects encountered until the closing [`Air::TplClose`] will be
/// parented to this template rather than the parent [`Pkg`].
/// See [`Air::TplOpen`] for more information.
BuildingTpl(
(ObjectIndex<Pkg>, ExprStack<Held>),
ObjectIndex<Tpl>,
Option<SPair>,
),
}
impl Default for AirAggregate {
@ -460,6 +547,16 @@ impl Display for AirAggregate {
BuildingExpr(_, es, _) => {
write!(f, "building expression with {es}")
}
BuildingTpl((_, es), _, None) => {
write!(f, "building anonymous template with {es}")
}
BuildingTpl((_, es), _, Some(name)) => {
write!(
f,
"building named template {} with {es}",
TtQuote::wrap(name)
)
}
}
}
}
@ -482,6 +579,7 @@ impl ParseState for AirAggregate {
use Air::*;
use AirAggregate::*;
// TODO: Seems to be about time for refactoring this...
match (self, tok) {
(st, Todo) => Transition(st).incomplete(),
@ -518,8 +616,8 @@ impl ParseState for AirAggregate {
Transition(BuildingExpr(oi_pkg, es.push(poi), oi)).incomplete()
}
(st @ Empty(..), ExprOpen(_, span)) => {
Transition(st).err(AsgError::InvalidExprContext(span))
(st @ Empty(..), ExprOpen(_, span) | TplOpen(span)) => {
Transition(st).err(AsgError::PkgExpected(span))
}
(st @ (Empty(..) | PkgDfn(..)), ExprClose(span)) => {
@ -607,6 +705,34 @@ impl ParseState for AirAggregate {
Transition(st).incomplete()
}
(PkgDfn(oi_pkg, es), TplOpen(span)) => {
let oi_tpl = asg.create(Tpl::new(span));
Transition(BuildingTpl((oi_pkg, es.hold()), oi_tpl, None))
.incomplete()
}
(BuildingExpr(..), TplOpen(_span)) => todo!("BuildingExpr TplOpen"),
(BuildingTpl((oi_pkg, es), oi_tpl, None), BindIdent(name)) => asg
.lookup_or_missing(name)
.bind_definition(asg, name, oi_tpl)
.map(|oi_ident| oi_pkg.defines(asg, oi_ident))
.map(|_| ())
.transition(BuildingTpl((oi_pkg, es), oi_tpl, Some(name))),
(BuildingTpl((oi_pkg, es), oi_tpl, _), TplClose(span)) => {
oi_tpl.close(asg, span);
Transition(es.release_st(oi_pkg)).incomplete()
}
(BuildingTpl(..), tok) => todo!("BuildingTpl body: {tok:?}"),
(
st @ (Empty(..) | PkgDfn(..) | BuildingExpr(..)),
TplClose(span),
) => Transition(st).err(AsgError::UnbalancedTpl(span)),
(
st,
tok @ (IdentDecl(..) | IdentExternDecl(..) | IdentDep(..)

View File

@ -356,7 +356,7 @@ fn expr_without_pkg() {
assert_eq!(
vec![
Err(ParseError::StateError(AsgError::InvalidExprContext(S1))),
Err(ParseError::StateError(AsgError::PkgExpected(S1))),
// RECOVERY
Ok(Parsed::Incomplete), // PkgOpen
Ok(Parsed::Incomplete), // PkgClose
@ -1119,6 +1119,34 @@ fn idents_share_defining_pkg() {
)
}
// A template is defined by the package containing it,
// like an expression.
#[test]
fn tpl_defining_pkg() {
let id_tpl = SPair("_tpl_".into(), S3);
let toks = vec![
Air::PkgOpen(S1),
Air::TplOpen(S2),
Air::BindIdent(id_tpl),
Air::TplClose(S4),
Air::PkgClose(S5),
];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
let asg = sut.finalize().unwrap().into_context();
let tpl = asg.expect_ident_obj::<Tpl>(id_tpl);
assert_eq!(S2.merge(S4).unwrap(), tpl.span());
let oi_id_tpl = asg.lookup(id_tpl).unwrap();
assert_eq!(
S1.merge(S5),
oi_id_tpl.src_pkg(&asg).map(|pkg| pkg.resolve(&asg).span()),
);
}
fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
where
I::IntoIter: Debug,

View File

@ -34,6 +34,16 @@ use crate::{
use super::TransitionError;
/// An error from an ASG operation.
///
///
/// Note that the user may encounter an equivalent error in the source
/// document format
/// (e.g. XML via [XIR->NIR lowering](crate::nir))
/// and therefore may never see some of these errors.
/// However,
/// a source IR _may_ choose to allow certain errors through to ease the
/// burden on its maintenance/development,
/// or a system may utilize this IR directly.
#[derive(Debug, PartialEq)]
pub enum AsgError {
/// An object could not change state in the manner requested.
@ -68,7 +78,7 @@ pub enum AsgError {
InvalidPkgCloseContext(Span),
/// Attempted to open an expression in an invalid context.
InvalidExprContext(Span),
PkgExpected(Span),
/// An expresion is not reachable by any other expression or
/// identifier.
@ -85,17 +95,12 @@ pub enum AsgError {
/// Attempted to close an expression with no corresponding opening
/// delimiter.
///
/// Note that the user may encounter an equivalent error in the source
/// document format
/// (e.g. XML via [XIR->NIR lowering](crate::nir))
/// and therefore may never see this error.
/// However,
/// a source IR _may_ choose to allow improperly nested expressions
/// through to this IR,
/// or may utilize this IR directly.
UnbalancedExpr(Span),
/// Attempted to close a template with no corresponding opening
/// delimiter.
UnbalancedTpl(Span),
/// Attempted to bind the an identifier to an expression while not in an
/// expression context.
///
@ -123,12 +128,13 @@ impl Display for AsgError {
InvalidPkgCloseContext(_) => {
write!(f, "invalid context for package close",)
}
InvalidExprContext(_) => write!(f, "invalid expression context"),
PkgExpected(_) => write!(f, "expected package definition"),
DanglingExpr(_) => write!(
f,
"dangling expression (anonymous expression has no parent)"
),
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
InvalidExprBindContext(_) => {
write!(f, "invalid expression identifier binding context")
}
@ -197,8 +203,8 @@ impl Diagnostic for AsgError {
),
],
InvalidExprContext(span) => {
vec![span.error("an expression is not allowed here")]
PkgExpected(span) => {
vec![span.error("a package definition was expected here")]
}
DanglingExpr(span) => vec![
@ -214,7 +220,11 @@ impl Diagnostic for AsgError {
],
UnbalancedExpr(span) => {
vec![span.error("there is no open expression to end here")]
vec![span.error("there is no open expression to close here")]
}
UnbalancedTpl(span) => {
vec![span.error("there is no open template to close here")]
}
InvalidExprBindContext(span) => vec![

View File

@ -22,7 +22,7 @@
use super::{
super::{Asg, AsgError, ObjectIndex, ObjectKind},
Expr, Object, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy,
ObjectRelatable, Pkg,
ObjectRelatable, Pkg, Tpl,
};
use crate::{
diagnose::{Annotate, Diagnostic},
@ -983,6 +983,7 @@ object_rel! {
Ident -> {
tree Ident,
tree Expr,
tree Tpl,
}
}

View File

@ -24,15 +24,29 @@ use std::fmt::Display;
use super::{
Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy, ObjectRelatable,
};
use crate::span::Span;
use crate::{asg::Asg, f::Functor, span::Span};
/// Template.
/// Template with associated name.
#[derive(Debug, PartialEq, Eq)]
pub struct Tpl;
pub struct Tpl(Span);
impl Tpl {
pub fn span(&self) -> Span {
todo!("Tpl::span")
match self {
Self(span) => *span,
}
}
pub fn new(span: Span) -> Self {
Self(span)
}
}
impl Functor<Span> for Tpl {
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
match self {
Self(span) => Self(f(span)),
}
}
}
@ -48,3 +62,17 @@ object_rel! {
// ...
}
}
impl ObjectIndex<Tpl> {
/// Complete a template definition.
///
/// This simply updates the span of the template to encompass the entire
/// definition.
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
self.map_obj(asg, |tpl| {
tpl.map(|open_span| {
open_span.merge(close_span).unwrap_or(open_span)
})
})
}
}

View File

@ -31,7 +31,7 @@
use super::object::{
DynObjectRel, Expr, Object, ObjectIndex, ObjectRelTy, OiPairObjectInner,
Pkg,
Pkg, Tpl,
};
use crate::{
asg::{
@ -40,7 +40,7 @@ use crate::{
},
diagnose::Annotate,
diagnostic_panic, diagnostic_unreachable,
parse::{prelude::*, Transitionable},
parse::{prelude::*, util::SPair, Transitionable},
span::{Span, UNKNOWN_SPAN},
sym::{
st::{URI_LV_CALC, URI_LV_RATER, URI_LV_TPL},
@ -185,7 +185,9 @@ impl<'a> TreeContext<'a> {
self.emit_expr(expr, paired_rel.source(), depth)
}
Object::Tpl((tpl, oi)) => todo!("tpl: {tpl:?}, {oi:?}"),
Object::Tpl((tpl, _)) => {
self.emit_template(tpl, paired_rel.source(), depth)
}
Object::Root(..) => diagnostic_unreachable!(
vec![],
@ -265,6 +267,28 @@ impl<'a> TreeContext<'a> {
))
}
/// Emit a template definition.
fn emit_template(
&mut self,
tpl: &Tpl,
src: &Object<OiPairObjectInner>,
depth: Depth,
) -> Option<Xirf> {
match src {
Object::Ident((ident, _)) => {
self.push(attr_name(ident.name()));
Some(Xirf::open(
QN_TEMPLATE,
OpenSpan::without_name_span(tpl.span()),
depth,
))
}
_ => todo!("emit_template: {src:?}"),
}
}
fn push(&mut self, tok: Xirf) {
if self.stack.is_full() {
diagnostic_panic!(
@ -320,6 +344,10 @@ fn ns(qname: QName, uri: UriStaticSymbolId, span: Span) -> Xirf {
Xirf::attr(qname, uri, (span, span))
}
fn attr_name(name: SPair) -> Xirf {
Xirf::attr(QN_NAME, name, (name.span(), name.span()))
}
fn expr_ele(expr: &Expr, depth: Depth) -> Xirf {
use ExprOp::*;

View File

@ -195,6 +195,8 @@ pub enum NirEntity {
/// Disjunctive () expression.
Any,
/// Template.
Tpl,
/// Template parameter (metavariable).
TplParam,
}
@ -224,6 +226,7 @@ impl Display for NirEntity {
All => write!(f, "conjunctive (∧) expression"),
Any => write!(f, "disjunctive () expression"),
Tpl => write!(f, "template"),
TplParam => write!(f, "template param (metavariable)"),
}
}

View File

@ -87,6 +87,14 @@ impl ParseState for NirToAir {
Transition(Ready).ok(Air::ExprOpen(ExprOp::Disj, span))
}
(Ready, Nir::Open(NirEntity::Tpl, span)) => {
Transition(Ready).ok(Air::TplOpen(span))
}
(Ready, Nir::Close(NirEntity::Tpl, span)) => {
Transition(Ready).ok(Air::TplClose(span))
}
(
Ready,
Nir::Close(

View File

@ -116,3 +116,24 @@ fn logic_exprs() {
Sut::parse(toks.into_iter()).collect(),
);
}
#[test]
fn tpl_with_name() {
let name = SPair("_tpl_name_".into(), S2);
let toks = vec![
Nir::Open(NirEntity::Tpl, S1),
Nir::BindIdent(name),
Nir::Close(NirEntity::Tpl, S3),
];
#[rustfmt::skip]
assert_eq!(
Ok(vec![
O(Air::TplOpen(S1)),
O(Air::BindIdent(name)),
O(Air::TplClose(S3)),
]),
Sut::parse(toks.into_iter()).collect(),
);
}

View File

@ -1390,11 +1390,12 @@ ele_parse! {
/// See also [`InlineTemplate`] for template definitions.
///
/// Templates are applied using [`ApplyTemplate`] or [`TplApplyShort`].
TemplateStmt := QN_TEMPLATE {
TemplateStmt := QN_TEMPLATE(_, ospan) {
@ {
QN_NAME => TodoAttr,
QN_NAME => BindIdent,
QN_DESC => TodoAttr,
} => Todo,
} => NirEntity::Tpl.open(ospan),
/(cspan) => NirEntity::Tpl.close(cspan),
TplHeading,
AnyStmtOrExpr,

View File

@ -31,4 +31,6 @@
</any>
<any />
</classify>
<template name="_empty_" />
</package>

View File

@ -35,5 +35,6 @@
</any>
<any />
</classify>
</package>
<template name="_empty_" />
</package>