// XML representation of graph objects // // 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 . //! XML representation of graph objects via [XIR](crate::xir). //! //! Attempts to produce a representation of graph [`Object`]s that is //! familiar to those writing TAME's XML-based source language. //! //! _This representation will change over time_ as TAME's source language //! evolves. //! There is no guarantee that this representation will stay over time; //! it was written for transitional purposes, //! but may be useful in the future for concrete code suggestions/fixes, //! or observing template expansions. use super::object::{ DynObjectRel, Expr, Object, ObjectIndex, ObjectRelTy, OiPairObjectInner, Pkg, }; use crate::{ asg::{ visit::{Depth, TreeWalkRel}, Asg, ExprOp, }, diagnose::Annotate, diagnostic_panic, diagnostic_unreachable, parse::{prelude::*, util::SPair, Transitionable}, span::{Span, UNKNOWN_SPAN}, sym::{ st::{URI_LV_CALC, URI_LV_RATER, URI_LV_TPL}, UriStaticSymbolId, }, xir::{ flat::{Text, XirfToken}, st::qname::*, OpenSpan, QName, }, }; use arrayvec::ArrayVec; use std::{convert::Infallible, fmt::Display, marker::PhantomData}; #[derive(Debug, PartialEq, Eq)] pub enum AsgTreeToXirf<'a> { Ready(PhantomData<&'a ()>), } impl<'a> Default for AsgTreeToXirf<'a> { fn default() -> Self { Self::Ready(PhantomData::default()) } } impl<'a> Display for AsgTreeToXirf<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "generating XIRF sources from ASG tree") } } type Xirf = XirfToken; impl<'a> ParseState for AsgTreeToXirf<'a> { type Token = TreeWalkRel; type Object = Xirf; type Error = Infallible; type Context = TreeContext<'a>; fn parse_token( self, TreeWalkRel(dyn_rel, depth): Self::Token, TreeContext(toks, asg): &mut TreeContext, ) -> TransitionResult { match (toks.pop(), depth) { // Empty the token stack before processing any further. // Note that we must yield the token as lookahead to ensure that // we do eventually process it. (Some(emit), _) => Transition(self) .ok(emit) .with_lookahead(TreeWalkRel(dyn_rel, depth)), // Used by `eof_tok` only to empty the token stack, // which we're now done with. // We consume the token by not yielding any lookahead. (None, Depth(0)) => Transition(self).incomplete(), // The stack is empty, // so proceed with processing the provided relation. (None, depth) => { derive_src(toks, asg, dyn_rel, depth).transition(self) } } } fn is_accepting(&self, TreeContext(toks, _): &Self::Context) -> bool { toks.is_empty() } fn eof_tok( &self, TreeContext(toks, _): &Self::Context, ) -> Option { // If the stack is not empty on EOF, // yield a dummy token just to invoke `parse_token` to finish // emptying it. (!toks.is_empty()).then_some(TreeWalkRel( DynObjectRel::new( ObjectRelTy::Root, ObjectRelTy::Root, ObjectIndex::new(0.into(), UNKNOWN_SPAN), ObjectIndex::new(0.into(), UNKNOWN_SPAN), None, ), // This is the only part that really matters; // the tree walk will never yield a depth of 0. Depth(0), )) } } /// Size of the token stack. /// /// See [`TokenStack`] for more information. const TOK_STACK_SIZE: usize = 8; /// Given a [`DynObjectRel`], /// derive a legacy TAME representation that will faithfully represent an /// equivalent program when compiled by the XSLT-based TAME compiler. /// /// The [`TokenStack`] may be used to pre-generate [XIRF](Xirf) to be /// yielded on subsequent iterations rather than having to introduce /// [`AsgTreeToXirf`] states for each individual token. /// Adjust [`TOK_STACK_SIZE`] as necessary. /// /// The provided [`Depth`] represent the depth of the tree at the position /// of the provided [`DynObjectRel`]. /// See [`TreeWalkRel`] for more information. fn derive_src( toks: &mut TokenStack, asg: &Asg, dyn_rel: DynObjectRel, depth: Depth, ) -> Option { // TODO: Verify that the binary does not perform unnecessary // resolution in branches that do not utilize the source. let paired_rel = dyn_rel.resolve_oi_pairs(asg); match paired_rel.target() { Object::Pkg((pkg, _)) => emit_package(toks, pkg, depth), // Identifiers will be considered in context; // pass over it for now. Object::Ident(..) => None, Object::Expr((expr, _)) => { emit_expr(toks, expr, paired_rel.source(), depth) } Object::Root(..) => diagnostic_unreachable!( vec![], "tree walk is not expected to emit Root", ), } } /// Emit tokens representing the root package element. fn emit_package( toks: &mut TokenStack, pkg: &Pkg, depth: Depth, ) -> Option { let span = pkg.span(); toks.push_all([ ns(QN_XMLNS_T, URI_LV_TPL, span), ns(QN_XMLNS_C, URI_LV_CALC, span), ns(QN_XMLNS, URI_LV_RATER, span), ]); Some(package(pkg, depth)) } /// Emit an expression as a legacy TAME statement or expression. /// /// Identified expressions must be represented using statements in /// legacy TAME, /// such as ``. /// Anonymous expressions are nested within statements. /// /// This system will emit statements and expressions that are compatible /// with the information on the [ASG](crate::asg) and recognized by the /// downstream XSLT-based compiler. /// There is no guarantee, /// however, /// that what is emitted is exactly representative of what the user /// originally entered. /// /// Please ensure that the system matches your expectations using the system /// tests in `:tamer/tests/xmli`. fn emit_expr( toks: &mut TokenStack, expr: &Expr, src: &Object, depth: Depth, ) -> Option { match src { Object::Ident((ident, _)) => { toks.push(yields(ident.name(), expr.span())); Some(stmt(expr, depth)) } _ => Some(expr_ele(expr, depth)), } } fn package(pkg: &Pkg, depth: Depth) -> Xirf { Xirf::open(QN_PACKAGE, OpenSpan::without_name_span(pkg.span()), depth) } fn ns(qname: QName, uri: UriStaticSymbolId, span: Span) -> Xirf { Xirf::attr(qname, uri, (span, span)) } fn stmt(expr: &Expr, depth: Depth) -> Xirf { match expr.op() { ExprOp::Sum => { Xirf::open(QN_RATE, OpenSpan::without_name_span(expr.span()), depth) } _ => todo!("stmt: {expr:?}"), } } fn yields(name: SPair, span: Span) -> Xirf { Xirf::attr(QN_YIELDS, name, (span, name)) } fn expr_ele(expr: &Expr, depth: Depth) -> Xirf { let qname = match expr.op() { ExprOp::Sum => QN_C_SUM, op => todo!("expr_ele qname: {op:?}"), }; Xirf::open(qname, OpenSpan::without_name_span(expr.span()), depth) } pub struct TreeContext<'a>(TokenStack, &'a Asg); // Custom `Debug` impl to omit ASG rendering, // since it's large and already included while rendering other parts of // the lowering pipeline. // Of course, // that's assuming this is part of the lowering pipeline. impl<'a> std::fmt::Debug for TreeContext<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_tuple("TreeContext") .field(&self.0) .field(&AsgElided) .finish() } } /// Used a placeholder for [`TreeContext`]'s [`Debug`]. #[derive(Debug)] struct AsgElided; impl<'a> From<&'a Asg> for TreeContext<'a> { fn from(asg: &'a Asg) -> Self { Self(Default::default(), asg) } } /// Token stack to hold generated tokens between [`AsgTreeToXirf`] /// iterations. /// /// The token stack is used to avoid having to create separate states for /// emitting each individual token. /// It is populated by [`AsgTreeToXirf`] if more than a single [`XirfToken`] /// needs to be emitted, /// and tokens are removed on each subsequent iteration until empty. /// /// This need only be big enough to accommodate [`AsgTreeToXirf`]'s /// implementation; /// the size is independent of user input. #[derive(Debug, Default)] struct TokenStack(ArrayVec); impl TokenStack { fn push(&mut self, tok: Xirf) { let Self(stack) = self; if stack.is_full() { diagnostic_panic!( vec![tok .internal_error("while emitting a token for this object")], "token stack exhausted (increase TOK_STACK_SIZE)", ) } stack.push(tok) } fn pop(&mut self) -> Option { match self { Self(stack) => stack.pop(), } } fn is_empty(&self) -> bool { match self { Self(stack) => stack.is_empty(), } } fn push_all(&mut self, toks: impl IntoIterator) { toks.into_iter().for_each(|x| self.push(x)) } } // System tests covering this functionality can be found in // `tamer/tests/xir/`.