diff --git a/tamer/src/ir/asg/ident.rs b/tamer/src/ir/asg/ident.rs index fe5ff87b..002d7920 100644 --- a/tamer/src/ir/asg/ident.rs +++ b/tamer/src/ir/asg/ident.rs @@ -343,6 +343,18 @@ impl AsRef for Dim { } } +impl From for u8 { + fn from(dim: Dim) -> Self { + dim.0 + } +} + +impl From for SymbolId { + fn from(dim: Dim) -> Self { + st::decimal1(dim.0).as_sym() + } +} + impl std::fmt::Display for Dim { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { (self.0).fmt(fmt) diff --git a/tamer/src/ir/legacyir.rs b/tamer/src/ir/legacyir.rs index 62875190..e2c2c1b9 100644 --- a/tamer/src/ir/legacyir.rs +++ b/tamer/src/ir/legacyir.rs @@ -27,7 +27,7 @@ //! This IR should be converted into a higher-level IR quickly, //! especially considering that it will be going away in the future. -use crate::sym::SymbolId; +use crate::sym::{GlobalSymbolResolve, SymbolId, st}; use std::convert::TryFrom; use std::result::Result; @@ -271,15 +271,29 @@ pub enum SymDtype { Empty, } +impl SymDtype { + pub fn as_sym(&self) -> SymbolId { + match self { + SymDtype::Boolean => st::L_BOOLEAN, + SymDtype::Integer => st::L_INTEGER, + SymDtype::Float => st::L_FLOAT, + SymDtype::Empty => st::L_EMPTY, + } + .as_sym() + } +} + +impl Into for SymDtype { + fn into(self) -> SymbolId { + self.as_sym() + } +} + +// TODO: Remove after xmle writer is removed impl AsRef for SymDtype { /// Produce `xmlo`-compatible representation. fn as_ref(&self) -> &str { - match self { - SymDtype::Boolean => &"boolean", - SymDtype::Integer => &"integer", - SymDtype::Float => &"float", - SymDtype::Empty => &"empty", - } + self.as_sym().lookup_str().as_str() } } @@ -307,12 +321,7 @@ impl TryFrom<&[u8]> for SymDtype { impl std::fmt::Display for SymDtype { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::Boolean => write!(fmt, "boolean"), - Self::Integer => write!(fmt, "integer"), - Self::Float => write!(fmt, "float"), - Self::Empty => write!(fmt, "(unknown)"), - } + write!(fmt, "{}", self.as_sym().lookup_str()) } } diff --git a/tamer/src/obj/xmle/xir.rs b/tamer/src/obj/xmle/xir.rs index be73ec56..7e7b5a55 100644 --- a/tamer/src/obj/xmle/xir.rs +++ b/tamer/src/obj/xmle/xir.rs @@ -21,7 +21,9 @@ use crate::{ ir::{ - asg::{IdentObject, IdentObjectData, Sections, SectionsIter}, + asg::{ + IdentKind, IdentObject, IdentObjectData, Sections, SectionsIter, + }, xir::{AttrValue, QName, Token}, }, ld::LSPAN, @@ -33,6 +35,8 @@ use std::iter::Chain; 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_NAME: :L_NAME, @@ -91,11 +95,12 @@ struct DepListIter<'a, T: IdentObjectData> { relroot: AttrValue, } -type DepIter<'a, T> = DepListIter<'a, T>; - impl<'a, T: IdentObjectData> DepListIter<'a, T> { fn refill_toks(&mut self) -> Option { // 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| { let ident = obj.as_ident().expect("unexpected non-identifier object"); @@ -110,6 +115,10 @@ impl<'a, T: IdentObjectData> DepListIter<'a, T> { }).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 { self.toks.push(Token::AttrValue(AttrValue::Escaped(pkg_name), LSPAN)); self.toks.push(Token::AttrValueFragment(self.relroot, LSPAN)); @@ -121,11 +130,8 @@ impl<'a, T: IdentObjectData> DepListIter<'a, T> { false => None, }); - self.toks_push_attr(QN_DESC, src.desc); - self.toks_push_attr(QN_YIELDS, src.yields); - self.toks_push_attr(QN_PARENT, src.parent); self.toks_push_attr(QN_NAME, Some(sym)); - self.toks_push_attr(QN_TYPE, Some(kind.as_sym())); + self.toks_push_obj_attrs(kind); Some(Token::Open(QN_P_SYM, LSPAN)) }) @@ -139,6 +145,44 @@ impl<'a, T: IdentObjectData> DepListIter<'a, T> { 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, T: IdentObjectData> Iterator for DepListIter<'a, T> { @@ -152,7 +196,7 @@ impl<'a, T: IdentObjectData> Iterator for DepListIter<'a, T> { fn deps<'a, T: IdentObjectData>( sections: &'a Sections, relroot: SymbolId, -) -> DepIter<'a, T> { +) -> DepListIter<'a, T> { DepListIter { iter: sections.iter_all(), toks: ArrayVec::new(), @@ -180,7 +224,7 @@ fn footer() -> FooterIter { /// since this iterator will receive hundreds of thousands of calls for /// large programs. pub struct LowerIter<'a, T: IdentObjectData>( - Chain>, FooterIter>, + Chain>, FooterIter>, ); impl<'a, T: IdentObjectData> Iterator for LowerIter<'a, T> { @@ -209,6 +253,7 @@ pub fn lower_iter<'a, T: IdentObjectData>( pub mod test { use super::*; use crate::convert::ExpectInto; + use crate::ir::legacyir::SymDtype; use crate::ir::{ asg::{Dim, IdentKind, Source}, xir::{ @@ -220,6 +265,16 @@ pub mod test { type TestResult = Result<(), Box>; + macro_rules! assert_attr{ + ($attrs:ident, $name:ident, $expected:expr, $($args:expr),*) => { + assert_eq!( + $attrs.find($name).and_then(|a| a.value_atom()), + $expected, + $($args),* + ) + } + } + #[test] fn test_produces_header() -> TestResult { let empty = Sections::::new(); @@ -257,20 +312,7 @@ pub mod test { let objs = [ IdentObject::Ident( - "a".intern(), - IdentKind::Meta, - Source { - desc: Some("test desc".intern()), - ..Default::default() - }, - ), - IdentObject::Ident( - "b".intern(), - IdentKind::MapHead, - Default::default(), - ), - IdentObject::Ident( - "c".intern(), + "cgentest".intern(), IdentKind::Cgen(Dim::from_u8(1)), Source { yields: Some("yieldsValue".intern()), @@ -280,6 +322,94 @@ pub mod test { ..Default::default() }, ), + IdentObject::Ident( + "classtest".intern(), + IdentKind::Class(Dim::from_u8(2)), + Default::default(), + ), + IdentObject::Ident( + "consttest".intern(), + IdentKind::Const(Dim::from_u8(0), SymDtype::Boolean), + Default::default(), + ), + IdentObject::Ident( + "functest".intern(), + IdentKind::Func(Dim::from_u8(1), SymDtype::Integer), + Default::default(), + ), + IdentObject::Ident( + "gentest".intern(), + IdentKind::Gen(Dim::from_u8(1), SymDtype::Boolean), + Default::default(), + ), + IdentObject::Ident( + "lparamtest".intern(), + IdentKind::Gen(Dim::from_u8(2), SymDtype::Float), + Default::default(), + ), + IdentObject::Ident( + "paramtest".intern(), + IdentKind::Gen(Dim::from_u8(0), SymDtype::Integer), + Default::default(), + ), + IdentObject::Ident( + "ratetest".intern(), + IdentKind::Rate(SymDtype::Integer), + Default::default(), + ), + IdentObject::Ident( + "tpltest".intern(), + IdentKind::Tpl, + Default::default(), + ), + IdentObject::Ident( + "typetest".intern(), + IdentKind::Type(SymDtype::Integer), + Default::default(), + ), + IdentObject::Ident( + "mapheadtest".intern(), + IdentKind::MapHead, + Default::default(), + ), + IdentObject::Ident( + "maptest".intern(), + IdentKind::Map, + Default::default(), + ), + IdentObject::Ident( + "maptailtest".intern(), + IdentKind::MapTail, + Default::default(), + ), + IdentObject::Ident( + "retmapheadtest".intern(), + IdentKind::RetMapHead, + Default::default(), + ), + IdentObject::Ident( + "retmaptest".intern(), + IdentKind::RetMap, + Default::default(), + ), + IdentObject::Ident( + "retmaptailtest".intern(), + IdentKind::RetMapTail, + Default::default(), + ), + IdentObject::Ident( + "metatest".intern(), + IdentKind::Meta, + Source { + desc: Some("test desc".intern()), + ..Default::default() + }, + ), + IdentObject::Ident( + "worksheettest".intern(), + IdentKind::Worksheet, + Default::default(), + ), ]; objs.iter().for_each(|x| sections.consts.push_body(x)); @@ -316,9 +446,9 @@ pub mod test { ele }); - p_syms.enumerate().for_each(|(i, sym)| { + p_syms.enumerate().for_each(|(i, ele)| { let ident = objs[i].as_ident().unwrap(); - let attrs = sym.attrs(); + let attrs = ele.attrs(); assert_eq!( attrs.find(QN_NAME).and_then(|a| a.value_atom()), @@ -346,19 +476,17 @@ pub mod test { } if let Some(Source { parent, .. }) = ident.src() { - assert_eq!( - attrs - .find("parent".unwrap_into()) - .and_then(|a| a.value_atom()), + assert_attr!( + attrs, + QN_PARENT, parent.map(|x| AttrValue::Escaped(x)), ); } if let Some(Source { yields, .. }) = ident.src() { - assert_eq!( - attrs - .find("yields".unwrap_into()) - .and_then(|a| a.value_atom()), + assert_attr!( + attrs, + QN_YIELDS, yields.map(|x| AttrValue::Escaped(x)), ); } @@ -371,10 +499,7 @@ pub mod test { // uninterned and therefore cannot be compared as a // `SymbolId`. Once the reader takes care of creating the // symbol, we'll have no such problem. - match attrs - .find("desc".unwrap_into()) - .and_then(|a| a.value_atom()) - { + match attrs.find(QN_DESC).and_then(|a| a.value_atom()) { Some(AttrValue::Escaped(given)) => { assert_eq!(desc.lookup_str(), given.lookup_str()); } @@ -400,6 +525,54 @@ pub mod test { invalid => panic!("unexpected desc: {:?}", invalid), } } + + // Object-specific attributes + match ident.kind().unwrap() { + IdentKind::Cgen(dim) | IdentKind::Class(dim) => { + assert_attr!( + attrs, + QN_DIM, + Some(AttrValue::Escaped((*dim).into())), + "invalid {:?} @dim", + ident.kind() + ); + } + + IdentKind::Const(dim, dtype) + | IdentKind::Func(dim, dtype) + | IdentKind::Gen(dim, dtype) + | IdentKind::Lparam(dim, dtype) + | IdentKind::Param(dim, dtype) => { + assert_attr!( + attrs, + QN_DIM, + Some(AttrValue::Escaped((*dim).into())), + "invalid {:?} @dim", + ident.kind() + ); + + assert_attr!( + attrs, + QN_DTYPE, + Some(AttrValue::Escaped((*dtype).into())), + "invalid {:?} @dtype", + ident.kind() + ); + } + + IdentKind::Rate(dtype) | IdentKind::Type(dtype) => { + assert_attr!( + attrs, + QN_DTYPE, + Some(AttrValue::Escaped((*dtype).into())), + "invalid {:?} @dim", + ident.kind() + ); + } + + // The others have no additional attributes + _ => {} + } }); Ok(()) diff --git a/tamer/src/sym/prefill.rs b/tamer/src/sym/prefill.rs index 2717766d..b822436f 100644 --- a/tamer/src/sym/prefill.rs +++ b/tamer/src/sym/prefill.rs @@ -187,6 +187,9 @@ static_symbol_newtypes! { /// common in many programming languages. cid: CIdentStaticSymbolId, + /// Base-10 (decimal) integer value as a string. + dec: DecStaticSymbolId, + /// A symbol resembling a QName of the form `prefix:local`. /// /// A symbol of this type does _not_ mean that the symbol is intended to @@ -241,18 +244,67 @@ static_symbol_newtypes! { pub mod st { use super::*; + // Convert `0 ≤ n ≤ 9` into a static symbol representing a single + // decimal digit. + // + // Panics + // ====== + // This will panic if `n > 9`. + pub fn decimal1(n: u8) -> DecStaticSymbolId { + assert!(n < 10); + + // The symbols are expected to be in a very specific position in the + // pool (n+1). + // This is verified by tests at the bottom of this file. + DecStaticSymbolId(unsafe { + ::NonZero::new_unchecked( + (n as global::ProgSymSize) + 1, + ) + }) + } + + impl From for DecStaticSymbolId { + // Convert `0 ≤ n ≤ 9` into a static symbol representing a single + // decimal digit. + // + // See [`decimal1`]. + fn from(n: u8) -> Self { + decimal1(n) + } + } + static_symbols! { ; + // Decimal strings are expected to be at index (n+1). + // See `decimal1`. + N0: dec "0", + N1: dec "1", + N2: dec "2", + N3: dec "3", + N4: dec "4", + N5: dec "5", + N6: dec "6", + N7: dec "7", + N8: dec "8", + N9: dec "9", + + + L_BOOLEAN: cid "boolean", L_CGEN: cid "cgen", L_CLASS: cid "class", L_CONST: cid "const", L_DEP: cid "dep", L_DESC: cid "desc", + L_DIM: cid "dim", + L_DTYPE: cid "dtype", + L_EMPTY: cid "empty", L_FALSE: cid "false", + L_FLOAT: cid "float", L_FUNC: cid "func", L_GEN: cid "gen", L_GENERATED: cid "generated", + L_INTEGER: cid "integer", L_L: cid "l", L_LPARAM: cid "lparam", L_MAP: cid "map", @@ -281,8 +333,8 @@ pub mod st { L_YIELDS: cid "yields", URI_LV_RATER: uri "http://www.lovullo.com/rater", - URI_LV_PREPROC: uri "http://www.lovullo.com/preproc", - URI_LV_LINKER: uri "http://www.lovullo.com/linker", + URI_LV_PREPROC: uri "http://www.lovullo.com/rater/preproc", + URI_LV_LINKER: uri "http://www.lovullo.com/rater/linker", // [Symbols will be added here as they are needed.] @@ -319,8 +371,8 @@ pub mod st16 { #[cfg(test)] mod test { - use super::{st, st16}; - use crate::sym::{GlobalSymbolIntern, SymbolId}; + use super::{st, st16, DecStaticSymbolId}; + use crate::sym::{GlobalSymbolIntern, GlobalSymbolResolve, SymbolId}; #[test] fn global_sanity_check_st() { @@ -356,4 +408,23 @@ mod test { the prefill contains duplicate strings!" ); } + + #[test] + fn decimal1_0_to_9() { + for n in 0..=9 { + assert_eq!(st::decimal1(n).as_sym().lookup_str(), n.to_string()); + + // From + assert_eq!( + DecStaticSymbolId::from(n).as_sym().lookup_str(), + n.to_string() + ); + } + } + + #[test] + #[should_panic] + fn decimal1_gt_9_panics() { + st::decimal1(10); + } }