407 lines
13 KiB
Rust
407 lines
13 KiB
Rust
// Lowering operations into XIR.
|
|
//
|
|
// Copyright (C) 2014-2021 Ryan Specialty Group, 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/>.
|
|
|
|
//! Lower [`XmleSections`] into a XIR [`Token`] stream for `xmle` output.
|
|
//!
|
|
//! This is the final step in the linker,
|
|
//! producing the `xmle` file that can be used to produce the standalone
|
|
//! `js` file
|
|
//! (which is still part of the XSLT-based system at the time of
|
|
//! writing).
|
|
//!
|
|
//! Use [`lower_iter`] to produce the lowering iterator,
|
|
//! which can then use [`XmlWriter`](crate::xir::writer::XmlWriter)
|
|
//! for writing.
|
|
|
|
use super::{super::LSPAN, section::XmleSections};
|
|
use crate::{
|
|
asg::{IdentKind, IdentObject},
|
|
sym::{st::*, SymbolId},
|
|
xir::{
|
|
iter::{elem_wrap, ElemWrapIter},
|
|
QName, Text, Token,
|
|
},
|
|
};
|
|
use arrayvec::ArrayVec;
|
|
use std::{array, collections::hash_set, iter::Chain, vec};
|
|
|
|
qname_const! {
|
|
QN_DESC: :L_DESC,
|
|
QN_DIM: :L_DIM,
|
|
QN_DTYPE: :L_DTYPE,
|
|
QN_GENERATED: L_PREPROC:L_GENERATED,
|
|
QN_L_DEP: L_L:L_DEP,
|
|
QN_L_EXEC: L_L:L_EXEC,
|
|
QN_L_FROM: L_L:L_FROM,
|
|
QN_L_MAP_EXEC: L_L:L_MAP_EXEC,
|
|
QN_L_MAP_FROM: L_L:L_MAP_FROM,
|
|
QN_L_RETMAP_EXEC: L_L:L_RETMAP_EXEC,
|
|
QN_L_STATIC: L_L:L_STATIC,
|
|
QN_NAME: :L_NAME,
|
|
QN_PACKAGE: :L_PACKAGE,
|
|
QN_PARENT: :L_PARENT,
|
|
QN_PROGRAM: :L_PROGRAM,
|
|
QN_P_SYM: L_PREPROC:L_SYM,
|
|
QN_SRC: :L_SRC,
|
|
QN_TITLE: :L_TITLE,
|
|
QN_TYPE: :L_TYPE,
|
|
QN_UUROOTPATH: :L_UUROOTPATH,
|
|
QN_XMLNS: :L_XMLNS,
|
|
QN_XMLNS_L: L_XMLNS:L_L,
|
|
QN_XMLNS_PREPROC: L_XMLNS:L_PREPROC,
|
|
QN_YIELDS: :L_YIELDS,
|
|
}
|
|
|
|
const HEADER_SIZE: usize = 14;
|
|
type HeaderIter = array::IntoIter<Token, HEADER_SIZE>;
|
|
|
|
/// Beginning [`Token`]s representing the root node of an `xmle` document
|
|
/// and its immediate child.
|
|
#[inline]
|
|
fn header(pkg_name: SymbolId, relroot: SymbolId) -> HeaderIter {
|
|
[
|
|
Token::AttrName(QN_XMLNS, LSPAN),
|
|
Token::AttrValue(raw::URI_LV_RATER, LSPAN),
|
|
Token::AttrName(QN_XMLNS_PREPROC, LSPAN),
|
|
Token::AttrValue(raw::URI_LV_PREPROC, LSPAN),
|
|
Token::AttrName(QN_XMLNS_L, LSPAN),
|
|
Token::AttrValue(raw::URI_LV_LINKER, LSPAN),
|
|
Token::AttrName(QN_TITLE, LSPAN),
|
|
Token::AttrValue(pkg_name, LSPAN),
|
|
Token::AttrName(QN_PROGRAM, LSPAN),
|
|
Token::AttrValue(raw::L_TRUE, LSPAN),
|
|
Token::AttrName(QN_NAME, LSPAN),
|
|
Token::AttrValue(pkg_name, LSPAN),
|
|
Token::AttrName(QN_UUROOTPATH, LSPAN),
|
|
Token::AttrValue(relroot, LSPAN),
|
|
]
|
|
.into_iter()
|
|
}
|
|
|
|
const DEP_MAX_ATTRS: usize = 9;
|
|
const DEP_MAX_ATTRS_KEY_VAL: usize = DEP_MAX_ATTRS * 2;
|
|
const DEP_CLOSE: usize = 1; // open is never stored; see `refill_toks`
|
|
|
|
/// Size of [`DepListIter`] [`Token`] buffer.
|
|
const DEP_TOK_SIZE: usize = DEP_MAX_ATTRS_KEY_VAL + DEP_CLOSE;
|
|
|
|
/// Iterator that lowers [`XmleSections`] into `l:dep` as a XIR [`Token`]
|
|
/// stream.
|
|
///
|
|
/// This iterator functions by allocating a constant-sized
|
|
/// [`ArrayVec`]-based buffer that is populated with token data each time
|
|
/// an object is requested from the underlying iterator.
|
|
/// Once the buffer runs out,
|
|
/// another object is requested and the buffer populated with the
|
|
/// appropriate token stream.
|
|
/// This repeats until no more section object data is available.
|
|
struct DepListIter<'a> {
|
|
/// Source data to lower into `l:deps`.
|
|
iter: vec::IntoIter<&'a IdentObject>,
|
|
/// Constant-size [`Token`] buffer used as a stack.
|
|
toks: ArrayVec<Token, DEP_TOK_SIZE>,
|
|
/// Relative path to project root.
|
|
relroot: SymbolId,
|
|
}
|
|
|
|
impl<'a> DepListIter<'a> {
|
|
#[inline]
|
|
fn new(iter: vec::IntoIter<&'a IdentObject>, relroot: SymbolId) -> Self {
|
|
Self {
|
|
iter,
|
|
toks: ArrayVec::new(),
|
|
relroot,
|
|
}
|
|
}
|
|
|
|
/// Re-fill buffer with a new list of [`Token]s representing the next
|
|
/// available object from the inner iterator.
|
|
///
|
|
/// Each token is pushed onto the buffer _in reverse_,
|
|
/// since it is treated like a stack;
|
|
/// this allows us to cheaply `pop` with each [`Iterator::next`]
|
|
/// call.
|
|
fn refill_toks(&mut self) -> Option<Token> {
|
|
// Tokens will be popped, so push in reverse.
|
|
// They are arranged in the same order as the original writer so
|
|
// that we can diff the two;
|
|
// TODO: re-order sensibly once we're done.
|
|
self.iter.next().map(|obj| {
|
|
match obj {
|
|
IdentObject::Ident(sym, kind, src)
|
|
| IdentObject::IdentFragment(sym, kind, src, _) => (*sym, kind, src),
|
|
_ => unreachable!(
|
|
"identifier should have been filtered out during sorting: {:?}",
|
|
obj,
|
|
),
|
|
}
|
|
}).and_then(|(sym, kind, src)| {
|
|
self.toks.push(Token::Close(None, LSPAN));
|
|
|
|
self.toks_push_attr(QN_DESC, src.desc);
|
|
self.toks_push_attr(QN_YIELDS, src.yields);
|
|
self.toks_push_attr(QN_PARENT, src.parent);
|
|
|
|
if let Some(pkg_name) = src.pkg_name {
|
|
// TODO: Introduce newtypes so that we do not have to make unsafe
|
|
// assumptions.
|
|
self.toks.push(Token::AttrValue(pkg_name, LSPAN));
|
|
self.toks.push(Token::AttrValueFragment(self.relroot, LSPAN));
|
|
self.toks.push(Token::AttrName(QN_SRC, LSPAN));
|
|
}
|
|
|
|
self.toks_push_attr(QN_GENERATED, match src.generated {
|
|
true => Some(L_TRUE.as_sym()),
|
|
false => None,
|
|
});
|
|
|
|
self.toks_push_attr(QN_NAME, Some(sym));
|
|
self.toks_push_obj_attrs(kind);
|
|
|
|
Some(Token::Open(QN_P_SYM, LSPAN))
|
|
})
|
|
}
|
|
|
|
/// Optionally push an attribute if it has a `value`.
|
|
///
|
|
/// Like [`refill_toks`](DepListIter::refill_toks),
|
|
/// we push in reverse.
|
|
#[inline]
|
|
fn toks_push_attr(&mut self, name: QName, value: Option<SymbolId>) {
|
|
if let Some(val) = value {
|
|
self.toks.push(Token::AttrValue(val, LSPAN));
|
|
self.toks.push(Token::AttrName(name, LSPAN));
|
|
}
|
|
}
|
|
|
|
/// Generate object-specific attributes.
|
|
///
|
|
/// All objects will produce a [`QN_TYPE`] attribute.
|
|
fn toks_push_obj_attrs(&mut self, kind: &IdentKind) {
|
|
match kind {
|
|
IdentKind::Cgen(dim) | IdentKind::Class(dim) => {
|
|
self.toks_push_attr(QN_DIM, Some((*dim).into()));
|
|
}
|
|
|
|
IdentKind::Const(dim, dtype)
|
|
| IdentKind::Func(dim, dtype)
|
|
| IdentKind::Gen(dim, dtype)
|
|
| IdentKind::Lparam(dim, dtype)
|
|
| IdentKind::Param(dim, dtype) => {
|
|
self.toks_push_attr(QN_DTYPE, Some((*dtype).into()));
|
|
self.toks_push_attr(QN_DIM, Some((*dim).into()));
|
|
}
|
|
|
|
IdentKind::Rate(dtype) | IdentKind::Type(dtype) => {
|
|
self.toks_push_attr(QN_DTYPE, Some((*dtype).into()));
|
|
}
|
|
|
|
// No additional attributes (explicit match so that the
|
|
// exhaustiveness check will warn us if new ones are added)
|
|
IdentKind::Tpl
|
|
| IdentKind::MapHead
|
|
| IdentKind::Map
|
|
| IdentKind::MapTail
|
|
| IdentKind::RetMapHead
|
|
| IdentKind::RetMap
|
|
| IdentKind::RetMapTail
|
|
| IdentKind::Meta
|
|
| IdentKind::Worksheet => {}
|
|
}
|
|
|
|
self.toks_push_attr(QN_TYPE, Some(kind.as_sym()));
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for DepListIter<'a> {
|
|
type Item = Token;
|
|
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.toks.pop().or_else(|| self.refill_toks())
|
|
}
|
|
}
|
|
|
|
// Maximum size of token buffer.
|
|
//
|
|
// See [`MapFromIter::refill_toks`].
|
|
const MAP_FROM_TOK_SIZE: usize = 3;
|
|
|
|
/// Generate `l:map-from` section.
|
|
struct MapFromsIter {
|
|
/// Source data to lower into `l:deps`.
|
|
iter: hash_set::IntoIter<SymbolId>,
|
|
/// Token buffer.
|
|
toks: ArrayVec<Token, MAP_FROM_TOK_SIZE>,
|
|
}
|
|
|
|
impl MapFromsIter {
|
|
#[inline]
|
|
fn new<'a>(iter: hash_set::IntoIter<SymbolId>) -> Self {
|
|
let iter = Self {
|
|
iter,
|
|
// Most of the time we have a single `from` (4 tokens).
|
|
toks: ArrayVec::new(),
|
|
};
|
|
|
|
iter
|
|
}
|
|
|
|
#[inline]
|
|
fn refill_toks(&mut self) -> Option<Token> {
|
|
self.iter.next().and_then(|from| {
|
|
self.toks.push(Token::Close(None, LSPAN));
|
|
|
|
self.toks.push(Token::AttrValue(from, LSPAN));
|
|
self.toks.push(Token::AttrName(QN_NAME, LSPAN));
|
|
|
|
Some(Token::Open(QN_L_FROM, LSPAN))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Iterator for MapFromsIter {
|
|
type Item = Token;
|
|
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.toks.pop().or_else(|| self.refill_toks())
|
|
}
|
|
}
|
|
|
|
/// Produce text fragments associated with objects.
|
|
///
|
|
/// Here, "text" refers to the compiled program text.
|
|
struct FragmentIter {
|
|
iter: vec::IntoIter<SymbolId>,
|
|
}
|
|
|
|
impl FragmentIter {
|
|
#[inline]
|
|
fn new(iter: vec::IntoIter<SymbolId>) -> Self {
|
|
Self { iter }
|
|
}
|
|
}
|
|
|
|
impl Iterator for FragmentIter {
|
|
type Item = Token;
|
|
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.iter
|
|
.by_ref()
|
|
.map(|frag| Token::Text(Text::Escaped(frag), LSPAN))
|
|
.next()
|
|
}
|
|
}
|
|
|
|
/// Iterator that lazily lowers `xmle` object files into XIR.
|
|
///
|
|
/// This serves primarily to encapsulate the nasty iterator type without
|
|
/// having to resort to dynamic dispatch,
|
|
/// since this iterator will receive over a million calls on larger
|
|
/// programs (and hundreds of thousands on smaller).
|
|
pub struct LowerIter<'a>(
|
|
ElemWrapIter<
|
|
Chain<
|
|
Chain<
|
|
Chain<
|
|
Chain<
|
|
Chain<
|
|
Chain<HeaderIter, ElemWrapIter<DepListIter<'a>>>,
|
|
ElemWrapIter<MapFromsIter>,
|
|
>,
|
|
ElemWrapIter<FragmentIter>,
|
|
>,
|
|
ElemWrapIter<FragmentIter>,
|
|
>,
|
|
ElemWrapIter<FragmentIter>,
|
|
>,
|
|
ElemWrapIter<FragmentIter>,
|
|
>,
|
|
>,
|
|
);
|
|
|
|
impl<'a> Iterator for LowerIter<'a> {
|
|
type Item = Token;
|
|
|
|
/// Produce the next XIR [`Token`] representing the lowering of
|
|
/// [`XmleSections`] from the [ASG](crate::asg).
|
|
///
|
|
/// This produces a single token at a time,
|
|
/// but [`DepListIter`] buffers tokens before emitting them,
|
|
/// so certain `next` calls have a processing cost while others are
|
|
/// essentially free.
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.0.next()
|
|
}
|
|
}
|
|
|
|
/// Lower [`XmleSections`] into a XIR [`Token`] stream for writing.
|
|
///
|
|
/// This produces the final representation for the `xmle` file,
|
|
/// which can be written using
|
|
/// [`XmlWriter`](crate::xir::writer::XmlWriter).
|
|
#[inline]
|
|
pub fn lower_iter<'a, S: XmleSections<'a>>(
|
|
mut sections: S,
|
|
pkg_name: SymbolId,
|
|
relroot: SymbolId,
|
|
) -> LowerIter<'a> {
|
|
LowerIter(elem_wrap(
|
|
QN_PACKAGE,
|
|
LSPAN,
|
|
header(pkg_name, relroot)
|
|
.chain(elem_wrap(
|
|
QN_L_DEP,
|
|
LSPAN,
|
|
DepListIter::new(sections.take_deps().into_iter(), relroot),
|
|
))
|
|
.chain(elem_wrap(
|
|
QN_L_MAP_FROM,
|
|
LSPAN,
|
|
MapFromsIter::new(sections.take_map_froms().into_iter()),
|
|
))
|
|
.chain(elem_wrap(
|
|
QN_L_MAP_EXEC,
|
|
LSPAN,
|
|
FragmentIter::new(sections.take_map().into_iter()),
|
|
))
|
|
.chain(elem_wrap(
|
|
QN_L_RETMAP_EXEC,
|
|
LSPAN,
|
|
FragmentIter::new(sections.take_retmap().into_iter()),
|
|
))
|
|
.chain(elem_wrap(
|
|
QN_L_STATIC,
|
|
LSPAN,
|
|
FragmentIter::new(sections.take_static().into_iter()),
|
|
))
|
|
.chain(elem_wrap(
|
|
QN_L_EXEC,
|
|
LSPAN,
|
|
FragmentIter::new(sections.take_exec().into_iter()),
|
|
)),
|
|
))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod test;
|