knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
use super::Keyword;

#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum StructuralKey {
    Struct,
    Field,
    Variant,
    Part,
    Bare,
}

impl StructuralKey {
    pub const ALL: [StructuralKey; 5] = [
        StructuralKey::Struct,
        StructuralKey::Field,
        StructuralKey::Variant,
        StructuralKey::Part,
        StructuralKey::Bare,
    ];

    pub fn from_keyword(symbol_name: &str) -> Option<StructuralKey> {
        let name = Keyword::field_name(symbol_name)?;
        Self::ALL.into_iter().find(|key| key.name() == name)
    }

    pub fn name(self) -> &'static str {
        match self {
            StructuralKey::Struct => "struct",
            StructuralKey::Field => "field",
            StructuralKey::Variant => "variant",
            StructuralKey::Part => "part",
            StructuralKey::Bare => "bare",
        }
    }

    pub fn keyword(self) -> String {
        format!("{}{}", Keyword::MARKER, self.name())
    }

    fn accepts(self, placeholder: &StructuralPiece) -> bool {
        match placeholder {
            StructuralPiece::Literal(_) => true,
            StructuralPiece::Parameter(_) | StructuralPiece::Name => {
                !matches!(self, StructuralKey::Field | StructuralKey::Part)
            }
            StructuralPiece::Parts => {
                matches!(self, StructuralKey::Struct | StructuralKey::Variant)
            }
            StructuralPiece::Field => self == StructuralKey::Field,
            StructuralPiece::Value => matches!(self, StructuralKey::Field | StructuralKey::Part),
        }
    }

    pub fn accepted_placeholders(self, parameters: usize) -> Vec<String> {
        let parameters = (0..parameters).map(StructuralPiece::Parameter);
        let named = [
            StructuralPiece::Name,
            StructuralPiece::Parts,
            StructuralPiece::Field,
            StructuralPiece::Value,
        ];
        parameters
            .chain(named)
            .filter(|piece| self.accepts(piece))
            .map(|piece| piece.placeholder())
            .collect()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum StructuralPiece {
    Literal(String),
    Parameter(usize),
    Name,
    Parts,
    Field,
    Value,
}

impl StructuralPiece {
    const NAME: &'static str = "name";
    const PARTS: &'static str = "parts";
    const FIELD: &'static str = "field";
    const VALUE: &'static str = "value";

    fn named(text: &str) -> Option<StructuralPiece> {
        match text {
            Self::NAME => Some(StructuralPiece::Name),
            Self::PARTS => Some(StructuralPiece::Parts),
            Self::FIELD => Some(StructuralPiece::Field),
            Self::VALUE => Some(StructuralPiece::Value),
            _ => text.parse().ok().map(StructuralPiece::Parameter),
        }
    }

    pub fn placeholder(&self) -> String {
        match self {
            StructuralPiece::Literal(text) => text.clone(),
            StructuralPiece::Parameter(index) => format!("{{{index}}}"),
            StructuralPiece::Name => format!("{{{}}}", Self::NAME),
            StructuralPiece::Parts => format!("{{{}}}", Self::PARTS),
            StructuralPiece::Field => format!("{{{}}}", Self::FIELD),
            StructuralPiece::Value => format!("{{{}}}", Self::VALUE),
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct UnfilledPlaceholder(pub String);

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct StructuralTemplate {
    text: String,
    pieces: Vec<StructuralPiece>,
}

impl StructuralTemplate {
    pub fn parse(
        text: &str,
        key: StructuralKey,
        parameters: usize,
    ) -> Result<StructuralTemplate, UnfilledPlaceholder> {
        let pieces = TemplateScanner::new(text).pieces()?;
        if let Some(unfilled) = pieces
            .iter()
            .find(|piece| !Self::fills(key, parameters, piece))
        {
            return Err(UnfilledPlaceholder(unfilled.placeholder()));
        }
        Ok(StructuralTemplate {
            text: text.to_string(),
            pieces,
        })
    }

    fn fills(key: StructuralKey, parameters: usize, piece: &StructuralPiece) -> bool {
        let in_range = !matches!(piece, StructuralPiece::Parameter(index) if *index >= parameters);
        in_range && key.accepts(piece)
    }

    pub fn text(&self) -> &str {
        &self.text
    }

    pub fn fill(&self, values: &dyn StructuralValues) -> String {
        self.pieces
            .iter()
            .map(|piece| match piece {
                StructuralPiece::Literal(text) => text.clone(),
                StructuralPiece::Parameter(index) => values.parameter(*index),
                StructuralPiece::Name => values.name(),
                StructuralPiece::Parts => values.parts(),
                StructuralPiece::Field => values.field(),
                StructuralPiece::Value => values.value(),
            })
            .collect()
    }
}

pub trait StructuralValues {
    fn parameter(&self, index: usize) -> String;
    fn name(&self) -> String;
    fn parts(&self) -> String;
    fn field(&self) -> String;
    fn value(&self) -> String;
}

struct TemplateScanner<'t> {
    rest: &'t str,
    literal: String,
    pieces: Vec<StructuralPiece>,
}

impl<'t> TemplateScanner<'t> {
    const OPEN: char = '{';
    const CLOSE: char = '}';
    const ESCAPED_OPEN: &'static str = "{{";
    const ESCAPED_CLOSE: &'static str = "}}";

    fn new(text: &'t str) -> Self {
        Self {
            rest: text,
            literal: String::new(),
            pieces: Vec::new(),
        }
    }

    fn pieces(mut self) -> Result<Vec<StructuralPiece>, UnfilledPlaceholder> {
        while let Some(character) = self.rest.chars().next() {
            self.scan(character)?;
        }
        self.end_literal();
        Ok(self.pieces)
    }

    fn scan(&mut self, character: char) -> Result<(), UnfilledPlaceholder> {
        if let Some(rest) = self.rest.strip_prefix(Self::ESCAPED_OPEN) {
            self.push_literal(Self::OPEN, rest);
        } else if let Some(rest) = self.rest.strip_prefix(Self::ESCAPED_CLOSE) {
            self.push_literal(Self::CLOSE, rest);
        } else if character == Self::OPEN {
            self.placeholder()?;
        } else if character == Self::CLOSE {
            return Err(UnfilledPlaceholder(Self::CLOSE.to_string()));
        } else {
            self.push_literal(character, &self.rest[character.len_utf8()..]);
        }
        Ok(())
    }

    fn push_literal(&mut self, character: char, rest: &'t str) {
        self.literal.push(character);
        self.rest = rest;
    }

    fn placeholder(&mut self) -> Result<(), UnfilledPlaceholder> {
        let inner = &self.rest[Self::OPEN.len_utf8()..];
        let Some(end) = inner.find(Self::CLOSE) else {
            return Err(UnfilledPlaceholder(self.rest.to_string()));
        };
        let written = &self.rest[..end + Self::OPEN.len_utf8() + Self::CLOSE.len_utf8()];
        let piece = StructuralPiece::named(&inner[..end])
            .ok_or_else(|| UnfilledPlaceholder(written.to_string()))?;
        self.end_literal();
        self.pieces.push(piece);
        self.rest = &inner[end + Self::CLOSE.len_utf8()..];
        Ok(())
    }

    fn end_literal(&mut self) {
        if !self.literal.is_empty() {
            self.pieces
                .push(StructuralPiece::Literal(std::mem::take(&mut self.literal)));
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct StructuralClause {
    templates: Vec<(StructuralKey, StructuralTemplate)>,
}

impl StructuralClause {
    pub fn new(templates: Vec<(StructuralKey, StructuralTemplate)>) -> Self {
        Self { templates }
    }

    pub fn template(&self, key: StructuralKey) -> &StructuralTemplate {
        self.templates
            .iter()
            .find(|(held, _)| *held == key)
            .map(|(_, template)| template)
            .expect("a structural clause holds every key")
    }

    pub fn templates(&self) -> &[(StructuralKey, StructuralTemplate)] {
        &self.templates
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct NamedValues;

    impl StructuralValues for NamedValues {
        fn parameter(&self, index: usize) -> String {
            format!("parameter_{index}")
        }

        fn name(&self) -> String {
            "point".to_string()
        }

        fn parts(&self) -> String {
            ".x()".to_string()
        }

        fn field(&self) -> String {
            "x".to_string()
        }

        fn value(&self) -> String {
            "&self.x".to_string()
        }
    }

    #[test]
    fn a_template_fills_each_placeholder_and_unescapes_braces() {
        let template = StructuralTemplate::parse(
            "open({1}, \"{name}\"){parts} {{}}",
            StructuralKey::Struct,
            2,
        )
        .expect("the template parses");
        assert_eq!(
            template.fill(&NamedValues),
            "open(parameter_1, \"point\").x() {}"
        );
    }

    #[test]
    fn a_template_rejects_a_placeholder_its_key_cannot_fill() {
        assert_eq!(
            StructuralTemplate::parse(".part({field})", StructuralKey::Part, 2),
            Err(UnfilledPlaceholder("{field}".to_string()))
        );
    }

    #[test]
    fn a_template_rejects_a_parameter_past_the_method() {
        assert_eq!(
            StructuralTemplate::parse("{2}", StructuralKey::Bare, 2),
            Err(UnfilledPlaceholder("{2}".to_string()))
        );
    }

    #[test]
    fn a_template_rejects_an_unknown_placeholder() {
        assert_eq!(
            StructuralTemplate::parse("{other}", StructuralKey::Bare, 2),
            Err(UnfilledPlaceholder("{other}".to_string()))
        );
    }

    #[test]
    fn a_template_rejects_an_unclosed_placeholder() {
        assert_eq!(
            StructuralTemplate::parse("x{name", StructuralKey::Bare, 2),
            Err(UnfilledPlaceholder("{name".to_string()))
        );
    }

    #[test]
    fn a_template_rejects_a_lone_closing_brace() {
        assert_eq!(
            StructuralTemplate::parse("x}", StructuralKey::Bare, 2),
            Err(UnfilledPlaceholder("}".to_string()))
        );
    }

    #[test]
    fn each_key_lists_what_it_fills() {
        assert_eq!(
            StructuralKey::Bare.accepted_placeholders(2),
            vec!["{0}", "{1}", "{name}"]
        );
        assert_eq!(
            StructuralKey::Struct.accepted_placeholders(2),
            vec!["{0}", "{1}", "{name}", "{parts}"]
        );
        assert_eq!(
            StructuralKey::Field.accepted_placeholders(2),
            vec!["{field}", "{value}"]
        );
        assert_eq!(
            StructuralKey::Part.accepted_placeholders(2),
            vec!["{value}"]
        );
    }

    #[test]
    fn a_key_reads_from_its_keyword() {
        assert_eq!(
            StructuralKey::from_keyword(":variant"),
            Some(StructuralKey::Variant)
        );
        assert_eq!(StructuralKey::from_keyword("variant"), None);
        assert_eq!(StructuralKey::from_keyword(":other"), None);
    }
}